· 6 years ago · Sep 16, 2019, 04:40 PM
1/* globals ctvApp, console, playerImage, Foundation, debug */
2
3// Add the module to app
4ctvApp.modules = ctvApp.modules || {};
5/* FANTASYTEAMS MODULE
6
7 HOW TO CREATE A NEW FANTASY TEAM AND INITIALISE IT
8
9 var myTeam = new ctvApp.modules.fantasyteams;
10 myTeam.init({
11 $info : $( '.fantasyteams_info' ),
12 $playerFinder : $( '.fantasyteams_playerFinder' ), // optional
13 $pitch : $( '.fantasyteams_pitch' ),
14 $pitch_bar : $( '.fantasyteams_squad_compact' ), // optional
15 $wrapper : $( '.fantasyteams' ),
16 autocomplete_noFullTeam : true, // optional, default: false, blocks autocomplete when the team is full
17 limitTransfers : true, // optional, default: false, limits the number of added players
18 however, if the wildcard is in play, the transfers will not be limited
19 nameReadOnly : true, // optional, default: null, allows only viewing the entry name (otherwise allows editing it, unless the viewOnly is already on)
20 pin : pin, // optional, default: null,
21 playerFinder_allStats : true, // optional, default: false, displays all the stat columns in the player finder
22 playerFinder_roundId : 1, // optional, gets the player stats for the specific stat period
23 playerFinder_singleTable : true, // optional, default: false, creates a single table for all the player categories
24 registrationPage : false, // optional, after the team selection will redirect to the registration page instead of displaying a popup
25 roundId : 1, // optional, displays the entry and points for the selected round (viewOnly and userEntry become true)
26 userEntry : true, // optional, default: null,
27 viewOnly : true, // optional, default: null, allows only viewing the entry (otherwise allows editing it)
28 });
29
30 FANTASYTEAMS PUBLIC PROPERTIES
31
32 When working with the fantasyteams module you don't have to do anything else besides the initialisation. This is for a documentation of the modules. Only properties are here, while the methods are documented in the code.
33
34 $
35 Objects from the initialization become accessible jQuery objects, like: $info, $wrapper, etc. Access them directly, for example "$info"
36
37 args
38 Holds the parameters used for the initialisation, like: $info, pin, etc. jQuery elements are the same ones that you can access directly.
39
40 gameSettings
41 This are the settings received by the game-settings API. So, there will be properties like: assistant, categories, type, etc.
42 Example request: http://ctvstaging.dreamleague.co.uk/CleverTV_clevergames/xmlapi?type=game-settings&competitionid=1&language=en&outputtype=json
43
44 pitch
45 This is an instance of the pitch module, used for the team UI.
46
47 pitch_bar
48 This is an insance of the pitch module, used for the team UI in the pitch bar version.
49
50 players
51 An array of the player objects. It is retreived through the playerlistnew API.
52 Example request: http://ctvstaging.dreamleague.co.uk/CleverTV_clevergames/xmlapi?type=playerlistnew&competitionid=1&language=en&outputtype=json
53 A player object has properties such as: categoryId, code, rules, value, etc. Some properties are added to the player by the modules. Such as "dt_name_normalized" which is a latin-based value of the name property and used by the playerfinder filtering.
54
55 team
56 Object that holds information about the current team.
57
58 formationIx
59 This is the index of the current formation. Index is from the application config.
60
61 players
62 An array used to hold information about the players in the team. Model is:
63 team.players = [ categoryId, categoryId ... ]
64 categoryId = [ position, position ... ]
65 position = ( null || { player, row } )
66 player = object from the players array
67 row = row node in the playerfinder, provided by the datatables
68
69 EVENTS
70
71 There are events which are used for the communication between the modules. For example, when a user successfully removes a player from the pitch then the pitch module triggers the event "removePlayer" providing the information on which player has been removed. The pitch module only works with the display of players so, in addition, we want the player to be actually removed from the team. Team object is a part of the fantasyteams module. The fantasyteams module listens to this event and executes the code required for the player to be removed from the team. In a similar way, the playerfinder module has the event "removePlayer" for when the user removes the player from the playerfinder table.
72
73 The communication in this case is simple, since the fantasyteams creates the instances of the pitch and playerfinder modules and provides them self as the parent. So, the triggers are simply placed on the parent's $pitch and $playerFinder.
74
75 The information part ($info) is not given a module. It is a part of the fantasyteams module.
76
77 (todo: consider if this list of events has any value)
78
79 fantasyteams
80 $info
81 '.formation_items' > [templated item here]
82 .on( 'click' ) : _private.formation_select()
83 $pitch
84 on( 'keepPlayer' ) : _private.team.keepPlayer()
85 on( 'selectCaptain' ) : _private.team.selectCaptain()
86 on( 'selectViceCaptain' ) : _private.team.selectViceCaptain()
87 on( 'selectRuleScorer' ) : _private.team.selectRuleScorer()
88 on( 'swapSlots' ) : _private.team.swapSlots()
89 on( 'removePlayer' ) : _private.team.removePlayer()
90 on( 'playerInfo' ) : _private.team.playerInfo( args );
91 $pitch_bar
92 same events as the $pitch
93 $playerFinder
94 on( 'addPlayer' ) : _private.team.addPlayer()
95 on( 'removePlayer' ) : _private.team.removePlayer()
96 on( 'setRowStates' ) : _private.setPlayerFinderRowStates()
97 $wrapper
98 on click events:
99 '.fantasyteams_autocomplete' : _private.team.autocomplete()
100 '.fantasyteams_cancelTransfers' : _private.team.cancelTransfers()
101 '.fantasyteams_emptyTeam' : _private.team.removeAllPlayers()
102 '.fantasyteams_keepAllPlayers' : _private.team.keepAllPlayers()
103 '.fantasyteams_makeTransfers' : _private.team.makeTransfers()
104 '.fantasyteams_save' : _private.team.save() ==> triggers 'afterSave' event
105 '.fantasyteams_switchPitch' : _private.switchPitch()
106
107 playerfinder
108 change of the category checkboxes
109 on( 'change' ) : _private.categoryFilter_change()
110 change of the category dropdown
111 $( '.playerFinder_categories_dropdown select' ).on( 'change' ) :
112 _private.categoryFilter_dropdown_updateCategories
113 update of the player name filter
114 on( 'keyup' ) : _private.applyFilter()
115 redraw of the datatable
116 table_dt.on( 'draw.dt' ) : _private.categoryTable_redrawPager()
117 datatable row selection ( click on '.dataTable tbody tr' )
118 triggers the action 'addPlayer' or 'removePlayer' on the $playerFinder, listened by the fantasyteams
119 player info, displays the modal dialog by _parent.$pitch.trigger( 'playerInfo' ... )
120 .on( 'click', '.dataTable .action.info' )
121 "only available" option
122 on( 'change' ) : _private.applyFilter()
123
124 pitch
125 click on the slot elements will trigger the events on the $pitch:
126 '.action.remove'
127 '.player_autocomplete_keep'
128 '.action.captain:not(.selected)'
129 '.action.viceCaptain:not(.selected)'
130 '.action.ruleScorer:not(.selected)'
131 click on an empty slot triggers the 'selectEmptySlot'
132
133 if present, the fantasyteams_ruleScorer should have an empty select that will be used to designate a player as one
134 $( '.fantasyteams_ruleScorer select' ).on( 'change' )
135 if present, the fantasyteams_captain should have an empty select that will be used to designate a player as one
136 $( '.fantasyteams_captain select' ).on( 'change' )
137
138 VIEW TEMPLATE REQUIREMENTS (todo)
139
140 FURTHER DEVELOPMENT
141
142 - There are many additional options, such as the dropdown rule scorer, which methods are called in across the modules. Instead of calling the methods we should be using events. This approach has been applied already to major modules (pitch and playerfinder). There may be other places where this should be applied.
143
144 - There are many mathods which should be refactored. While the structure of this script is solid enough, there are many blocks which have been added without detailed planning and based on incomplete assumptions.
145*/
146ctvApp.modules.fantasyteams = function() {
147 'use strict';
148
149 var _private = {},
150
151 // PUBLIC
152 _public = {
153 /*
154 Team object
155 - formationIx : Index of the current formation. Index is from the application config.
156 - team.players = [ categoryId, categoryId ... ]
157 categoryId = [ position, position ... ]
158 position = ( null || { player, row } )
159 player = object from the players array
160 row = row node in the playerfinder, provided by the datatables
161 */
162 team: {
163 formationIx: 0,
164 remainingBudget: 0,
165 remainingTransfers: 0,
166 remainingTransfersInitial: 0,
167 players: {}
168 },
169
170 roles: [
171 'captain',
172 'viceCaptain',
173 'ruleScorer'
174 ],
175
176 // Setup the instance, get the API data and continue to private init
177 init: function(args) {
178 debug.time('public init');
179
180 // setup the loading state on elements
181 if(args.$playerFinder) {
182 args.$playerFinder.addClass('loading');
183 }
184
185 // transform the args
186 // if the roundId is given
187 if(args.roundId) {
188 // viewOnly becomes true
189 // if no pin then userEntry becomes true
190 if(!args.pin) {
191 args.userEntry = true;
192 }
193 args.viewOnly = true;
194 }
195
196 var deferreds = [],
197 // get the game settings
198 gameSettings = $.Deferred();
199
200 _private.getGameSettings(gameSettings);
201
202 deferreds.push(gameSettings);
203
204 // next stat period
205 var currentStatPeriodId;
206 var nextStatPeriodId;
207 // find the current period in the list
208 var c = -1;
209 // find the current period
210 for(var i = 0; i < ctvApp.config.competition.statPeriods.length; i++) {
211 if(ctvApp.config.competition.statPeriods[i].id == ctvApp.config.competition.currentStatPeriod.id) {
212 c = i;
213 }
214 }
215 // get the current stat period fixtures, if found
216 if(c >= 0 && ctvApp.config.competition.statPeriods[c])
217 currentStatPeriodId = ctvApp.config.competition.statPeriods[c].id;
218
219 if(currentStatPeriodId) {
220 var currentStatPeriod = $.Deferred();
221 ctvApp.API.getPeriodFixtures({
222 statsPeriodId: currentStatPeriodId,
223 callback: function(data) {
224 _public.currentStatPeriod = data;
225
226 //console.log('have currentStatPeriod', new Date().getTime());
227 currentStatPeriod.resolve(_public.currentStatPeriod);
228 }
229 });
230 deferreds.push(currentStatPeriod);
231 }
232
233 // get the next period fixtures
234 // note: if the current period was not found this will look for the first one
235 if(ctvApp.config.competition.statPeriods[c + 1]) {
236 nextStatPeriodId = ctvApp.config.competition.statPeriods[c + 1].id;
237 if(nextStatPeriodId) {
238 var nextStatPeriod = $.Deferred();
239 ctvApp.API.getPeriodFixtures({
240 statsPeriodId: nextStatPeriodId,
241 callback: function(data) {
242 _public.nextStatPeriod = data;
243
244 //console.log('have nextStatPeriod', new Date().getTime());
245 nextStatPeriod.resolve(_public.nextStatPeriod);
246 }
247 });
248
249 deferreds.push(nextStatPeriod);
250 }
251 }
252
253 // get the players
254 var playerList = $.Deferred();
255
256 ctvApp.API.getPlayerListFacts({
257 roundId: args.playerFinder_roundId,
258 callback: function(data) {
259 // setup the players from the response
260 _private.setPlayers(data);
261 //console.log('havePlayerList', new Date().getTime());
262 playerList.resolve(data);
263 }
264 });
265 deferreds.push(playerList);
266
267 var entryDeferred = $.Deferred();
268 // get an entry of the user
269 if(args.userEntry) {
270
271 if(ctvApp.config.competition.weekly) {
272 // are we displaying a round?
273 if(args.roundId) {
274
275 // get the round for the entry
276 ctvApp.API.getEntryRound({
277 pin: args.pin,
278 roundId: args.roundId,
279 callback: function(data) {
280 // store the entry
281 _public.entry = data;
282 //console.log('have entryRound/args.pin', new Date().getTime());
283 entryDeferred.resolve(data);
284 }
285 });
286
287 } else {
288 // not a round - get the specified entry
289 var pin = ctvApp.config.entry.pin;
290
291 ctvApp.API.getEntry({
292 pin: pin,
293 callback: function(data) {
294 // store the entry
295 _public.entry = data;
296 //console.log('have entry/args.pin', new Date().getTime());
297 entryDeferred.resolve(data);
298 }
299 });
300 }
301
302 } else {
303 ctvApp.API.getUser({
304 callback: function(data) {
305
306 // are we displaying a round?
307 if(args.roundId) {
308 // get the round for the current entry
309 ctvApp.API.getEntryRound({
310 roundId: args.roundId,
311 callback: function(data) {
312 // store the entry
313 _public.entry = data;
314
315 //console.log('have entryRound/user', new Date().getTime());
316 entryDeferred.resolve(data);
317 }
318 });
319
320 var pin = ctvApp.config.entry.pin;
321 var gspInt = parseInt(args.roundId, 10);
322 ++gspInt;
323 ctvApp.API.getTransferStatus({
324 pin: pin,
325 gsp: (gspInt),
326 callback: function(data) {
327 //display the toal transfers made
328 $('#totalTransfer').text(data.totalTransfers);
329
330 //display period transfers made, and cost
331 $('#periodTransfers-' + args.roundId).text(data.entryPeriodTransfers);
332 if (data.entryPenaltyPoints > 0) {
333 $('#periodTransferCost-' + args.roundId).text('(-' + data.entryPenaltyPoints + ctvApp.core.translations['PTS']+ ')');
334 }
335 }
336 });
337
338 } else {
339 // get the entry property
340 var entry = data.entries;
341 // if entry is an array then find the first one with the compid
342 if(entry.length >= 1) {
343 entry = $.grep(entry, function(e) {
344 return e.competitionId == ctvApp.config.competition.id;
345 });
346 entry = entry[0];
347 }
348 // get the first entry
349 var pin = entry.pin;
350
351 // get entry
352 ctvApp.API.getEntry({
353 pin: pin,
354 callback: function(data) {
355 // store the entry
356 _public.entry = data;
357 //console.log('have entry/user', new Date().getTime());
358 entryDeferred.resolve(data);
359 }
360 });
361 }
362 //console.log('have User', new Date().getTime());
363 }
364 });
365 }
366
367 } else if(args.pin) {
368 // if entry is required
369 // are we displaying a round?
370 if(args.roundId) {
371
372 // get the round for the entry
373
374 ctvApp.API.getEntryRound({
375 pin: args.pin,
376 roundId: args.roundId,
377 callback: function(data) {
378 // store the entry
379 _public.entry = data;
380
381 //console.log('have entryRound/args.pin', new Date().getTime());
382 entryDeferred.resolve(data);
383 }
384 });
385
386 var gspInt = parseInt(args.roundId, 10);
387 ++gspInt;
388 ctvApp.API.getTransferStatus({
389 pin: args.pin,
390 gsp: (gspInt),
391 callback: function(data) {
392 //display the toal transfers made
393 $('#totalTransfer').text(data.totalTransfers);
394
395 //display period transfers made, and cost
396 $('#periodTransfers-' + args.roundId).text(data.entryPeriodTransfers);
397 if (data.entryPenaltyPoints > 0) {
398 $('#periodTransferCost-' + args.roundId).text('(-' + data.entryPenaltyPoints + ctvApp.core.translations['PTS']+ ')');
399 }
400
401 }
402 });
403
404 } else {
405
406 // not a round - get the specified entry
407 var pin = args.pin;
408
409 ctvApp.API.getEntry({
410 pin: pin,
411 callback: function(data) {
412 // store the entry
413 _public.entry = data;
414 //console.log('have entry/args.pin', new Date().getTime());
415 entryDeferred.resolve(data);
416 }
417 });
418
419 }
420 } else {
421 //no entry
422 entryDeferred.resolve();
423 }
424
425 deferreds.push(entryDeferred);
426
427 $.when.apply(null, deferreds).then(function() {
428 // continue the setup
429 //console.log('done', deferreds);
430 debug.timeEnd('public init');
431 _private.init(args);
432 });
433 },
434
435 /*
436 Loop through all team players through _public.gameSettings.categories
437 and for each one, execute the callback with player and category arguments
438 */
439 forEachTeamPlayer: function(callback) {
440 var categories = _public.gameSettings.categories;
441 for(var i = 0; i < categories.length; i++) {
442 var category = categories[i],
443 categoryPlayers = _public.team.players[category.id];
444
445 for(var j = 0; j < categoryPlayers.length; j++) {
446 var categoryPlayer = categoryPlayers[j];
447 if(categoryPlayer) {
448 callback({
449 'player': categoryPlayer,
450 'category': category,
451 'playerIndex': j,
452 'categoryIndex': i
453 });
454 }
455 }
456 }
457 },
458
459 /* Test for any changes in the team */
460 isTeamChanged: function() {
461
462 // changes are confirmed if there was no previous entry
463 if(!_public.entry) return true;
464
465 var isCodeChanged = function(a, b) {
466 var c = false;
467 // both exist
468 if(a && b) {
469 // but are different
470 if(a.code != b.code) {
471 c = true;
472 }
473 }
474 // only one exists
475 else if((!a && b) || (b && !a)) {
476 c = true;
477 }
478 // return
479 return c;
480 };
481
482 var changes = false;
483
484 // formation
485 if(_public.entry.formationIx != _public.team.formationIx) {
486 changes = true;
487 } else if(isCodeChanged(_public.entry.captain, _public.team.captain) ||
488 isCodeChanged(_public.entry.viceCaptain, _public.team.viceCaptain) ||
489 isCodeChanged(_public.entry.ruleScorer, _public.team.ruleScorer)
490 ) {
491 // captain, viceCaptain, ruleScorer
492
493 changes = true;
494 } else {
495 // players
496
497 // wip
498 // create a list of team player codes
499 // todo: (similar is done in the validation and may be refactored)
500 var allPlayers = true, // assume valid
501 playerCodes_pitch = '',
502 playerCodes_bench = '',
503 players = _public.team.players,
504 benchPlayers = [],
505 categories = _public.gameSettings.categories,
506 category, cplayers;
507
508 // loop through the team and flag if any player is missing
509 for(var i = 0, ii = categories.length; i < ii; i++) {
510 category = categories[i];
511 cplayers = players[category.id];
512 for(var p = 0; p < cplayers.length; p++) {
513 if(!cplayers[p]) {
514 allPlayers = false;
515 break;
516 }
517 // create the player codes, place the bench players at the end
518 var formationMax = _public.getFormationPartFromCategoryId(category.id);
519 if(p < formationMax) {
520 // update the pitch codes
521 if(playerCodes_pitch !== '') playerCodes_pitch += ':';
522 playerCodes_pitch += cplayers[p].player.code;
523 } else {
524 benchPlayers.push(cplayers[p]);
525 }
526 }
527
528 }
529
530 benchPlayers = _public.sortArrayOfPlayersByBenchPriority(benchPlayers);
531
532 for(i = 0, ii = benchPlayers.length; i < ii; i++) {
533 // update the bench codes
534 if(playerCodes_bench !== '') playerCodes_bench += ':';
535 playerCodes_bench += benchPlayers[i].player.code;
536 }
537
538 // if not all players then team is not complete (while the original entry had to be)
539 if(!allPlayers) {
540 changes = true;
541 }
542
543 // otherwise, check if all the players are at the same place
544 else {
545 // update the player codes
546 var playerCodes = playerCodes_pitch;
547 if(playerCodes !== '' && playerCodes_bench !== '') {
548 playerCodes += ':';
549 }
550 if(playerCodes_bench !== '') {
551 playerCodes += playerCodes_bench;
552 }
553 // compare the team and entry player codes
554 if(playerCodes != ctvApp.fantasyteams.entry.playerCodes.join(':')) {
555 changes = true;
556 }
557 }
558 }
559
560 // return
561 return changes;
562 },
563
564 /* Updates for team changes */
565 updateTeamChanges: function() {
566 if(_public.isTeamChanged()) {
567 _public.$wrapper.attr('data-team-changed', '');
568 } else {
569 _public.$wrapper.removeAttr('data-team-changed');
570 }
571 },
572
573 /* Get a next fixture by ID */
574 getNextFixture: function(arg_id) {
575 var oFixture;
576 // check the current period
577 if(_public.currentStatPeriod)
578 if(_public.currentStatPeriod.fixtures) {
579 oFixture = $.grep(_public.currentStatPeriod.fixtures, function(fixture) {
580 return fixture.id == arg_id;
581 })[0];
582 }
583 // check the next period
584 if(!oFixture && _public.nextStatPeriod)
585 if(_public.nextStatPeriod.fixtures) {
586 oFixture = $.grep(_public.nextStatPeriod.fixtures, function(fixture) {
587 return fixture.id == arg_id;
588 })[0];
589 }
590 return oFixture;
591 },
592
593 /* Get a player by code */
594 getPlayerByCode: function(code) {
595 return $.grep(_public.players, function(player) {
596 return player.code == code;
597 })[0];
598 },
599
600 resetPlayerRoles: function(player) {
601 player.captain = undefined;
602 player.ruleScorer = undefined;
603 player.viceCaptain = undefined;
604 },
605
606 /**
607 * @method insertPlayerImage
608 * @param args
609 * @param args.$container - container for player image
610 * @param args.player player
611 * @param args.alt contents of the alt attribute, usually player.info1 or player.name
612 * @return void
613 */
614 insertPlayerImage: function(args) {
615 var pathPrefix = '/CleverTV_' + ctvApp.config.clientDirectory + '/clients/' + ctvApp.config.clientDirectory + '/_rsc/img/',
616 teamShirtPath = pathPrefix + 'shirts/',
617 playerShirtPath = pathPrefix + 'players/',
618 imageExtension = '.png',
619 sideName = args.player.sideInfo1 || $.grep(ctvApp.API.gameSettings.sides, function(side) {
620 return(side.id == args.player.sideId);
621 })[0].info1,
622 playerImageUrl = playerShirtPath + args.player.code + imageExtension,
623 teamImageUrl = teamShirtPath + sideName + imageExtension,
624 insert = function(url) {
625 args.$container.empty().append('<img ' +
626 'src="' + url + '" ' +
627 'alt="' + args.alt + '" ' +
628 'title="' + args.alt + '">');
629 };
630
631 if(!ctvApp.config.playerImage) {
632 insert(teamImageUrl);
633 return;
634 }
635
636 if(args.player.playerShirt) {
637 insert(args.player.playerShirt);
638 return;
639 }
640
641 $.ajax({
642 url: playerImageUrl,
643 type: 'HEAD'
644 }).done(function() {
645 args.player.playerShirt = playerImageUrl;
646 insert(playerImageUrl);
647 }).fail(function() {
648 args.player.playerShirt = teamImageUrl;
649 insert(teamImageUrl);
650 });
651 },
652
653 getPlayerInTeam: function(player) {
654 var teamCategory = _public.team.players[player.categoryId],
655 code = player.code;
656
657 return $.grep(teamCategory, function(player) {
658 return player.player.code == code;
659 })[0];
660 },
661
662 getCategoryIndex: function(categoryId) {
663 var categoryItem = $.grep(_public.gameSettings.categories, function(category) {
664 return category.id == categoryId;
665 })[0];
666
667 return _public.gameSettings.categories.indexOf(categoryItem);
668 },
669
670 /*
671 Returns the formation as an array of numbers
672 - args_formation (optional) : formation string to parse
673 - works with the formation string like "1-4-4-2"
674 - if no args provided then takes the current formation
675 */
676 getFormation: function(args_formation) {
677 var formation = (args_formation || ctvApp.config.competition.formations[_public.team.formationIx]) || '3-5-1';
678 // use the separator to create an array
679 formation = formation.split('-');
680 // convert the values to integer
681 for(var f = 0; f < formation.length; f++) {
682 formation[f] = parseInt(formation[f]);
683 }
684 // return
685 return formation;
686 },
687
688 // Returns the formation string for an array of numbers
689 // - args_formation_array : formation array to turn to string
690 getFormationString: function(args_formation_array) {
691 // use the separator to create the string
692 var formation = args_formation_array.join('-');
693 // return
694 return formation;
695 },
696
697 // Returns the current formation part value from the category id
698 getFormationPartFromCategoryId: function(categoryId) {
699 var cix = 0;
700 for(var i = 0; i < _public.gameSettings.categories.length; i++) {
701 if(_public.gameSettings.categories[i].id == categoryId) {
702 cix = i;
703 break;
704 }
705 }
706 var formation = _public.getFormation();
707 return formation[cix];
708 },
709
710 /*
711 Returns the number of players or the empty positions in the team
712 - args.autocomplete : count those with the autocomplete flag
713 - args.noAutocomplete : count those without the autocomplete flag
714 - args.transfers : count the new players
715 - args.emptyPositions : count the empty positions
716
717 - returned count will be a sum of the selected options
718 - arguments can be used to specify a subset, such as those marked as autocomplete or transfers
719 - if no options are set then it will count all the players
720 - example: getTeamPlayersCount({ emptyPositions: true })
721 */
722 getTeamPlayersCount: function(args) {
723 args = args || {};
724
725 var count = 0,
726 countAutocomplete = 0,
727 countNoAutocomplete = 0,
728 countTransfers = 0,
729 countEmptyPositions = 0,
730 players = _public.team.players,
731 categories = _public.gameSettings.categories,
732 category;
733
734 // loop through the categories
735 for(var i = 0, ii = categories.length; i < ii; i++) {
736 category = categories[i];
737
738 // loop through the category positions
739 var cplayers = players[category.id];
740 for(var p = 0; p < cplayers.length; p++) {
741
742 // is there a player in the position
743 var cplayer = cplayers[p];
744 if(cplayer) {
745
746 // increase the total counter
747 count++;
748
749 // process the autocomplete counters
750 if(cplayer.player.autocomplete) {
751 countAutocomplete++;
752 } else {
753 countNoAutocomplete++;
754 }
755
756 // process the transfers counter
757 if(args.transfers && _public.entry) {
758 if(_public.entry.playerCodes.indexOf(cplayer.player.code) < 0) {
759 countTransfers++;
760 }
761 }
762
763 } else {
764
765 // increase the empty position counter
766 countEmptyPositions++;
767 }
768 }
769
770 }
771
772 // sum the counters as per options
773 var options = false;
774 var optionsCount = 0;
775 if(args.autocomplete) {
776 options = true;
777 optionsCount += countAutocomplete;
778 }
779 if(args.noAutocomplete) {
780 options = true;
781 optionsCount += countNoAutocomplete;
782 }
783 if(args.transfers) {
784 options = true;
785 optionsCount += countTransfers;
786 }
787 if(args.emptyPositions) {
788 options = true;
789 optionsCount += countEmptyPositions;
790 }
791 if(options) {
792 return optionsCount;
793 } else {
794 return count;
795 }
796 },
797
798 /*
799 Displays a message in the dialog
800 - args.title : html for the title
801 - args.details : html for the details
802
803 - uses a common dialog with the id "#modal_info" if it is present on the page
804 - example: .generalInfo({ title: 'my title', details: 'my details' })
805 */
806 generalInfo: function(args) {
807 $('#modal_info .info_title').html(args.title);
808 $('#modal_info .info_details').html(args.details);
809 $('#modal_info').foundation('reveal', 'open');
810 },
811
812 // Rule Scorer dropdown - add player and select the option if the player is the rule scorer
813 ruleScorer_add: function(player) {
814
815 if(!_public.$dropdownRuleScorer) return;
816 var $select = $('select', _public.$dropdownRuleScorer);
817
818 // check if the player is already included
819 var $option;
820 $('option', $select).each(function() {
821 if($(this).attr('value') == player.code) {
822 $option = $(this);
823 }
824 });
825
826 // if the player is not included then add the option
827 if(!$option) {
828
829 // add the option
830 $option = $(
831 '<option value="' + player.code + '">' +
832 player.name +
833 '</option>'
834 );
835 $select.append($option);
836 }
837
838 // if the player is rulescorer then select the option
839 if(player.ruleScorer) {
840 $option.prop('selected', true);
841 }
842
843 // show the dropdown
844 _public.$dropdownRuleScorer.removeClass('hide');
845 // hide the dropdown if there are no remaining changes
846 if(_public.entry) {
847 if(!parseInt(_public.entry.ruleScorerChangesRemaining)) {
848 _public.$dropdownRuleScorer.addClass('hide');
849 }
850 }
851 },
852
853 // Rule Scorer dropdown - remove player
854 ruleScorer_remove: function(player) {
855
856 if(!_public.$dropdownRuleScorer) return;
857 var $select = $('select', _public.$dropdownRuleScorer);
858
859 // find the player
860 var i = -1;
861 $('option', $select).each(function(index) {
862 if($(this).attr('value') == player.code) {
863 i = index;
864 }
865 });
866
867 // remove the player
868 $('option:eq(' + i + ')', $select).remove();
869
870 // check if there are players left in the dropdown
871 var anyleft = false;
872 $('option', $select).each(function() {
873 if($(this).attr('value')) {
874 anyleft = true;
875 }
876 });
877
878 // if there are no players left then hide the dropdown
879 if(!anyleft) {
880 _public.$dropdownRuleScorer.addClass('hide');
881 }
882 },
883
884 // Rule Scorer dropdown - empty
885 // leaves the options which have no value set (could be used as "select one...")
886 ruleScorer_empty: function() {
887
888 if(!_public.$dropdownRuleScorer) return;
889 var $select = $('select', _public.$dropdownRuleScorer);
890
891 // exit if empty
892 if(!$('option', $select).length) return;
893
894 // remove options which have a value
895 // todo: unsure if the remove corrupts the loop
896 $('option', $select).each(function() {
897 if($(this).attr('value')) {
898 $(this).remove();
899 }
900 });
901 },
902
903 // Rule Scorer dropdown - on change select a rule scorer
904 ruleScorer_onChange: function() {
905
906 if(!_public.$dropdownRuleScorer) return;
907 var $select = $('select', _public.$dropdownRuleScorer);
908
909 // get the selected option's value
910 var player_code = $('option:selected', $select).attr('value');
911 // is the value a player code
912 if(player_code) {
913
914 // find the player in the team
915 var player_categoryId, player_position;
916 for(var c in _public.team.players) {
917 for(var p in _public.team.players[c]) {
918 if(_public.team.players[c][p]) {
919 if(_public.team.players[c][p].player.code == player_code) {
920 player_categoryId = c;
921 player_position = p;
922 }
923 }
924 }
925 }
926
927 // if the player was not found this looks like an error, just exit for now
928 if(!player_categoryId || !player_position) return;
929
930 // set it as the rule scorer
931 _private.team.selectRuleScorer({ categoryId: player_categoryId, position: player_position });
932 // reset the pitch (todo: this may be a poor way)
933 _public.pitch.setPlayers();
934 // todo: alternative (not too much better):
935 // get the player position on the pitch
936 // find the player on the pitch and trigger on change on its rulescorer icon
937
938 // set the state on the wrapper (todo: this is just one case, it needs to be done for other cases too)
939 // replaced by the global changed state
940 // _public.$wrapper.attr( 'data-ruleScorer-changed', 'true' );
941 } else {
942
943 // unselect any existing
944 if(_public.team.ruleScorer) {
945 _public.team.ruleScorer.ruleScorer = false;
946 _public.team.ruleScorer = null;
947 // don't track the reversion, user removed the rulescorer
948 _public.team.ruleScorer_revert = null;
949 }
950 // reset the pitch (todo: this may be a poor way)
951 _public.pitch.setPlayers();
952
953 // set the state on the wrapper (todo: this is just one case, it needs to be done for other cases too)
954 // replaced by the global changed state
955 // _public.$wrapper.removeAttr( 'data-ruleScorer-changed' );
956 }
957
958 // update the global team changes states
959 _public.updateTeamChanges();
960 },
961
962 // Captain dropdown - add player and select the option if the player is the one
963 captain_add: function(player) {
964
965 if(!_public.$dropdownCaptain) return;
966 var $select = $('select', _public.$dropdownCaptain);
967
968 // check if the player is already included
969 var $option;
970 $('option', $select).each(function() {
971 if($(this).attr('value') == player.code) {
972 $option = $(this);
973 }
974 });
975
976 // don't add a bench player
977 if(player.benchPriority >= 0) return;
978
979 // if the player is not included then add the option
980 if(!$option) {
981
982 // add the option
983 $option = $(
984 '<option value="' + player.code + '">' +
985 player.name +
986 '</option>'
987 );
988 $select.append($option);
989 }
990
991 // if the player is rulescorer then select the option
992 if(player.captain) {
993 $option.prop('selected', true);
994 }
995
996 // show the dropdown
997 _public.$dropdownCaptain.removeClass('hide');
998 // hide the dropdown if there are no remaining changes
999 if(_public.entry) {
1000 if(!parseInt(_public.entry.captainChangesRemaining)) {
1001 _public.$dropdownCaptain.addClass('hide');
1002 }
1003 }
1004 },
1005
1006 // Captain dropdown - remove player
1007 captain_remove: function(player) {
1008
1009 if(!_public.$dropdownCaptain) return;
1010 var $select = $('select', _public.$dropdownCaptain);
1011
1012 // find the player
1013 var i = -1;
1014 $('option', $select).each(function(index) {
1015 if($(this).attr('value') == player.code) {
1016 i = index;
1017 // remove the player
1018 $('option:eq(' + i + ')', $select).remove();
1019 }
1020 });
1021
1022 // check if there are players left in the dropdown
1023 var anyleft = false;
1024 $('option', $select).each(function() {
1025 if($(this).attr('value')) {
1026 anyleft = true;
1027 }
1028 });
1029
1030 // if there are no players left then hide the dropdown
1031 if(!anyleft) {
1032 _public.$dropdownCaptain.addClass('hide');
1033 }
1034 },
1035
1036 // Captain dropdown - empty
1037 // leaves the options which have no value set (could be used as "select one...")
1038 captain_empty: function() {
1039
1040 if(!_public.$dropdownCaptain) return;
1041 var $select = $('select', _public.$dropdownCaptain);
1042
1043 // exit if empty
1044 if(!$('option', $select).length) return;
1045
1046 // remove options which have a value
1047 // todo: unsure if the remove corrupts the loop
1048 $('option', $select).each(function() {
1049 if($(this).attr('value')) {
1050 $(this).remove();
1051 }
1052 });
1053 },
1054
1055 // Captain dropdown - on change select one
1056 captain_onChange: function() {
1057
1058 if(!_public.$dropdownCaptain) return;
1059 var $select = $('select', _public.$dropdownCaptain);
1060
1061 // get the selected option's value
1062 var player_code = $('option:selected', $select).attr('value');
1063 // is the value a player code
1064 if(player_code) {
1065
1066 // find the player in the team
1067 var player_categoryId, player_position;
1068 for(var c in _public.team.players) {
1069 for(var p in _public.team.players[c]) {
1070 if(_public.team.players[c][p] && _public.team.players[c][p].player.code == player_code) {
1071 player_categoryId = c;
1072 player_position = p;
1073 }
1074 }
1075 }
1076
1077 // if the player was not found this looks like an error, just exit for now
1078 if(!player_categoryId || !player_position) return;
1079
1080 // set it as the captain
1081 _private.team.selectCaptain({ categoryId: player_categoryId, position: player_position });
1082
1083 // remove the same player as the vice captain
1084 if(_public.team.captain.viceCaptain) {
1085 _public.team.captain.viceCaptain = false;
1086 _public.team.viceCaptain = null;
1087 // don't track the reversion, user removed the one
1088 _public.team.viceCaptain_revert = null;
1089 }
1090
1091 // reset the pitch (todo: this may be a poor way)
1092 _public.pitch.setPlayers();
1093 // todo: alternative (not too much better):
1094 // get the player position on the pitch
1095 // find the player on the pitch and trigger on change on its rulescorer icon
1096
1097 // set the state on the wrapper (todo: this is just one case, it needs to be done for other cases too)
1098 // replaced by the global changed state
1099 // _public.$wrapper.attr( 'data-captain-changed', 'true' );
1100 } else {
1101
1102 // unselect any existing
1103 if(_public.team.captain) {
1104 _public.team.captain.captain = false;
1105 _public.team.captain = null;
1106 // don't track the reversion, user removed the one
1107 _public.team.captain_revert = null;
1108 }
1109 // reset the pitch (todo: this may be a poor way)
1110 _public.pitch.setPlayers();
1111
1112 // set the state on the wrapper (todo: this is just one case, it needs to be done for other cases too)
1113 // replaced by the global changed state
1114 // _public.$wrapper.removeAttr( 'data-captain-changed' );
1115 }
1116
1117 // update the global team changes states
1118 _public.updateTeamChanges();
1119 },
1120
1121 // Vice Captain dropdown - add player and select the option if the player is the one
1122 viceCaptain_add: function(player) {
1123
1124 if(!_public.$dropdownViceCaptain) return;
1125 var $select = $('select', _public.$dropdownViceCaptain);
1126
1127 // check if the player is already included
1128 var $option;
1129 $('option', $select).each(function() {
1130 if($(this).attr('value') == player.code) {
1131 $option = $(this);
1132 }
1133 });
1134
1135 // don't add a bench player
1136 if(player.benchPriority >= 0) return;
1137
1138 // if the player is not included then add the option
1139 if(!$option) {
1140
1141 // add the option
1142 $option = $(
1143 '<option value="' + player.code + '">' +
1144 player.name +
1145 '</option>'
1146 );
1147 $select.append($option);
1148 }
1149
1150 // if the player is vicecaptain then select the option
1151 if(player.viceCaptain) {
1152 $option.prop('selected', true);
1153 }
1154
1155 // show the dropdown
1156 _public.$dropdownViceCaptain.removeClass('hide');
1157 // hide the dropdown if there are no remaining changes
1158 if(_public.entry) {
1159 if(!parseInt(_public.entry.vicecaptainChangesRemaining)) {
1160 _public.$dropdownViceCaptain.addClass('hide');
1161 }
1162 }
1163 },
1164
1165 // Vice Captain dropdown - remove player
1166 viceCaptain_remove: function(player) {
1167
1168 if(!_public.$dropdownViceCaptain) return;
1169 var $select = $('select', _public.$dropdownViceCaptain);
1170
1171 // find the player
1172 var i = -1;
1173 $('option', $select).each(function(index) {
1174 if($(this).attr('value') == player.code) {
1175 i = index;
1176 // remove the player
1177 $('option:eq(' + i + ')', $select).remove();
1178 }
1179 });
1180
1181 // check if there are players left in the dropdown
1182 var anyleft = false;
1183 $('option', $select).each(function() {
1184 if($(this).attr('value')) {
1185 anyleft = true;
1186 }
1187 });
1188
1189 // if there are no players left then hide the dropdown
1190 if(!anyleft) {
1191 _public.$dropdownViceCaptain.addClass('hide');
1192 }
1193 },
1194
1195 // Vice Captain dropdown - empty
1196 // leaves the options which have no value set (could be used as "select one...")
1197 viceCaptain_empty: function() {
1198
1199 if(!_public.$dropdownViceCaptain) return;
1200 var $select = $('select', _public.$dropdownViceCaptain);
1201
1202 // exit if empty
1203 if(!$('option', $select).length) return;
1204
1205 // remove options which have a value
1206 // todo: unsure if the remove corrupts the loop
1207 $('option', $select).each(function() {
1208 if($(this).attr('value')) {
1209 $(this).remove();
1210 }
1211 });
1212 },
1213
1214 // Vice Captain dropdown - on change select one
1215 viceCaptain_onChange: function() {
1216
1217 if(!_public.$dropdownViceCaptain) return;
1218 var $select = $('select', _public.$dropdownViceCaptain);
1219
1220 // get the selected option's value
1221 var player_code = $('option:selected', $select).attr('value');
1222 // is the value a player code
1223 if(player_code) {
1224
1225 // find the player in the team
1226 var player_categoryId, player_position;
1227 for(var c in _public.team.players) {
1228 for(var p in _public.team.players[c]) {
1229 if(_public.team.players[c][p] && _public.team.players[c][p].player.code == player_code) {
1230 player_categoryId = c;
1231 player_position = p;
1232 }
1233 }
1234 }
1235
1236 // if the player was not found this looks like an error, just exit for now
1237 if(!player_categoryId || !player_position) return;
1238
1239 // set it as the vice captain
1240 _private.team.selectViceCaptain({ categoryId: player_categoryId, position: player_position });
1241 // remove the same player as the captain
1242 if(_public.team.viceCaptain.captain) {
1243 _public.team.captain.captain = false;
1244 _public.team.captain = null;
1245 // don't track the reversion, user removed the one
1246 _public.team.captain_revert = null;
1247 }
1248 // reset the pitch (todo: this may be a poor way)
1249 _public.pitch.setPlayers();
1250 // todo: alternative (not too much better):
1251 // get the player position on the pitch
1252 // find the player on the pitch and trigger on change on its rulescorer icon
1253
1254 // set the state on the wrapper (todo: this is just one case, it needs to be done for other cases too)
1255 // replaced by the global changed state
1256 // _public.$wrapper.attr( 'data-captain-changed', 'true' );
1257 } else {
1258
1259 // unselect any existing
1260 if(_public.team.viceCaptain) {
1261 _public.team.viceCaptain.viceCaptain = false;
1262 _public.team.viceCaptain = null;
1263 // don't track the reversion, user removed the one
1264 _public.team.viceCaptain_revert = null;
1265 }
1266 // reset the pitch (todo: this may be a poor way)
1267 _public.pitch.setPlayers();
1268
1269 // set the state on the wrapper (todo: this is just one case, it needs to be done for other cases too)
1270 // replaced by the global changed state
1271 // _public.$wrapper.removeAttr( 'data-captain-changed' );
1272 }
1273
1274 // update the global team changes states
1275 _public.updateTeamChanges();
1276 },
1277
1278 reorderTeamCategoriesByBenchPriority: function(categories) {
1279
1280 for(var i = 0, ii = categories.length; i < ii; i++) {
1281 var players = _public.team.players[categories[i]];
1282 players = _public.sortArrayOfPlayersByBenchPriority(players);
1283 }
1284 },
1285
1286 sortArrayOfPlayersByBenchPriority: function(players) {
1287 players.sort(function(a, b) {
1288 var ap = a.player.benchPriority,
1289 bp = b.player.benchPriority;
1290
1291 if(ap > bp) {
1292 return 1;
1293 }
1294 if(ap < bp) {
1295 return -1;
1296 }
1297 return 0;
1298 });
1299
1300 return players;
1301 },
1302
1303 getCategoryBounds: function(categoryId) {
1304 var cats = _public.gameSettings.categories,
1305 i = 0;
1306
1307 for(; i < cats.length; i++) {
1308 if(cats[i].id == categoryId) {
1309 return {
1310 squadMin: cats[i].squad.split("-")[0],
1311 squadMax: cats[i].squad.split("-")[1],
1312 teamMin: cats[i].team.split("-")[0],
1313 teamMax: cats[i].team.split("-")[1],
1314 benchMin: cats[i].squad.split("-")[0] - cats[i].team.split("-")[1],
1315 benchMax: cats[i].squad.split("-")[1] - cats[i].team.split("-")[0]
1316 };
1317 }
1318 }
1319 },
1320
1321 // gets the players from a specific period and re-inits the playerfinder
1322 // intended for use with the playerslist page only
1323 // since it will break the link with the existing players of the team/pitch
1324 setPeriodPlayers: function(roundId) {
1325 // no roundId for the overall period
1326 if(roundId == 'overall') roundId = '';
1327 // set the roundId argument
1328 _public.args.playerFinder_roundId = roundId;
1329 // set the loading state on the wrapper
1330 _public.$playerFinder.addClass('loading');
1331 // get the players
1332 ctvApp.API.getPlayerListFacts({
1333 roundId: roundId,
1334 callback: function(data) {
1335 // setup the players from the response
1336 _private.setPlayers(data);
1337 _private.initPlayerFinder();
1338 // remove the loading state from the wrapper
1339 _public.$playerFinder.removeClass('loading');
1340 }
1341 });
1342 },
1343 getFilledPositions: function() {
1344 return _private.team.getFilledPositions();
1345 }
1346 };
1347
1348 // IMPLEMENTATION
1349 _private = {
1350
1351 // Setup the instance
1352 init: function(args) {
1353
1354 // setup the libs
1355 // default sorting for the datatable
1356 $.fn.dataTable.defaults.column.asSorting = ['desc', 'asc'];
1357
1358 // store the args
1359 _public.args = args;
1360
1361 // add the component wrappers from the args
1362 _public.$info = args.$info;
1363 _public.$pitch = args.$pitch;
1364 _public.$pitch_bar = args.$pitch_bar;
1365 _public.$playerFinder = args.$playerFinder;
1366 _public.$wrapper = args.$wrapper;
1367
1368 // add the $squad reference
1369 _public.$squad = $('.fantasyteams_squad', _public.$wrapper);
1370 if(!_public.$squad.length) _public.$squad = null;
1371
1372 // setup the wrapper
1373 // viewOnly
1374 if(_public.args.viewOnly) {
1375 // apply class to modules
1376 _public.$wrapper.addClass('viewOnly');
1377 if(_public.$squad) _public.$squad.addClass('viewOnly');
1378 if(_public.$pitch_bar) {
1379 _public.$pitch_bar.addClass('viewOnly');
1380 }
1381 // team name is readonly
1382 $('.info_teamName [type=text]', _public.$info).prop('readonly', true);
1383 }
1384 // gameType
1385 // apply class to modules
1386 var gameTypeClass = 'gameType-' + _public.gameSettings.type;
1387 _public.$wrapper.addClass(gameTypeClass);
1388 if(_public.$squad) _public.$squad.addClass(gameTypeClass);
1389 if(_public.$pitch_bar) {
1390 _public.$pitch_bar.addClass(gameTypeClass);
1391 }
1392 // captain/vice-captain/rule-scorer options on modules
1393 if(_public.$squad) {
1394 _public.$squad.attr('data-captain', ctvApp.config.competition.captain);
1395 _public.$squad.attr('data-vicecaptain', ctvApp.config.competition.viceCaptain);
1396 _public.$squad.attr('data-ruleScorer', ctvApp.config.competition.ruleScorer);
1397 }
1398 if(_public.$pitch_bar) {
1399 _public.$pitch_bar.attr('data-captain', ctvApp.config.competition.captain);
1400 _public.$pitch_bar.attr('data-vicecaptain', ctvApp.config.competition.viceCaptain);
1401 _public.$pitch_bar.attr('data-ruleScorer', ctvApp.config.competition.ruleScorer);
1402 }
1403 // lock editing the team name
1404 if(_public.args.nameReadOnly) {
1405 $('.info_teamName [type=text]', _public.$info).prop('readonly', true);
1406 }
1407 // limited transfers
1408 if(_public.args.limitTransfers) {
1409 // apply class to modules
1410 _public.$wrapper.addClass('limitedTransfers');
1411 }
1412 // view the round points
1413 if(_public.args.roundId) {
1414 // apply class to modules
1415 _public.$wrapper.addClass('roundStatistics');
1416 }
1417 // dropdown rule scorer
1418 if($('.fantasyteams_ruleScorer', _public.$wrapper).length) {
1419 _public.$dropdownRuleScorer = $('.fantasyteams_ruleScorer', _public.$wrapper);
1420 // event - select a rule scorer
1421 $('select', _public.$dropdownRuleScorer).on('change', _public.ruleScorer_onChange);
1422 }
1423 // dropdown captain
1424 if($('.fantasyteams_captain', _public.$wrapper).length) {
1425 _public.$dropdownCaptain = $('.fantasyteams_captain', _public.$wrapper);
1426 // event - select a captain
1427 $('select', _public.$dropdownCaptain).on('change', _public.captain_onChange);
1428 }
1429 // dropdown viceCaptain
1430 if($('.fantasyteams_vice_captain', _public.$wrapper).length) {
1431 _public.$dropdownViceCaptain = $('.fantasyteams_vice_captain', _public.$wrapper);
1432 // event - select a vice captain
1433 $('select', _public.$dropdownViceCaptain).on('change', _public.viceCaptain_onChange);
1434 }
1435
1436 //For weekly competition, select random teamname and affinity1 and save team
1437 if(ctvApp.config.competition.weekly) {
1438 $(document).off('open.fndtn.reveal', '#selectteams-proceed').on('open.fndtn.reveal', '#selectteams-proceed', function() {
1439 var $modal = $(this),
1440 $affinityOptions = $modal.find('.info_affinityTeam option');
1441 $modal.find('.info_teamName input').val('team' + Date.now() + Math.floor(Math.random() * 10000));
1442 $affinityOptions.eq($affinityOptions.length - 1).prop('selected', true);
1443 debug.log('fantasyteams.js random teamname', $modal.find('.info_teamName input').val());
1444
1445 $modal.css('left', '-9999em');
1446
1447 $modal.find('.fantasyteams_save').trigger('click');
1448 });
1449 }
1450 // transfer cost
1451 if(_public.args.transferCost) {
1452 // add data attribute
1453 _public.$wrapper.attr('data-transferCost', '');
1454 }
1455
1456 if(!_public.args.viewOnly && _public.entry && _public.entry.pin) {
1457 ctvApp.API.getTransferStatus({
1458 pin: _public.entry.pin.text,
1459 gsp: ctvApp.config.competition.nextStatPeriod,
1460 callback: function(data) {
1461 _public.team.transferStatus = data;
1462
1463 // transfer-related wrapper attributes
1464 if(!data.periodFreeTransfers) {
1465 _public.$wrapper.addClass('noExtraTransfers');
1466 }
1467 //display the toal transfers made
1468 $('#totalTransfer').text(data.totalTransfers);
1469
1470 //display period transfers made, and cost
1471 $('#periodTransfers-' + ctvApp.config.competition.currentStatPeriod.id).text(data.entryPeriodTransfers);
1472 if (data.entryPenaltyPoints > 0) {
1473 $('#periodTransferCost-' + ctvApp.config.competition.currentStatPeriod.id).text('(-' + data.entryPenaltyPoints + ctvApp.core.translations['PTS']+ ')');
1474 }
1475
1476 //display wildcard info - available or date used
1477 if(data.wildcardAvailable) {
1478 $('#wildcard-status').text(ctvApp.core.translations['Wildcard'] + ' - ' + ctvApp.core.translations['Available']);
1479 } else {
1480 $('#wildcard-status').text(ctvApp.core.translations['Wildcard'] + ' - ' + ctvApp.core.translations['Activated'] + ': ' + data.wildCardActivationPeriod);
1481 }
1482
1483 // wildcard-related wrapper attributes
1484 if(!data.wildcardAvailable) {
1485 _public.$wrapper.addClass('noWildcards');
1486 }
1487
1488 if(data.periodFreeTransfers > 0 && data.periodPenaltyPoints > 0) {
1489 // unlimited transfers
1490 _public.$wrapper.removeClass('limitedTransfers');
1491 _public.args.limitTransfers = null;
1492 }
1493
1494 //change the transfer cost in the tooltip
1495 var tooltipSelector = "#" + $('.info_transferCost').data('selector'),
1496 tooltipText = $(tooltipSelector).text();
1497
1498 tooltipText = tooltipText.replace(/\[\[extraTransferPenalty\]\]/, data.periodPenaltyPoints);
1499 $(tooltipSelector).text(tooltipText);
1500
1501 //free transfers
1502 _public.team.remainingTransfers = (data.periodFreeTransfers) ?
1503 data.periodFreeTransfers - data.entryPeriodTransfers :
1504 data.availableTransfers - data.totalTransfersWihoutWCAndAmnesty;
1505
1506 if(_public.team.remainingTransfers < 0) _public.team.remainingTransfers = 0;
1507 _public.team.remainingTransfersInitial = _public.team.remainingTransfers;
1508
1509 ctvApp.config.isAmnesty = data.amnesty;
1510
1511 _private.team.setTransfers({ newValue: _public.team.remainingTransfers });
1512
1513 $('.info_transferCost').toggleClass('hide', data.amnesty || data.wildcardActive || !data.periodPenaltyPoints);
1514
1515 // only if not amnesty
1516 if(data.amnesty === false) {
1517 // available
1518 if(!data.wildcardActive && data.wildcardAvailable) {
1519 _public.$wrapper.attr('data-wildcard', '');
1520 }
1521
1522 // already active
1523 if(data.wildcardActive) {
1524 _public.$wrapper.attr('data-wildcard', 'active');
1525 }
1526
1527 // event - click the activate
1528 $('.wildcard_activate', _public.$info).on('click', _private.wildcard_activate);
1529 // event - click the cancel
1530 $('.wildcard_cancel', _public.$info).on('click', _private.wildcard_cancel);
1531 } else {
1532 _public.args.limitTransfers = null;
1533 _public.$wrapper.removeClass('limitedTransfers');
1534 $('modal_transferCost').addClass('hide');
1535 }
1536 }
1537 });
1538 }
1539
1540 _public.hasPlayerFinder = $.Deferred();
1541
1542 if(!ctvApp.config.competition.weekly) {
1543 // ADD THE PLAYERFINDER
1544 _private.initPlayerFinder();
1545 } else {
1546 _private.initWeeklyGame();
1547 }
1548
1549 $.when(_public.hasPlayerFinder).done(function() {
1550 // init the team
1551 _private.team.init();
1552
1553 // set up the info
1554 _private.setInfo();
1555
1556 // ADD THE PITCH
1557 if(_public.$pitch) {
1558 _public.pitch = new ctvApp.modules.pitch();
1559 _public.pitch.init({
1560 _parent: _public,
1561 $wrapper: _public.$pitch
1562 });
1563 // event - remove player
1564 _public.$pitch.on('removePlayer', function(event, args) {
1565 _private.team.removePlayer(args);
1566 });
1567 // event - keep player
1568 _public.$pitch.on('keepPlayer', function(event, args) {
1569 _private.team.keepPlayer(args);
1570 });
1571 // event - swap slots
1572 _public.$pitch.on('swapSlots', function(event, args) {
1573 _private.team.swapSlots(args);
1574 });
1575 // event - select captain
1576 _public.$pitch.on('selectCaptain', function(event, args) {
1577 _private.team.selectCaptain(args);
1578 });
1579 // event - select vice captain
1580 _public.$pitch.on('selectViceCaptain', function(event, args) {
1581 _private.team.selectViceCaptain(args);
1582 });
1583 // event - select rule scorer
1584 _public.$pitch.on('selectRuleScorer', function(event, args) {
1585 _private.team.selectRuleScorer(args);
1586 });
1587 // event
1588 _public.$pitch.on('changeCurrentRound', function(event, args) {
1589 _private.changeCurrentRound(args);
1590 });
1591 // event - clicking an empty slot filters the playerfinder
1592 // - args.categoryId
1593 _public.$pitch.on('selectEmptySlot', function(event, args) {
1594 // exit if no playerfinder
1595 if(!_public.$playerFinder) return;
1596 // select all
1597 var $allButton = $('.playerFinder_categories input[type=checkbox]', _public.$playerFinder).not('[data-categoryid]');
1598 if(!$allButton.prop('checked')) {
1599 $allButton.click();
1600 }
1601 // select the category
1602 var $categoryButton = $('.playerFinder_categories input[data-categoryid=' + args.categoryId + ']', _public.$playerFinder);
1603 $categoryButton.click();
1604 // update the categories dropdown
1605 //_public.playerfinder.categoryDropdown_updateToCategory(args.categoryId);
1606 // swith to pitch_bar view if we are on the small screen
1607 if(_public.$pitch_bar &&
1608 (ctvApp.view.getScreen_isSmall() || ctvApp.view.getScreen_isMedium()) &&
1609 !_public.$pitch.hasClass('hide') &&
1610 !_public.$wrapper.hasClass('sidebar-active')
1611 ) {
1612 _private.switchPitch();
1613 // and scroll the top of the playerfinder until it is below the pitch bar
1614 var top = _public.$pitch_bar.offset().top - $(window).scrollTop();
1615 var height = _public.$pitch_bar.outerHeight(true);
1616 var total = top + height;
1617 if(_public.$playerFinder) {
1618 $('html, body').animate({
1619 scrollTop: _public.$playerFinder.offset().top - total
1620 }, 500, 'swing');
1621 }
1622 }
1623 // blink
1624 _public.$playerFinder.addClass('box-glow');
1625 window.setTimeout(function() {
1626 _public.$playerFinder.removeClass('box-glow');
1627 }, 1500);
1628 });
1629 }
1630
1631 // ADD THE PITCH-BAR
1632 if(_public.$pitch_bar) {
1633 _public.pitch_bar = new ctvApp.modules.pitch();
1634 _public.pitch_bar.init({
1635 _parent: _public,
1636 $wrapper: _public.$pitch_bar
1637 });
1638 // event - remove player
1639 _public.$pitch_bar.on('removePlayer', function(event, args) {
1640 _private.team.removePlayer(args);
1641 });
1642 // event - keep player
1643 _public.$pitch_bar.on('keepPlayer', function(event, args) {
1644 _private.team.keepPlayer(args);
1645 });
1646 // event - swap slots
1647 _public.$pitch_bar.on('swapSlots', function(event, args) {
1648 _private.team.swapSlots(args);
1649 });
1650 // event - select captain
1651 _public.$pitch_bar.on('selectCaptain', function(event, args) {
1652 _private.team.selectCaptain(args);
1653 });
1654 // event - select vice captain
1655 _public.$pitch_bar.on('selectViceCaptain', function(event, args) {
1656 _private.team.selectViceCaptain(args);
1657 });
1658 // event - select rule scorer
1659 _public.$pitch_bar.on('selectRuleScorer', function(event, args) {
1660 _private.team.selectRuleScorer(args);
1661 });
1662 // event - clicking an empty slot filters the playerfinder
1663 // - args.categoryId
1664 _public.$pitch_bar.on('selectEmptySlot', function(event, args) {
1665 // exit if no playerfinder
1666 if(!_public.$playerFinder) return;
1667 // select all
1668 var $allButton = $('.playerFinder_categories input[type=checkbox]', _public.$playerFinder).not('[data-categoryid]');
1669 if(!$allButton.prop('checked')) {
1670 $allButton.click();
1671 }
1672 // select the category
1673 var $categoryButton = $('.playerFinder_categories input[data-categoryid=' + args.categoryId + ']', _public.$playerFinder);
1674 $categoryButton.click();
1675 // update the categories dropdown
1676 _public.playerfinder.categoryDropdown_updateToCategory(args.categoryId);
1677 });
1678 } else {
1679 // no pitch-bar
1680 // hide the switch
1681 // $( '.fantasyteams_switchPitch', _public.$wrapper ).addClass( 'hide' );
1682 }
1683
1684 // POPULATE AN EXISTING ENTRY
1685 if(_public.entry) {
1686 _private.team.populateExisting(_public.entry);
1687 }
1688
1689 // ADD THE REST
1690 // event - autocomplete
1691 $('.fantasyteams_autocomplete', _public.$wrapper).on('click', _private.team.autocomplete);
1692 // event - save
1693 // can be configured to use the transfer api instead
1694 $('.fantasyteams_save', _public.$wrapper).on('click', function() {
1695 var $button = $(this);
1696 $button.prop('disabled', true);
1697 setTimeout(function() {
1698 $button.prop("disabled", false);
1699 }, 5000);
1700 if(args.saveMode == 'transfer') {
1701 _private.team.makeTransfers();
1702 } else {
1703 _private.team.save();
1704 }
1705
1706 });
1707
1708 // event - make transfers
1709 $('.fantasyteams_makeTransfers', _public.$wrapper).on('click', _private.team.makeTransfers);
1710 // event - make transfers
1711 $('.fantasyteams_cancelTransfers', _public.$wrapper).on('click', _private.team.cancelTransfers);
1712 // event - empty the team
1713 $('.fantasyteams_emptyTeam', _public.$wrapper).on('click', _private.team.removeAllPlayers);
1714 // event - keep all players
1715 $('.fantasyteams_keepAllPlayers', _public.$wrapper).on('click', _private.team.keepAllPlayers);
1716 // event - switchPitch
1717 $('.fantasyteams_switchPitch', _public.$wrapper).on('click', _private.switchPitch);
1718 // event - toggle between pitch and playerfinder on mobile
1719 $('.sidebar-toggle', _public.$wrapper).on('click', function() {
1720 $('.positions_player').removeClass('just-added');
1721 _private.toggleSidebar();
1722 });
1723 // event - toggleSidebar
1724 _public.$wrapper.on('toggleSidebar', _private.toggleSidebar);
1725
1726 // event - playerInfo
1727 _public.$wrapper.on('playerInfo', function(event, args) {
1728 _private.team.playerInfo(args);
1729 });
1730 $('.mod').trigger('fantasyteamsInitDone');
1731 });
1732 },
1733
1734 setEntryForRound: function(args) {
1735 // get the round for the current entry
1736 ctvApp.API.getEntryRound({
1737 roundId: args.statPeriodId,
1738 callback: function(data) {
1739 // store the entry
1740 _public.entry = data;
1741 $('.fantasyteams_subs tbody', _public.$wrapper).empty();
1742 _public.$wrapper.toggleClass('hasAutoSubs', !!_public.entry.substitution.playerSubs);
1743
1744 _private.team.populateExisting(_public.entry);
1745
1746 // blink
1747 _public.$pitch.addClass('box-glow shadow-pulse');
1748 window.setTimeout(function() {
1749 _public.$pitch.removeClass('box-glow');
1750 }, 1500);
1751
1752 }
1753 });
1754 var pin = ctvApp.config.entry.pin;
1755 var gspInt = parseInt(args.statPeriodId, 10);
1756 gspInt++;
1757 ctvApp.API.getTransferStatus({
1758 pin: pin,
1759 gsp: (gspInt),
1760 callback: function(data) {
1761 //display the toal transfers made
1762 $('#totalTransfer').text(data.totalTransfers);
1763
1764 //display period transfers made, and cost
1765 $('#periodTransfers-' + args.statPeriodId).text(data.entryPeriodTransfers);
1766 if (data.entryPenaltyPoints > 0) {
1767 $('#periodTransferCost-' + args.statPeriodId).text('(-' + data.entryPenaltyPoints + ctvApp.core.translations['PTS']+ ')');
1768 }
1769 }
1770 });
1771 },
1772
1773 changeCurrentRound: function(args) {
1774 _private.setEntryForRound(args);
1775 },
1776
1777 getGameSettings: function(deferred) {
1778 ctvApp.API.getGameSettings({
1779 callback: function(data) {
1780 _public.gameSettings = data;
1781
1782 // additional stats are added as rules
1783 _public.gameSettings.rules.push({
1784 abbr: ctvApp.core.translations.TSB,
1785 desc: ctvApp.core.translations['Teams Selected By %']
1786 });
1787 _public.gameSettings.rules.push({
1788 abbr: ctvApp.core.translations.PTS,
1789 desc: ctvApp.core.translations.Points
1790 });
1791
1792 //console.log('have gameSettings');
1793 deferred.resolve(_public.gameSettings);
1794 }
1795 });
1796 },
1797
1798 initPlayerFinder: function() {
1799 // ADD THE PLAYERFINDER
1800 if(_public.$playerFinder) {
1801 _public.playerfinder = new ctvApp.modules.playerfinder();
1802 _public.playerfinder.init({
1803 _parent: _public,
1804 $wrapper: _public.$playerFinder
1805 });
1806 // events
1807 if(_public.$pitch || _public.$pitch_bar) {
1808 // event - add player
1809 _public.$playerFinder.on('addPlayer', function(event, args) {
1810 _private.team.addPlayer(args);
1811 });
1812 // event - remove player
1813 _public.$playerFinder.on('removePlayer', function(event, args) {
1814 _private.team.removePlayer(args);
1815 });
1816 }
1817 }
1818 _public.hasPlayerFinder.resolve();
1819 },
1820
1821 initWeeklyGame: function() {
1822
1823 var roundId = _private.getActiveRoundId(),
1824 teamsPlayingDeferred = $.Deferred();
1825
1826 if(roundId !== _public.currentStatPeriod.statsPeriodId) {
1827
1828 ctvApp.API.getPeriodFixtures({
1829 statsPeriodId: roundId,
1830 callback: function(data) {
1831 _public.nextStatPeriod = data;
1832
1833 var fixtures = (typeof data.fixtures !== 'undefined' && data.fixtures.length) ? data.fixtures : _public.currentStatPeriod.fixtures;
1834
1835 teamsPlayingDeferred.resolve(_private.getTeamsPlayingInRound(fixtures));
1836 }
1837 });
1838 } else {
1839 teamsPlayingDeferred.resolve(_private.getTeamsPlayingInRound(_public.currentStatPeriod.fixtures));
1840 }
1841
1842 $.when(teamsPlayingDeferred).done(function(teamsPlaying) {
1843 _public.teamsPlaying = teamsPlaying;
1844 // set the roundId argument
1845 _public.args.playerFinder_roundId = roundId;
1846 // get the players
1847 var data = { players: _public.players };
1848 for(var i = data.players.length - 1; i > -1; i--) {
1849 if($.inArray(data.players[i].sideId, teamsPlaying) === -1) {
1850 data.players.splice(i, 1);
1851 }
1852 }
1853 _private.initPlayerFinder();
1854 // setup the players from the response
1855 _private.setPlayers(data);
1856 });
1857 },
1858
1859 getActiveRoundId: function() {
1860 var roundDate = new Date(ctvApp.config.competition.currentStatPeriod.endDate),
1861 activationDate = new Date(ctvApp.config.competition.nextActivationDate),
1862 roundId = ctvApp.config.competition.currentStatPeriod.id;
1863
1864 if(roundDate.valueOf() < activationDate.valueOf() && ctvApp.config.competition.nextStatPeriod) {
1865 roundId = ctvApp.config.competition.nextStatPeriod;
1866 }
1867
1868 return roundId;
1869 },
1870
1871 getTeamsPlayingInRound: function(fixtures) {
1872 var teamsPlaying = [],
1873 sides = _public.gameSettings.sides;
1874 for(var i = 0; i < fixtures.length; i++) {
1875 var fixture = fixtures[i];
1876 if($.inArray(fixture.teamAAbbr, teamsPlaying) === -1) teamsPlaying.push(fixture.teamAAbbr);
1877 if($.inArray(fixture.teamBAbbr, teamsPlaying) === -1) teamsPlaying.push(fixture.teamBAbbr);
1878 }
1879
1880 for(i = 0; i < sides.length; i++) {
1881 var idx = $.inArray(sides[i].abbr, teamsPlaying);
1882 if(idx > -1) teamsPlaying[idx] = sides[i].id;
1883 }
1884
1885 return teamsPlaying;
1886 },
1887
1888 // Working with the team
1889 team: {
1890
1891 // Setup an empty team
1892 init: function() {
1893
1894 // budget
1895 _public.team.remainingBudget = _public.gameSettings.budget;
1896
1897 // create empty positions
1898 var formationPart = 0, // formation parts are related to the category order
1899 players = _public.team.players,
1900 formation = _public.getFormation(),
1901 categories = _public.gameSettings.categories;
1902
1903 for(var i = 0, ii = categories.length; i < ii; i++) {
1904 var category = categories[i],
1905 categoryId = category.id,
1906 categorySize = formation[formationPart];
1907
1908 players[categoryId] = [];
1909 // for a squad game add the bench players
1910 if(_public.gameSettings.type_squad) {
1911 categorySize = category.squad_max;
1912 }
1913 for(var p = 0; p < categorySize; p++) {
1914 players[categoryId].push(null);
1915 }
1916 formationPart++;
1917 }
1918
1919 },
1920
1921 /*
1922 Validates the team (for saving)
1923 - on invalid team displays the message to the user
1924 - returns object:
1925 - playerCodes
1926 - teamName
1927 - valid: true/false
1928 - args (optional):
1929 - confirmTransfers: true // default: false, if true will display a modal with the details for confirmation
1930 */
1931 validateAndConfirm: function(args) {
1932
1933 // args placeholder (so you can test for any property directly)
1934 args = args || {};
1935
1936 var valid = true, // assume valid
1937 // prepare the validation message
1938 validation_message =
1939 '<p>' + ctvApp.core.translations['Please, make sure that your team:'] + '</p>' +
1940 '<ol>',
1941 // check for all the players
1942 allPlayers = true, // assume valid
1943 playerCodes_pitch = '',
1944 playerCodes_bench = '',
1945 benchPlayers = [],
1946 categories = _public.gameSettings.categories,
1947 players = _public.team.players,
1948 category, cplayers;
1949
1950 for(var i = 0, ii = categories.length; i < ii; i++) {
1951 category = categories[i];
1952 cplayers = players[category.id];
1953 for(var p = 0; p < cplayers.length; p++) {
1954 if(!cplayers[p]) {
1955 allPlayers = false;
1956 break;
1957 }
1958 // create the player codes, place the bench players at the end
1959 var formationMax = _public.getFormationPartFromCategoryId(category.id);
1960 if(p < formationMax) {
1961 // update the pitch codes
1962 if(playerCodes_pitch !== '') playerCodes_pitch += ':';
1963 playerCodes_pitch += cplayers[p].player.code;
1964 } else {
1965 // update the bench players array
1966 benchPlayers.push(cplayers[p]);
1967 }
1968 }
1969 }
1970
1971 benchPlayers = _public.sortArrayOfPlayersByBenchPriority(benchPlayers);
1972
1973 // update the bench codes
1974 for(i = 0, ii = benchPlayers.length; i < ii; i++) {
1975 if(playerCodes_bench !== '') playerCodes_bench += ':';
1976 playerCodes_bench += benchPlayers[i].player.code;
1977 }
1978
1979 // update the player codes
1980 var playerCodes = playerCodes_pitch;
1981 if(playerCodes !== '' && playerCodes_bench !== '') {
1982 playerCodes += ':';
1983 }
1984 if(playerCodes_bench !== '') {
1985 playerCodes += playerCodes_bench;
1986 }
1987 if(!allPlayers) {
1988 validation_message += '<li>' + ctvApp.core.translations['is complete with all the players'] + '</li>';
1989 valid = false; // invalid
1990 }
1991 // check for the team name
1992 // first, check in a modal
1993 var teamName = $('.fantasyteams_modal .info_teamName input[type=text]').val();
1994 if(!teamName) {
1995 // try to find it in the info module
1996 teamName = $('.info_teamName input[type=text]', _public.$info).val();
1997 }
1998 if(teamName !== '') teamName = teamName.trim();
1999 if(!teamName) {
2000 validation_message += '<li>' + ctvApp.core.translations['has a name'] + '</li>';
2001 valid = false; // invalid
2002 }
2003 // check for the affinity team
2004 // if there is no entry already
2005 var affinityTeam_id = '';
2006 if(!_public.entry) {
2007 // first, check in a modal
2008 affinityTeam_id = $('.fantasyteams_modal .info_affinityTeam select').val();
2009 if(!affinityTeam_id) {
2010 // try to find it in the info module
2011 affinityTeam_id = $('.info_affinityTeam select', _public.$info).val();
2012 }
2013 if(!affinityTeam_id) {
2014 validation_message += '<li>' + ctvApp.core.translations['has an affinity team selected'] + '</li>';
2015 valid = false; // invalid
2016 }
2017 }
2018 // check for the affinity2 team
2019 if(ctvApp.config.competition.affinity2) {
2020 var affinity2Team_id = '';
2021 if(!_public.entry) {
2022 // first, check in a modal
2023 affinity2Team_id = $('.fantasyteams_modal .info_affinity2Team select').val();
2024 if(!affinity2Team_id) {
2025 // try to find it in the info module
2026 affinity2Team_id = $('.info_affinity2Team select', _public.$info).val();
2027 }
2028 if(!affinity2Team_id) {
2029 validation_message += '<li>' + ctvApp.core.translations['has an affinity team selected'] + '</li>';
2030 valid = false; // invalid
2031 }
2032 }
2033 }
2034 // check for the captain, vice captain and rule scorer
2035 if(ctvApp.config.competition.captain && !_public.team.captain) {
2036 validation_message += '<li>' + ctvApp.core.translations['has a Captain'] + '</li>';
2037 valid = false; // invalid
2038 }
2039 if(ctvApp.config.competition.viceCaptain && !_public.team.viceCaptain) {
2040 validation_message += '<li>' + ctvApp.core.translations['has a Vice Captain'] + '</li>';
2041 valid = false; // invalid
2042 }
2043 if(ctvApp.config.competition.ruleScorer && !_public.team.ruleScorer) {
2044 validation_message += '<li>' + ctvApp.core.translations['has a Rule Scorer'] + '</li>';
2045 valid = false; // invalid
2046 }
2047
2048 // if not valid then display the message and return
2049 if(!valid) {
2050 validation_message += '</ol>';
2051 _public.generalInfo({
2052 title: ctvApp.core.translations['In order to proceed...'],
2053 details: '<ol>' + validation_message + '</ol>'
2054 });
2055 }
2056
2057 // if valid then ask for confirmation to proceed
2058 // confirmation turned off at the moment
2059 /*
2060 if( valid ){
2061 valid = confirm( ctvApp.core.translations[ 'Do you want to proceed with saving this team?' ]);
2062 };
2063 */
2064
2065 // if valid and the confirmTransfers is on, then ask for confirmation to proceed
2066 if(valid && args.confirmTransfers) {
2067
2068 // setup the modal
2069 var $modal = $('#modal_confirmTransfers');
2070 $('.modal_playerTransfers .details', $modal).empty();
2071 $('.modal_teamChanges .details', $modal).empty();
2072
2073 // original and current player codes
2074 var original_playerCodes = _public.entry.playerCodes,
2075 current_playerCodes = playerCodes.split(':'),
2076 out_playerCodes = [],
2077 in_playerCodes = [],
2078 code;
2079 // who is out?
2080 for(i = 0; i < original_playerCodes.length; i++) {
2081 code = original_playerCodes[i];
2082 if(current_playerCodes.indexOf(code) < 0) {
2083 out_playerCodes.push(code);
2084 }
2085 }
2086 // who is in?
2087 for(i = 0; i < current_playerCodes.length; i++) {
2088 code = current_playerCodes[i];
2089 if(original_playerCodes.indexOf(code) < 0) {
2090 in_playerCodes.push(code);
2091 }
2092 }
2093
2094 // any transfers?
2095 if(out_playerCodes.length || in_playerCodes.length) {
2096 $('.modal_playerTransfers', $modal).removeClass('hide');
2097 var $details;
2098 // out
2099 $details = $('.modal_playerTransfers .out .details', $modal);
2100 for(i = 0; i < out_playerCodes.length; i++) {
2101 code = out_playerCodes[i];
2102 $details.append('<div>' + _public.getPlayerByCode(code).name + '</div>');
2103 }
2104 // in
2105 $details = $('.modal_playerTransfers .in .details', $modal);
2106 for(i = 0; i < in_playerCodes.length; i++) {
2107 code = in_playerCodes[i];
2108 $details.append('<div>' + _public.getPlayerByCode(code).name + '</div>');
2109 }
2110 }
2111
2112 // team changes (captain, vicecaptain, rulescorer)
2113 var changes = false;
2114 if(ctvApp.config.competition.captain) {
2115 if(_public.entry.captain.code != _public.team.captain.code) {
2116 $('.modal_teamChanges .captain .out .details', $modal).html(
2117 _public.getPlayerByCode(_public.entry.captain.code).name
2118 );
2119 $('.modal_teamChanges .captain .in .details', $modal).html(
2120 _public.getPlayerByCode(_public.team.captain.code).name
2121 );
2122 $('.modal_teamChanges .captain', $modal).removeClass('hide');
2123 changes = true;
2124 }
2125 }
2126 if(ctvApp.config.competition.viceCaptain) {
2127 if(_public.entry.viceCaptain.code != _public.team.viceCaptain.code) {
2128 $('.modal_teamChanges .viceCaptain .out .details', $modal).html(
2129 _public.getPlayerByCode(_public.entry.viceCaptain.code).name
2130 );
2131 $('.modal_teamChanges .viceCaptain .in .details', $modal).html(
2132 _public.getPlayerByCode(_public.team.viceCaptain.code).name
2133 );
2134 $('.modal_teamChanges .viceCaptain', $modal).removeClass('hide');
2135 changes = true;
2136 }
2137 }
2138 if(ctvApp.config.competition.ruleScorer) {
2139 if(_public.entry.ruleScorer.code != _public.team.ruleScorer.code) {
2140 $('.modal_teamChanges .ruleScorer .out .details', $modal).html(
2141 _public.getPlayerByCode(_public.entry.ruleScorer.code).name
2142 );
2143 $('.modal_teamChanges .ruleScorer .in .details', $modal).html(
2144 _public.getPlayerByCode(_public.team.ruleScorer.code).name
2145 );
2146 $('.modal_teamChanges .ruleScorer', $modal).removeClass('hide');
2147 changes = true;
2148 }
2149 }
2150 // show the section if any changes
2151 if(changes) {
2152 $('.modal_teamChanges', $modal).removeClass('hide');
2153 }
2154
2155 // Wildcard?
2156 if(!!_public.$wrapper.attr('data-wildcard')) {
2157 $('.modal_wildcard', $modal).removeClass('hide');
2158 $('.modal_transferCost', $modal).addClass('hide');
2159 }
2160
2161 // open the modal
2162 $modal.foundation('reveal', 'open');
2163
2164 // EVENT = proceed on click
2165 $('.proceed', $modal).off('click').on('click', function() {
2166 // make transfers with no validation
2167 console.log('modal make transfers')
2168 _private.team.makeTransfers({ noConfirm: true });
2169 });
2170
2171 // EVENT = cancel button
2172 $('.cancel', $modal).off('click').on('click', function() {
2173 $modal.foundation('reveal', 'close');
2174 });
2175
2176 // return as invalid
2177 valid = false;
2178 }
2179
2180 return {
2181 playerCodes: playerCodes,
2182 teamName: teamName,
2183 affinityTeam_id: affinityTeam_id,
2184 affinity2Team_id: affinity2Team_id,
2185 valid: valid
2186 };
2187 },
2188
2189 // Default Save Callback
2190 defaultTeamSaveCallback: function(data) {
2191 // save done
2192
2193 // check for the message
2194 if(data.message) {
2195 if(data.message == 'saved') {
2196 // do we already have a logged in user
2197 if(ctvApp.config.user.name) {
2198 // yes, then proceed to the userhome
2199 window.location.href = 'entryhome.do';
2200 } else {
2201
2202 // no user, go about the login/register
2203
2204 // check about the registration page option
2205 if(_public.args.registrationPage) {
2206 // go to the registration page
2207 window.location.href = 'registeruser.do';
2208 } else {
2209 // display the login dialog
2210 $('#loginPopup').foundation('reveal', 'open');
2211 }
2212
2213 }
2214 }
2215 } else {
2216
2217 // display error
2218 _public.generalInfo({
2219 title: ctvApp.core.translations['There was an error in the process!'],
2220 details: ctvApp.core.translations['Please, try again later or contact us for the assistance.']
2221 });
2222 }
2223 },
2224
2225 // Save the team
2226 save: function() {
2227 // exit if the team is invalid (or not confirmed)
2228 var validDetails = _private.team.validateAndConfirm();
2229 if(!validDetails.valid) {
2230 return;
2231 }
2232
2233 // save
2234 var options = {
2235 teamName: validDetails.teamName,
2236 affinityTeam_id: validDetails.affinityTeam_id,
2237 playerCodes: validDetails.playerCodes,
2238 sponsorOptin: $('.info_sponsorOptin input[type=checkbox]').prop('checked') ? 'Y' : 'N',
2239 callback: _public.args.teamSaveCallback || _private.team.defaultTeamSaveCallback
2240 };
2241
2242 if(ctvApp.config.competition.selectteamemailoptin) {
2243 options.optinemail = $('.info_optinemail input[type=checkbox]').prop('checked') ? 'Y' : 'N';
2244 }
2245 if(ctvApp.config.competition.captain) {
2246 options.captainCodes = _public.team.captain.code;
2247 }
2248 if(ctvApp.config.competition.viceCaptain) {
2249 options.viceCaptain = _public.team.viceCaptain.code;
2250 }
2251 if(ctvApp.config.competition.ruleScorer) {
2252 options.ruleScorer = _public.team.ruleScorer.code;
2253 }
2254 if(ctvApp.config.competition.affinity2) {
2255 options.affinity2Team_id = validDetails.affinity2Team_id;
2256 }
2257 ctvApp.API.saveEntry(options);
2258 },
2259
2260 // Cancel the transfers - ask for the confirmation and reload the page
2261 cancelTransfers: function() {
2262 ctvApp.view.modalConfirm({
2263 title: ctvApp.core.translations.Confirm,
2264 details: ctvApp.core.translations['Do you want to cancel the transfers?'],
2265 btncancel: ctvApp.core.translations['No'],
2266 btnconfirm: ctvApp.core.translations['Yes'],
2267 callback: (function() {
2268 window.location.reload();
2269 })
2270 });
2271 },
2272
2273 /*
2274 Make the transfers - submit the API request
2275 - args (optional):
2276 - noConfirm: false // if true then will skip the validation (this is used for the modal callback)
2277 */
2278 makeTransfers: function(args) {
2279
2280 // placeholder for args
2281 args = args || {};
2282
2283 // exit if the team is invalid (or not confirmed)
2284 var validDetails = _private.team.validateAndConfirm({ confirmTransfers: !args.noConfirm });
2285 if(!validDetails.valid) {
2286 return;
2287 }
2288
2289 // makeTransfers
2290 var options = {
2291 pin: _public.entry.pin.text,
2292 playerCodes: validDetails.playerCodes,
2293 teamName: validDetails.teamName,
2294 wildcard: _public.$wrapper.attr('data-wildcard') == 'activation',
2295 errorCallback: function(jqXHR, textStatus) {
2296 if(textStatus === 'timeout') {
2297 setTimeout(function() {
2298 ctvApp.view.generalInfo({
2299 title: ctvApp.core.translations['There was an error in the process!'],
2300 details: ctvApp.core.translations['We encountered an error while processing your changes. Please try again.'],
2301 processing: false
2302 });
2303 $(document).off('closed.fndtn.reveal', '#modal_info').on('closed.fndtn.reveal', '#modal_info', function() {
2304 window.location.reload();
2305 });
2306 }, 500);
2307 } else {
2308 // display generic error
2309 setTimeout(function() {
2310 ctvApp.view.generalInfo({
2311 title: ctvApp.core.translations['There was an error in the process!'],
2312 details: ctvApp.core.translations['Please, try again later or contact us for the assistance.'],
2313 processing: false
2314 });
2315 }, 500);
2316 }
2317 },
2318 callback: function(data) {
2319 $('.fantasyteams_makeTransfers, #modal_confirmTransfers .proceed').prop('disabled', false);
2320 $('#modal_processing').foundation('reveal', 'close');
2321
2322 // makeTransfers done
2323 // assume no success
2324 var success = false;
2325
2326 // check for the confirmation
2327 var $pin = $('transfer pin', data);
2328 if($pin.length) {
2329 if($pin.text() == _public.entry.pin.text) {
2330 success = true;
2331 }
2332 }
2333
2334 // was it a success
2335 if(success) {
2336
2337 // proceed to the team view
2338 window.location.href = 'entryhome.do';
2339 } else {
2340
2341 var $error = $('error', data);
2342 if($error.text() == 'Other transfer is ongoing for entry ' + _public.entry.pin.text) {
2343
2344 // display error for locked transfer table
2345 setTimeout(function() {
2346 ctvApp.view.generalInfo({
2347 title: ctvApp.core.translations['There was an error in the process!'],
2348 details: ctvApp.core.translations['Previous transfers for this entry are currently in progress. Please try again in 15 minutes.'],
2349 processing: false
2350 });
2351 }, 500);
2352 } else {
2353 // display generic error
2354 setTimeout(function() {
2355 ctvApp.view.generalInfo({
2356 title: ctvApp.core.translations['There was an error in the process!'],
2357 details: ctvApp.core.translations['Please, try again later or contact us for the assistance.'],
2358 processing: false
2359 });
2360 }, 500);
2361 }
2362 }
2363 }
2364 };
2365 if(ctvApp.config.competition.captain) {
2366 options.captainCodes = _public.team.captain.code;
2367 }
2368 if(ctvApp.config.competition.viceCaptain) {
2369 options.viceCaptain = _public.team.viceCaptain.code;
2370 }
2371 if(ctvApp.config.competition.ruleScorer) {
2372 options.ruleScorer = _public.team.ruleScorer.code;
2373 }
2374
2375 $('.fantasyteams_makeTransfers, #modal_confirmTransfers .proceed').prop('disabled', true);
2376
2377 $('#modal_confirmTransfers').foundation('reveal', 'close');
2378 setTimeout(function() {
2379 ctvApp.view.generalInfo({
2380 title: ctvApp.core.translations['Your change(s) are being processed. Please wait.'],
2381 details: '<div class="text-center"><i class="fa fa-spinner fa-3x fa-pulse"></i></div>',
2382 processing: true
2383 });
2384 }, 250);
2385
2386 ctvApp.API.makeTransfers(options);
2387 },
2388
2389 // Populate an existing entry
2390 populateExisting: function(entry) {
2391
2392 // remaining budget and transfers
2393 _private.team.setBudget({ newValue: entry.remainingMoney });
2394 _private.team.setTransfers({ newValue: _public.team.remainingTransfers });
2395
2396 // latest points
2397 $('.round-points', _public.$pitch).html(entry.mdpoints);
2398 $('.overall-points', _public.$pitch).html(entry.totalPoints);
2399
2400 // name
2401 $('.info_teamName [type=text]', _public.$info).val(entry.teamName);
2402
2403 // if there are no players then set the state and exit
2404 if(!entry.playerCode) {
2405 _public.$wrapper.attr('data-noplayers', 'true');
2406 return;
2407 }
2408
2409 // get the players on the pitch
2410 // requirements:
2411 // - the players appear in the order by position
2412 // - the players fill up the team formation (so, no partial teams)
2413 var entryPlayers = [],
2414 categoryCounters = [],
2415 benchPriority = 1,
2416 player;
2417 for(var e = 0; e < entry.playerCode.length; e++) {
2418 // get the player by code
2419 var entryPlayer = entry.playerCode[e]; // original entry player
2420 for(var p = 0; p < _public.players.length; p++) {
2421 player = _public.players[p]; // player from the list of players
2422 // if we found the player
2423 if(entryPlayer.text == player.code) {
2424 // store the player
2425 _public.resetPlayerRoles(player);
2426 entryPlayers.push(player);
2427 // store if the player is the captain/viceCaptain
2428 if(entryPlayer.captain == 'Y') {
2429 player.captain = true;
2430 _public.team.captain = player;
2431 _public.entry.captain = player; // keeps the original
2432 }
2433 if(entryPlayer.captain == 'V' || entryPlayer.viceCaptain == 'Y') {
2434 player.viceCaptain = true;
2435 _public.team.viceCaptain = player;
2436 _public.entry.viceCaptain = player; // keeps the original
2437 }
2438 if(entryPlayer.captain == 'R' || entryPlayer.ruleScorer == 'Y') {
2439 player.ruleScorer = true;
2440 _public.team.ruleScorer = player;
2441 _public.entry.ruleScorer = player; // keeps the original
2442 }
2443
2444 // save details on the player
2445 player.pointsScored = entryPlayer.pointsScored;
2446
2447 // do we have the fixture details?
2448 if(entryPlayer.fixture) {
2449
2450 player.fixture = entryPlayer.fixture;
2451 player.pointsScored = entryPlayer.pointsScored || 0;
2452
2453 // keep track of any stats in the fixtures
2454 var anyStats = false;
2455
2456 // sum the points from the fixtures
2457 for(var i = 0; i < player.fixture.length; i++) {
2458 var singleFixture = player.fixture[i];
2459 player.pointsScored += parseInt(singleFixture.pointScored); // todo: api typo, should be "pointsScored"?
2460 // flag if there were any stats
2461 if( singleFixture.rules ){
2462 anyStats = true;
2463 }
2464 }
2465
2466 // if there were no stats then fallback to '-' instead of the player score
2467 if(!anyStats) {
2468 player.pointsScored = '-';
2469 }
2470 }
2471
2472 // category counter, only for players which are the pitch, not the bench
2473 if(e < ctvApp.config.competition.playersInTeam) {
2474 if(categoryCounters[player.categoryId]) {
2475 categoryCounters[player.categoryId]++;
2476 } else {
2477 categoryCounters[player.categoryId] = 1;
2478 }
2479 player.benchPriority = -1;
2480 } else {
2481 // set bench priority
2482 var catBounds = _public.getCategoryBounds(player.categoryId);
2483 if(catBounds.teamMin == 1 && catBounds.teamMax == 1 && catBounds.benchMin == 1 && catBounds.benchMax == 1) {
2484 player.benchPriority = 0;
2485 } else {
2486 player.benchPriority = benchPriority;
2487 benchPriority++;
2488 }
2489 }
2490 // break the loop
2491 break;
2492 }
2493 }
2494 }
2495
2496 // create the formation from the category counters
2497 var entryFormation = '',
2498 categories = _public.gameSettings.categories;
2499 for(var j = 0; j < categories.length; j++) {
2500 entryFormation += categoryCounters[categories[j].id].toString() + '-';
2501 }
2502 // remove the last separator
2503 entryFormation = entryFormation.substr(0, entryFormation.length - 1);
2504
2505 // auto-subs
2506 if(entry.substitution && entry.substitution.playerSubs) {
2507 // mark the wrapper
2508 _public.$wrapper.addClass('hasAutoSubs');
2509 // populate the table
2510 var $subs = $('.fantasyteams_subs', _public.$wrapper),
2511 $rows = $('.subs_rows', $subs),
2512 tpl = $('[data-name="subs_row"]', $subs).html(),
2513 playerSubs = entry.substitution.playerSubs;
2514
2515 for(j = 0; j < playerSubs.length; j++) {
2516 var sub = entry.substitution.playerSubs[j];
2517
2518 ctvApp.view.templatePlace({
2519 $target: $rows,
2520 method: 'append',
2521 template: tpl,
2522 template_data: {
2523 playerInName: sub.playerIn,
2524 playerOutName: sub.playerOut
2525 }
2526 });
2527
2528 // update team to reflect auto-subs
2529 var formationArray = _public.getFormation(entryFormation),
2530 playerInIndex = $.inArray(sub.playerInCode, entry.playerCodes),
2531 playerOutIndex = $.inArray(sub.playerOutCode, entry.playerCodes),
2532 playerIn = entryPlayers[playerInIndex],
2533 playerInBenchPrio = entryPlayers[playerInIndex].benchPriority,
2534 playerOutBenchPrio = entryPlayers[playerOutIndex].benchPriority,
2535 playerInCategoryIndex = $('.positions_category', _private.$wrapper).index($('[data-categoryid=' + entryPlayers[playerInIndex].categoryId + ']')),
2536 playerOutCategoryIndex = $('.positions_category', _private.$wrapper).index($('[data-categoryid=' + entryPlayers[playerOutIndex].categoryId + ']'));
2537
2538 entryPlayers[playerInIndex] = entryPlayers[playerOutIndex];
2539 entryPlayers[playerOutIndex] = playerIn;
2540 entryPlayers[playerInIndex].benchPriority = playerInBenchPrio;
2541 entryPlayers[playerOutIndex].benchPriority = playerOutBenchPrio;
2542
2543 formationArray[playerInCategoryIndex]++;
2544 formationArray[playerOutCategoryIndex]--;
2545
2546 entryFormation = formationArray.join('-');
2547 }
2548 }
2549
2550 // determine the formation
2551 var formationIx = ctvApp.config.competition.formations.indexOf(entryFormation);
2552
2553 // set up the team
2554 // only if were we able to determine the formation
2555 if(formationIx >= 0) {
2556
2557 // set up the formation
2558 _public.entry.formationIx = formationIx;
2559 _public.team.formationIx = formationIx;
2560 _private.formation_select(null, $('.formation_items [data-index=' + formationIx + ']', _public.$info));
2561
2562 // populate the team
2563 var players = {};
2564 for(e = 0; e < entryPlayers.length; e++) {
2565 player = entryPlayers[e];
2566 if(!players[player.categoryId]) {
2567 players[player.categoryId] = [];
2568 }
2569
2570 // update the player finder
2571 var row;
2572 if(_public.playerfinder) {
2573 row = _public.playerfinder.playerAdded({ player: player, batchAdding: true });
2574 }
2575
2576 // store the position
2577 players[player.categoryId].push({ player: player, row: row });
2578 }
2579 if(_public.$playerFinder) _public.$playerFinder.trigger('setRowStates');
2580
2581 // store the players
2582 _public.team.players = players;
2583
2584 // display players
2585 _private.team.displayPlayers();
2586 }
2587
2588 },
2589
2590 // Display players
2591 displayPlayers: function() {
2592 // populate the pitch
2593 _public.pitch.setPlayers();
2594 if(_public.pitch_bar) {
2595 _public.pitch_bar.setPlayers();
2596 }
2597
2598 // notify the team changes
2599 _private.team.onChange_update();
2600 },
2601
2602 // Autocomplete
2603 autocomplete: function() {
2604
2605 // temporarily switch off the "only available" option to speed up the process
2606 var onlyAvailable_selected = _public.playerfinder.onlyAvailableOff(),
2607 onlyAffordable_selected = _public.playerfinder.onlyAffordableOff(),
2608 categories = _public.gameSettings.categories,
2609 category, categoryPlayers, position,
2610 i = 0,
2611 ii = categories.length,
2612 j, jj;
2613
2614 // remove any autocompleted players
2615 _public.forEachTeamPlayer(function(data) {
2616 var position = data.player;
2617 if(position && position.player && position.player.autocomplete) _private.team.removePlayer(position);
2618 });
2619
2620 // count the empty positions
2621 // and track the side players
2622 var maxPerTeam = _public.gameSettings.maxPerTeam,
2623 positions_empty = 0,
2624 positions = [],
2625 sideSelected = {},
2626 category_empty = 0;
2627
2628 for(i = 0; i < ii; i++) {
2629 category = categories[i];
2630 categoryPlayers = _public.team.players[category.id];
2631 category_empty = 0;
2632 for(j = 0, jj = categoryPlayers.length; j < jj; j++) {
2633 position = categoryPlayers[j];
2634 if(!position) {
2635 // track the empty positions
2636 category_empty++;
2637 } else {
2638 // track the side players
2639 if(!sideSelected[position.player.sideId]) {
2640 sideSelected[position.player.sideId] = 0;
2641 }
2642 sideSelected[position.player.sideId]++;
2643 }
2644 }
2645 positions.push(category_empty);
2646 positions_empty += category_empty;
2647 }
2648
2649 // are there any free positions in the team
2650 if(positions_empty <= 0) {
2651 // no
2652
2653 // if this instance should not handle this case then exit
2654 if(_public.args.autocomplete_noFullTeam) {
2655 return;
2656 }
2657
2658 // confirm the remove all players
2659 if(!_private.team.removeAllPlayers()) {
2660 // restore the "only available" option to the previous state
2661 _public.playerfinder.onlyAvailableRestore(onlyAvailable_selected);
2662 _public.playerfinder.onlyAffordableRestore(onlyAffordable_selected);
2663 return; // break and exit the autocomplete
2664 }
2665 // confirmed to remove all the players
2666 // reset the counters
2667 positions_empty = 0;
2668 positions = [];
2669 sideSelected = {};
2670 for(i = 0; i < ii; i++) {
2671 category = categories[i];
2672 category_empty = _public.team.players[category.id].length;
2673 positions.push(category_empty);
2674 positions_empty += category_empty;
2675 }
2676 }
2677
2678 // report if there is no remaining budget
2679 var budget = _public.team.remainingBudget;
2680 if(budget <= 0) {
2681 _public.generalInfo({
2682 title: ctvApp.core.translations['Autocomplete cannot proceed'],
2683 details: ctvApp.core.translations['Insufficient remaining budget']
2684 });
2685 // restore the "only available" option to the previous state
2686 _public.playerfinder.onlyAvailableRestore(onlyAvailable_selected);
2687 _public.playerfinder.onlyAffordableRestore(onlyAffordable_selected);
2688 return;
2689 }
2690
2691 // METHOD "MIN+UPGRADES"
2692 /*
2693 example:
2694 - budget: 17
2695 - fill-in positions: 1-3-0-0
2696 - pool, for the requested positions (id_value): GK( 1_2, 2_4, 3_6, 4_6, 5_6, 6_8 ), DEF( 1_2, 2_4, 3_4, 4_6, 5_8 )
2697 - manual selection (one of the optimal variants): GK( 2_4 ), DEF( 4_6, 2_4, 1_2 ) = 16 (team value)
2698
2699 1. make a common pool and shuffle it (category_id_value):
2700 GK_4_6 | DEF_4_6 | GK_1_2 | GK_6_8 | DEF_5_8 | DEF_1_2 | GK_3_6 | DEF_3_4 | GK_5_6 | GK_2_4 | DEF_2_4
2701 - todo: optimise - remove the exhausted teams (where already is max selected)
2702
2703 2. "MINIMAL SELECTION" PART: This step ensures that we have the complete team
2704
2705 Select the cheapest (there may be non-filled positions when insufficient budget)
2706 [ loop through the selection / loop through the pool / select the pool one if it is lower than the selected one and its team is not exhausted ]
2707 GK( 1_2 ), DEF( 1_2, 3_4, 2_4 ) = 12 (team value), remaining budget: 5
2708 - track the side counters
2709
2710 3. "UPGRADES" PART: This step tries to upgrade the team
2711
2712 - track if any replacements have been made
2713
2714 Loop through the pool:
2715 - can the pool player replace any selected player and stay within the budget (test for the max players too)?
2716 - yes:
2717 - replace the first option (also, track the side counters)
2718 - or, todo: consider another criteria with the goal of spending the most of the budget in the final selection
2719 - no, continue the loop
2720
2721 Iterations over the example (notes of the replacements only):
2722 - GK_1_2 -> GK_4_6, new team value: 16
2723
2724 4: if any replacements have been made then back to the step 3
2725 - With the given example, this goes through one more loop from step 3 and there will be no replacements.
2726
2727 - The final team is:
2728 GK( 4_6 ), DEF( 1_2, 3_4, 2_4 ) = 16 (team value)
2729 */
2730
2731 /*
2732 1. PREPARE THE POOL AND THE TEAM SELECTION
2733
2734 model player = { categoryId, sideId, value, rowId }
2735 model players = [ player, ... ]
2736 model selection = { categoryId[ player, ... ], ... }
2737 */
2738 var players = [],
2739 selection = {},
2740 pp = 0;
2741
2742 // loop through the categories
2743 // var categories = _public.gameSettings.categories; // defined previously
2744 for(var c = 0; c < categories.length; c++) {
2745
2746 // skip if the positions are not requested in the category
2747 if(positions[c] < 1) continue;
2748
2749 // get the data
2750 category = categories[c];
2751 var rows = category.dataTable.rows({ 'order': 'index' }),
2752 data = rows.data(),
2753 nodes = rows.nodes();
2754
2755 // loop through the data
2756 for(i = 0; i < data.length; i++) {
2757
2758 // skip if the player row is selected
2759 if($(nodes[i]).hasClass('selected')) continue;
2760
2761 // skip if the player has any state or a status
2762 if(data[i].status || data[i].state) continue;
2763
2764 // add the player to the players
2765 players.push({
2766 categoryId: category.id,
2767 sideId: data[i].sideId,
2768 value: data[i].value,
2769 rowId: i
2770 });
2771
2772 // prepare the side counter
2773 if(!sideSelected[data[i].sideId]) {
2774 sideSelected[data[i].sideId] = 0;
2775 }
2776 }
2777
2778 // create the position placeholders (null)
2779 selection[category.id] = [];
2780 for(var p = 0; p < positions[c]; p++) {
2781 selection[category.id].push(null);
2782 }
2783 }
2784
2785 // shuffle the pool
2786 ctvApp.core.array_shuffle(players);
2787
2788 /*
2789 2. "MINIMAL SELECTION" PART
2790 This step ensures that we have the complete team
2791 */
2792 // track the team value
2793 var teamValue = 0,
2794 pplayer, sp, splayer, cselection, cselected, sp2, splayer2;
2795 // loop through the categories
2796 for(c = 0; c < categories.length; c++) {
2797 category = categories[c];
2798 cselection = selection[category.id];
2799 // skip if the category is not in the selection
2800 if(!cselection) continue;
2801 // loop through the category selection
2802 for(sp = 0; sp < cselection.length; sp++) {
2803 splayer = cselection[sp];
2804 for(pp = 0; pp < players.length; pp++) {
2805 pplayer = players[pp];
2806 // skip if the player is of a different category
2807 if(pplayer.categoryId != category.id) continue;
2808 // skip if the player side has reached the limit
2809 if(sideSelected[pplayer.sideId] >= maxPerTeam) continue;
2810 // skip if the player has any state or a status
2811 if(pplayer.status || pplayer.state) continue;
2812 // skip if the player is already in in the category selection
2813 // todo: this can probably be handled better
2814 cselected = false;
2815 for(sp2 = 0; sp2 < cselection.length; sp2++) {
2816 splayer2 = cselection[sp2];
2817 if(splayer2 && splayer2.rowId == pplayer.rowId) {
2818 cselected = true;
2819 break;
2820 }
2821 }
2822 if(cselected) {
2823 continue;
2824 }
2825 // is the selection player empty?
2826 if(!splayer) {
2827 // selection player is empty
2828 // does the player fit in the remaining budget?
2829 if(teamValue + pplayer.value <= budget) {
2830 // yes, take the pool player
2831 splayer = pplayer;
2832 // increase the team value
2833 teamValue += splayer.value;
2834 // increase the side counter
2835 sideSelected[splayer.sideId]++;
2836 }
2837 } else {
2838 // selection player is not empty
2839 // is the pool player cheaper?
2840 if(pplayer.value < splayer.value) {
2841 // switch the players
2842 // decrease the team value
2843 teamValue -= splayer.value;
2844 // decrease the side counter
2845 sideSelected[splayer.sideId]--;
2846 // take the pool player
2847 splayer = pplayer;
2848 // increase the team value
2849 teamValue += splayer.value;
2850 // increase the side counter
2851 sideSelected[splayer.sideId]++;
2852 }
2853 }
2854 }
2855 // update the selection
2856 cselection[sp] = splayer;
2857 }
2858 // update the selection
2859 selection[category.id] = cselection;
2860 }
2861
2862 /*
2863 3. "UPGRADES" PART
2864 This step tries to upgrade the team
2865 */
2866
2867 //track if any replacements have been made
2868 var replacements;
2869 do {
2870
2871 replacements = false;
2872
2873 // Loop through the pool:
2874 for(pp = 0; pp < players.length; pp++) {
2875
2876 // can the pool player replace any selected player and stay within the budget (test for the max players too)?
2877 pplayer = players[pp];
2878 // tracks the index of the selected player to replace
2879 cselection = selection[pplayer.categoryId];
2880 // skip if the category is not in the selection
2881 if(!cselection) continue;
2882 // loop through the category selection
2883 for(sp = 0; sp < cselection.length; sp++) {
2884
2885 splayer = cselection[sp];
2886 // break if we don't have further selections in this category
2887 if(!splayer) break;
2888
2889 // skip if the pool player is not more expensive
2890 if(pplayer.value <= splayer.value) continue;
2891 // skip if the pool player's side has reached the limit
2892 if(sideSelected[pplayer.sideId] >= maxPerTeam) continue;
2893 // skip if the pool player is too expensive
2894 if(teamValue - splayer.value + pplayer.value > budget) continue;
2895 // skip if the player is already in in the category selection
2896 // todo: this can probably be handled better
2897 cselected = false;
2898 for(sp2 = 0; sp2 < cselection.length; sp2++) {
2899 splayer2 = cselection[sp2];
2900 if(splayer2 && splayer2.rowId == pplayer.rowId) {
2901 cselected = true;
2902 break;
2903 }
2904 }
2905 if(cselected) continue;
2906
2907 // everything ok
2908 // switch the players
2909 // decrease the team value
2910 teamValue -= splayer.value;
2911 // decrease the side counter
2912 sideSelected[splayer.sideId]--;
2913 // take the pool player
2914 splayer = pplayer;
2915 // increase the team value
2916 teamValue += splayer.value;
2917 // increase the side counter
2918 sideSelected[splayer.sideId]++;
2919 // mark the replacement has been made
2920 replacements = true;
2921 // store the selection
2922 cselection[sp] = splayer;
2923 // end the loop
2924 break;
2925 }
2926 // store the selection
2927 selection[pplayer.categoryId] = cselection;
2928 }
2929 } while (replacements); // repeat this if there were any replacements
2930
2931 // by here we have the final team
2932
2933 // PLACE THE SELECTION
2934 // loop through the categories
2935 // mark if any position has no offers by autocomplete
2936 var noOffer = false;
2937 for(c = 0; c < categories.length; c++) {
2938 category = categories[c];
2939 cselection = selection[category.id];
2940 // skip if the category is not in the selection
2941 if(!cselection) continue;
2942 if(!cselection[0]) {
2943 // we have some empty selections
2944 noOffer = true;
2945 continue;
2946 }
2947 // loop through the category selection
2948 for(sp = 0; sp < cselection.length; sp++) {
2949 splayer = cselection[sp];
2950 // break if we don't have further selections in this category
2951 if(!splayer) {
2952 // we have some empty selections
2953 noOffer = true;
2954 break;
2955 }
2956 var row = category.dataTable.row(splayer.rowId);
2957 var player = row.data();
2958 player.autocomplete = true;
2959 _private.team.addPlayer({
2960 row: row.node(),
2961 player: player,
2962 batchAdding: true
2963 });
2964 }
2965 }
2966 if(_public.$playerFinder) _public.$playerFinder.trigger('setRowStates');
2967
2968 // restore the "only available" option to the previous state
2969 _public.playerfinder.onlyAvailableRestore(onlyAvailable_selected);
2970 _public.playerfinder.onlyAffordableRestore(onlyAffordable_selected);
2971
2972 // if there have been any no-offers then notify the user
2973 if(noOffer) {
2974 _public.generalInfo({
2975 title: ctvApp.core.translations['Autocomplete cannot fill in all the positions'],
2976 details: ctvApp.core.translations['Try removing some selected players to increase the reamaining budget.']
2977 });
2978 }
2979
2980 // set captain
2981 if(!_public.team.captain) {
2982 var captain = {},
2983 captValue = 0;
2984 _public.forEachTeamPlayer(function(data) {
2985 var position = data.player.player,
2986 isBenchPlayer = typeof position.benchPriority !== 'undefined' && position.benchPriority >= 0;
2987 //we want the highest valued non-bench player
2988 if(position.value >= captValue && !isBenchPlayer) {
2989 captValue = position.value;
2990 captain.categoryId = position.categoryId;
2991 captain.position = data.playerIndex;
2992 }
2993 });
2994 _private.team.selectCaptain({ categoryId: captain.categoryId, position: captain.position });
2995 _public.captain_onChange();
2996 }
2997
2998 // set vice captain
2999 if(!_public.team.viceCaptain) {
3000 var viceCaptain = {},
3001 viceCaptValue = 0;
3002 _public.forEachTeamPlayer(function(data) {
3003 var position = data.player.player,
3004 isCaptain = data.playerIndex === captain.position,
3005 isBenchPlayer = typeof position.benchPriority !== 'undefined' && position.benchPriority >= 0;
3006 //we want the highest valued non-bench player which is not the captain
3007 if(position.value >= viceCaptValue && !isBenchPlayer && !isCaptain) {
3008 viceCaptValue = position.value;
3009 viceCaptain.categoryId = position.categoryId;
3010 viceCaptain.position = data.playerIndex;
3011 }
3012 });
3013 _private.team.selectViceCaptain({ categoryId: viceCaptain.categoryId, position: viceCaptain.position });
3014 _public.viceCaptain_onChange();
3015 }
3016
3017 _public.$wrapper.trigger('toggleSidebar');
3018
3019 // update the global team changes states
3020 _public.updateTeamChanges();
3021 },
3022
3023 /*
3024 Set the player as captain (and un-select any other)
3025 - args: categoryId, position
3026 */
3027 selectCaptain: function(args) {
3028 // unselect any existing
3029 if(_public.team.captain) {
3030 _public.team.captain.captain = false;
3031 }
3032 // set the current
3033 var player = _public.team.players[args.categoryId][args.position].player;
3034 player.captain = true;
3035 _public.team.captain = player;
3036 // unselect them as viceCaptain
3037 if(_public.team.captain.viceCaptain) {
3038 _public.team.captain.viceCaptain = false;
3039 _public.team.viceCaptain = false;
3040 }
3041 // update the dropdown
3042 _public.captain_add(player);
3043 // update the global team changes states
3044 _public.updateTeamChanges();
3045 },
3046
3047 /*
3048 Set the player as vice-captain (and un-select any other)
3049 - args: categoryId, position
3050 */
3051 selectViceCaptain: function(args) {
3052 // unselect any existing
3053 if(_public.team.viceCaptain) {
3054 _public.team.viceCaptain.viceCaptain = false;
3055 }
3056 // set the current
3057 var player = _public.team.players[args.categoryId][args.position].player;
3058 player.viceCaptain = true;
3059 _public.team.viceCaptain = player;
3060 // unselect them as captain
3061 if(_public.team.viceCaptain.captain) {
3062 _public.team.viceCaptain.captain = false;
3063 _public.team.captain = false;
3064 }
3065 // update the dropdown
3066 _public.viceCaptain_add(player);
3067 // update the global team changes states
3068 _public.updateTeamChanges();
3069 },
3070
3071 /*
3072 Set the player as rule-scorer (and un-select any other)
3073 - args: categoryId, position
3074 */
3075 selectRuleScorer: function(args) {
3076 // unselect any existing
3077 if(_public.team.ruleScorer) {
3078 _public.team.ruleScorer.ruleScorer = false;
3079 }
3080 // set the current
3081 var player = _public.team.players[args.categoryId][args.position].player;
3082 player.ruleScorer = true;
3083 _public.team.ruleScorer = player;
3084 // update the dropdown
3085 _public.ruleScorer_add(player);
3086 // update the global team changes states
3087 _public.updateTeamChanges();
3088 },
3089
3090 // Display the player info popup
3091 playerInfo: function(args) {
3092 var playerCode = args.playerCode || '';
3093 // get the player
3094 if(!playerCode) {
3095 var player = _public.team.players[args.categoryId][args.position].player;
3096 // code to work with the old player info
3097 playerCode = player.code;
3098 }
3099
3100 // get the player profile
3101 ctvApp.API.getPlayerProfile({
3102 playerCode: playerCode,
3103 callback: function(data) {
3104
3105 // display the player profile
3106
3107 // define the modal
3108 var player = data;
3109 var $playerProfile = $('#modal_playerProfile');
3110
3111 // populate the modal
3112
3113 // name
3114 $('.js_name', $playerProfile).html(player.info1);
3115
3116 // image
3117 _public.insertPlayerImage({
3118 '$container': $('.js_image', $playerProfile),
3119 'player': player,
3120 'alt': player.name
3121 });
3122
3123 // todo: playerImage is defined outside this module, should be implemented better
3124 /* $( '.js_image', $playerProfile ).empty().append( '<img ' +
3125 'src="' + _public.getPlayerImage(player.code, player.sideInfo1) + '" ' +
3126 'alt="' + player.info1 + '" ' +
3127 'title="' + player.info1 + '">'
3128 );*/
3129
3130 // absence
3131 var $jsNews = $('.js_news', $playerProfile);
3132 $jsNews.addClass('hide').removeClass('status-i status-s status-d status-u');
3133 var absenceTypes = ['I', 'S', 'D', 'U'];
3134 if(player.status && $.inArray(player.status, absenceTypes) > -1) {
3135
3136 $jsNews.removeClass('hide').addClass('status-' + player.status.toLowerCase());
3137 $jsNews.find('.absence-type').text(ctvApp.core.translations.player_status[player.status]);
3138 $jsNews.find('.absence-reason').toggleClass('hide', !player.absenseReason).text((player.absenseReason) ? "- " + player.absenseReason : "");
3139 $jsNews.find('.absence-date').toggleClass('hide', !player.returnDate).find('span').text((player.returnDate) ? player.returnDate : "");
3140 }
3141
3142 // side
3143 player.sideInfo1 = $.grep(ctvApp.API.gameSettings.sides, function(side) {
3144 return(side.id == player.sideId);
3145 })[0].info1;
3146 // side
3147 player.side = $.grep(ctvApp.API.gameSettings.sides, function(side) {
3148 return(side.id == player.sideId);
3149 })[0].name;
3150 $('.js_side', $playerProfile).html(player.side);
3151
3152 // category
3153 player.category_name = $.grep(ctvApp.API.gameSettings.categories, function(category) {
3154 return(category.id == player.categoryId);
3155 })[0].name;
3156 $('.js_category', $playerProfile).html(player.category_name);
3157
3158 // general info
3159 $('.js_value', $playerProfile).html(player.value);
3160 $('.js_teamsSelected', $playerProfile).html(player.teamsSelected);
3161 $('.js_categoryRank', $playerProfile).html(player.categoryRank);
3162 $('.js_rank', $playerProfile).html(player.rank);
3163 $('.js_last_week_Points', $playerProfile).html(player.lastWeekPoints);
3164 $('.js_totalPoints', $playerProfile).html(player.totalPoints);
3165 if(ctvApp.config.previousSeasonPoints) {
3166 $('.js_lastSeasonPoints', $playerProfile).removeClass('hide');
3167 $('.js_lastSeasonPoints_value', $playerProfile).html(player.lastSeasonPoints);
3168 }
3169 if(player.starter) {
3170 $('.js_starter').removeClass('hide');
3171 } else {
3172 $('.js_starter').addClass('hide');
3173 }
3174
3175 // info2DataFields
3176 // get the info2 holder
3177 var $info2DataHolder = $('[info2DataHolder]', $playerProfile);
3178 // remove the existing info2 rows
3179 $('.info2Data', $info2DataHolder).remove();
3180 // if there are any defined fields and if there is any data
3181 if(typeof(player.info2) !== 'undefined' && ctvApp.config.info2DataFields) {
3182 // split the data
3183 var title = ctvApp.config.info2DataFields;
3184 var value = player.info2.split(':');
3185 // get the template
3186 var tpl = $('[data-name="info2DataFields"]', $playerProfile).html()
3187 // while there are defined fields and the available data
3188 var i = 0;
3189 while(typeof(title[i]) !== 'undefined' && typeof(value[i]) !== 'undefined') {
3190 // prepare the title (translation or fall back to original)
3191 var tpl_title = ctvApp.core.translations[title[i]];
3192 if(typeof(tpl_title) == 'undefined') tpl_title = title[i];
3193 // append the template to the holder
3194 ctvApp.view.templatePlace({
3195 $target: $info2DataHolder,
3196 template: tpl,
3197 template_data: {
3198 title: tpl_title,
3199 value: value[i]
3200 }
3201 });
3202 // next
3203 i++;
3204 }
3205 }
3206
3207 // fixtures
3208 var $table = $('.js_fixtures', $playerProfile).empty();
3209 $table.append($(
3210 '<thead>' +
3211 '<tr>' +
3212 '<th>' + ctvApp.core.translations.Date + '</th>' +
3213 '<th>' + ctvApp.core.translations.Opponent + '</th>' +
3214 '</tr>' +
3215 '</thead>' +
3216 '<tbody></tbody>'
3217 ));
3218 var $tbody = $('tbody', $table),
3219 i, fixture, opponentId;
3220
3221 if(player.fixtures) {
3222 for(i = 0; i < player.fixtures.length; i++) {
3223 fixture = player.fixtures[i];
3224 // opponent
3225 opponentId = (player.sideId == fixture.teamAId) ? fixture.teamBId : fixture.teamAId;
3226
3227 fixture.opponent = $.grep(ctvApp.API.gameSettings.sides, function(side) {
3228 return(side.id == opponentId);
3229 })[0];
3230
3231 // formatted date
3232 var dateFormat = ctvApp.config.dateFormat || 'dd/MM/yyyy HH:mm';
3233 fixture.dateFormatted = $.format.date(fixture.date, dateFormat);
3234
3235 // append the row
3236 $tbody.append($(
3237 '<tr>' +
3238 '<td>' + fixture.dateFormatted + '</td>' +
3239 '<td class="side side_' + fixture.opponent.abbr + '">' + fixture.opponent.name + '</td>' +
3240 '</tr>'
3241 ));
3242 }
3243 }
3244
3245 // history
3246 $table = $('.js_history', $playerProfile).empty();
3247 $table.append($(
3248 '<thead>' +
3249 '<tr>' +
3250 '<th>' + ctvApp.core.translations.GW + '</th>' +
3251 '<th class="playerinfo-versus">' + ctvApp.core.translations.vs + '</th>' +
3252 '</tr>' +
3253 '</thead>' +
3254 '<tbody></tbody>'
3255 ));
3256 var $row = $('thead tr', $table),
3257 gameRules = ctvApp.fantasyteams.gameSettings.rules;
3258
3259 $tbody = $('tbody', $table);
3260
3261 if(ctvApp.config.competition.weekly) {
3262 $row.append($(
3263 '<th>' + ctvApp.core.translations.TSB + '</th>'
3264 ));
3265 }
3266
3267 // stat headings
3268 for(i = 0; i < gameRules.length; i++) {
3269 var rule_no_space = gameRules[i].abbr.replace(/\s+/g, '');
3270 if(ctvApp.config.hideRules && ctvApp.config.hideRules.length) {
3271 if($.inArray(gameRules[i].abbr, ctvApp.config.hideRules) > -1) continue;
3272 }
3273
3274 // Do not show PTS or TSB columns in player profile
3275 if(gameRules[i].abbr.toLowerCase() === 'pts' || gameRules[i].abbr.toLowerCase() === 'pop') continue;
3276 $row.append($(
3277 '<th data-tooltip title="' + gameRules[i].desc + '" class="header_image_' + rule_no_space + '">' + gameRules[i].abbr + '</th>'
3278 ));
3279 }
3280
3281 // period points heading
3282 $row.append($(
3283 '<th data-tooltip title="' + ctvApp.core.translations['Matchday points'] + '">' +
3284 ctvApp.core.translations.MDP +
3285 '</th>'
3286 ));
3287
3288 // periods
3289 if(player.history) {
3290 var historyLength = player.history.length;
3291 for(i = 0; i < player.history.length; i++) {
3292 var period = player.history[i];
3293
3294 // period fixtures
3295 if(period.fixture) {
3296 for(var j = 0; j < period.fixture.length; j++) {
3297 fixture = period.fixture[j];
3298 // init the row
3299 $row = $('<tr></tr>');
3300 // period name
3301 $row.append($('<td>' + (historyLength - i) + '</td>'));
3302 // opponent
3303 var previousSideDate = new Date(player.previousSideDate),
3304 fixtureDate = new Date(fixture.date);
3305
3306 if(!isNaN(previousSideDate.getTime()) && previousSideDate.getTime() > fixtureDate.getTime()) {
3307 opponentId = (player.previousSideId === fixture.teamAId) ? fixture.teamBId : fixture.teamAId;
3308 } else {
3309 opponentId = (player.sideId === fixture.teamAId) ? fixture.teamBId : fixture.teamAId;
3310 }
3311 var venue = ' (A)';
3312 if(opponentId !== fixture.teamAId) {
3313 venue = ' (H)';
3314 }
3315 fixture.opponent = $.grep(ctvApp.API.gameSettings.sides, function(side) {
3316 return(side.id == opponentId);
3317 })[0];
3318 $row.append($(
3319 '<td class="side side_' + fixture.opponent.abbr + ' playerinfo-versus">' + fixture.opponent.name + venue + '</td>'
3320 ));
3321
3322 if(ctvApp.config.competition.weekly) {
3323 $row.append($(
3324 '<td>' + period.weeklyPopularity + '%</td>'
3325 ));
3326 }
3327
3328 // stats
3329 var points = 0;
3330 for(var k = 0; k < gameRules.length; k++) {
3331 if(ctvApp.config.hideRules && ctvApp.config.hideRules.length) {
3332 if($.inArray(gameRules[k].abbr, ctvApp.config.hideRules) > -1) continue;
3333 }
3334 // Do not show PTS or TSB columns in player profile
3335 if(gameRules[k].abbr.toLowerCase() === 'pts' || gameRules[k].abbr.toLowerCase() === 'pop') continue;
3336 var fixtureRules = [];
3337 if(fixture.rules) {
3338 fixtureRules = $.grep(fixture.rules, function(fixtureRule) {
3339 return(gameRules[k].abbr == fixtureRule.abbr);
3340 });
3341 }
3342
3343 points = 0;
3344 if(fixtureRules.length) points = fixtureRules[0].count;
3345 $row.append($(
3346 '<td>' + points + '</td>'
3347 ));
3348
3349 }
3350
3351 // period points
3352 points = period.points || 0;
3353 $row.append($(
3354 '<td>' + points + '</td>'
3355 ));
3356
3357 // append the row
3358 $tbody.append($row);
3359 }
3360 }
3361 }
3362 }
3363
3364 // display the modal
3365 $playerProfile.foundation('reveal', 'open');
3366 }
3367 });
3368 },
3369
3370 /*
3371 Add the player
3372 - args.row = row element - required for the response to the player finder
3373 - args.player = player object
3374 */
3375 addPlayer: function(args) {
3376
3377 var player = args.player,
3378 row = args.row,
3379 info_message = ''; // collect information to be displayed to the user (in case of any issues)
3380
3381
3382 // is the remaining budget sufficient
3383 if(_public.team.remainingBudget < player.value) {
3384
3385 // no budget - report
3386 info_message += '<p>' + ctvApp.core.translations['You do not have enough budget to afford'] + ' ' + player.name + '</p>';
3387 } else {
3388
3389 // count the team occurances
3390 var teamCounter = 0;
3391
3392 _public.forEachTeamPlayer(function(data) {
3393 if(data.player) teamCounter += (data.player.player.side == player.side);
3394 });
3395
3396 // did we reach the max players per team?
3397 if(teamCounter >= _public.gameSettings.maxPerTeam) {
3398
3399 // max teams - report
3400 info_message += '<p>' + ctvApp.core.translations['You have already selected the maximum players from'] + ' ' + player.side + '</p>';
3401 } else {
3402
3403 // get the category id for the player category abbreviation
3404 var categoryId = player.categoryId;
3405
3406 // get the first available place in the category
3407 var position = -1;
3408 var players = _public.team.players[categoryId];
3409 for(var p = 0; p < players.length; p++) {
3410 if(players[p] === null) {
3411 position = p;
3412 break;
3413 }
3414 }
3415
3416 var filledPositions = _private.team.getFilledPositions(),
3417 i = 0;
3418 //If fluid formations are enabled, check if it is possible to add player by switching formation
3419 if(position < 0 && ctvApp.config.fluidFormations) {
3420 var categoryTeamMax = _public.getCategoryBounds(categoryId).teamMax,
3421 categoryIndex = _public.getCategoryIndex(categoryId);
3422 if(categoryTeamMax > players.length) {
3423 var desiredPlayers = players.length + 1,
3424 formations = ctvApp.config.competition.formations;
3425 for(i = 0; i < formations.length; i++) {
3426 var newFormation = _public.getFormation(formations[i]),
3427 oldFormation = _public.getFormation();
3428 if(newFormation[categoryIndex] === desiredPlayers) {
3429 for(var j = 0; j < newFormation.length; j++) {
3430 if(newFormation[j] < oldFormation[j] && newFormation[j] >= filledPositions[j]) {
3431 _private.formation_select(null, $('.formation_items [data-index=' + i + ']', _public.$info));
3432 i = formations.length;
3433 position = desiredPlayers - 1;
3434 break;
3435 }
3436 }
3437 }
3438 }
3439 }
3440 }
3441
3442 // is there an available place in the category
3443 if(position < 0) {
3444 var currentCategory = ctvApp.fantasyteams.gameSettings.categoryIds[categoryId],
3445 currentCategoryTeamMax = _public.getCategoryBounds(categoryId).teamMax;
3446
3447 if(!ctvApp.config.fluidFormations || currentCategoryTeamMax <= players.length) {
3448 // no place - report
3449 info_message += '<p>' + ctvApp.core.translations['Sorry, you have no room for any more'] + ' ' + currentCategory.name + '</p>';
3450 } else {
3451 var messageSet = false;
3452 for(i = 0; i < filledPositions.length; i++) {
3453 var cat = ctvApp.fantasyteams.gameSettings.categories[i],
3454 catMin = _public.getCategoryBounds(cat.id).teamMin;
3455 if(filledPositions[i] < catMin) {
3456 messageSet = true;
3457 info_message += '<p>' + ctvApp.core.translations['Sorry, you need at least'] + ' ' + catMin + ' ' + cat.name + '</p>';
3458 continue;
3459 }
3460 }
3461
3462 //
3463 if(!messageSet) {
3464 info_message += '<p>' + ctvApp.core.translations['Please check your formation'] + '</p>';
3465 }
3466 }
3467 } else {
3468
3469 // are there remaining transfers
3470 // or the player exists in the original entry
3471 var newPlayer = false;
3472 if(_public.entry) {
3473 if(_public.entry.playerCodes.indexOf(player.code) < 0) {
3474 newPlayer = true;
3475 }
3476 }
3477 if(!_public.args.limitTransfers || _public.$wrapper.attr('data-wildcard') === 'activation' || _public.team.remainingTransfers || !newPlayer) {
3478
3479 // add the player to the team
3480 players[position] = {
3481 player: player,
3482 row: row
3483 };
3484
3485 // update the rulescorer state
3486 // if there is no current rulescorer
3487 // and there is the reversion info
3488 if(!_public.team.ruleScorer && _public.team.ruleScorer_revert) {
3489 // if the category/position matches
3490 if(categoryId == _public.team.ruleScorer_revert.categoryId && position == _public.team.ruleScorer_revert.position) {
3491 // set the player as the rulescorer
3492 _private.team.selectRuleScorer({
3493 categoryId: categoryId,
3494 position: position
3495 });
3496 }
3497 }
3498
3499 // mark if the player is new or not
3500 player.new = newPlayer;
3501
3502 // update the remaining budget
3503 _private.team.setBudget({ decrease: player.value });
3504
3505 // update the pitch ui
3506 if(_public.pitch) {
3507 _public.pitch.setSlot({ player: player, slot: position });
3508 }
3509 if(_public.pitch_bar) {
3510 _public.pitch_bar.setSlot({ player: player, slot: position });
3511 }
3512
3513 // update the player finder
3514 _public.playerfinder.playerAdded({ row: args.row, batchAdding: (args.batchAdding) ? args.batchAdding : false });
3515
3516 var categoryMax = _public.getFormationPartFromCategoryId(player.categoryId);
3517 if(position < categoryMax) {
3518 // update the rule scorer dropdown
3519 _public.ruleScorer_add(player);
3520
3521 // update the captain dropdown
3522 _public.captain_add(player);
3523
3524 // update the vice captain dropdown
3525 _public.viceCaptain_add(player);
3526 }
3527
3528 // notify the team changes
3529 _private.team.onChange_update();
3530
3531 // show the 'empty the team' button
3532 $('.fantasyteams_emptyTeam', _public.$wrapper).removeClass('hide');
3533
3534 // show the 'keep all players' button
3535 $('.fantasyteams_keepAllPlayers', _public.$wrapper).removeClass('hide');
3536 }
3537 }
3538 }
3539 }
3540
3541 // display any messages for the user
3542 if(info_message !== '') {
3543 _public.generalInfo({
3544 title: player.name + ' ' + ctvApp.core.translations['has not been added!'],
3545 details: info_message
3546 });
3547 } else {
3548 _public.$wrapper.trigger('toggleSidebar');
3549 }
3550
3551 // update the global team changes states
3552 _public.updateTeamChanges();
3553 },
3554
3555 /*
3556 Remove the player
3557 accepts either args set:
3558 player, row
3559 args.row = row element - currently required for the response to the player finder
3560 args.player = player object
3561 categoryId, position
3562 */
3563 removePlayer: function(args) {
3564
3565 var categoryId, position, players, player, row;
3566 // check the args set
3567 if(args.player && args.row) {
3568
3569 // player and row
3570 player = args.player;
3571 row = args.row;
3572 // set autocomplete to false
3573 player.autocomplete = false;
3574
3575 // get the category id for the player category abbreviation
3576 categoryId = player.categoryId;
3577
3578 // find the player in the category
3579 position = -1;
3580 players = _public.team.players[categoryId];
3581 for(var p = 0; p < players.length; p++) {
3582 if(players[p]) {
3583 if(players[p].player.code == player.code) {
3584 position = p;
3585 break;
3586 }
3587 }
3588 }
3589 } else {
3590
3591 // categoryId and position
3592 categoryId = args.categoryId;
3593 position = args.position;
3594 players = _public.team.players[categoryId];
3595 player = players[position].player;
3596 row = players[position].row;
3597 }
3598
3599 // has the player been found?
3600 if(position >= 0) {
3601
3602 // remove the player from the team
3603 players[position] = null;
3604
3605 // update the remaining budget
3606 _private.team.setBudget({ increase: player.value });
3607
3608 // turn off the player's flags
3609 player.autocomplete = false;
3610 player.captain = false;
3611 player.viceCaptain = false;
3612 player.ruleScorer = false;
3613 if(_public.team.captain) {
3614 if(_public.team.captain.code == player.code) {
3615 _public.team.captain = null;
3616 }
3617 }
3618 if(_public.team.viceCaptain) {
3619 if(_public.team.viceCaptain.code == player.code) {
3620 _public.team.viceCaptain = null;
3621 }
3622 }
3623 if(_public.team.ruleScorer) {
3624 if(_public.team.ruleScorer.code == player.code) {
3625 _public.team.ruleScorer = null;
3626 // keep the info for the reversion
3627 _public.team.ruleScorer_revert = {
3628 categoryId: categoryId,
3629 position: position
3630 };
3631 }
3632 }
3633
3634 // update the pitch ui
3635 _public.pitch.removePlayer({ player: player, slot: position });
3636 if(_public.pitch_bar) {
3637 _public.pitch_bar.removePlayer({ player: player, slot: position });
3638 }
3639
3640 // update the player finder
3641 _public.playerfinder.playerRemoved({ row: row });
3642
3643 // update the rule scorer dropdown
3644 _public.ruleScorer_remove(player);
3645
3646 // update the captain dropdown
3647 _public.captain_remove(player);
3648
3649 // update the vice captain dropdown
3650 _public.viceCaptain_remove(player);
3651
3652 // notify the team changes
3653 _private.team.onChange_update();
3654
3655 // if no players in the team
3656 if(!_public.getTeamPlayersCount()) {
3657 $('.fantasyteams_emptyTeam', _public.$wrapper).addClass('hide');
3658 }
3659
3660 // if no players in the team have the autocomplete
3661 if(!_public.getTeamPlayersCount({ autocomplete: true })) {
3662 $('.fantasyteams_keepAllPlayers', _public.$wrapper).addClass('hide');
3663 }
3664 }
3665
3666 // update the global team changes states
3667 _public.updateTeamChanges();
3668 },
3669
3670 /*
3671 Remove all the players from team
3672 - if args.noConfirmation is true then this will not ask the user for the confirmation
3673 */
3674 removeAllPlayers: function(args) {
3675 args = args || {};
3676 // ask the user to confirm
3677 if(!args.noConfirmation) {
3678 ctvApp.view.modalConfirm({
3679 title: ctvApp.core.translations['Confirm'],
3680 details: ctvApp.core.translations['This will replace your whole team. Do you want to proceed?'],
3681 callback: _private.team.removeAllPlayersCallback
3682 });
3683
3684 // var c = confirm( ctvApp.core.translations[ 'This will replace your whole team. Do you want to proceed?' ]);
3685 // if( !c ){ return; };
3686 } else {
3687 _private.team.removeAllPlayersCallback();
3688 }
3689
3690 },
3691
3692 removeAllPlayersCallback: function() {
3693 // temporarily switch off the "only available" option to speed up the process
3694 var onlyAvailable_selected = _public.playerfinder.onlyAvailableOff();
3695 var onlyAffordable_selected = _public.playerfinder.onlyAffordableOff();
3696
3697 // remove all the players
3698 _public.forEachTeamPlayer(function(data) {
3699 if(data.player) {
3700 _private.team.removePlayer(data.player);
3701 }
3702 });
3703 // restore the "only available" option to the previous state
3704 _public.playerfinder.onlyAvailableRestore(onlyAvailable_selected);
3705 _public.playerfinder.onlyAffordableRestore(onlyAffordable_selected);
3706 },
3707
3708 /*
3709 Keep the player (accept it permanently from the autocomplete)
3710 - args: categoryId, position
3711 - this assumes that the pitch slot has already been set up (autocomplete removed)
3712 */
3713 keepPlayer: function(args) {
3714 // categoryId and position
3715 var categoryId = args.categoryId;
3716 var position = args.position;
3717 var players = _public.team.players[categoryId];
3718 players[position].player.autocomplete = false;
3719 // if no players in the team have the autocomplete
3720 if(!_public.getTeamPlayersCount({ autocomplete: true })) {
3721 $('.fantasyteams_keepAllPlayers', _public.$wrapper).addClass('hide');
3722 }
3723 // notify the team changes
3724 _private.team.onChange_update();
3725 },
3726
3727 // Keep all the players from team
3728 keepAllPlayers: function() {
3729 _public.forEachTeamPlayer(function(data) {
3730 if(data.player) {
3731 _private.team.keepPlayer({ categoryId: data.category.id, position: data.playerIndex });
3732 _public.pitch.removeAutocomplete({ categoryId: data.category.id, position: data.playerIndex });
3733 if(_public.pitch_bar) {
3734 _public.pitch_bar.removeAutocomplete({ categoryId: data.category.id, position: data.playerIndex });
3735 }
3736 }
3737 });
3738 },
3739
3740 getFilledPositions: function() {
3741 var filledPositions = [],
3742 categories = _public.gameSettings.categories;
3743 for(var i = 0; i < categories.length; i++) {
3744 var category = categories[i],
3745 categoryPlayers = _public.team.players[category.id],
3746 playerCounter = 0;
3747 for(var j = 0; j < categoryPlayers.length; j++) {
3748 if(categoryPlayers[j] !== null) playerCounter++;
3749 }
3750 filledPositions.push(playerCounter);
3751 }
3752 return filledPositions;
3753 },
3754 /*
3755 Swap the slots
3756 - args.formationIx : optional
3757 - if formationIx is set, there will be a formation change
3758 - args.pitchCategory : category of the player from the pitch to go to the bench
3759 - args.pitchPosition : position of the player from the pitch to go to the bench
3760 - args.benchCategory : category of the player from the bench to go to the pitch
3761 - args.benchPosition : position of the player from the bench to go to the pitch
3762 - if formationIx is not set, there will be a change of players in the same category (any combination of bench/pitch)
3763 - args.categoryId : category of both players
3764 - args.position1 : player 1, can be from the bench or the pitch
3765 - args.position2 : player 2, can be from the bench or the pitch
3766 */
3767 swapSlots: function(args) {
3768
3769 // check for the formation changing
3770 var players;
3771
3772 if(!args.formationIx) {
3773 // no formation change
3774 // todo: maybe we could use the same method as for the formation changing
3775 // - to update the team and reset the pitch ui, instead of setting the slots
3776
3777 var position1 = args.position1,
3778 position2 = args.position2,
3779 benchPriority1 = args.benchPriority1,
3780 benchPriority2 = args.benchPriority2;
3781
3782 if(args.categoryId1) {
3783 //both on the bench, different categories
3784 var categoryId1 = args.categoryId1,
3785 categoryId2 = args.categoryId2,
3786 players1 = _public.team.players[categoryId1],
3787 players2 = _public.team.players[categoryId2];
3788
3789 players1[position1].player.benchPriority = benchPriority2;
3790 players2[position2].player.benchPriority = benchPriority1;
3791
3792 var categoriesToReorder = [categoryId1, categoryId2];
3793
3794 _public.reorderTeamCategoriesByBenchPriority(categoriesToReorder);
3795
3796 } else {
3797 var categoryId = args.categoryId;
3798
3799 // get the players (note: a slot may be empty)
3800 players = _public.team.players[categoryId];
3801 // swap the slots
3802 var swap = players[position1];
3803 players[position1] = players[position2];
3804 if(players[position1]) players[position1].player.benchPriority = benchPriority1;
3805 players[position2] = swap;
3806 if(players[position2]) players[position2].player.benchPriority = benchPriority2;
3807 // get each player
3808 var player1, player2;
3809 if(players[position1]) { player1 = players[position1].player; }
3810 if(players[position2]) { player2 = players[position2].player; }
3811
3812 // update the pitch ui
3813 // send the player or categoryId if no player
3814 // todo: we could have swapped the slot elements on ui instead of updating them but it would break the drag&drop
3815 /*
3816 _public.pitch.setSlot({ categoryId: categoryId, player: player1, slot: position1 });
3817 _public.pitch.setSlot({ categoryId: categoryId, player: player2, slot: position2 });
3818 if( _public.pitch_bar ){
3819 _public.pitch_bar.setSlot({ categoryId: categoryId, player: player1, slot: position1 });
3820 _public.pitch_bar.setSlot({ categoryId: categoryId, player: player2, slot: position2 });
3821 }
3822 */
3823 }
3824 // update the pitch ui
3825 _public.pitch.setPlayers();
3826 if(_public.pitch_bar) {
3827 _public.pitch_bar.setPlayers();
3828 }
3829 // update the global team changes states
3830 _public.updateTeamChanges();
3831 } else {
3832
3833 // formation changing
3834
3835 var formationIx = args.formationIx;
3836 var pitchCategory = args.pitchCategory;
3837 var pitchPosition = args.pitchPosition;
3838 var benchCategory = args.benchCategory;
3839 var benchPosition = args.benchPosition;
3840 var benchModifier = args.benchModifier; // Number of pitchCategory bench players that are preceding the new one
3841 var benchPriority = args.benchPriority;
3842
3843 // arrange the pitch player
3844 // pull out the player
3845 players = _public.team.players[pitchCategory];
3846 var player = players[pitchPosition];
3847 if(player && player.player) player.player.benchPriority = benchPriority;
3848
3849 // goes to the last position of the current formation increased by the benchModifier
3850 var newPosition = benchModifier + _public.getFormationPartFromCategoryId(pitchCategory) - 1;
3851
3852 // pull the players
3853 for(var p = pitchPosition; p < newPosition; p++) {
3854 players[p] = players[p + 1];
3855 }
3856 // insert the player
3857 players[newPosition] = player;
3858
3859 // bench player
3860 players = _public.team.players[benchCategory];
3861 player = players[benchPosition];
3862 if(player && player.player) player.player.benchPriority = -1;
3863 // goes to one position past the current formation
3864 newPosition = _public.getFormationPartFromCategoryId(benchCategory);
3865 // pull the players
3866 for(p = benchPosition; newPosition < p; p--) {
3867 players[p] = players[p - 1];
3868 }
3869 // insert the player
3870 players[newPosition] = player;
3871
3872 // update the formation to formationIx
3873 // it will update the pitch ui too
3874 _private.formation_select(null, $('.formation_items [data-index=' + formationIx + ']', _public.$info));
3875 }
3876
3877 // check for captains on the bench
3878 if(_public.gameSettings.type_squad) {
3879 _public.pitch.clearBenchCaptains();
3880 if(_public.pitch_bar) {
3881 _public.pitch_bar.clearBenchCaptains();
3882 }
3883 }
3884
3885 // update the global team changes states
3886 _public.updateTeamChanges();
3887 },
3888
3889 // Update the remaining budget (args: newValue, decrease, increase)
3890 setBudget: function(args) {
3891 // calculate the new value
3892 var newValue = args.newValue;
3893 if(newValue === null || typeof newValue === 'undefined') {
3894 newValue = _public.team.remainingBudget;
3895 }
3896 newValue += (args.increase || 0);
3897 newValue -= (args.decrease || 0);
3898 // update the displayed value
3899 _public.team.remainingBudget = newValue;
3900 var $remainingBudget_value = $('.remainingBudget_value', _public.$info);
3901 $remainingBudget_value.text(_public.team.remainingBudget);
3902 },
3903
3904 // Update the remaining transfers (args: newValue, decrease, increase)
3905 setTransfers: function(args) {
3906 // calculate the new value
3907 var newValue = (typeof args.newValue !== 'undefined') ? args.newValue : _public.team.remainingTransfers;
3908 newValue += (args.increase || 0);
3909 newValue -= (args.decrease || 0);
3910 // update the displayed value
3911 if(ctvApp.config.isAmnesty) newValue = 0;
3912 _public.team.remainingTransfers = newValue;
3913 var value = newValue;
3914 if(value < 0 && _public.team.transferStatus) value = 0;
3915 $('.remainingTransfers_value', _public.$info).html((value >= 100 || ctvApp.config.isAmnesty) ? '<span class="infinity">' + ctvApp.core.translations['Infinity'] + '</span>' : value);
3916 // update the wildcard feature transfers cost
3917 var $modal = $('#modal_confirmTransfers .modal_transferCost');
3918 $modal.addClass('hide');
3919 if(newValue <= 0 && _public.team.transferStatus) {
3920 var cost = newValue * _public.team.transferStatus.periodPenaltyPoints;
3921 $('.transferCost_value', _public.$wrapper).text(cost);
3922 // styling
3923 if(cost < 0) {
3924 $('.info_transferCost', _public.$wrapper).addClass('negative');
3925 } else {
3926 $('.info_transferCost', _public.$wrapper).removeClass('negative');
3927 }
3928 // modal info
3929 if(newValue < 0) {
3930 $('.transferCost_value', $modal).text(-cost);
3931 $modal.removeClass('hide');
3932 }
3933 }
3934 // update the tranfers data
3935 value = newValue;
3936 if(value < 1) value = 0;
3937 _public.$wrapper.attr('data-transfers', value);
3938 },
3939
3940 // Updates for when the team changes
3941 onChange_update: function() {
3942
3943 // formation selector
3944 _private.formationSelector_update();
3945
3946 // hide the autocomplete if the team is full and that option is true
3947 if(_public.args.autocomplete_noFullTeam) {
3948 var $button = $('.fantasyteams_autocomplete', _public.$wrapper);
3949
3950 // is the team full (also no autocomplete flag on any player)
3951 if(_public.getTeamPlayersCount({ noAutocomplete: true }) == ctvApp.config.competition.playersInTeam) {
3952 // yes, hide the autocomplete
3953 $button.addClass('hide');
3954 } else {
3955 // no, display the autocomplete
3956 $button.removeClass('hide');
3957 }
3958 }
3959
3960 // update the transfers
3961 // if there is an entry
3962 if(_public.entry) {
3963 var remainingTransfers = _public.team.remainingTransfersInitial - _public.getTeamPlayersCount({ transfers: true });
3964 _private.team.setTransfers({ newValue: remainingTransfers });
3965 }
3966
3967 // update the global team changes states
3968 _public.updateTeamChanges();
3969 }
3970 },
3971
3972 // Switch between the pitch and the pitch-bar view
3973 switchPitch: function() {
3974 // toggle the display
3975 _public.$wrapper.toggleClass('mode-compact');
3976 /*
3977 _public.$pitch.toggleClass( 'hide' );
3978 if( _public.$pitch_bar ){
3979 _public.$pitch_bar.toggleClass( 'hide' );
3980 // toggle class on the switch wrapper
3981 $( '.fantasyteams_switchPitch', _public.$wrapper )
3982 .closest( '.fantasyteams_switchPitch_wrapper' )
3983 .toggleClass( 'mode-compact' );
3984 };
3985 // re-create on display
3986 if( _public.$pitch.hasClass( 'hide' ) ){
3987 if( _public.pitch_bar ){
3988 _public.pitch_bar.setPlayers();
3989 };
3990 } else {
3991 _public.pitch.setPlayers();
3992 };
3993 */
3994 },
3995
3996 toggleSidebar: function() {
3997 _public.$wrapper.toggleClass('sidebar-active');
3998 // Check if the media query is activated
3999 if(matchMedia(Foundation.media_queries['small-only']).matches && !_public.$wrapper.hasClass('sidebar-active') && $('.positions_player.just-added').length) {
4000 var scrollPosition = $('.positions_player.just-added').offset().top - 50;
4001 $('html, body').stop().animate({
4002 scrollTop: scrollPosition
4003 }, 300);
4004 }
4005 },
4006
4007 // Used to initially set the info elements (budget, transfers, formations)
4008 setInfo: function() {
4009
4010 // set the remaining budget and transfers
4011 _private.team.setBudget({ newValue: _public.team.remainingBudget });
4012 _private.team.setTransfers({ newValue: _public.team.remainingTransfers });
4013
4014 // formations
4015 var $items = $('.formation_items', _public.$info),
4016 tpl = $('[data-name="formation_item"]', _public.$info).html(),
4017 formations = ctvApp.config.competition.formations;
4018
4019 // add the item templates
4020 for(var fi = 0; fi < formations.length; fi++) {
4021 var formation = (ctvApp.config.formationRemoveFirstPos) ? formations[fi].substring(formations[fi].indexOf('-') + 1) : formations[fi],
4022 $item = ctvApp.view.templatePlace({
4023 $target: $items,
4024 template: tpl,
4025 template_data: {
4026 title: formation,
4027 formation: formations[fi]
4028 }
4029 }),
4030 $wrapper = $item.first();
4031
4032 $wrapper.attr('data-index', fi);
4033
4034 // when not viewOnly mode
4035 if(!_public.args.viewOnly) {
4036 // event - click
4037 $wrapper.on('click', _private.formation_select);
4038 }
4039 }
4040 // select the first one
4041 var $selected = $items.children('[data-index]:first');
4042 $selected.addClass('selected');
4043 // set it to collapsed
4044 $items.addClass('collapsed');
4045 // hide/display the formation selector based on the number of formations
4046 // hide for the single formation
4047 // display for other cases
4048 var $selector = $('.info_formation', _public.$info);
4049 if($items.children().length < 2) {
4050 $selector.hide();
4051 } else {
4052 $selector.show();
4053 }
4054 // set up the close button
4055 $('.close-formations', $selector).on('click', function() {
4056 $(this).closest('.formation_items').addClass('collapsed');
4057 });
4058 // close when clicked outside
4059 $(document).on('click', function(event) {
4060 if(!$(event.target).closest('.formation_items').length) {
4061 $('.formation_items').addClass('collapsed');
4062 }
4063 });
4064 },
4065
4066 // Wildcard - Activate
4067 wildcard_activate: function() {
4068
4069 // only while not activated
4070 if(_public.$wrapper.attr('data-wildcard') !== '') return;
4071
4072 // set the activation state
4073 _public.$wrapper.removeClass('limitedTransfers');
4074 _public.$wrapper.attr('data-wildcard', 'activation');
4075 },
4076
4077 // Wildcard - Cancel
4078 wildcard_cancel: function() {
4079
4080 // only while activation
4081 if(_public.$wrapper.attr('data-wildcard') != 'activation') return;
4082
4083 // remove the activation state
4084 _public.$wrapper.attr('data-wildcard', '');
4085
4086 if(_public.args.limitTransfers) _public.$wrapper.addClass('limitedTransfers');
4087
4088 var $modal = $('#modal_confirmTransfers');
4089 $('.modal_wildcard', $modal).addClass('hide');
4090 if($('.transferCost_value', $modal).text())
4091 $('.modal_transferCost', $modal).removeClass('hide');
4092 },
4093
4094 // Update the available formations
4095 formationSelector_update: function() {
4096
4097 // for the squad game all the formations are always available
4098 if(_public.gameSettings.type_squad) {
4099 return;
4100 }
4101
4102 // for each formation available
4103 $('.formation_items [data-index]', _public.$info).each(function() {
4104
4105 var formation = ctvApp.config.competition.formations[$(this).attr('data-index')];
4106 var formation_parts = _public.getFormation(formation);
4107
4108 // prepare the failure flag
4109 var flag;
4110
4111 // for each formation part
4112 for(var fp = 0; fp < formation_parts.length; fp++) {
4113
4114 // get the category index for the formation part
4115 var categoryId = _public.gameSettings.categories[fp].id,
4116 // get the number of used positions in the category
4117 usedPositions = 0,
4118 categoryPlayers = _public.team.players[categoryId],
4119 position;
4120
4121 for(var i = 0, ii = categoryPlayers.length; i < ii; i++) {
4122 position = categoryPlayers[i];
4123 usedPositions += (!!position);
4124 }
4125
4126 // is the number of positions less than the number of used positions
4127 if(formation_parts[fp] < usedPositions) {
4128
4129 // flag the failure and exit the loop
4130 flag = true;
4131 break;
4132 }
4133 }
4134
4135 // was it a failure
4136 if(flag) {
4137
4138 // disable the formation
4139 $(this).addClass('disabled');
4140 $(this).attr('title', ctvApp.core.translations['Please remove a player in order to change formation']);
4141
4142 } else {
4143
4144 // enable the formation
4145 $(this).removeClass('disabled');
4146 $(this).attr('title', '');
4147 }
4148 });
4149 },
4150
4151 /*
4152 Select the formation
4153 - note: requires that the present players are fitting in (ensured previous to selecting the formation)
4154 - argument $formation is optional, by default consider $( this ) as the selected formation (like in the case of the click event)
4155 */
4156 formation_select: function(event, $formation) {
4157
4158 // is a formation button (item) clicked directly?
4159 var buttonClicked = event && $(event.target).closest('.formation_items').length > 0;
4160
4161 if(!$formation) $formation = $(this);
4162
4163 // collapsed state of the list
4164 // toggle if the formation button has been clicked
4165 // otherwise, just keep it closed
4166 if(buttonClicked) {
4167 $formation.closest('.formation_items').toggleClass('collapsed');
4168 } else {
4169 $formation.closest('.formation_items').addClass('collapsed');
4170 }
4171
4172 // if the item is already selected, or is disabled then break
4173 if($formation.is('.selected,.disabled')) {
4174 return;
4175 }
4176
4177 // unselect the previous formation
4178 $('.formation_items .selected', _public.$info).removeClass('selected');
4179
4180 // set the formation
4181 var formationIx = $formation.attr('data-index');
4182 _public.team.formationIx = formationIx;
4183 $formation.addClass('selected');
4184
4185 // for the squad game update the ui and exit
4186 if(_public.gameSettings.type_squad) {
4187 _public.pitch.setPlayers({ 'updateBenchPriority': buttonClicked, 'stickyCaptain': buttonClicked });
4188 _public.pitch.clearBenchCaptains();
4189 if(_public.pitch_bar) {
4190 _public.pitch_bar.setPlayers({ 'updateBenchPriority': buttonClicked, 'stickyCaptain': buttonClicked });
4191 _public.pitch_bar.clearBenchCaptains();
4192 }
4193 // update the global team changes states
4194 _public.updateTeamChanges();
4195 return;
4196 }
4197
4198 // mark the pitch changes
4199 var pitchChanges = false;
4200
4201 // for each formation part
4202 var formation_parts = _public.getFormation();
4203 for(var fp = 0; fp < formation_parts.length; fp++) {
4204
4205 // get the category index for the formation part
4206 var categoryId = _public.gameSettings.categories[fp].id,
4207
4208 // compare the current positions with the new positions
4209 currentPositions = _public.team.players[categoryId].length,
4210 newPositions = formation_parts[fp],
4211 i = 0;
4212
4213 // no changes if the number of positions are equal
4214 if(currentPositions < newPositions) {
4215
4216 // current has less positions than the new one
4217 for(i = newPositions - currentPositions; i > 0; i--) {
4218 _public.team.players[categoryId].push(null);
4219 }
4220
4221 } else if(currentPositions > newPositions) {
4222
4223 // current has more positions than the new one
4224 // how many empty positions are required?
4225 var toEmpty = currentPositions - newPositions,
4226 // repack the array of players
4227 newPositionsArray = [],
4228 categoryPlayers = _public.team.players[categoryId],
4229 position;
4230
4231 for(i = 0; i < categoryPlayers.length; i++) {
4232 position = categoryPlayers[i];
4233 // is the position empty and we still require empty positions
4234 if(toEmpty && !position) {
4235 // don't use it and just decrease the required empty positions
4236 toEmpty--;
4237 } else {
4238 // use the existing position
4239 newPositionsArray.push(position);
4240 }
4241 }
4242 _public.team.players[categoryId] = newPositionsArray;
4243 }
4244
4245 // note the changes
4246 if(currentPositions != newPositions) {
4247 pitchChanges = true;
4248 }
4249 }
4250
4251 // if there were any changes then recreate the pitch
4252 if(pitchChanges) {
4253 _public.pitch.setPlayers();
4254 if(_public.pitch_bar) {
4255 _public.pitch_bar.setPlayers();
4256 }
4257 // update the global team changes states
4258 _public.updateTeamChanges();
4259 }
4260 },
4261
4262 // set the players from the data
4263 setPlayers: function(data) {
4264 var players = data.players,
4265 sides = _public.gameSettings.sides,
4266 playersInfo = {};
4267
4268 // transform the data
4269 for(var i = 0; i < players.length; i++) {
4270 var player = players[i],
4271 // Set the side name
4272 sideId = player.sideId,
4273 side = $.grep(sides, function(side) {
4274 return(side.id == sideId);
4275 })[0];
4276 player.side = side.name;
4277 player.sideAbbreviation = side.abbr;
4278 player.sideInfo1 = side.info1;
4279
4280 // Create rule stats
4281 player.rules = player.rules || {};
4282
4283 var rules = _public.gameSettings.rules,
4284 rule;
4285
4286 for(var j = 0; j < rules.length; j++) {
4287 rule = rules[j];
4288 // find the player rule by abbr
4289 var playerRule = $.grep(player.rules, function(playerRule) {
4290 return(playerRule.abbr == rule.abbr);
4291 }) || {};
4292 playerRule = playerRule[0] || {};
4293 // set the rule details on the player
4294 player['rule_' + rule.abbr + '_count'] = playerRule.count || 0;
4295 }
4296
4297 // additional stats are added as rules
4298 // total selected by
4299 var s = 'rule_' + ctvApp.core.translations.TSB + '_count';
4300 if(player.teams_selected) {
4301 player[s] = player.teams_selected + '%';
4302 } else {
4303 player[s] = '0%';
4304 }
4305 // total points
4306 s = 'rule_' + ctvApp.core.translations.PTS + '_count';
4307 player[s] = player.totalPoints || 0;
4308 // round points
4309 s = 'rule_' + ctvApp.core.translations.MDP + '_count';
4310 var t = 0;
4311 for(var r = 0; r < player.rules.length; r++) {
4312 t += player.rules[r].points || 0;
4313 }
4314 player[s] = t;
4315
4316 if(ctvApp.config.previousSeasonPoints) {
4317 player.lastSeasonPoints = player.lastSeasonPoints || 0;
4318 }
4319
4320 // info4
4321 player.info4 = player.info4 || '';
4322 playersInfo.anyInfo4 = playersInfo.anyInfo4 || player.info4;
4323
4324 // save the player
4325 players[i] = player;
4326 }
4327
4328 // store the players
4329 _public.players = players;
4330 _public.players.info = playersInfo;
4331 },
4332
4333 transmitTopPlayer: function(teamTopPlayer) {
4334 var topPlayer;
4335 _public.forEachTeamPlayer(function(data) {
4336 if(data.player) {
4337 if(data.player.player.code === teamTopPlayer.code) {
4338 topPlayer = data.player.player;
4339 }
4340 }
4341 });
4342
4343 //determine if there are any TC modules subscribed to the starplayer channel and send the topPlayer data
4344 if(topPlayer && ctvApp.tc && ctvApp.tc.connectors.starplayer) {
4345 ctvApp.tc.connectors.starplayer.notify(null, 'onSetStarPlayer', topPlayer, true);
4346 }
4347 }
4348 };
4349
4350 // INTERFACE
4351 return _public;
4352};
4353
4354// PLAYER FINDER MODULE
4355ctvApp.modules.playerfinder = function() {
4356 'use strict';
4357 // PARENT
4358 var _parent = {},
4359
4360 // IMPLEMENTATION
4361 _private = {
4362
4363 DT_MATCH: 'dt_match', // used for datatables filtering,
4364 categoryMode_dropdown: ctvApp.config.competition.playerFinder_categoryMode_dropdown,
4365
4366 /*
4367 this is the maximum rows to be displayed for all the categories combined
4368 it is used when some of the categories are hidden
4369 and if there are no category defaults provided for the game
4370 */
4371 results_rows: 20,
4372
4373 /*
4374 update the total number of displayed results rows
4375 this should be called on table collapse/uncollapse
4376 but currently not on the fillter applying or any other
4377 event which may change the number of rows (such as paging)
4378 */
4379 updateResultsRows: function() {
4380
4381 // check the number of uncollapsed categories
4382 var categoriesLength = 0,
4383 uncollapsed = 0,
4384 categories = _parent.gameSettings.categories;
4385 // loop through the category tables
4386 for(var i = 0; i < categories.length; i++) {
4387 categoriesLength++;
4388 if(categories[i].$table.css('display') != 'none') {
4389 uncollapsed++;
4390 }
4391 }
4392
4393 // if all are uncollapsed then set the number of rows to the default
4394 if(uncollapsed == categoriesLength) {
4395 for(i = 0; i < categories.length; i++) {
4396 if(categories[i].dataTable.page.len() !== categories[i].tablerows)
4397 categories[i].dataTable.page.len(categories[i].tablerows).draw();
4398 }
4399 } else {
4400
4401 // if some are collapsed then ditribute the rows evenly up to the maximum rows
4402 var remainingRows = _private.results_rows;
4403 var remainingTables = uncollapsed;
4404 // loop through the category tables
4405 for(i = 0; i < categories.length; i++) {
4406 var category = categories[i];
4407 // for each uncollapsed
4408 if(category.$table.css('display') != 'none') {
4409 // number of rows to be displayed
4410 var rows = Math.floor(remainingRows / remainingTables);
4411 category.dataTable.page.len(rows).draw();
4412 remainingRows -= rows;
4413 remainingTables--;
4414 }
4415 }
4416 }
4417 },
4418
4419 /*
4420 apply the currently selected filter
4421 todo: consider applying to the uncollapsed categories only
4422 it should speed up the results but has to be applied on any uncollapse
4423 todo: see if there is a way to work with the filter to show only the rows from datatable filter indexes
4424 this may be faster, and would not need the filter column
4425 */
4426 applyFilter: function() {
4427
4428 //Make a list of sides checked from the dropdown
4429 var selectedSides = [];
4430 if(ctvApp.config.playerFinderMultiSelectTeams) {
4431 $('[name="playerFinderSide"]:checked').each(function() {
4432 selectedSides.push($(this).val());
4433 });
4434 } else {
4435 if($('.playerFinder_teams option:selected', _private.$wrapper).attr('value'))
4436 selectedSides.push($('.playerFinder_teams option:selected', _private.$wrapper).val());
4437 }
4438
4439 // pick up the filter
4440 var filter = {
4441 name_normalized: ctvApp.core.normalize(
4442 $('.playerFinder_playerName', _private.$wrapper).val()
4443 ),
4444 sides: selectedSides,
4445 value_min: Foundation.utils.data_options(
4446 $('.playerFinder_value .value_slider', _private.$wrapper)
4447 ).start,
4448 value_max: $('.playerFinder_value .value_slider', _private.$wrapper).attr('data-slider'),
4449 onlyAvailable: $('.playerFinder_onlyAvailable [type=checkbox]', _private.$wrapper).prop('checked'),
4450 onlyAffordable: $('.playerFinder_onlyAffordable [type=checkbox]', _private.$wrapper).prop('checked'),
4451 onlyPlaying: $('.playerFinder_onlyPlaying [type=checkbox]', _private.$wrapper).prop('checked')
4452 };
4453
4454 if($('.playerFinder_singleTableCategories_dropdown select', _private.$wrapper).length)
4455 filter.singleTableCategory = $('.playerFinder_singleTableCategories_dropdown select', _private.$wrapper).val();
4456
4457 var remainingBudget = _parent.team.remainingBudget,
4458 categories = _parent.gameSettings.categories;
4459
4460 // loop through the category tables
4461 for(var i = 0; i < categories.length; i++) {
4462 var category = categories[i],
4463 // flag for the category matching
4464 category_match = true;
4465
4466 // only available and the open positions in category
4467 if(filter.onlyAvailable) {
4468
4469 // check if there are any available positions in the category
4470 if($.inArray(null, _parent.team.players[category.id]) < 0) {
4471 category_match = false;
4472 }
4473 }
4474
4475 // get the table
4476 var table_dt = category.dataTable;
4477
4478 // mark the matches
4479 // loop through the rows
4480 table_dt.rows().eq(0).each(function(rowIdx) {
4481
4482 // match marker value
4483 var match = _private.DT_MATCH;
4484
4485 // data
4486 var row = table_dt.row(rowIdx);
4487 var row_data = row.data();
4488
4489 // only active players (which means the state is not present)
4490 if(row_data.state) {
4491 match = '';
4492 }
4493
4494 // skip the filter if the row is selected and display it!
4495 // this is an option, which is currently not active
4496 // if( $( row.node() ).hasClass( 'selected' ) ) {
4497 if(match !== '') {
4498
4499 // if the category does not match, then mark the row as hidden
4500 if(!category_match) {
4501
4502 match = '';
4503 } else {
4504
4505 // tests for the filter
4506 // name
4507 if(filter.name_normalized) {
4508 if(row_data.dt_name_normalized.indexOf(filter.name_normalized) < 0) {
4509 match = '';
4510 }
4511 }
4512 // side
4513 if(filter.sides.length) {
4514 if($.inArray(row_data.side, filter.sides) === -1) {
4515 match = '';
4516 }
4517 }
4518 // onlyPlaying
4519 if(filter.onlyPlaying) {
4520 if(row_data.info4 === '') {
4521 match = '';
4522 }
4523 }
4524 // singleTableCategory
4525 if(filter.singleTableCategory && filter.singleTableCategory != 'all') {
4526 if(row_data.categoryId != filter.singleTableCategory) {
4527 match = '';
4528 }
4529 }
4530 // value in the range
4531 if(row_data.value < filter.value_min || row_data.value > filter.value_max) {
4532 match = '';
4533 }
4534
4535 // test for only affordable option
4536 if(filter.onlyAffordable && row_data.value > remainingBudget) {
4537 match = '';
4538 }
4539
4540 // tests for only available option
4541 if(filter.onlyAvailable) {
4542
4543 // sufficient budget
4544 if(row_data.value > remainingBudget) {
4545 match = '';
4546 }
4547
4548 // selected players and max players from the team
4549 var selected = false;
4550 var teamCounter = 0;
4551 // loop through the team
4552
4553 _parent.forEachTeamPlayer(function(data) {
4554 if(data.player) {
4555 teamCounter += (data.player.player.side == row_data.side);
4556 if(data.player.player.code == row_data.code) {
4557 selected = true;
4558 }
4559 }
4560 });
4561
4562 if(teamCounter >= _parent.gameSettings.maxPerTeam) {
4563 // already max players from the team
4564 match = '';
4565 }
4566 if(selected) {
4567 // hide the selected players
4568 match = '';
4569 }
4570 }
4571 }
4572 }
4573
4574 // mark the match
4575 table_dt.cell(rowIdx, 'dt_filter:name').data(match);
4576 });
4577
4578 // search for the matches
4579 table_dt.column('dt_filter:name').search(_private.DT_MATCH);
4580 // refresh the table
4581 table_dt.draw();
4582 }
4583 },
4584
4585 // populate the teams filter
4586 setTeams: function() {
4587 var sides = [];
4588 for(var i = 0; i < _parent.gameSettings.sides.length; i++) {
4589 //In a weekly game, use only teams playing in the active round
4590 if(!_parent.teamsPlaying || $.inArray(_parent.gameSettings.sides[i].id, _parent.teamsPlaying) > -1)
4591 sides.push(_parent.gameSettings.sides[i].name);
4592 }
4593
4594 sides.sort();
4595
4596 if(ctvApp.config.playerFinderMultiSelectTeams) {
4597 var tpl_sides = $('[data-name="playerFinder_sides_list"]', _private.$wrapper).html();
4598 ctvApp.view.templatePlace({
4599 $target: $('.playerFinder_teams', _private.$wrapper),
4600 template: tpl_sides,
4601 template_data: sides,
4602 method: 'replace'
4603 });
4604
4605 _private.$wrapper.on('click', '.pf-teams-all, .pf-teams-none', function() {
4606 var check = $(this).hasClass('pf-teams-all');
4607 _private.$wrapper.find('[name="playerFinderSide"]').prop('checked', check);
4608 _private.setTeams_callback();
4609 });
4610
4611 _private.$wrapper.on('change', '[name="playerFinderSide"]', _private.setTeams_callback);
4612 } else {
4613
4614 // populate the select
4615 ctvApp.view.selectPopulate({
4616 $element: $('.playerFinder_teams', _private.$wrapper),
4617 addAll: true,
4618 options: sides,
4619 values: sides,
4620 callback: _private.setTeams_callback
4621 });
4622 }
4623 },
4624
4625 setTeams_callback: function() {
4626 if(ctvApp.config.playerFinderMultiSelectTeams) {
4627 var count = _private.$wrapper.find('[name="playerFinderSide"]:checked').length,
4628 $button = _private.$wrapper.find('.button-pf-teams'),
4629 label = (count) ? count + " " + $button.data('label') : $button.data('label-default');
4630
4631 $button.html(label);
4632 }
4633
4634 // update the filter
4635 _private.applyFilter();
4636 },
4637
4638 // populate the stats filter
4639 setStats: function() {
4640 var ruleDescriptions = [];
4641 var ruleAbbreviations = [];
4642 for(var i = 0; i < _parent.gameSettings.rules.length; i++) {
4643 ruleDescriptions.push(_parent.gameSettings.rules[i].desc);
4644 ruleAbbreviations.push(_parent.gameSettings.rules[i].abbr);
4645 }
4646
4647 if(ctvApp.config.previousSeasonPoints) {
4648 ruleDescriptions.push(ctvApp.core.translations['Previous Season Points']);
4649 ruleAbbreviations.push(ctvApp.core.translations['PSP']);
4650 }
4651
4652 ruleDescriptions.push(ctvApp.core.translations.previous_matchday_points);
4653 ruleAbbreviations.push(ctvApp.core.translations.previous_matchday_points_abbr);
4654
4655 ctvApp.view.selectPopulate({
4656 $element: $('.playerFinder_stats', _private.$wrapper),
4657 options: ruleDescriptions,
4658 values: ruleAbbreviations,
4659 callback: _private.setStats_callback
4660 });
4661
4662 // sort list
4663 var $options = $('.playerFinder_stats option', _private.$wrapper);
4664 $options.sort(function(a, b) {
4665 if(a.text > b.text) return 1;
4666 else if(a.text < b.text) return -1;
4667 else return 0;
4668 });
4669 $('.playerFinder_stats', _private.$wrapper).empty().append($options);
4670
4671 },
4672
4673 /*
4674 stats filter is set up, continue - update the stats column and redraw
4675 todo: maybe don't redraw now if there are more filters to set up first?
4676 */
4677 setStats_callback: function(args_value) {
4678 var categories = _parent.gameSettings.categories;
4679
4680 // update the stats column
4681 for(var i = 0; i < categories.length; i++) {
4682 var category = categories[i],
4683 // get the table
4684 table_dt = category.dataTable,
4685 // default order
4686 defaultOrderColumn = 'rule_' + ctvApp.core.translations.PTS + ':name';
4687 // check if the scoring has started
4688 if(ctvApp.config.competition.beforeScoring) {
4689 // before the scoring
4690 if(ctvApp.config.competition.beforeScoringStat) {
4691 defaultOrderColumn = 'rule_' + ctvApp.core.translations[ctvApp.config.competition.beforeScoringStat] + ':name';
4692 }
4693 }
4694
4695 // do we require all the stats
4696 if(_parent.args.playerFinder_allStats) {
4697
4698 // do not hide any stats
4699 // set the order
4700 // are we looking into a round?
4701 if(_parent.args.playerFinder_roundId) {
4702 var MDPColName = 'rule_' + ctvApp.core.translations.MDP + ':name';
4703 table_dt.order([
4704 [table_dt.column(MDPColName).index(), 'desc'], // sort by mdp first
4705 [table_dt.column(defaultOrderColumn).index(), 'desc'], // cms config or value
4706 [table_dt.column('value:name').index(), 'desc'] // value
4707 ]);
4708 } else {
4709 table_dt.order([
4710 [table_dt.column(defaultOrderColumn).index(), 'desc'], // cms config or value
4711 [table_dt.column('value:name').index(), 'desc'] // value
4712 ]);
4713 }
4714 } else {
4715
4716 // hide the stat columns (no re-draw)
4717 for(var j = 0; j < category.statColumns.length; j++) {
4718 table_dt.column(category.statColumns[j]).visible(false, false);
4719 }
4720 // display the selected stat column (no re-draw)
4721 var colnameStat = 'rule_' + ctvApp.core.translations['PTS'] + ':name';
4722 if(args_value) {
4723 colnameStat = 'rule_' + args_value + ':name';
4724 }
4725 // if the selected stats is the value column then just hide all the stats
4726 if(args_value == 'value') {
4727 colnameStat = 'value:name';
4728 }
4729 table_dt.column(colnameStat).visible(true);
4730 // set the order
4731 table_dt.order([
4732 [table_dt.column(colnameStat).index(), 'desc'], // passed argument (selected stat)
4733 [table_dt.column(defaultOrderColumn).index(), 'desc'], // cms config or value
4734 [table_dt.column('value:name').index(), 'desc'] // value
4735 ]);
4736 }
4737 // re-draw
4738 table_dt.draw();
4739 }
4740 },
4741
4742 // set up the categories filter
4743 setCategories: function() {
4744 var $categories = $('.playerFinder_categories', _private.$wrapper);
4745 var tpl_category = $('[data-name="playerFinder_categories_item"]', _private.$wrapper).html();
4746 var results_rows = 0;
4747 var $dropdown = $('.playerFinder_categories_dropdown select', _private.$wrapper);
4748 var dropdown = ($dropdown.length > 0);
4749 var $singleTableDropdown = $('.playerFinder_singleTableCategories_dropdown select', _private.$wrapper);
4750 var singleTableDropdown = ($singleTableDropdown.length > 0);
4751 var $buttons = $('.playerFinder_categories_buttons .button-group', _private.$wrapper);
4752 var buttons = ($buttons.length > 0);
4753 var tpl_button = $('[data-name="playerFinder_categories_button"]', _private.$wrapper).html();
4754
4755 // add option all
4756 ctvApp.view.templatePlace({
4757 $target: $categories,
4758 template: tpl_category,
4759 template_data: {
4760 name: $categories.attr('data-all'),
4761 abbreviation: $categories.attr('data-allabbreviation')
4762 }
4763 });
4764 // buttons
4765 if(buttons) {
4766 ctvApp.view.templatePlace({
4767 $target: $buttons,
4768 template: tpl_button,
4769 template_data: {
4770 name: $categories.attr('data-all'),
4771 abbreviation: $categories.attr('data-allabbreviation')
4772 }
4773 });
4774 }
4775
4776 // singleTableDropdown
4777 if(singleTableDropdown) {
4778 $singleTableDropdown.empty();
4779 $singleTableDropdown.append(
4780 '<option value="all">' +
4781 $categories.attr('data-all') +
4782 '</option>'
4783 );
4784 }
4785
4786 // add the categories
4787 // create the category elements
4788 var categories = _parent.gameSettings.categories;
4789 for(var i = 0; i < categories.length; i++) {
4790 var category = categories[i],
4791 $category = ctvApp.view.templatePlace({
4792 $target: $categories,
4793 template: tpl_category,
4794 template_data: {
4795 name: category.name,
4796 abbreviation: category.abbr
4797 }
4798 }),
4799 $checkbox = ctvApp.core.DOM.findSelf($category, '[type=checkbox]'),
4800 $label = ctvApp.core.DOM.findSelf($category, 'label');
4801 $checkbox.attr('data-categoryid', category.id);
4802 $label.attr('data-categoryid', category.id);
4803 results_rows += category.tablerows;
4804 // dropdown
4805 if(dropdown) {
4806 $dropdown.append(
4807 '<option value="' + category.id + '">' +
4808 category.name +
4809 '</option>'
4810 );
4811 }
4812 // singleTableDropdown
4813 if(singleTableDropdown) {
4814 $singleTableDropdown.append(
4815 '<option value="' + category.id + '">' +
4816 category.name +
4817 '</option>'
4818 );
4819 }
4820 // buttons
4821 if(buttons) {
4822 var $button = ctvApp.view.templatePlace({
4823 $target: $buttons,
4824 template: tpl_button,
4825 template_data: {
4826 name: category.name,
4827 abbreviation: category.abbr
4828 }
4829 });
4830 $button = ctvApp.core.DOM.findSelf($button, '.button');
4831 $button.attr('data-categoryid', category.id);
4832 }
4833 }
4834 _private.results_rows = results_rows || _private.results_rows;
4835
4836 $('[type=checkbox]', $categories).each(function() {
4837 $(this).prop('checked', false);
4838 });
4839 var $checkboxAll = $('[type=checkbox]:not([data-categoryid])', $categories);
4840 if(ctvApp.config.playerFinderDefaultAll) {
4841 // set the checked states (only all should be checked)
4842 $checkboxAll.prop('checked', true);
4843 } else {
4844 if(buttons) $buttons.append($('li:eq(0)', $buttons));
4845 $categories.prepend($checkboxAll.parent());
4846 $('[type=checkbox]', $categories).eq(0).prop('checked', true);
4847 }
4848 // buttons
4849 if(buttons) {
4850 $('.button', $buttons).removeClass('active');
4851 $('.button:eq(0)', $buttons).addClass('active');
4852 }
4853
4854 // add the events
4855 $('[type=checkbox]', $categories).off('change').on('change', _private.categoryFilter_change);
4856 if(dropdown) {
4857 $dropdown.off('change').on('change', _private.categoryFilter_dropdown_updateCategories);
4858 }
4859 if(singleTableDropdown) {
4860 $singleTableDropdown.off('change').on('change', _private.applyFilter);
4861 }
4862 if(buttons) {
4863 $('.button', $buttons).off('click').on('click', _private.categoryFilter_button_click);
4864 }
4865 },
4866
4867 // on clicking the categories filter
4868 categoryFilter_change: function() {
4869
4870 // if the 'All' option is clicked
4871 if(!$(this).attr('data-categoryid')) {
4872
4873 // Is it checked now?
4874 if($(this).prop('checked')) {
4875
4876 // yes, then uncheck all the others
4877 var $options = $('.playerFinder_categories [type=checkbox][data-categoryid]', _private.$wrapper);
4878 for(var i = 0; i < $options.length; i++) {
4879 $($options[i]).prop('checked', false);
4880 }
4881
4882 // update the filter state
4883 _private.categoryFilter_update(true);
4884
4885 } else {
4886
4887 // no, then check it again (you can't uncheck the 'All' option directly)
4888 $(this).prop('checked', true);
4889 }
4890 } else {
4891
4892 // the clicked option is not the 'All' option
4893 // update the 'All' options
4894 _private.categoryFilter_updateAllOptions();
4895 // update the filter state
4896 _private.categoryFilter_update(true);
4897 }
4898
4899 // update the buttons
4900 var $buttons = $('.playerFinder_categories_buttons .button-group', _private.$wrapper);
4901 var buttons = ($buttons.length > 0);
4902 if(buttons) {
4903 // option 'All'
4904 var $checkbox = $('.playerFinder_categories [type=checkbox]:not([data-categoryid])', _private.$wrapper);
4905 var $button = $('.button:not([data-categoryid])', $buttons);
4906 if($checkbox.prop('checked')) {
4907 $button.addClass('active');
4908 } else {
4909 $button.removeClass('active');
4910 }
4911 // categories
4912 $('.playerFinder_categories [type=checkbox][data-categoryid]', _private.$wrapper).each(function() {
4913 $button = $('.button[data-categoryid=' + $(this).attr('data-categoryid') + ']', $buttons);
4914 var $checkbox = $(this);
4915 if($checkbox.prop('checked')) {
4916 $button.addClass('active');
4917 } else {
4918 $button.removeClass('active');
4919 }
4920 });
4921 }
4922 },
4923
4924 // update categories from the category dropdown
4925 categoryFilter_dropdown_updateCategories: function() {
4926
4927 // get the category id from the current dropdown option
4928 var $dropdown = $('.playerFinder_categories_dropdown select', _private.$wrapper);
4929 // exit if no dropdown
4930 if(!$dropdown.length) {
4931 return;
4932 }
4933
4934 var categoryId = $dropdown.val();
4935 // unselect the checkboxes, except for the one with the id of the current dropdown option
4936 var $options = $('.playerFinder_categories [type=checkbox]', _private.$wrapper);
4937 for(var i = 0; i < $options.length; i++) {
4938 var checked = ($($options[i]).attr('data-categoryid') == categoryId);
4939 $($options[i]).prop('checked', checked);
4940 }
4941 // update the 'All' options
4942 _private.categoryFilter_updateAllOptions();
4943 // update the filter state
4944 _private.categoryFilter_update(true);
4945 },
4946
4947 // update categories from the category button
4948 categoryFilter_button_click: function() {
4949 if(ctvApp.config.playerfinderButtonType === 'radio') {
4950 var $allButton = $('.playerFinder_categories input[type=checkbox]:not([data-categoryid])', _private.$wrapper);
4951 if(!$allButton.prop('checked')) {
4952 $allButton.click();
4953 }
4954 }
4955
4956 // get the category id from the current button
4957 var categoryId = $(this).attr('data-categoryid');
4958 // find the corresponding checkbox
4959 var $checkbox;
4960 if(categoryId) {
4961 $checkbox = $('.playerFinder_categories [type=checkbox][data-categoryid=' + categoryId + ']', _private.$wrapper);
4962 } else {
4963 // option 'All'
4964 $checkbox = $('.playerFinder_categories [type=checkbox]:not([data-categoryid])', _private.$wrapper);
4965 }
4966 // click on the checkbox
4967 $checkbox.click();
4968 },
4969
4970 // update the state of the 'All' options
4971 categoryFilter_updateAllOptions: function() {
4972 // 'All' options are those with no categoryId
4973 // 'Category' options are those with the categoryId
4974
4975 // Check if all the 'Category' options are selected
4976 var allCategories = true;
4977 // Check if any 'Category' option is selected
4978 var anyCategories = false;
4979 var $options = $('.playerFinder_categories [type=checkbox][data-categoryid]', _private.$wrapper);
4980 for(var i = 0; i < $options.length; i++) {
4981 allCategories = allCategories && $($options[i]).prop('checked');
4982 anyCategories = anyCategories || $($options[i]).prop('checked');
4983 }
4984
4985 // are all categories selected?
4986 if(allCategories) {
4987 // yes, unselect them
4988 for(i = 0; i < $options.length; i++) {
4989 $($options[i]).prop('checked', false);
4990 }
4991 }
4992
4993 // if not anyCategories are selected then allCategories should be
4994 if(!anyCategories) {
4995 allCategories = true;
4996 }
4997
4998 // Set all the 'All' options to the state of the allCategories
4999 $options = $('.playerFinder_categories [type=checkbox]:not([data-categoryid])', _private.$wrapper);
5000 for(i = 0; i < $options.length; i++) {
5001 $($options[i]).prop('checked', allCategories);
5002 }
5003
5004 // if allCategories are selected then be sure to show all the tables too
5005 if(allCategories) {
5006 $('.results_group', _private.$wrapper).each(function() {
5007 var $this = $(this);
5008 if($('table', $this).css('display') == 'none') {
5009 _private.categoryTable_toggleTitle($('.group_title', $this));
5010 }
5011 });
5012 }
5013 },
5014
5015 /*
5016 update of displayed categories between the filter and the tables
5017 - if updateTables is false or omitted then the update is to the filter
5018 - if updateTables is true then the update is to the tables
5019 */
5020 categoryFilter_update: function(updateTables) {
5021
5022 // get the state of the 'All' option
5023 var allChecked = $('.playerFinder_categories [type=checkbox]:not([data-categoryid])', _private.$wrapper).prop('checked');
5024
5025 // for each 'Category' option
5026 var $options = $('.playerFinder_categories [type=checkbox][data-categoryid]', _private.$wrapper);
5027 for(var i = 0; i < $options.length; i++) {
5028 var $option = $($options[i]),
5029 selected = $option.prop('checked') || allChecked,
5030 categoryId = $option.attr('data-categoryid'),
5031 title = '.results_group[data-categoryid="' + categoryId + '"] .group_title',
5032 $title = $(title, _private.$wrapper),
5033 visible = ($('.results_group[data-categoryid="' + categoryId + '"] table', _private.$wrapper).css('display') != 'none');
5034
5035 // if updating the tables
5036 if(updateTables) {
5037 // if the states are different
5038 if(visible != selected) {
5039 // toggle the table state
5040 _private.categoryTable_toggleTitle($title);
5041 }
5042 } else {
5043 // not updating the tables
5044 // set the checkbox to the table state
5045 $option.prop('checked', visible);
5046 }
5047 }
5048 // update the 'All' options
5049 _private.categoryFilter_updateAllOptions();
5050 // update the results rows
5051 _private.updateResultsRows();
5052 },
5053
5054 // player name filter - update the results on change
5055 setPlayerName: function() {
5056 var $playerName = $('.playerFinder_playerName', _private.$wrapper);
5057 // empty it
5058 $playerName.val('');
5059 // keyup event
5060 $playerName.off('keyup').on('keyup', function() {
5061 // delay the resonse while waiting for the typing to complete
5062 clearTimeout(_private.setPlayerName_timer);
5063 _private.setPlayerName_timer = setTimeout(
5064 _private.applyFilter, 250
5065 );
5066 });
5067 },
5068
5069 // Stat Periods
5070 setStatPeriods: function() {
5071
5072 // stat periods element
5073 var $statPeriods = $('.playerFinder_statPeriods select', _private.$wrapper);
5074 $statPeriods.empty();
5075
5076 // exit if the element is not found
5077 if(!$statPeriods.length) return;
5078
5079 // add the overall period option
5080 $statPeriods.append($(
5081 '<option selected="selected" value="overall">' +
5082 ctvApp.core.translations.Overall +
5083 '</option>'
5084 ));
5085
5086 // which period is displayed?
5087 var currentPeriodId = ctvApp.config.competition.currentStatPeriod.id;
5088 var currentPediodDisplayed = false;
5089 var displayedId = _parent.args.playerFinder_roundId;
5090 // this in case we want to default to the current period instead of the overall
5091 // var displayedId = _parent.args.playerFinder_roundId || currentPeriodId;
5092
5093 // populate and select the displayed period
5094 for(var i = 0; i < ctvApp.config.competition.statPeriods.length; i++) {
5095
5096 // get the period
5097 var period = ctvApp.config.competition.statPeriods[i];
5098 // only display the current and previous periods
5099 if(!currentPediodDisplayed) {
5100 if(period.id == currentPeriodId) {
5101 currentPediodDisplayed = true;
5102 }
5103 // setup the option
5104 var $option = $(
5105 '<option value="' + period.id + '">' +
5106 period.name +
5107 '</option>'
5108 );
5109 // is this the displayed period?
5110 if(period.id == displayedId) {
5111 // select the option
5112 $option.attr('selected', 'selected');
5113 }
5114 // add the option
5115 $statPeriods.append($option);
5116 }
5117 }
5118
5119 // event - on changing the option
5120 // set the period players
5121 $statPeriods.off('change'); // in case this event was already attached
5122 $statPeriods.on('change', function() {
5123 // delay the resonse while waiting for the ui
5124 clearTimeout(_private.setStatPeriods_timer);
5125 _private.setStatPeriods_timer = setTimeout(
5126 _parent.setPeriodPlayers($(this).val()), 500
5127 );
5128 });
5129 },
5130
5131 /*
5132 setup the results datatables
5133 todo: refactor
5134 */
5135 setResults: function() {
5136 //console.time('datatable init');
5137
5138 // data
5139 var players = _parent.players;
5140
5141 // Set the Price Range Slider
5142 // Get the Min and Max
5143 var priceMin, priceMax;
5144 for(var i = 0; i < players.length; i++) {
5145 var value = players[i].value;
5146 if(value < priceMin || !priceMin) priceMin = value;
5147 if(value > priceMax || !priceMax) priceMax = value;
5148 }
5149 // Init the Slider
5150 ctvApp.view.sliderInit({
5151 $container: $('.playerFinder_value', _private.$wrapper),
5152 step: 0.5,
5153 min: priceMin,
5154 max: priceMax,
5155 // callback: _private.slider_callback
5156 callback: function() {
5157 // delay the resonse while waiting for the action to complete
5158 clearTimeout(_private.slider_timer);
5159 _private.slider_timer = setTimeout(
5160 _private.slider_callback, 500
5161 );
5162 }
5163 });
5164
5165 // setup the displayed filters
5166 if(!_parent.players.info.anyInfo4) {
5167 $('.playerFinder_onlyPlaying', _private.$wrapper).addClass('hide');
5168 }
5169
5170 // Set up the results
5171 var $results = $('.playerFinder_results', _private.$wrapper);
5172 $results.empty();
5173
5174 // Number of buttons in the pager (this is global!)
5175 $.fn.dataTable.ext.pager.numbers_length = 6;
5176 // NOTE: the stat columns require the name to have the prefix "rule_"
5177 var table_columns = [
5178 {
5179 visible: false,
5180 name: 'dt_filter',
5181 data: 'dt_filter',
5182 defaultContent: '' // this key is required for the columns which are not in the data source
5183 },
5184 {
5185 responsivePriority: 1,
5186 title: ctvApp.core.translations.Player,
5187 name: 'name',
5188 data: 'name',
5189 render: function(data, type, full) {
5190 var html = '';
5191 var x = '<div class="playerName"';
5192 var t = false;
5193 if(full.status) {
5194 x += ctvApp.core.translations['player_status_' + full.status] + ' ';
5195 t = true;
5196 }
5197 if(full.absenseReason) {
5198 x += '- ' + full.absenseReason + ' ';
5199 t = true;
5200 }
5201 if(full.returnDate) {
5202 if(t) x += '- ';
5203 x += ctvApp.core.translations['Return date'] + ': ' + full.returnDate + ' ';
5204 t = true;
5205 }
5206 x += '">';
5207 var s = '';
5208 if( full.starter ){
5209 s += '<i class="fa fa-check-circle starter" title="' +
5210 ctvApp.core.translations[ 'Starting XV' ] +
5211 '"></i>';
5212 }
5213 html = x +
5214 '<div class="icon action toggle-player">' +
5215 '<i class="fa fa-circle-thin add"></i>' +
5216 '<i class="fa fa-check-circle remove"></i>' + '</div>'+
5217 '</div class="player-name">' + data + '</div>' + s;
5218 return html;
5219 }
5220 },
5221 {
5222 visible: false,
5223 name: 'dt_name_normalized',
5224 data: 'dt_name_normalized',
5225 defaultContent: ''
5226 },
5227 {
5228 responsivePriority: 2,
5229 title: ctvApp.core.translations.Team,
5230 name: 'sideAbbreviation',
5231 data: 'sideAbbreviation',
5232 render: function(data) {
5233 return '<div class="teamside">' + data + '</div>';
5234 }
5235 },
5236 {
5237 responsivePriority: 3,
5238 title: ctvApp.core.translations.Position_short,
5239 visible: _parent.args.playerFinder_singleTable === true,
5240 name: 'categoryId',
5241 data: 'categoryId',
5242 render: function(data) {
5243 var categoryIndex = _parent.getCategoryIndex(data);
5244 var categoryAbbr = _parent.gameSettings.categories[categoryIndex].abbr;
5245 return '<div class="category category_' + categoryAbbr + '">' + categoryAbbr + '</div>';
5246 }
5247 },
5248 {
5249 visible: false,
5250 name: 'side',
5251 data: 'side'
5252 },
5253 {
5254 visible: false,
5255 name: 'info4',
5256 data: 'info4'
5257 },
5258 {
5259 responsivePriority: 4,
5260 visible: true,
5261 title: 'Rank',
5262 name: 'value',
5263 className: 'playerfinder_value_cell',
5264 data: 'value',
5265 render: function(data, type, full) {
5266 return '<div class="points">'+ full.rank + '</div>';
5267 }
5268 }
5269 ];
5270
5271 if(ctvApp.config.previousSeasonPoints) {
5272 table_columns.push({
5273 title: '<span title="' + ctvApp.core.translations['Previous Season Points'] + '">' + ctvApp.core.translations['PSP'] + '</span>',
5274 name: 'rule_' + ctvApp.core.translations['PSP'],
5275 data: 'lastSeasonPoints'
5276 });
5277 }
5278
5279 table_columns.push({
5280 title: '<span title="' + ctvApp.core.translations.previous_matchday_points + '">' + ctvApp.core.translations.previous_matchday_points_abbr + '</span>',
5281 name: 'rule_' + ctvApp.core.translations.previous_matchday_points_abbr,
5282 data: 'lastWeekPoints'
5283 });
5284
5285 var ruleAbbreviation, ruleDescription;
5286 // add the stats columns
5287 // NOTE: the stat columns require the name to have the prefix "rule_"
5288 for(i = 0; i < _parent.gameSettings.rules.length; i++) {
5289 ruleAbbreviation = _parent.gameSettings.rules[i].abbr;
5290 ruleDescription = _parent.gameSettings.rules[i].desc;
5291 var rule_no_space = ruleAbbreviation.replace(/\s+/g, '');
5292
5293 if(ctvApp.config.hideRules && ctvApp.config.hideRules.length) {
5294 if($.inArray(ruleAbbreviation, ctvApp.config.hideRules) > -1) continue;
5295 }
5296 var column = {
5297 className: 'header_image_' + rule_no_space,
5298 title: '<span title="' + ruleDescription + '">' + ruleAbbreviation + '</span>',
5299 //orderable: false,
5300 //visible: false, // no need to do here because the set stats filter will handle this
5301 name: 'rule_' + ruleAbbreviation,
5302 data: 'rule_' + ruleAbbreviation + '_count'
5303 };
5304 if(ruleAbbreviation === 'PTS' || ruleAbbreviation === 'TSB') {
5305 //console.log('pts or tsb');
5306 column.responsivePriority = 6;
5307 }
5308 table_columns.push(column);
5309 }
5310
5311 // add the MDP stat column (since it's not in the game settings)
5312 // only when we want all the stats
5313 // and when we filter by round, not the overall
5314 if(_parent.args.playerFinder_allStats && _parent.args.playerFinder_roundId) {
5315 ruleAbbreviation = ctvApp.core.translations.MDP;
5316 ruleDescription = ctvApp.core.translations['Matchday Points'];
5317 table_columns.push({
5318 title: '<span title="' + ruleDescription + '">' + ruleAbbreviation + '</span>',
5319 name: 'rule_' + ruleAbbreviation,
5320 data: 'rule_' + ruleAbbreviation + '_count'
5321 });
5322 }
5323
5324 // add the actions column
5325 table_columns.push({
5326 responsivePriority: 5,
5327 orderable: false,
5328 name: 'dt_actions',
5329 render: function(data, type, full) {
5330 var html = '' +
5331 '<div class="icon action icon action info"' +
5332 'data-code="' + full.code + '" data-id="' + full.sideId + '">' +
5333 '<i class="fa fa-info" aria-hidden="true"></i>' +
5334 '</div>';
5335 return html;
5336 }
5337 /*
5338 this is replaced by the render
5339 defaultContent:
5340 '<i class="fa icon action fa-bar-chart info playerProfileLink"' +
5341 '</i>'
5342 */
5343 });
5344
5345 // setup the wrapper classes from args
5346 if(_parent.args.playerFinder_singleTable) {
5347 _private.$wrapper.addClass('singleTable');
5348 }
5349 // if the single table is required, then add all the players to the first category table
5350 var singleTableProcessed = false,
5351 categories = _parent.gameSettings.categories;
5352
5353 // setup the category tables
5354 for(i = 0; i < categories.length; i++) {
5355 var category = categories[i];
5356 // add the wrapper
5357 var $group = $('<div class="results_group" data-categoryid="' + category.id + '"></div>');
5358 $results.append($group);
5359
5360 // filter the players
5361 var players_category = players;
5362 // is the single table option active
5363 if(_parent.args.playerFinder_singleTable) {
5364
5365 // the single table option is active
5366 // is it processed already?
5367 if(singleTableProcessed) {
5368 // processed, give this category no players
5369 players_category = [];
5370 }
5371 } else {
5372
5373 // not a single table option, grep the category players
5374 players_category = $.grep(players, function(player) {
5375 return player.categoryId == category.id;
5376 });
5377 }
5378
5379 // add the table
5380 var $table = $('<table></table>');
5381 $group.append($table);
5382 // setup the datatable
5383 var table_dt = $table.DataTable({
5384 // options
5385 "responsive": true,
5386 "language": {
5387 "emptyTable": "No data available in table",
5388 "info": "Showing _START_ to _END_ of _TOTAL_ entries",
5389 "infoEmpty": "Showing 0 to 0 of 0 entries",
5390 "infoFiltered": "(filtered from _MAX_ total entries)",
5391 "infoPostFix": "",
5392 "thousands": ",",
5393 "lengthMenu": "Show _MENU_ entries",
5394 "loadingRecords": "Loading...",
5395 "processing": "Processing...",
5396 "search": "Search:",
5397 "zeroRecords": ctvApp.core.translations['No matching records found'],
5398 "paginate": {
5399 "first": ctvApp.core.translations.paginate_first, //"<<",
5400 "previous": ctvApp.core.translations.paginate_previous, //"<",
5401 "next": ctvApp.core.translations.paginate_next, //">",
5402 "last": ctvApp.core.translations.paginate_last //">>"
5403 },
5404 "aria": {
5405 "sortAscending": ": activate to sort column ascending",
5406 "sortDescending": ": activate to sort column descending"
5407 }
5408 },
5409 pagingType: 'ellipses', //'ellipses', //'full_numbers',
5410 iShowPages: 4, // ellipses option
5411 pageLength: category.tablerows,
5412 dom: 't<"group_header"<"group_title"><"group_pager"p>>',
5413 autoWidth: false,
5414 // data
5415 data: players_category,
5416 columns: table_columns,
5417 // on row creation
5418 createdRow: function(row, data) {
5419 if(data.status) {
5420 $(row).addClass('status' + data.status);
5421 }
5422 }
5423 });
5424
5425 // normalize the names
5426 // todo: maybe this can be in the column render
5427 table_dt.rows().eq(0).each(function(rowIdx) {
5428 var d = table_dt.cell(rowIdx, 'name:name').data();
5429 table_dt.cell(rowIdx, 'dt_name_normalized:name').data(ctvApp.core.normalize(d));
5430 });
5431
5432 // redraw the pager
5433 _private.categoryTable_redrawPager($table);
5434 // set up the events
5435 // Draw event - fired once the table has completed a draw
5436 table_dt.on('draw.dt', function() {
5437 // redraw the pager
5438 _private.categoryTable_redrawPager($(this));
5439 // init the foundation tooltips
5440 // todo: too slow on firefox, it is noticable when the "only show players" is clicked the first time
5441 // $(document).foundation('tooltip', 'reflow');
5442 });
5443
5444 // store the table
5445 category.dataTable = table_dt;
5446 category.$table = $table; // shortcut for $(DataTable().table().node())
5447 // store the stat columns
5448 // NOTE: the stat columns require the name to have the prefix "rule_"
5449 category.statColumns = [];
5450 for(var j = 0; j < table_columns.length; j++) {
5451 if(table_columns[j].name.indexOf('rule_') === 0) {
5452 category.statColumns.push(j);
5453 }
5454 }
5455
5456 // add the group title
5457 var $title = $('.group_title', $group);
5458 $title.text(category.name);
5459 // when the title is clicked
5460 $title.on('click', function() {
5461 // toggle the table visibility
5462 _private.categoryTable_toggleTitle($(this));
5463 // update the category filter (this is not a part of the toggle)
5464 _private.categoryFilter_update();
5465 });
5466
5467 // colum title, todo: should be customisable (for the comp)
5468 // only when not the single tables
5469 if(!_parent.args.playerFinder_singleTable)
5470 $('th:eq(0)', $group).text(category.name);
5471
5472 // set the single table processed
5473 if(_parent.args.playerFinder_singleTable)
5474 singleTableProcessed = true;
5475 }
5476 // setup the initial stats dropdown option
5477 // before the scoring
5478 if(ctvApp.config.competition.beforeScoring) {
5479 var s = 'PTS';
5480 if(ctvApp.config.competition.beforeScoringStat !== '') {
5481 s = ctvApp.config.competition.beforeScoringStat;
5482 }
5483 s = ctvApp.core.translations[s];
5484 $('.playerFinder_stats', _private.$wrapper).val(s);
5485 } else {
5486 // during the scoring
5487 $('.playerFinder_stats', _private.$wrapper).val(ctvApp.core.translations.PTS);
5488 }
5489 // update the datatables columns and sorting
5490 _private.setStats_callback(
5491 $('option:selected', $('.playerFinder_stats', _private.$wrapper)).attr('value')
5492 );
5493
5494 // event - row selection
5495 _private.$wrapper.on('click', '.dataTable tbody tr', function() {
5496 var table_dt = $(this).closest('table').DataTable();
5497 var playerData = table_dt.row(this).data();
5498 var row = table_dt.row(this).node();
5499 var action = 'addPlayer'; // default action
5500 // is the player selected
5501 if($(row).hasClass('selected')) {
5502 // removing the player
5503 action = 'removePlayer';
5504 }
5505 _private.$wrapper.trigger(action, { row: row, player: playerData });
5506 });
5507
5508 // player info
5509 _private.$wrapper.on('click', '.dataTable .action.info', function(event) {
5510 event.stopPropagation();
5511
5512 _parent.$wrapper.trigger('playerInfo', { playerCode: $(this).data('code') });
5513
5514 // code to work with the old player info
5515 /*
5516 var playerCode=$(this).attr('name'),
5517 startdate=new Date().yyyymmdd(),
5518 sidename=$(this).attr('id');
5519 getPlayerInfo(playerCode);
5520 getPlayerFixture(startdate, sidename);
5521 getPlayerHistory(playerCode);
5522 */
5523 });
5524 // update the categories from the category dropdown
5525 _private.categoryFilter_dropdown_updateCategories();
5526 // call the init done
5527 _private.init_done();
5528 //console.timeEnd('datatable init');
5529 },
5530
5531 /*
5532 player's category table - title in results - update on change
5533 this should not feature the category filter update since it's a part of it already
5534 instead, the category filter update is included in the clicking event
5535 */
5536 categoryTable_toggleTitle: function($title) {
5537 // get the group
5538 var $group = $title.parents('.results_group');
5539 // get the elements
5540 var $table = $('table.dataTable', $group);
5541 // toggle the table
5542 $table.toggleClass('hide');
5543 // update the group info
5544 var hidden = $table.hasClass('hide');
5545 $group.attr('data-collapsed', hidden);
5546 // redraw the pager
5547 _private.categoryTable_redrawPager($table);
5548 },
5549
5550 // player's category table - redraw the pager
5551 categoryTable_redrawPager: function($table) {
5552 var pages = $table.DataTable().page.info().pages;
5553 var $pager = $('.group_pager', $table.parent());
5554 // hide the pager if there is a single page or the table is hidden
5555 if(pages < 2 || $table.css('display') == 'none') {
5556 $pager.hide();
5557 } else {
5558 $pager.show();
5559 }
5560 },
5561
5562 // on value slider changes - update the results (just apply the filter)
5563 slider_callback: function() {
5564 // update the filter
5565 _private.applyFilter();
5566 },
5567
5568 // when playerfinder is done setting up - update the foundation
5569 init_done: function() {
5570 // Remove the loading state
5571 _private.$wrapper.removeClass('loading');
5572 // Reflow the added elements with Foundation
5573 $(document).foundation();
5574
5575 if(ctvApp.config.autoQuickPick) {
5576 setTimeout(function() {
5577 $('#auto-select').click();
5578 $("<div class='alert-box text-center info no-margin quick-pick-alert'>" + ctvApp.core.translations.QuickPickAlert + "</div>").insertAfter("#firstStop");
5579 }, 500);
5580 }
5581 },
5582 setPlayerFinderRowStates: function() {
5583 if(!ctvApp.config.setPlayerFinderRowStates) return;
5584 //console.time('setRowStates');
5585 var categories = _parent.gameSettings.categories;
5586 //build teamSides array
5587 var teamSides = [];
5588 _parent.forEachTeamPlayer(function(data) {
5589 if(!teamSides[data.player.player.side]) {
5590 teamSides[data.player.player.side] = 1;
5591 } else {
5592 teamSides[data.player.player.side] += 1;
5593 }
5594 });
5595 for(var i = 0; i < categories.length; i++) {
5596 var table = categories[i].dataTable,
5597 tableRows = table.rows()[0],
5598 categoryPlayers = _parent.team.players[categories[i].id],
5599 freeSlots = 0;
5600 for(var j = 0; j < categoryPlayers.length; j++) {
5601 if(categoryPlayers[j] === null) freeSlots++;
5602 }
5603 var $rows = table.rows().nodes().to$();
5604 $rows.toggleClass('grey-out', freeSlots === 0);
5605 if(freeSlots > 0) {
5606 for(j = 0; j < tableRows.length; j++) {
5607 var $row = $(table.row(j).node()),
5608 rowData = table.row(j).data(),
5609 fullCategory = freeSlots === 0,
5610 noBudget = _parent.team.remainingBudget < rowData.value,
5611 maxFromSide = false;
5612 if(!fullCategory && !noBudget && teamSides[rowData.side]) {
5613 maxFromSide = teamSides[rowData.side] >= _parent.gameSettings.maxPerTeam;
5614 }
5615 $row.toggleClass('grey-out', fullCategory || noBudget || maxFromSide);
5616 }
5617 }
5618 }
5619 //console.timeEnd('setRowStates');
5620 }
5621 };
5622
5623 // INTERFACE
5624 return {
5625
5626 /*
5627 Set up the playerfinder
5628 - args.$wrapper: jQuery wrapper element
5629 */
5630 init: function(args) {
5631 //console.time('player finder init');
5632
5633 // Configure the instance
5634 _parent = args._parent;
5635 _private.$wrapper = args.$wrapper;
5636
5637 // Set up the components
5638 // filter
5639 _private.setPlayerName();
5640 _private.setTeams();
5641 _private.setStats();
5642 _private.setCategories();
5643 // stat periods
5644 _private.setStatPeriods();
5645 // onlyAvailable, onlyAffordable, onlyPlaying events
5646 $('.playerFinder_onlyAvailable, .playerFinder_onlyAffordable, .playerFinder_onlyPlaying', _private.$wrapper).off('change').on('change', '[type=checkbox]', _private.applyFilter);
5647
5648 _parent.$playerFinder.on('setRowStates', function() {
5649 _private.setPlayerFinderRowStates();
5650 });
5651 // results
5652 _private.setResults();
5653 // apply the filter initially (for the filter defaults)
5654 if(!ctvApp.config.playerFinderDefaultAll) {
5655 $('.playerFinder_categories [type=checkbox]', _private.$wrapper).eq(0).trigger('change');
5656 }
5657 _private.applyFilter();
5658 //console.timeEnd('player finder init');
5659 },
5660
5661 /*
5662 on player added - setup his row
5663 - argument is either row element or the player object
5664 - returns the row element
5665 */
5666 playerAdded: function(args) {
5667
5668 var $row = $(args.row);
5669
5670 // if there is no row then find it
5671 if(!$row.length) {
5672
5673 // exit if there is no player
5674 if(!args.player) return;
5675
5676 // find the row index by player code
5677 var code = args.player.code;
5678 var table = _parent.gameSettings.categoryIds[args.player.categoryId].dataTable;
5679 var rowIdx = -1;
5680 var tableRows = table.rows()[0];
5681 for(var r = 0; r < tableRows.length; r++) {
5682 if(table.row(tableRows[r]).data().code == code) {
5683 rowIdx = tableRows[r];
5684 break;
5685 }
5686 }
5687
5688 // exit if the row has not been found
5689 if(rowIdx < 0) return;
5690
5691 // get the row element
5692 $row = $(table.row(rowIdx).node());
5693 }
5694
5695 if(!args.batchAdding) _private.setPlayerFinderRowStates();
5696 _parent.$wrapper.trigger('playerAdded');
5697 return $row;
5698 },
5699
5700 // on player removed - setup his row
5701 playerRemoved: function(args) {
5702 var $row = $(args.row);
5703 $row.removeClass('selected');
5704 // if the only available or only affordable is selected then apply the filter
5705 if($('.playerFinder_onlyAvailable [type=checkbox]', _private.$wrapper).prop('checked') ||
5706 $('.playerFinder_onlyAffordable [type=checkbox]', _private.$wrapper).prop('checked')) {
5707 _private.applyFilter();
5708 }
5709 if(!args.batchRemoving) _private.setPlayerFinderRowStates();
5710 _parent.$wrapper.trigger('playerRemoved');
5711 },
5712
5713 // turns off the onlyAvailable option and returns its initial state (so that it can be restored later)
5714 onlyAvailableOff: function() {
5715 var $onlyAvailable = $('.playerFinder_onlyAvailable [type=checkbox]', _private.$wrapper);
5716 var onlyAvailable_selected = $onlyAvailable.prop('checked');
5717 $onlyAvailable.prop('checked', false);
5718 return onlyAvailable_selected;
5719 },
5720
5721 // restore the onlyAvailable option
5722 onlyAvailableRestore: function(onlyAvailable_selected) {
5723 $('.playerFinder_onlyAvailable [type=checkbox]', _private.$wrapper)
5724 .prop('checked', onlyAvailable_selected);
5725 if(onlyAvailable_selected) {
5726 _private.applyFilter();
5727 }
5728 },
5729
5730 // turns off the onlyAffordable option and returns its initial state (so that it can be restored later)
5731 onlyAffordableOff: function() {
5732 var $onlyAffordable = $('.playerFinder_onlyAffordable [type=checkbox]', _private.$wrapper);
5733 var onlyAffordable_selected = $onlyAffordable.prop('checked');
5734 $onlyAffordable.prop('checked', false);
5735 return onlyAffordable_selected;
5736 },
5737
5738 // restore the onlyAffordable option
5739 onlyAffordableRestore: function(onlyAffordable_selected) {
5740 $('.playerFinder_onlyAffordable [type=checkbox]', _private.$wrapper)
5741 .prop('checked', onlyAffordable_selected);
5742 if(onlyAffordable_selected) {
5743 _private.applyFilter();
5744 }
5745 },
5746
5747 // update dropdown to category id
5748 categoryDropdown_updateToCategory: function(id) {
5749
5750 // get the dropdown
5751 var $dropdown = $('.playerFinder_categories_dropdown select', _private.$wrapper);
5752 // exit if no dropdown
5753 if(!$dropdown.length) {
5754 return;
5755 }
5756 // set it to category id
5757 $dropdown.val(id);
5758 }
5759 };
5760};
5761
5762/*
5763 PITCH MODULE
5764 this does not update the team, just provides the UI for working with it
5765*/
5766ctvApp.modules.pitch = function() {
5767 'use strict';
5768
5769 // PARENT
5770 var _parent = {},
5771
5772 // IMPLEMENTATION
5773 _private = {
5774
5775 addPlayer: function(item, wrap) {
5776 wrap.removeClass('slot-empty').addClass('remove');
5777 $('.players-list').empty();
5778 const playerItem = wrap.find('.player-item.player');
5779 playerItem.show();
5780 wrap.find('.player-item.no-player').hide();
5781 playerItem.find('.name').text(item.fullName);
5782 playerItem.find('.sideAbbreviation').text(item.sideAbbreviation);
5783 playerItem.find('.side').addClass('side' + '_' + item.sideAbbreviation);
5784 wrap.find('.players-list').addClass('selected').empty();
5785
5786 },
5787
5788 playerSwapClickHandler: function() {
5789 var $player = $(this).closest('.positions_player'),
5790 isOnBench = $(this).closest('.fantasyteams_bench').length > 0,
5791 swapClassSuffix = (isOnBench) ? 'in' : 'out';
5792
5793 if($player.hasClass('player-swap-target')) return;
5794
5795 //set player for swapping
5796 if(!$player.hasClass('player-swap')) {
5797 _private.removeSwapTargets();
5798 $player.addClass('player-swap player-swap-' + swapClassSuffix);
5799 _private.setSwapTargets({ '$player': $player, 'isOnBench': isOnBench });
5800 } else {
5801 _private.removeSwapTargets();
5802 }
5803 },
5804
5805 //Setup player swap targets
5806 setSwapTargets: function(args) {
5807 var $source = (args && args.$player) ? args.$player : $(args.target),
5808 isSourceOnBench = (args && args.isOnBench) ? args.isOnBench : $source.closest('.fantasyteams_bench').length > 0,
5809 dnd = !!(args && args.target),
5810 i, formationIx,
5811 formationUpsizedArray = [],
5812 formationDownsizedArray = [];
5813
5814 var sourceCategoryId = $source.closest((isSourceOnBench) ? '.category_position' : '.positions_category').attr('data-categoryid'),
5815 // get the category index (in the formation)
5816 sourceCategoryIndex = $('.positions_category', _private.$wrapper).index($('.positions_category[data-categoryid=' + sourceCategoryId + ']', _private.$wrapper)),
5817 $targets = $('.positions_category[data-categoryid=' + sourceCategoryId + '] .positions_player', _private.$wrapper);
5818
5819 $targets = $targets.add('.category_position[data-categoryid=' + sourceCategoryId + '] .positions_player', _private.$bench_players);
5820
5821 if(!isSourceOnBench) {
5822 // formation changing
5823 // note the formation where the draggable's category is downsized by 1
5824
5825 // get the formation downsized array
5826 formationDownsizedArray = _parent.getFormation();
5827 formationDownsizedArray[sourceCategoryIndex]--;
5828
5829 // for each slot on the bench (of a different category than the draggable)
5830 var $benchPotentialTargets = $('.category_position:not([data-categoryid="' + sourceCategoryId + '"]) .positions_player', _private.$bench_players);
5831
5832 for(i = 0; i < $benchPotentialTargets.length; i++) {
5833 // consider the formation where the target's category is upsized by 1
5834 var $slot = $benchPotentialTargets.eq(i),
5835 targetCategoryId = $slot.closest('.category_position').attr('data-categoryid'),
5836 targetCategoryIndex = $('.positions_category', _private.$wrapper).index($('.positions_category[data-categoryid=' + targetCategoryId + ']', _private.$wrapper));
5837
5838 formationUpsizedArray = formationDownsizedArray.slice();
5839
5840 formationUpsizedArray[targetCategoryIndex]++;
5841
5842 var formationUpsized = _parent.getFormationString(formationUpsizedArray);
5843
5844 formationIx = ctvApp.config.competition.formations.indexOf(formationUpsized);
5845
5846 if(formationIx >= 0) {
5847 $slot.attr('data-formationix', formationIx);
5848 $targets = $targets.add($slot);
5849 }
5850 }
5851 } else {
5852 //source player is on the bench
5853 if($source.data('bench-priority') > 0) {
5854 $targets = $targets.add('.category_position .positions_player[data-bench-priority!="0"]', _private.$bench_players);
5855
5856 // formation changing
5857 // get the category index (in the formation)
5858 sourceCategoryId = $source.closest('.category_position').data('categoryid');
5859 var $sourceCategory = $('.positions_category[data-categoryid=' + sourceCategoryId + ']', _private.$wrapper),
5860 categoryBounds = _parent.getCategoryBounds(sourceCategoryId);
5861
5862 sourceCategoryIndex = $('.positions_category', _private.$wrapper).index($sourceCategory);
5863
5864 // get the formation array
5865 formationUpsizedArray = _parent.getFormation();
5866
5867 // if upsizing is possible, proceed with formation changing
5868 if(categoryBounds.teamMax > formationUpsizedArray[sourceCategoryIndex]) {
5869 // note the formation where the draggable's category is upsized by 1
5870 formationUpsizedArray[sourceCategoryIndex]++;
5871
5872 var $pitchCategories = $('.positions_category:not([data-categoryid="' + sourceCategoryId + '"])', _private.$wrapper);
5873
5874 for(i = 0; i < $pitchCategories.length; i++) {
5875 // consider the formation where the current category is downsized by 1
5876 var $currentCategory = $pitchCategories.eq(i),
5877 currentCategoryIndex = $('.positions_category', _private.$wrapper).index($currentCategory);
5878
5879 formationDownsizedArray = formationUpsizedArray.slice(); // slice is to copy an array
5880 formationDownsizedArray[currentCategoryIndex]--;
5881
5882 var formationDownsized = _parent.getFormationString(formationDownsizedArray);
5883 formationIx = ctvApp.config.competition.formations.indexOf(formationDownsized);
5884
5885 // if such formation is listed
5886 if(formationIx >= 0) {
5887 // mark the formation index on players in the current category
5888 $currentCategory.find('.positions_player').attr('data-formationix', formationIx);
5889 $targets = $targets.add($currentCategory.find('.positions_player'));
5890 }
5891 }
5892 }
5893 }
5894 }
5895
5896 $targets = $targets.not('.player-swap');
5897
5898 if($targets.length) {
5899 _parent.$squad.addClass('swap-active');
5900 }
5901
5902 if(dnd) {
5903 $targets.droppable({
5904 hoverClass: "ui-state-hover",
5905 drop: function(event, ui) {
5906 _private.swapTargetHandler({ '$source': $(ui.draggable), '$target': $(this) });
5907 }
5908 });
5909 } else {
5910 $targets.addClass('player-swap-target');
5911 $targets.on('click.swaptarget', function() {
5912 _private.swapTargetHandler({ '$source': $source, '$target': $(this) });
5913 });
5914 }
5915 },
5916
5917 swapTargetHandler: function(args) {
5918 var data = {},
5919 $source = args.$source,
5920 isSourceOnBench = $source.closest('.fantasyteams_bench').length > 0,
5921 sourceCategoryId = $source.closest((isSourceOnBench) ? '.category_position' : '.positions_category').attr('data-categoryid'),
5922 $sourceCategory = $('.positions_category[data-categoryid=' + sourceCategoryId + ']', _private.$wrapper),
5923 sourceCategoryTeamSize = _parent.getFormationPartFromCategoryId(sourceCategoryId),
5924
5925 $target = args.$target,
5926 isTargetOnBench = $target.closest('.fantasyteams_bench').length > 0,
5927 targetCategoryId = $target.closest((isTargetOnBench) ? '.category_position' : '.positions_category').attr('data-categoryid'),
5928 $targetCategory = $('.positions_category[data-categoryid=' + targetCategoryId + ']', _private.$wrapper),
5929 targetCategoryTeamSize = _parent.getFormationPartFromCategoryId(targetCategoryId);
5930
5931 data.position1 = (isSourceOnBench) ?
5932 sourceCategoryTeamSize + $('.category_position[data-categoryid=' + sourceCategoryId + '] .positions_player', _private.$bench_players).index($source) :
5933 $('.positions_player', $sourceCategory).index($source);
5934 data.position2 = (isTargetOnBench) ?
5935 targetCategoryTeamSize + $('.category_position[data-categoryid=' + targetCategoryId + '] .positions_player', _private.$bench_players).index($target) :
5936 $('.positions_player', $targetCategory).index($target);
5937 data.benchPriority1 = $source.data('bench-priority');
5938 data.benchPriority2 = $target.data('bench-priority');
5939
5940 if(typeof data.benchPriority1 === 'undefined' && isSourceOnBench) {
5941 data.benchPriority1 = $source.closest('.category_position').index();
5942 }
5943
5944 if(typeof data.benchPriority2 === 'undefined' && isTargetOnBench) {
5945 data.benchPriority2 = $target.closest('.category_position').index();
5946 }
5947
5948 if(sourceCategoryId === targetCategoryId) {
5949 //same category
5950 data.categoryId = sourceCategoryId;
5951 } else if(isSourceOnBench && isTargetOnBench) {
5952 //both on the bench, different categories
5953 data.categoryId1 = sourceCategoryId;
5954 data.categoryId2 = targetCategoryId;
5955 } else {
5956 //formation change
5957 data.formationIx = $target.attr('data-formationix');
5958 data.pitchCategory = (isSourceOnBench) ? targetCategoryId : sourceCategoryId;
5959 data.pitchPosition = (isSourceOnBench) ? data.position2 : data.position1;
5960 data.benchCategory = (!isSourceOnBench) ? targetCategoryId : sourceCategoryId;
5961 data.benchPosition = (!isSourceOnBench) ? data.position2 : data.position1;
5962 data.benchModifier = (isSourceOnBench) ?
5963 $source.parent().prevAll('[data-categoryid=' + targetCategoryId + ']').length :
5964 $target.parent().prevAll('[data-categoryid=' + sourceCategoryId + ']').length;
5965 data.benchPriority = (isSourceOnBench) ? data.benchPriority1 : data.benchPriority2;
5966 }
5967
5968 _private.removeSwapTargets();
5969 _private.$wrapper.trigger('swapSlots', data);
5970 },
5971
5972 removeSwapTargets: function() {
5973 _private.$wrapper.find('.player-swap, .player-swap-target').removeClass('player-swap player-swap-out player-swap-in player-swap-target').off('click.swaptarget');
5974 $('.ui-droppable', _private.$wrapper).droppable('destroy');
5975 $('.positions_player', _private.$wrapper).removeAttr('data-formationix');
5976 // if bench
5977 if(_private.$bench_players) {
5978 _private.$bench_players.find('.player-swap, .player-swap-target').removeClass('player-swap player-swap-out player-swap-in player-swap-target').off('click.swaptarget');
5979 // remove the formation index mark on all the slots
5980 $('.positions_player', _private.$bench_players).removeAttr('data-formationix');
5981 // remove the droppable
5982 $('.ui-droppable', _private.$bench_players).droppable('destroy');
5983 }
5984
5985 _parent.$squad.removeClass('swap-active');
5986 },
5987
5988 // Init the lineup ui
5989 lineupInit: function() {
5990
5991 // exit if the component is not present
5992 if(!$('.component-lineup', _private.$wrapper).length) {
5993 return;
5994 }
5995
5996 // add the lineup property
5997 _private.$lineup = $('.component-lineup', _private.$wrapper);
5998
5999 // setup the slider
6000 $('.lineup_slider', _private.$lineup).slider({
6001 range: true,
6002 min: 1,
6003 max: 9,
6004 values: [3, 10 - 3],
6005 create: function() {
6006 // store the initial values
6007 $(this).attr('data-handler0', $(this).slider('values', 0));
6008 $(this).attr('data-handler1', $(this).slider('values', 1));
6009 },
6010 slide: function(event, ui) {
6011
6012 // this
6013 var $slider = $(this);
6014
6015 // def = first handler
6016 var vDEF = ui.values[0];
6017 // for = 10 - second handler
6018 var vFOR = 10 - ui.values[1];
6019
6020 // check for errors
6021 var failure = false;
6022
6023 // the previous values
6024 var vDEFp = $slider.attr('data-handler0');
6025 var vFORp = 10 - $slider.attr('data-handler1');
6026
6027 // check only about the changed handler
6028 if(vDEF != vDEFp) {
6029
6030 // first, it can go only 1 - 5
6031 if(vDEF < 1) {
6032 vDEF = 1;
6033 failure = true;
6034 }
6035 if(vDEF > 5) {
6036 vDEF = 5;
6037 failure = true;
6038 }
6039
6040 // second, it is limited by the other
6041 if(vDEF < 5 - vFOR) {
6042 vDEF = 5 - vFOR;
6043 failure = true;
6044 }
6045 if(vDEF > 9 - vFOR) {
6046 vDEF = 9 - vFOR;
6047 failure = true;
6048 }
6049 } else
6050 if(vFOR != vFORp) {
6051
6052 // first, it can go only 1 - 5
6053 if(vFOR < 1) {
6054 vFOR = 1;
6055 failure = true;
6056 }
6057 if(vFOR > 5) {
6058 vFOR = 5;
6059 failure = true;
6060 }
6061
6062 // second, it is limited by the other
6063 if(vFOR < 5 - vDEF) {
6064 vFOR = 5 - vDEF;
6065 failure = true;
6066 }
6067 if(vFOR > 9 - vDEF) {
6068 vFOR = 9 - vDEF;
6069 failure = true;
6070 }
6071 }
6072
6073 // update the slider
6074 // first handler = def
6075 // second handler = 10 - for
6076 $slider.slider('values', [vDEF, 10 - vFOR]);
6077
6078 // store the current values
6079 $slider.attr('data-handler0', vDEF);
6080 $slider.attr('data-handler1', 10 - vFOR);
6081
6082 // display the values
6083 var vMID = 10 - vDEF - vFOR;
6084 $('.lineup_def .value', _private.$lineup).html(vDEF);
6085 $('.lineup_mid .value', _private.$lineup).html(vMID);
6086 $('.lineup_for .value', _private.$lineup).html(vFOR);
6087
6088 // update the pitch
6089 _private.lineupUpdate();
6090
6091 // on failure return false
6092 if(failure) {
6093 return false;
6094 }
6095 }
6096 });
6097
6098 // display the initial values
6099 $('.lineup_def .value', _private.$lineup).html($('.lineup_slider').slider('values', 0));
6100 $('.lineup_mid .value', _private.$lineup).html($('.lineup_slider').slider('values', 1) - $('.lineup_slider').slider('values', 0));
6101 $('.lineup_for .value', _private.$lineup).html(10 - $('.lineup_slider').slider('values', 1));
6102 },
6103
6104 // Update the lineup display according to the slider component
6105 lineupUpdate: function() {
6106
6107 // exit if no lineup
6108 if(!_private.$lineup) {
6109 return;
6110 }
6111
6112 // get the category element
6113 var $category = $('.positions_category[data-index=1]', _private.$wrapper);
6114
6115 // DEF players
6116 var startPosition = 1;
6117 var positions = parseInt($('.lineup_def .value', _private.$lineup).text());
6118 var endPosition = startPosition + positions - 1;
6119 var positionWidth = (100 / positions).toString() + '%';
6120 for(var p = startPosition; p <= endPosition; p++) {
6121 $('.category_position:eq(' + (p - 1) + ')', $category).css('width', positionWidth);
6122 }
6123
6124 // MID players
6125 startPosition = endPosition + 1;
6126 positions = parseInt($('.lineup_mid .value', _private.$lineup).text());
6127 endPosition = startPosition + positions - 1;
6128 positionWidth = (100 / positions).toString() + '%';
6129 for(p = startPosition; p <= endPosition; p++) {
6130 $('.category_position:eq(' + (p - 1) + ')', $category).css('width', positionWidth);
6131 }
6132
6133 // FOR players
6134 startPosition = endPosition + 1;
6135 positions = parseInt($('.lineup_for .value', _private.$lineup).text());
6136 endPosition = startPosition + positions - 1;
6137 positionWidth = (100 / positions).toString() + '%';
6138 for(p = startPosition; p <= endPosition; p++) {
6139 $('.category_position:eq(' + (p - 1) + ')', $category).css('width', positionWidth);
6140 }
6141 },
6142
6143 reorderBench: function() {
6144 var $benchPlayers = _private.$bench_players.children('li');
6145
6146 if(!$benchPlayers.find('.positions_player[data-bench-priority]').length) return;
6147
6148 $benchPlayers.sort(function(a, b) {
6149 var ap = $(a).find('.positions_player').data('bench-priority'),
6150 bp = $(b).find('.positions_player').data('bench-priority');
6151
6152 if(ap > bp) {
6153 return 1;
6154 }
6155 if(ap < bp) {
6156 return -1;
6157 }
6158 return 0;
6159 });
6160
6161 $benchPlayers.detach().appendTo(_private.$bench_players);
6162 }
6163 },
6164
6165 // PUBLIC
6166 _public = {
6167
6168 /*
6169 Set up the pitch
6170 - args.$wrapper: jQuery wrapper element
6171 */
6172 init: function(args) {
6173
6174 //console.time('pitch init');
6175 // Configure the instance
6176 _parent = args._parent;
6177 _private.$wrapper = args.$wrapper;
6178 _private.$squad = _private.$wrapper.closest('.fantasyteams_squad');
6179 if(!_private.$squad.length)
6180 _private.$squad = _private.$wrapper.closest('.fantasyteams_squad_compact');
6181
6182 // Check for the lineup component and set it up
6183 _private.lineupInit();
6184
6185 // bench, get by standard class names
6186 var $bench = $('.fantasyteams_bench', _private.$squad);
6187
6188 var players = args._parent;
6189
6190 if($bench.length) {
6191 _private.$bench = $bench;
6192 }
6193
6194 // Set up the players
6195 _public.setPlayers();
6196
6197 // add the events
6198 // event - remove
6199 _private.$wrapper.on('click', '.action.remove', function() {
6200 var $button = $(this),
6201 $category = $button.closest('.positions_category'),
6202 $slot = $button.closest('.positions_player'),
6203 categoryId = $category.attr('data-categoryid'),
6204 position = $('.positions_player', $category).index($slot);
6205 _private.$wrapper.trigger('removePlayer', { categoryId: categoryId, position: position });
6206 });
6207 if(_private.$bench) {
6208 _private.$bench.on('click', '.action.remove', function() {
6209 var $button = $(this),
6210 $category = $button.closest('.category_position'),
6211 categoryId = $category.attr('data-categoryid'),
6212 formationPositions = _parent.getFormationPartFromCategoryId(categoryId),
6213 position = $('.category_position[data-categoryid=' + categoryId + ']', _private.$bench_players).index($category) + formationPositions;
6214 _private.$wrapper.trigger('removePlayer', { categoryId: categoryId, position: position });
6215 });
6216 }
6217 // event - keep the player
6218 _private.$wrapper.on('click', '.player_autocomplete_keep', function() {
6219 var $button = $(this),
6220 $category = $button.closest('.positions_category'),
6221 $slot = $button.closest('.positions_player'),
6222 categoryId = $category.attr('data-categoryid'),
6223 position = $('.positions_player', $category).index($slot);
6224 _private.$wrapper.trigger('keepPlayer', { categoryId: categoryId, position: position });
6225 _public.removeAutocomplete({ $slot: $slot });
6226 });
6227 if(_private.$bench) {
6228 _private.$bench.on('click', '.player_autocomplete_keep', function() {
6229 var $button = $(this),
6230 $category = $button.closest('.category_position'),
6231 $slot = $button.closest('.positions_player'),
6232 categoryId = $category.attr('data-categoryid'),
6233 formationPositions = _parent.getFormationPartFromCategoryId(categoryId),
6234 position = $('.category_position[data-categoryid=' + categoryId + ']', _private.$bench_players).index($category) + formationPositions;
6235 _private.$wrapper.trigger('keepPlayer', { categoryId: categoryId, position: position });
6236 _public.removeAutocomplete({ $slot: $slot });
6237 });
6238 }
6239 // event - select captain
6240 _private.$wrapper.on('click', '.action.captain:not(.selected)', function() {
6241 var $button = $(this),
6242 $category = $button.closest('.positions_category'),
6243 $slot = $button.closest('.positions_player');
6244 // exit if this is also the viceCaptain/rule-scorer
6245 if($slot.hasClass('viceCaptain')) return;
6246 if($slot.hasClass('ruleScorer')) return;
6247 var categoryId = $category.attr('data-categoryid'),
6248 position = $('.positions_player', $category).index($slot);
6249 // unselect the selected one
6250 $('.positions_player.captain', _private.$wrapper).removeClass('captain');
6251 $('.positions_player.captain', _private.$bench).removeClass('captain');
6252 // select the current one
6253 $slot.addClass('captain');
6254 // trigger the event
6255 _private.$wrapper.trigger('selectCaptain', { categoryId: categoryId, position: position });
6256 // keep the player
6257 $('.player_autocomplete_keep', $slot).click();
6258 });
6259 if(_private.$bench) {
6260 _private.$bench.on('click', '.action.captain:not(.selected)', function() {
6261 var $button = $(this),
6262 $category = $button.closest('.category_position'),
6263 $slot = $button.closest('.positions_player');
6264 // exit if this is also the viceCaptain/rule-scorer
6265 if($slot.hasClass('viceCaptain')) return;
6266 if($slot.hasClass('ruleScorer')) return;
6267 var categoryId = $category.attr('data-categoryid'),
6268 formationPositions = _parent.getFormationPartFromCategoryId(categoryId),
6269 position = $('.positions_player', $category).index($slot) + formationPositions;
6270 // unselect the selected one
6271 $('.positions_player.captain', _private.$wrapper).removeClass('captain');
6272 $('.positions_player.captain', _private.$bench).removeClass('captain');
6273 // select the current one
6274 $slot.addClass('captain');
6275 // trigger the event
6276 _private.$wrapper.trigger('selectCaptain', { categoryId: categoryId, position: position });
6277 // keep the player
6278 $('.player_autocomplete_keep', $slot).click();
6279 });
6280 }
6281 // event - select vice captain
6282 _private.$wrapper.on('click', '.action.viceCaptain:not(.selected)', function() {
6283 var $button = $(this),
6284 $category = $button.closest('.positions_category'),
6285 $slot = $button.closest('.positions_player');
6286 // exit if this is also the captain/rule-scorer
6287 if($slot.hasClass('captain')) return;
6288 if($slot.hasClass('ruleScorer')) return;
6289 var categoryId = $category.attr('data-categoryid');
6290 var position = $('.positions_player', $category).index($slot);
6291 // unselect the selected one
6292 $('.positions_player.viceCaptain', _private.$wrapper).removeClass('viceCaptain');
6293 $('.positions_player.viceCaptain', _private.$bench).removeClass('viceCaptain');
6294 // select the current one
6295 $slot.addClass('viceCaptain');
6296 // trigger the event
6297 _private.$wrapper.trigger('selectViceCaptain', { categoryId: categoryId, position: position });
6298 // keep the player
6299 $('.player_autocomplete_keep', $slot).click();
6300 });
6301 if(_private.$bench) {
6302 _private.$bench.on('click', '.action.viceCaptain:not(.selected)', function() {
6303 var $button = $(this),
6304 $category = $button.closest('.category_position'),
6305 $slot = $button.closest('.positions_player');
6306 // exit if this is also the captain
6307 if($slot.hasClass('captain')) return;
6308 var categoryId = $category.attr('data-categoryid'),
6309 formationPositions = _parent.getFormationPartFromCategoryId(categoryId),
6310 position = $('.positions_player', $category).index($slot) + formationPositions;
6311 // unselect the selected one
6312 $('.positions_player.viceCaptain', _private.$wrapper).removeClass('viceCaptain');
6313 $('.positions_player.viceCaptain', _private.$bench).removeClass('viceCaptain');
6314 // select the current one
6315 $slot.addClass('viceCaptain');
6316 // trigger the event
6317 _private.$wrapper.trigger('selectViceCaptain', { categoryId: categoryId, position: position });
6318 // keep the player
6319 $('.player_autocomplete_keep', $slot).click();
6320 });
6321 }
6322 // event - select rule scorer
6323 _private.$wrapper.on('click', '.action.ruleScorer:not(.selected)', function() {
6324 var $button = $(this),
6325 $category = $button.closest('.positions_category'),
6326 $slot = $button.closest('.positions_player');
6327 // exit if this is also the captain/vicecaptain
6328 if($slot.hasClass('captain')) return;
6329 if($slot.hasClass('viceCaptain')) return;
6330 var categoryId = $category.attr('data-categoryid'),
6331 position = $('.positions_player', $category).index($slot);
6332 // unselect the selected one
6333 $('.positions_player.ruleScorer', _private.$wrapper).removeClass('ruleScorer');
6334 $('.positions_player.ruleScorer', _private.$bench).removeClass('ruleScorer');
6335 // select the current one
6336 $slot.addClass('ruleScorer');
6337 // trigger the event
6338 _private.$wrapper.trigger('selectRuleScorer', { categoryId: categoryId, position: position });
6339 // keep the player
6340 $('.player_autocomplete_keep', $slot).click();
6341 });
6342 if(_private.$bench) {
6343 _private.$bench.on('click', '.action.ruleScorer:not(.selected)', function() {
6344 var $button = $(this),
6345 $category = $button.closest('.category_position'),
6346 $slot = $button.closest('.positions_player');
6347 // exit if this is also the captain/vicecaptain
6348 if($slot.hasClass('captain')) return;
6349 if($slot.hasClass('viceCaptain')) return;
6350 var categoryId = $category.attr('data-categoryid'),
6351 formationPositions = _parent.getFormationPartFromCategoryId(categoryId),
6352 position = $('.positions_player', $category).index($slot) + formationPositions;
6353 // unselect the selected one
6354 $('.positions_player.ruleScorer', _private.$wrapper).removeClass('ruleScorer');
6355 $('.positions_player.ruleScorer', _private.$bench).removeClass('ruleScorer');
6356 // select the current one
6357 $slot.addClass('ruleScorer');
6358 // trigger the event
6359 _private.$wrapper.trigger('selectRuleScorer', { categoryId: categoryId, position: position });
6360 // keep the player
6361 $('.player_autocomplete_keep', $slot).click();
6362 });
6363 }
6364 // event - select an empty slot
6365 _private.$squad.on('click', '.slot-empty', function() {
6366 var $button = $(this),
6367 // find the category element
6368 $category = $button.closest('.positions_category');
6369 if(!$category.attr('data-categoryid')) {
6370 $category = $button.closest('.category_position');
6371 }
6372 var categoryId = $category.attr('data-categoryid');
6373
6374 let playerAvailableList = _parent.players.filter(x => x.categoryId === parseInt(categoryId));
6375 const icon = $(this).find('.fa-angle-right');
6376 const playersListTpl = $(this).find('.players-list');
6377 const categoryPosition = $(this).parents('.positions_category').attr('data-categoryname');
6378 icon.toggleClass("fa-angle-down" );
6379 //$('.players-list').empty();
6380
6381 if (playersListTpl.is(':empty') && !playersListTpl.hasClass('selected')) {
6382 playerAvailableList.map(function(item) {
6383 //console.log(item);
6384 ctvApp.view.templatePlace({
6385 $target : playersListTpl,
6386 method : 'append',
6387 template: $( '[data-name="position_players_list"]', _private.$wrapper ).html(),
6388 template_data: {
6389 fullName: item.fullName,
6390 sideAbbreviation: item.sideAbbreviation,
6391 value: item.value,
6392 totalPoints: item.totalPoints,
6393 code: item.code,
6394 side: 'side side_'+ item.sideAbbreviation,
6395 categoryPosition: categoryPosition
6396 }
6397 });
6398
6399 });
6400 } else if(!playersListTpl.is(':empty')){
6401 icon.removeClass("fa-angle-down").addClass("fa-angle-right" );
6402 playersListTpl.empty();
6403 }
6404 else if (playersListTpl.hasClass('selected')) {
6405 playersListTpl.empty();
6406 }
6407
6408 _parent.$wrapper.trigger('toggleSidebar');
6409 _private.$wrapper.trigger('selectEmptySlot', { categoryId: categoryId });
6410
6411 });
6412
6413 _private.$squad.on('click', '.players-list__item', function() {
6414 const item = ctvApp.fantasyteams.players.filter(x => x.code === $(this).attr('data-code'))[0];
6415 const wrap = $(this).parents('.slot');
6416 _private.addPlayer(item, wrap);
6417
6418 });
6419
6420 _private.$squad.on('click', '.player-item.player', function() {
6421 $('#playerFinderModal').foundation('reveal','open');
6422 const categoryId = $(this).parents('.positions_category').attr('data-categoryid');
6423 $('.playerFinder_positions').val(categoryId).change();
6424 $('.playerFinder_positions').attr('disabled', true).trigger("chosen:updated");
6425 $(".playerFinder_teams").chosen({
6426 width: '100%',
6427 "disable_search": true
6428 });
6429 $(".playerFinder_positions").chosen({
6430 width: '100%',
6431 "disable_search": true
6432 });
6433
6434 });
6435
6436
6437 // event - player info
6438 _private.$squad.on('click', '.player_info', function() {
6439 var $button = $(this),
6440 playerCode = $button.data('code'),
6441 $category = $button.closest('.positions_category');
6442 if(!$category.attr('data-categoryid')) {
6443 $category = $button.closest('.category_position');
6444 }
6445 var categoryId = $category.attr('data-categoryid'),
6446 $slot = $button.closest('.positions_player'),
6447 position = $('.positions_player', $category).index($slot);
6448 _private.$wrapper.trigger('playerInfo', { categoryId: categoryId, position: position, playerCode: playerCode });
6449 });
6450 // player info by clicking the player image
6451 _private.$wrapper.on('click', '.positions_player:not(.slot-empty) .player_image', function() {
6452 // only for the small screen
6453 if($('body').attr('media') != 'small') return;
6454 var $button = $(this),
6455 playerCode = $button.data('code'),
6456 $category = $button.closest('.positions_category');
6457 if(!$category.attr('data-categoryid')) {
6458 $category = $button.closest('.category_position');
6459 }
6460 var categoryId = $category.attr('data-categoryid'),
6461 $slot = $button.closest('.positions_player'),
6462 position = $('.positions_player', $category).index($slot);
6463 _private.$wrapper.trigger('playerInfo', { categoryId: categoryId, position: position, playerCode: playerCode });
6464 });
6465
6466 if(_private.$wrapper.length) {
6467 var $modPitch = _private.$wrapper.closest('.mod-pitch');
6468 $modPitch.siblings('.pitch.gear-loading').addClass('hide');
6469 $modPitch.removeClass('hide');
6470 //console.timeEnd('pitch init');
6471 }
6472 },
6473
6474 /*
6475 Removes the autocomplete from the slot
6476 - requires either args.$slot or (args.categoryId and args.position)
6477 */
6478 removeAutocomplete: function(args) {
6479 var $slot = args.$slot;
6480 if(!$slot) {
6481 var categoryId = args.categoryId;
6482 var slotIndex = args.position;
6483 // is the slot on the pitch or the bench?
6484 var formationMax = _parent.getFormationPartFromCategoryId(categoryId);
6485 if(slotIndex < formationMax) {
6486 // get the slot from the pitch
6487 $slot = $('.positions_category[data-categoryid=' + categoryId + '] .positions_player:eq(' + slotIndex + ')', _private.$wrapper);
6488 } else {
6489 // get the slot from the bench
6490 if(_private.$bench) {
6491 $slot = $('.category_position[data-categoryid=' + categoryId + ']:eq(' + (slotIndex - formationMax) + ') .positions_player', _private.$bench);
6492 }
6493 }
6494 // exit if the slot has not been found
6495 if(!$slot.length) return;
6496 }
6497 // remove the autocomplete
6498 $slot.removeClass('autocomplete');
6499 },
6500
6501 clearBenchCaptains: function() {
6502 // todo: if there is no bench players then exit (i am not sure why there isn't one)
6503 if(!_private.$bench_players) return;
6504
6505 for(var i = 0, ii = _parent.roles.length; i < ii; i++) {
6506 var $captain = _private.$bench_players.find('.positions_player.' + _parent.roles[i]);
6507 if($captain.length) {
6508 $captain.removeClass(_parent.roles[i]);
6509 _parent.$wrapper.find('.fantasyteams_' + _parent.roles[i] + ' select').val('').trigger('change');
6510 }
6511 }
6512 },
6513
6514 /*
6515 Set up the players
6516 this is used for the initial setup and for any change in the players setup later
6517 (such as the formation change)
6518 */
6519 setPlayers: function(args) {
6520
6521 var updateBenchPriority = (args && args.updateBenchPriority) ? args.updateBenchPriority : false;
6522 var stickyCaptain = (args && args.stickyCaptain) ? args.stickyCaptain : false;
6523
6524 // set up the team players section
6525 if(!_private.$players) {
6526 _private.$players = $('.positions_players', _private.$wrapper);
6527 }
6528 // empty
6529 _private.$players.empty();
6530
6531 // empty
6532 if(_private.$bench_players) {
6533 _private.$bench_players.empty();
6534 }
6535
6536 // empty the dropdowns
6537 _parent.ruleScorer_empty();
6538 _parent.captain_empty();
6539 _parent.viceCaptain_empty();
6540
6541 // add player templates for the formation
6542 var $players = _private.$players,
6543 tpl_player = $('[data-name="positions_player"]', _private.$wrapper).html(),
6544 formation = _parent.getFormation(),
6545 teamSize;
6546
6547 // if the sticky captain option is on, make sure captain stays on the pitch
6548 if(stickyCaptain && _parent.team.captain) {
6549 var captain = _parent.getPlayerByCode(_parent.team.captain.code),
6550 captainInTeam = _parent.getPlayerInTeam(captain),
6551 categoryId = _parent.team.captain.categoryId,
6552 categoryIndex = _parent.getCategoryIndex(categoryId),
6553 captainIndex = _parent.team.players[categoryId].indexOf(captainInTeam);
6554
6555 teamSize = formation[categoryIndex];
6556
6557 if(captainIndex >= teamSize) {
6558 var tempPlayer = _parent.team.players[categoryId][teamSize - 1];
6559 _parent.team.players[categoryId][teamSize - 1] = captainInTeam;
6560 _parent.team.players[categoryId][captainIndex] = tempPlayer;
6561 }
6562 }
6563
6564 // loop through the categories
6565 var categories = _parent.gameSettings.categories,
6566 slotNumber = 0;
6567 for(var i = 0; i < categories.length; i++) {
6568 var category = categories[i],
6569 // get the players for the category
6570 players = _parent.team.players[category.id],
6571 $category = $('<ul class="positions_category category_row_' + category.abbr + '"></ul>');
6572
6573 // get the team size in the category
6574 teamSize = formation[i];
6575
6576 // add the category
6577 $category.attr('data-index', i);
6578 $category.attr('data-categoryid', category.id);
6579 $category.attr('data-categoryname', category.name);
6580 $category.attr('data-categoryabbr', category.abbr);
6581 $players.append($category);
6582 $category.append('<div class="category-header ' + category.abbr + '">' + category.name + '</div>');
6583
6584 // grid option
6585 //grid = _private.$wrapper.attr('data-grid');
6586
6587 // lineup feature excludes the grid, affects only the second category on the pitch
6588 // if(_private.$lineup && i == 1) {
6589 // grid = 'none';
6590 // }
6591
6592 // set the number of positions on the category grid
6593 // if(grid != 'none') {
6594 // // total columns
6595 // columns = teamSize;
6596 // columns_large = columns_medium = columns_small = columns;
6597 // // limits
6598 // if(columns_large > 5) columns_large = 5;
6599 // if(columns_medium > 5) columns_medium = 5;
6600 // if(columns_small > 2) columns_small = 2;
6601 // // set the grid
6602 // $category.addClass('large-block-grid-' + columns_large);
6603 // $category.addClass('medium-block-grid-' + columns_medium);
6604 // $category.addClass('small-block-grid-' + columns_small);
6605 // }
6606
6607 var benchPriority = 1;
6608 for(var p = 0; p < players.length; p++) {
6609
6610 // add the position element
6611 var $position = $('<li class="category_position"></li>');
6612
6613 // if the player is inside the team size
6614 if(p < teamSize) {
6615 // add the position to the category
6616 $category.append($position);
6617 } else {
6618 // if there is a bench
6619 if(_private.$bench_players) {
6620 // add the position to the bench
6621 $position.attr('data-categoryid', category.id);
6622 _private.$bench_players.append($position);
6623 } else {
6624 // destroy the position element
6625 $position = null;
6626 }
6627 }
6628
6629 // if there is a position created
6630 if($position) {
6631 // add the player template
6632 slotNumber++;
6633 var $item = ctvApp.view.templatePlace({
6634 $target: $position,
6635 template: tpl_player,
6636 template_data: {
6637 name: '',
6638 hasBench: !!_private.$bench_players,
6639 isOnBench: p >= teamSize,
6640 slotNumber: slotNumber
6641 }
6642 });
6643
6644 // sets the slot to player or empty
6645 var player = null;
6646 if(players[p]) {
6647 player = players[p].player;
6648 if(updateBenchPriority) {
6649 if(p < teamSize) {
6650 player.benchPriority = -1;
6651 } else {
6652 // set bench priority
6653 var catBounds = _parent.getCategoryBounds(player.categoryId);
6654 if(catBounds.teamMin == 1 && catBounds.teamMax == 1 && catBounds.benchMin == 1 && catBounds.benchMax == 1) {
6655 player.benchPriority = 0;
6656 } else {
6657 player.benchPriority = benchPriority;
6658 benchPriority++;
6659 }
6660 }
6661 }
6662 }
6663 _public.setSlot({ player: player, $slot: $item, categoryId: category.id });
6664
6665 // add the player to the dropdown rule scorer
6666 if(player) {
6667 _parent.ruleScorer_add(player);
6668 }
6669 // add the player to the dropdown captain
6670 if(player) {
6671 _parent.captain_add(player);
6672 }
6673 // add the player to the dropdown vice captain
6674 if(player) {
6675 _parent.viceCaptain_add(player);
6676 }
6677 }
6678 }
6679
6680 // draggables - only if not in a viewOnly mode
6681 if(!_parent.args.viewOnly) {
6682
6683 var $containment = $category;
6684 if(_parent.$squad) $containment = _parent.$squad;
6685
6686 // add the draggable
6687 // only where more than a single position
6688 if(players.length > 1) {
6689 var $positions = $('.positions_category[data-categoryid=' + category.id + '] .positions_player', _private.$wrapper).filter(function() {
6690 return !!$(this).closest('.fantasyteams_squad').length;
6691 });
6692
6693 if(!ctvApp.config.disableDragAndDrop) {
6694 $positions.draggable({
6695 containment: $containment,
6696 revert: true,
6697 revertDuration: 0, // we prefer this snappy timing, also the animation can break if draggable is not suspended during the animation (todo?)
6698 start: _private.setSwapTargets,
6699 stop: _private.removeSwapTargets
6700 });
6701 }
6702
6703 $positions.on('click', '.swap-icon', _private.playerSwapClickHandler);
6704 // if bench
6705 if(_private.$bench_players) {
6706 var $benchPositions = $('.category_position[data-categoryid=' + category.id + '] .positions_player', _private.$bench_players).filter(function() {
6707 return !!$(this).closest('.fantasyteams_squad').length;
6708 });
6709 if(!ctvApp.config.disableDragAndDrop) {
6710 $benchPositions.draggable({
6711 containment: $containment,
6712 revert: true,
6713 revertDuration: 0, // we prefer this snappy timing, also the animation can break if draggable is not suspended during the animation (todo?)
6714 start: _private.setSwapTargets,
6715 stop: _private.removeSwapTargets
6716 });
6717 }
6718
6719 $benchPositions.on('click', '.swap-icon', _private.playerSwapClickHandler);
6720
6721 }
6722 }
6723 }
6724 }
6725
6726 if(_private.$bench_players) {
6727 // reorder the bench
6728 _private.reorderBench();
6729 }
6730
6731 // update the lineup
6732 _private.lineupUpdate();
6733
6734 // re-init the foundation dropdowns
6735 /*
6736 todo:
6737 currently, this is in place for the playerPoints, which are displayed on the pages where there are no slot updates
6738 if there would be slot updates in addition to this feature then the dropdowns should be tested
6739 in case the foundation dropdowns would not be flexible we may want to create our own dropdowns instead
6740 note - placing the foundation init on the slot update didn't workout well for the tooltips
6741 */
6742 $(document).foundation('dropdown', 'reflow');
6743 },
6744
6745 /*
6746 Setup the player points with the dropdown
6747 - will remove the dropdown if the points are not defined for the player
6748 - args.player : optional, player data
6749 - args.$slot : slot
6750 */
6751 setPlayerPoints: function(args) {
6752
6753 var $slot = args.$slot;
6754 var $playerPoints = $('.player_points', $slot);
6755 var player = args.player;
6756 var $dropdown = $('#' + $playerPoints.attr('data-dropdown'));
6757
6758 // do we have a player
6759 if(!player) {
6760 // no player, empty the points element
6761 // todo: this bit is probably not right if the slots are updated
6762 $playerPoints.empty();
6763 // empty the dropdown
6764 if($dropdown) {
6765 $dropdown.empty();
6766 }
6767 return;
6768 }
6769
6770 // display the points
6771 $playerPoints.text(player.pointsScored);
6772
6773 // are there any points and fixtures?
6774 // if( player.pointsScored && player.fixture ){ // todo: old code
6775 if(typeof player.pointsScored !== 'undefined' && player.fixture) {
6776
6777 // NOTE: _private.$wrapper may not be correct because of the scope!
6778 // TODO: check the solution in setPlayerNextFixture
6779
6780 // if the dropdown is missing then create one
6781 if(!$dropdown.length) {
6782 var id = 'playerPointsDropdown_' + ctvApp.core.nextID();
6783 $dropdown = ctvApp.view.templatePlace({
6784 $target: _private.$wrapper,
6785 template: $('[data-name="player_points_dropdown"]', _private.$wrapper).html(),
6786 template_data: {
6787 teamA_abbr: 'test'
6788 }
6789 });
6790 $dropdown.attr('id', id);
6791 $playerPoints.attr('data-dropdown', id);
6792 }
6793
6794 // are there any points scored
6795 if(player.pointsScored == '-') {
6796
6797 // no points, display the message
6798 ctvApp.view.templatePlace({
6799 method: 'replace',
6800 $target: $dropdown,
6801 template: $('[data-name="player_points_dropdown_notplayed"]', _private.$wrapper).html()
6802 });
6803
6804 } else {
6805
6806 // empty the dropdown
6807 $dropdown.empty();
6808
6809 // add the content for each single fixture
6810 for(var i = 0; i < player.fixture.length; i++) {
6811 var singleFixture = player.fixture[i];
6812 // does this particular fixture have any rules
6813 // (in case the player has not scored in that one, but has in other ones)
6814 if(singleFixture.rules) {
6815 // update the stats in the dropdown
6816 var $content = ctvApp.view.templatePlace({
6817 $target: $dropdown,
6818 template: $('[data-name="player_points_dropdown_played"]', _private.$wrapper).html(),
6819 template_data: {
6820 teamA_abbr: $.grep(ctvApp.API.gameSettings.sides, function(side) {
6821 return(side.id == singleFixture.teamAId);
6822 })[0].abbr,
6823 teamB_abbr: $.grep(ctvApp.API.gameSettings.sides, function(side) {
6824 return(side.id == singleFixture.teamBId);
6825 })[0].abbr,
6826 teamA_score: singleFixture.teamAScore,
6827 teamB_score: singleFixture.teamBScore
6828 }
6829 });
6830
6831 // stats table
6832 var $statsTable = $('.dropdown_stats', $content);
6833
6834 // add rules from the fixture
6835 for(var j = 0; j < singleFixture.rules.length; j++) {
6836 var rule = singleFixture.rules[j];
6837
6838 // add the template
6839 ctvApp.view.templatePlace({
6840 $target: $('tbody', $statsTable),
6841 template: $('[data-name="player_points_dropdown_detailsRow"]', _private.$wrapper).html(),
6842 template_data: {
6843 rule_description: $.grep(ctvApp.API.gameSettings.rules, function(gameRule) {
6844 return(rule.abbr == gameRule.abbr);
6845 })[0].desc,
6846 rule_count: rule.count || 1,
6847 rule_points: rule.points
6848 }
6849 });
6850 }
6851 }
6852 }
6853 }
6854
6855 } else {
6856 // no stats, empty the dropdown
6857 // todo: this scenario is not described
6858 if($dropdown) {
6859 $dropdown.empty();
6860 }
6861 }
6862 },
6863
6864 /*
6865 Setup the display of player's next fixture
6866 - will remove the dropdown if the fixture is not defined for the player
6867 - args.player : optional, player data
6868 - args.$slot : slot
6869 */
6870 setPlayerNextFixture: function(args) {
6871
6872 var $slot = args.$slot;
6873 var $nextFixture = $('.player_nextFixture', $slot);
6874 var player = args.player;
6875 var $dropdown = $('#' + $nextFixture.attr('data-dropdown'));
6876
6877 // do we have a player
6878 if(player) {
6879
6880 var nextFixture = _parent.getNextFixture(player.nextFixtureId);
6881 if(nextFixture) {
6882
6883 // we cannot use the _private scope here (issues may be with the closure)
6884 // so this is instead of _private.$wrapper
6885 var $wrapper = $slot.closest('.fantasyteams_squad');
6886 if(!$wrapper.length)
6887 $wrapper = $slot.closest('.fantasyteams_squad_compact');
6888
6889 // home/away
6890 // if the players team is teamA then it's home, otherwise away
6891 var home = (nextFixture.teamA == player.side); // if the translations are handled for both
6892
6893 // display the next fixture
6894 ctvApp.view.templatePlace({
6895 $target: $nextFixture,
6896 method: 'replace',
6897 template: $('[data-name="player_nextFixture"]', $wrapper).html(),
6898 template_data: {
6899 home_away: home ? 'home' : 'away',
6900 team: home ? nextFixture.teamBInfo1 : nextFixture.teamAInfo1,
6901 team_abbr: home ? nextFixture.teamBAbbr : nextFixture.teamAAbbr
6902 }
6903 });
6904
6905 var fixtureDate;
6906
6907 // if the dropdown is missing then create one
6908 if(!$dropdown.length) {
6909 var id = 'playerNextFixtureDropdown_' + ctvApp.core.nextID(),
6910 dateFormat = ctvApp.config.dateFormat || 'dd/MM/yyyy';
6911
6912 fixtureDate = $.format.date(new Date(nextFixture.date), dateFormat);
6913 $dropdown = ctvApp.view.templatePlace({
6914 $target: $wrapper,
6915 template: $('[data-name="player_nextFixture_dropdown"]', $wrapper).html(),
6916 template_data: {
6917 fixture_date: fixtureDate,
6918 fixture_time: nextFixture.time
6919 }
6920 });
6921 $dropdown.attr('id', id);
6922 $nextFixture.attr('data-dropdown', id);
6923 }
6924
6925 // populate the dropdown
6926 ctvApp.view.templatePlace({
6927 $target: $dropdown,
6928 method: 'replace',
6929 template: $('[data-name="player_nextFixture_dropdown_content"]', $wrapper).html(),
6930 template_data: {
6931 fixture_date: fixtureDate,
6932 fixture_time: nextFixture.time
6933 }
6934 });
6935 }
6936 } else {
6937
6938 // no player, empty the next fixture element
6939 // todo: this bit is probably not right if the slots are updated
6940 $nextFixture.empty();
6941 // empty the dropdown
6942 if($dropdown) {
6943 $dropdown.empty();
6944 }
6945 }
6946 },
6947
6948 /*
6949 Sets the slot to player or to empty
6950 - args.player : optional, player data
6951 - args.$slot : slot - if present, then uses this element
6952 - args.categoryId : required if the player and $slot are missing
6953 - args.slot : index of the slot, required if the $slot is missing (todo: is this analog to position arg?)
6954 - todo: can this be refactored with the removePlayer?
6955 */
6956 setSlot: function(args) {
6957
6958 var player = args.player;
6959 var $slot = args.$slot;
6960
6961 // get the category id
6962 var categoryId = args.categoryId;
6963 // if not from the args, then from the player or the slot
6964 if(!categoryId) {
6965 if(player) {
6966 categoryId = player.categoryId;
6967 } else if($slot.length) {
6968 categoryId = $slot.closest('[data-categoryid]').attr('data-categoryid');
6969 }
6970 }
6971
6972 // get the slot if not in the args
6973 if(!$slot) {
6974 var slotIndex = args.slot;
6975 // is the slot on the pitch or the bench?
6976 var formationMax = _parent.getFormationPartFromCategoryId(categoryId);
6977 if(slotIndex < formationMax) {
6978 // get the slot from the pitch
6979 $slot = $('.positions_category[data-categoryid=' + categoryId + '] .positions_player:eq(' + slotIndex + ')', _private.$wrapper);
6980 if(player) player.benchPriority = -1;
6981 } else {
6982 // get the slot from the bench
6983 if(_private.$bench) {
6984 $slot = $('.category_position[data-categoryid=' + categoryId + ']:eq(' + (slotIndex - formationMax) + ') .positions_player', _private.$bench);
6985 if(player) player.benchPriority = $('.category_position .positions_player', _private.$bench).index($slot);
6986 }
6987 }
6988 // exit if the slot has not been found
6989 if(!$slot.length) return;
6990 // update the classes
6991 if(player) {
6992 if(player.autocomplete) {
6993 $slot.addClass('autocomplete');
6994 } else {
6995 $slot.removeClass('autocomplete');
6996 }
6997 }
6998 }
6999
7000 //Make sure we know which slot was just filled
7001 $('.positions_player').removeClass('just-added');
7002 $slot.addClass('just-added');
7003 // fill in the common details
7004 var catAbbr = _parent.gameSettings.categoryIds[categoryId].abbr;
7005 $('.player_categoryAbbreviation', $slot).text(catAbbr).addClass(catAbbr);
7006 var slotHtml = (ctvApp.config.fluidFormations) ? ctvApp.core.translations['Select Player'] : '<span class="category_select_label">' + ctvApp.core.translations.Select + ' </span>' + _parent.gameSettings.categoryIds[categoryId].name;
7007 $('.player_category', $slot).html(slotHtml);
7008
7009 // check if we have a player
7010 if(player) {
7011 if(player.hasOwnProperty('benchPriority') && player.benchPriority > -2) {
7012 $slot.attr('data-bench-priority', player.benchPriority);
7013 }
7014
7015 // fill in the slot
7016 $('.player_name', $slot).text(player.name);
7017 $('.player_value', $slot).text((ctvApp.config.playerValueDecimals > 0) ? player.value.toFixed(ctvApp.config.playerValueDecimals) : player.value);
7018 $('.player_info', $slot).attr('data-code', player.code);
7019 // update the player points
7020 _public.setPlayerPoints({ $slot: $slot, player: player });
7021 _public.setPlayerNextFixture({ $slot: $slot, player: player });
7022 $('.player_sideAbbreviation', $slot).text(player.sideAbbreviation);
7023 // setup the player image (todo: playerImage is defined outside this module)
7024 _parent.insertPlayerImage({
7025 '$container': $('.player_image', $slot),
7026 'player': player,
7027 'alt': player.name
7028 });
7029 if(player.starter) {
7030 $slot.find('.player_starter').removeClass('hide');
7031 }
7032 /*
7033 $( '.player_image', $slot ).empty().append( '<img ' +
7034 'src="' + _parent.getPlayerImage(player.code, player.sideInfo1) + '" ' +
7035 'alt="' + player.name + '" ' +
7036 'title="' + player.name + '">'
7037 );*/
7038 //show the overseas indicator if needed
7039 if (ctvApp.config.overseasPlayerLimit && player[ctvApp.config.overseasPlayerProp] === 'Y') {
7040 $slot.find('.player_overseas').removeClass('hide');
7041 }
7042 // update the classes
7043 $slot.removeClass('slot-empty');
7044 $slot.addClass('remove');
7045 if(player.autocomplete) {
7046 $slot.addClass('autocomplete');
7047 } else {
7048 $slot.removeClass('autocomplete');
7049 }
7050 if(player.captain) {
7051 $slot.addClass('captain');
7052 } else {
7053 $slot.removeClass('captain');
7054 }
7055 if(player.viceCaptain) {
7056 $slot.addClass('viceCaptain');
7057 } else {
7058 $slot.removeClass('viceCaptain');
7059 }
7060 if(player.ruleScorer) {
7061 $slot.addClass('ruleScorer');
7062 } else {
7063 $slot.removeClass('ruleScorer');
7064 }
7065 if(player.new) {
7066 $slot.addClass('new');
7067 } else {
7068 $slot.removeClass('new');
7069 }
7070 if(player.status) {
7071 $slot.attr('data-status', player.status);
7072 } else {
7073 $slot.removeAttr('data-status');
7074 }
7075 if(player.state) {
7076 $slot.attr('data-state', player.state);
7077 } else {
7078 $slot.removeAttr('data-state');
7079 }
7080 } else {
7081 // empty the slot
7082 $('.player_name', $slot).text('');
7083 $('.player_value', $slot).empty();
7084 $('.player_image', $slot).empty();
7085 _public.setPlayerPoints({ $slot: $slot });
7086 $('.player_sideAbbreviation', $slot).empty();
7087 $('.player_nextFixture', $slot).empty();
7088 $('.player_starter', $slot).addClass('hide');
7089 // update the classes
7090 $slot.addClass('slot-empty');
7091 $slot.removeClass('autocomplete remove captain viceCaptain ruleScorer new');
7092 $slot.removeAttr('data-status');
7093 $slot.removeAttr('data-state');
7094 }
7095 // init the foundation tooltips
7096 // disabled as this puts a major delay and actually prevents firefox from working correctly
7097 // todo: reconsider the way for tooltips (maybe one general reflow if we can organize the code?)
7098 // $(document).foundation('tooltip', 'reflow');
7099 },
7100
7101 /*
7102 Removes the player from the slot (sets the slot to empty)
7103 - args.player : player object
7104 - args.slot : slot index (position inside the category)
7105 - todo: can this be refactored with the setSlot?
7106 */
7107 removePlayer: function(args) {
7108
7109 var player = args.player,
7110 slotIndex = args.slot,
7111
7112 // get the category id for the player category abbreviation
7113 categoryId = player.categoryId,
7114
7115 // is the slot on the pitch or the bench?
7116 formationMax = _parent.getFormationPartFromCategoryId(categoryId),
7117 $slot;
7118
7119 if(slotIndex < formationMax) {
7120 // get the slot from the pitch
7121 $slot = $('.positions_category[data-categoryid=' + categoryId + '] .positions_player:eq(' + slotIndex + ')', _private.$wrapper);
7122 } else {
7123 // get the slot from the bench
7124 if(_private.$bench) {
7125 $slot = $('.category_position[data-categoryid=' + categoryId + ']:eq(' + (slotIndex - formationMax) + ') .positions_player', _private.$bench);
7126 }
7127 }
7128 // exit if the slot has not been found
7129 if(!$slot.length) return;
7130
7131 $('.positions_player').removeClass('just-added');
7132
7133 // empty the slot
7134 $('.player_name', $slot).text('');
7135 $('.player_value', $slot).empty();
7136 $('.player_image', $slot).empty();
7137 _public.setPlayerPoints({ $slot: $slot });
7138 $('.player_sideAbbreviation', $slot).empty();
7139 $('.player_nextFixture', $slot).empty();
7140 $('.player_starter', $slot).addClass('hide');
7141 // update the classes
7142 $slot.addClass('slot-empty');
7143 $slot.removeClass('autocomplete remove captain viceCaptain ruleScorer new');
7144 $slot.removeAttr('data-status');
7145 $slot.removeAttr('data-state');
7146 }
7147 };
7148
7149 // INTERFACE
7150 return _public;
7151};