<commit_before>const express = require('express');
const mongoose = require('mongoose');
const shortid = require('shortid');
const Orderer = require('./../models/Orderer');
const OrderNote = require('./../models/OrderNote');

mongoose.Promise = global.Promise;

const router = express.Router();

/*
	GET /api/orderer
	등록된 거래처 리스트를 조회

	ERROR CODES
		1 : PERMISSION DENIED
		2 : NO RESOURCES

*/
router.get('/', (req, res) => {
	const session = req.session;

	if (typeof session.loginInfo === 'undefined') {
		return res.status(401).json({
			error: 'PERMISSION DENIED',
			code: 1
		});
	}

	Orderer.find().sort({name: 1}).exec( (err, orderers) => {
		if (err) throw err;

		if(!orderers.length) {
			return res.status(400).json({
				error: 'NO RESOURCES',
				code: 2
			});
		}

		return res.json({
			success: true,
			orderers
		});
	});

});

/*
	GET /api/orderer/stat

	ERROR CODES
	1 : PERMISSION DENIED
*/
router.get('/stat', (req, res) => {
	const session = req.session;

	if (typeof session.loginInfo === 'undefined') {
		console.log('perm denied');
		return res.status(401).json({
			error: 'PERMISSION DENIED',
			code: 1
		});
	}

	const c_year = (new Date()).getFullYear();

	OrderNote.aggregate([
		{ $match: {
			'delivery.date': {
				$gte: new Date((c_year-1), 0, 1),
				$lt: new Date((c_year+1), 0, 1)
			}
		}},
		{ $project: {
			id: '$orderer.id',
			price: { $multiply: ['$delivery.price', '$delivery.count'] }
		}},
		{ $group: {
			_id: null,
			totalPrice: { $sum: '$price'},
			ordererPrice: { $sum: {
								$cond: {
									if: { $ne: ['$id', 'no'] },
									then: '$price',
									else: 0
								}
							}}
		}}
	], (err, p_res) => {
		if (err) {
			console.log(err);
			throw err;
		}

		if (!p_res || p_res.length === 0) {
			return res.json({
				priceData: null,
				yearData: null
			});
		}

		OrderNote.aggregate([
			{ $match: {
				$and : [
					{ 'delivery.date': {
						$gte: new Date((c_year-1), 0, 1),
						$lt: new Date((c_year+1), 0, 1)
					}},
					{ 'orderer.id': {
						$ne: 'no'
					}}
				]}
			},
			{ $project: {
				yearMonth: { $dateToString: { format: '%Y-%m', date: '$delivery.date' }},
				price: { $multiply: ['$delivery.price', '$delivery.count']}
			}},
			{ $group: {
				_id: '$yearMonth',
				monthPrice: { $sum: '$price' }
			}},
			{ $sort: { _id: 1 }}
		], (err2, y_res) => {
			if (err2) {
				console.log(err2);
				throw err2;
			}

			console.log("Server Data ", p_res, y_res);
			if (!y_res || y_res.length === 0) {
				return res.json({
					priceData: p_res[0],
					yearData: null
				});
			}

			return res.json({
				priceData: p_res[0],
				yearData: y_res
			});
		});
	});
});

/*
	GET /api/orderer/:id
	특정 거래처 정보(1년간의 데이터)를 조회

	ERROR CODES
		1 : INVALID ID
		2 : ORDERER NOT EXISTS
		3 : PERMISSION DENIED
*/
router.get('/:id', (req, res) => {
	const session = req.session;

	if (typeof session.loginInfo === 'undefined') {
		console.log('perm denied');
		return res.status(401).json({
			error: 'PERMISSION DENIED',
			code: 3
		});
	}

	if (!shortid.isValid(req.params.id)) {
		console.log('invalid id');
        return res.status(400).json({
            error: "INVALID ID",
            code: 1
        });
	}

	Orderer.find({'_id': req.params.id}, (err, orderer) => {
		if (err) throw err;

		if (!orderer || orderer.length === 0) {
			console.log('orderer not exists');
	        return res.status(400).json({
	            error: "ORDERER NOT EXISTS",
	            code: 2
	        });
		}

		const c_year = (new Date()).getFullYear();

		OrderNote.find({
			$and: [
				{'orderer.id': req.params.id},
				{'delivery.date': { $gte: (new Date(c_year, 0, 1)), $lt: (new Date((c_year+1), 0, 1))}}
			]
		}, {
			__v: 0,
			_id: 0,
			orderer: 0,
			'delivery.text': 0,
			date: 0,
			memo: 0
		}, {
			sort: {
				'delivery.date': -1	// 배송일자 내림차순 정렬
			}
		}, (err2, orders_res) => {
			const data = {};

			if (orders_res.length > 0) {
				let totalPrice = 0;
				let count = 0;
				let graphData = (new Array(12)).fill(0);

				orders_res.forEach((order) => {
					totalPrice += (order['delivery'].price * order['delivery'].count);
					graphData[(new Date(order['delivery'].date)).getMonth()] += (order['delivery'].price * order['delivery'].count);
					count++;
				});

				data['total'] = {price: totalPrice, count};
				data['graph'] = graphData;
				data['orders'] = orders_res;
			}

			return res.json({
				data
			});
		});
	});
});

/*
	POST /api/orderer
	거래처 정보 등록

	ERROR CODES
		1 : MISSING REQUIRED FIELD
		2 : PERMISSION DENIED
		3 : ORDERER EXISTS

	=== request body ===
	name
	phone
	address
	manager
	manager_phone
	def_ribtext
	description
*/
router.post('/', (req, res) => {
    // const phoneRegex = /^\d{2,3}-\d{3,4}-\d{4}$/;

	const session = req.session;

	if (typeof session.loginInfo === 'undefined') {
		return res.status(401).json({
			error: 'PERMISSION DENIED',
			code: 2
		});
	}

	if ((req.body.name.length === 0 || !req.body.name.trim()) ||
		(req.body.phone.length === 0 || !req.body.phone.trim())) {
    	return res.status(400).json({
    		error: 'MISSING REQUIRED FIELD',
    		code: 1
    	});
    }

    Orderer.findOne({ name: req.body.name.trim() }, (err, exists) => {
    	if (err) throw err;

    	if (exists) {
    		return res.status(409).json({
                error: "ORDERER EXISTS",
                code: 3
            });
    	}

    	const {
    		name,
    		phone,
    		address,
    		manager,
    		manager_phone,
    		def_ribtext,
    		description
    	} = req.body;

    	let orderer = new Orderer({
    		name,
    		phone,
    		address,
    		manager,
    		manager_phone,
    		def_ribtext,
    		description
    	});

    	orderer.save((err) => {
    		if (err) throw err;

    		// console.log(orderer);

    		return res.json({
    			success: true,
    			orderer
    		});
    	});
    });
});

/*
	PUT /api/orderer/:id
	특정 거래처 정보 수정

	ERROR CODES
		1 : INVALID ID
		2 : BAD ORDERER NAME
		3 : PERMISSION DENIED
		4 : ORDERER NOT EXISTS
*/
router.put('/:id', (req, res) => {
	const session = req.session;

	if (typeof session.loginInfo === 'undefined') {
		return res.status(401).json({
			error: 'PERMISSION DENIED',
			code: 3
		});
	}

	if(!shortid.isValid(req.params.id)) {
        return res.status(400).json({
            error: "INVALID ID",
            code: 1
        });
    }

    if (req.body.name.length === 0 || !req.body.name.trim()) {
    	return res.status(400).json({
    		error: 'BAD ORDERER NAME',
    		code: 2
    	});
    }

    Orderer.findById(req.params.id, (err, orderer) => {
		if (err) throw err;

		if (!orderer) {
			return res.status(400).json({
				error: "ORDERER NOT EXISTS",
				code: 4
			});
		}

    	const {
    		name,
    		phone,
    		address,
    		manager,
    		manager_phone,
    		def_ribtext,
    		description
    	} = req.body;

		orderer.name = name;
		orderer.phone = phone;
		orderer.address = address;
		orderer.manager = manager;
		orderer.manager_phone = manager_phone;
		orderer.def_ribtext = def_ribtext;
		orderer.description = description;
		orderer.date.modified = new Date();

		orderer.save( (err) => {
			if (err) throw err;

			return res.json({
				success: true,
				orderer
			});
		});
	});
});

/*
	DELETE /api/orderer/:id
	특정 거래처 정보 삭제
	이와 동시에 OrderNote 테이블 내에 있는 거래처 주문 내역을 조회해서
	orderer -> id 값을 초기화 상태인 "0"로 초기화 한다.

	ERROR CODES
		1 : INVALID ID
		2 : ORDERER NOT EXISTS
		3 : PERMISSION DENIED
		4 : NO RESOURCES
*/
router.delete('/:id', (req, res) => {
	const session = req.session;

	if (typeof session.loginInfo === 'undefined') {
		return res.status(401).json({
			error: 'PERMISSION DENIED',
			code: 3
		});
	}

	if (!shortid.isValid(req.params.id)) {
		return res.status(400).json({
			error: 'INVALID ID',
			code: 1
		});
	}

	Orderer.findOne({'_id': req.params.id}, (err, result) => {
		if (err) throw err;

		if (!result) {
			return res.status(400).json({
				error: 'ORDERER NOT EXISTS',
				code: 2
			});
		}

		let data;

		OrderNote.findOne({'orderer.id': req.params.id}, (err2, noteExists) => {
			if (err2) throw err2;

			if (noteExists) {
				OrderNote.update(
					{'orderer.id': req.params.id},
					{'orderer.id': 'no'},
					(err, raw) => {
						if (err) throw err;

						data = raw;
					});
			} else {
				data = [];
			}

			Orderer.remove({'_id': req.params.id}, (err3) => {
				if (err3) throw err3;

				return res.json({
					result: 'success',
					data: data
				});
			});
		})
	});
});

module.exports = router;
<commit_msg>주석제거 및 비거래처 거래액을 명시해줌<commit_after>const express = require('express');
const mongoose = require('mongoose');
const shortid = require('shortid');
const Orderer = require('./../models/Orderer');
const OrderNote = require('./../models/OrderNote');

mongoose.Promise = global.Promise;

const router = express.Router();

/*
	GET /api/orderer
	등록된 거래처 리스트를 조회

	ERROR CODES
		1 : PERMISSION DENIED
		2 : NO RESOURCES

*/
router.get('/', (req, res) => {
	const session = req.session;

	if (typeof session.loginInfo === 'undefined') {
		return res.status(401).json({
			error: 'PERMISSION DENIED',
			code: 1
		});
	}

	Orderer.find().sort({name: 1}).exec( (err, orderers) => {
		if (err) throw err;

		if(!orderers.length) {
			return res.status(400).json({
				error: 'NO RESOURCES',
				code: 2
			});
		}

		return res.json({
			success: true,
			orderers
		});
	});

});

/*
	GET /api/orderer/stat

	ERROR CODES
	1 : PERMISSION DENIED
*/
router.get('/stat', (req, res) => {
	const session = req.session;

	if (typeof session.loginInfo === 'undefined') {
		console.log('perm denied');
		return res.status(401).json({
			error: 'PERMISSION DENIED',
			code: 1
		});
	}

	const c_year = (new Date()).getFullYear();

	OrderNote.aggregate([
		{ $match: {
			'delivery.date': {
				$gte: new Date((c_year-1), 0, 1),
				$lt: new Date((c_year+1), 0, 1)
			}
		}},
		{ $project: {
			id: '$orderer.id',
			price: { $multiply: ['$delivery.price', '$delivery.count'] }
		}},
		{ $group: {
			_id: null,
			totalPrice: { $sum: '$price'},
			ordererPrice: { $sum: {
								$cond: {
									if: { $ne: ['$id', 'no'] },
									then: '$price',
									else: 0
								}
							}}
		}}
	], (err, p_res) => {
		if (err) {
			// console.log(err);
			throw err;
		}

		if (!p_res || p_res.length === 0) {
			return res.json({
				priceData: null,
				yearData: null
			});
		}

		OrderNote.aggregate([
			{ $match: {
				'delivery.date': {
					$gte: new Date((c_year-1), 0, 1),
					$lt: new Date((c_year+1), 0, 1)
				}
				// $and : [
				// 	{ 'delivery.date': {
				// 		$gte: new Date((c_year-1), 0, 1),
				// 		$lt: new Date((c_year+1), 0, 1)
				// 	}},
					// { 'orderer.id': {
					// 	$ne: 'no'
					// }}
				// ]}
			}},
			{ $project: {
				id: '$orderer.id',
				yearMonth: { $dateToString: { format: '%Y-%m', date: '$delivery.date' }},
				price: { $multiply: ['$delivery.price', '$delivery.count']}
			}},
			{ $group: {
				_id: '$yearMonth',
				ordererPrice: {
					$sum: {
						$cond: {
							if: { $ne: ['$id', 'no'] },
							then: '$price',
							else: 0
						}
					}
				},
				normalPrice: {
					$sum: {
						$cond: {
							if: { $eq: ['$id', 'no'] },
							then: '$price',
							else: 0
						}
					}
				}
			}},
			{ $sort: { _id: 1 }}
		], (err2, y_res) => {
			if (err2) {
				// console.log(err2);
				throw err2;
			}

			console.log("Server Data ", p_res, y_res);
			if (!y_res || y_res.length === 0) {
				return res.json({
					priceData: p_res[0],
					yearData: null
				});
			}

			return res.json({
				priceData: p_res[0],
				yearData: y_res
			});
		});
	});
});

/*
	GET /api/orderer/:id
	특정 거래처 정보(1년간의 데이터)를 조회

	ERROR CODES
		1 : INVALID ID
		2 : ORDERER NOT EXISTS
		3 : PERMISSION DENIED
*/
router.get('/:id', (req, res) => {
	const session = req.session;

	if (typeof session.loginInfo === 'undefined') {
		console.log('perm denied');
		return res.status(401).json({
			error: 'PERMISSION DENIED',
			code: 3
		});
	}

	if (!shortid.isValid(req.params.id)) {
		console.log('invalid id');
        return res.status(400).json({
            error: "INVALID ID",
            code: 1
        });
	}

	Orderer.find({'_id': req.params.id}, (err, orderer) => {
		if (err) throw err;

		if (!orderer || orderer.length === 0) {
			console.log('orderer not exists');
	        return res.status(400).json({
	            error: "ORDERER NOT EXISTS",
	            code: 2
	        });
		}

		const c_year = (new Date()).getFullYear();

		OrderNote.find({
			$and: [
				{'orderer.id': req.params.id},
				{'delivery.date': { $gte: (new Date(c_year, 0, 1)), $lt: (new Date((c_year+1), 0, 1))}}
			]
		}, {
			__v: 0,
			_id: 0,
			orderer: 0,
			'delivery.text': 0,
			date: 0,
			memo: 0
		}, {
			sort: {
				'delivery.date': -1	// 배송일자 내림차순 정렬
			}
		}, (err2, orders_res) => {
			const data = {};

			if (orders_res.length > 0) {
				let totalPrice = 0;
				let count = 0;
				let graphData = (new Array(12)).fill(0);

				orders_res.forEach((order) => {
					totalPrice += (order['delivery'].price * order['delivery'].count);
					graphData[(new Date(order['delivery'].date)).getMonth()] += (order['delivery'].price * order['delivery'].count);
					count++;
				});

				data['total'] = {price: totalPrice, count};
				data['graph'] = graphData;
				data['orders'] = orders_res;
			}

			return res.json({
				data
			});
		});
	});
});

/*
	POST /api/orderer
	거래처 정보 등록

	ERROR CODES
		1 : MISSING REQUIRED FIELD
		2 : PERMISSION DENIED
		3 : ORDERER EXISTS

	=== request body ===
	name
	phone
	address
	manager
	manager_phone
	def_ribtext
	description
*/
router.post('/', (req, res) => {
    // const phoneRegex = /^\d{2,3}-\d{3,4}-\d{4}$/;

	const session = req.session;

	if (typeof session.loginInfo === 'undefined') {
		return res.status(401).json({
			error: 'PERMISSION DENIED',
			code: 2
		});
	}

	if ((req.body.name.length === 0 || !req.body.name.trim()) ||
		(req.body.phone.length === 0 || !req.body.phone.trim())) {
    	return res.status(400).json({
    		error: 'MISSING REQUIRED FIELD',
    		code: 1
    	});
    }

    Orderer.findOne({ name: req.body.name.trim() }, (err, exists) => {
    	if (err) throw err;

    	if (exists) {
    		return res.status(409).json({
                error: "ORDERER EXISTS",
                code: 3
            });
    	}

    	const {
    		name,
    		phone,
    		address,
    		manager,
    		manager_phone,
    		def_ribtext,
    		description
    	} = req.body;

    	let orderer = new Orderer({
    		name,
    		phone,
    		address,
    		manager,
    		manager_phone,
    		def_ribtext,
    		description
    	});

    	orderer.save((err) => {
    		if (err) throw err;

    		// console.log(orderer);

    		return res.json({
    			success: true,
    			orderer
    		});
    	});
    });
});

/*
	PUT /api/orderer/:id
	특정 거래처 정보 수정

	ERROR CODES
		1 : INVALID ID
		2 : BAD ORDERER NAME
		3 : PERMISSION DENIED
		4 : ORDERER NOT EXISTS
*/
router.put('/:id', (req, res) => {
	const session = req.session;

	if (typeof session.loginInfo === 'undefined') {
		return res.status(401).json({
			error: 'PERMISSION DENIED',
			code: 3
		});
	}

	if(!shortid.isValid(req.params.id)) {
        return res.status(400).json({
            error: "INVALID ID",
            code: 1
        });
    }

    if (req.body.name.length === 0 || !req.body.name.trim()) {
    	return res.status(400).json({
    		error: 'BAD ORDERER NAME',
    		code: 2
    	});
    }

    Orderer.findById(req.params.id, (err, orderer) => {
		if (err) throw err;

		if (!orderer) {
			return res.status(400).json({
				error: "ORDERER NOT EXISTS",
				code: 4
			});
		}

    	const {
    		name,
    		phone,
    		address,
    		manager,
    		manager_phone,
    		def_ribtext,
    		description
    	} = req.body;

		orderer.name = name;
		orderer.phone = phone;
		orderer.address = address;
		orderer.manager = manager;
		orderer.manager_phone = manager_phone;
		orderer.def_ribtext = def_ribtext;
		orderer.description = description;
		orderer.date.modified = new Date();

		orderer.save( (err) => {
			if (err) throw err;

			return res.json({
				success: true,
				orderer
			});
		});
	});
});

/*
	DELETE /api/orderer/:id
	특정 거래처 정보 삭제
	이와 동시에 OrderNote 테이블 내에 있는 거래처 주문 내역을 조회해서
	orderer -> id 값을 초기화 상태인 "0"로 초기화 한다.

	ERROR CODES
		1 : INVALID ID
		2 : ORDERER NOT EXISTS
		3 : PERMISSION DENIED
		4 : NO RESOURCES
*/
router.delete('/:id', (req, res) => {
	const session = req.session;

	if (typeof session.loginInfo === 'undefined') {
		return res.status(401).json({
			error: 'PERMISSION DENIED',
			code: 3
		});
	}

	if (!shortid.isValid(req.params.id)) {
		return res.status(400).json({
			error: 'INVALID ID',
			code: 1
		});
	}

	Orderer.findOne({'_id': req.params.id}, (err, result) => {
		if (err) throw err;

		if (!result) {
			return res.status(400).json({
				error: 'ORDERER NOT EXISTS',
				code: 2
			});
		}

		let data;

		OrderNote.findOne({'orderer.id': req.params.id}, (err2, noteExists) => {
			if (err2) throw err2;

			if (noteExists) {
				OrderNote.update(
					{'orderer.id': req.params.id},
					{'orderer.id': 'no'},
					(err, raw) => {
						if (err) throw err;

						data = raw;
					});
			} else {
				data = [];
			}

			Orderer.remove({'_id': req.params.id}, (err3) => {
				if (err3) throw err3;

				return res.json({
					result: 'success',
					data: data
				});
			});
		})
	});
});

module.exports = router;
<|endoftext|>


<commit_before>define(['jquery', 'DoughBaseComponent', 'mediaQueries', 'utilities', 'common'], function($, DoughBaseComponent, mediaQueries, utilities, common) {
  'use strict';

  var GlobalNav;

  GlobalNav = function($el, config) {
    GlobalNav.baseConstructor.call(this, $el, config);

    this.$globalNav = this.$el;
    this.$mobileNavButton = $(document).find('[data-dough-mobile-nav-button]');
    this.$globalNavClumps = this.$el.find('[data-dough-nav-clumps]');
    this.$globalNavClump = this.$el.find('[data-dough-nav-clump]');
    this.$globalNavClumpHeading = this.$el.find('[data-dough-nav-clump-heading]');
    this.$globalSubNavHeading = this.$el.find('[data-dough-subnav-heading]');
    this.$globalSubNav = this.$el.find('[data-dough-subnav]');
    this.$mobileNavClose = this.$el.find('[data-dough-mobile-nav-close]');
    this.$mobileNavOverlay = $(document).find('[data-dough-mobile-nav-overlay]');
    this.delay = 250; // ms
  };

  /**
  * Inherit from base module, for shared methods and interface
  */
  DoughBaseComponent.extend(GlobalNav);
  GlobalNav.componentName = 'GlobalNav';

  /**
  * Initialize the component
  */
  GlobalNav.prototype.init = function(initialised) {
    this._initialisedSuccess(initialised);
    this._setUpMobileInteraction();
    this._setUpDesktopInteraction();
    this._setUpMobileAnimation();
    this._setUpKeyboardEvents();
    this.$globalNav.removeClass('uninitialised');

    $(window).on('resize', utilities.debounce($.proxy(this._setUpMobileAnimation, this), 100));

    return this;
  };

  /**
   * Set up keyboard events
   * Adds enhanced keyboard interation to nav
   */
  GlobalNav.prototype._setUpKeyboardEvents = function() {
    var self = this;

    this.$globalNav.keydown(function(e) {
      if (e.which === 32 || e.which === 40) {
        e.preventDefault();
      }
    });

    this.$globalNav.keyup(function(e) {
      var level;

      if ($(e.target).parents('[data-dough-subnav]').length > 0) {
        level = 'subnav';
      } else {
        level = 'top';
      }

      switch (e.which) {
        // tab
        case 9:
          if (level === 'top') {
            self._closeBlurredClumps(e.target);
          }
          break;
        // enter
        case 13:
          if (level === 'top') {
            e.preventDefault();
            self._openDesktopSubNav(e.target);
            self._moveFocusToSubNav(e.target);
          }
          break;
        // escape
        case 27:
          if (level === 'subnav') {
            self._closeDesktopSubNav($(e.target).parents('[data-dough-subnav]'));
            self._moveTopLevelFocus(e.target);
          }
          break;
        // spacebar
        case 32:
          e.preventDefault();

          if (level === 'top') {
            self._openDesktopSubNav(e.target);
          }
          break;
        // left arrow
        case 37:
          self._moveTopLevelFocus(e.target, 'left');

          if (level === 'subnav') {
            self._closeDesktopSubNav($(e.target).parents('[data-dough-subnav]'));
          }
          break;
        // up arrow
        case 38:
          if (level === 'subnav') {
            self._moveSubNavFocus(e.target, 'up');
          }
          break;
        // right arrow
        case 39:
          self._moveTopLevelFocus(e.target, 'right');

          if (level === 'subnav') {
            self._closeDesktopSubNav($(e.target).parents('[data-dough-subnav]'));
          }
          break;
        // down arrow
        case 40:
          if (level === 'top') {
            if ($(e.target).parents('[data-dough-nav-clump]').hasClass('is-active')) {
              self._moveFocusToSubNav(e.target);
            } else {
              self._openDesktopSubNav(e.target);
            }
          } else {
            self._moveSubNavFocus(e.target, 'down');
          }
          break;
      }
    });
  };

  /**
  * Moves focus to first link of subnav
  */
  GlobalNav.prototype._moveFocusToSubNav = function(el) {
    var firstLink = $(el).siblings('[data-dough-subnav]').find('[data-dough-subcategories]').find('a')[0];

    $(firstLink).focus();
  };

  /**
  * Moves focus of subnav up or down
  */
  GlobalNav.prototype._moveSubNavFocus = function(el, dir) {
    var subCatArray = $(el).parents('[data-dough-subcategories]').find('a');
    var index = $(subCatArray).index(el);

    if (dir === 'up') {
      if (index > 0) {
        subCatArray[index - 1].focus();
      } else {
        subCatArray[subCatArray.length - 1].focus();
      }
    } else {
      if (index < subCatArray.length - 1) {
        subCatArray[index + 1].focus();
      } else {
        subCatArray[0].focus();
      }
    }
  };

  /**
  * Moves focus of top level nav to left or right
  */
  GlobalNav.prototype._moveTopLevelFocus = function(el, dir) {
    var thisClump = $(el).parents('[data-dough-nav-clump]');
    var clumps = $(thisClump).siblings('[data-dough-nav-clump]');
    var firstClump = $(clumps).first();
    var lastClump = $(clumps).last();

    if (dir === 'left') {
      if ($(thisClump).prev('[data-dough-nav-clump]').length > 0) {
        $(thisClump).blur()
          .prev().children('[data-dough-nav-clump-heading]').focus();
      } else {
        $(lastClump).children('[data-dough-nav-clump-heading]').focus();
      }
    } else if (dir === 'right') {
      if ($(thisClump).next('[data-dough-nav-clump]').length > 0) {
        $(thisClump).blur()
          .next().children('[data-dough-nav-clump-heading]').focus();
      } else {
        $(firstClump).children('[data-dough-nav-clump-heading]').focus();
      }
    } else {
      $(thisClump).children('[data-dough-nav-clump-heading]').focus();
    }
  };

  /**
  * Closes all subnavs when top level focus shifts
  */
  GlobalNav.prototype._closeBlurredClumps = function(el) {
    this.$globalNavClump.not($(el).parents('[data-dough-nav-clump]')).removeClass('is-active');
  };

  /**
  * Ensure mobile nav not activated on resize or page load
  */
  GlobalNav.prototype._setUpMobileAnimation = function() {
    if (mediaQueries.atSmallViewport()) {
      this.$globalNav.removeClass('no-transition');
    } else {
      this.$globalNav.addClass('no-transition');
    }
  };

  /**
  * Set up events for mobile nav
  */
  GlobalNav.prototype._setUpMobileInteraction = function() {
    var self = this;

    this.$mobileNavButton.click(function(e) {
      e.preventDefault();
      self._toggleMobileNav();
    });

    this.$globalNavClumpHeading.click(function(e) {
      if (mediaQueries.atSmallViewport()) {
        e.preventDefault();
        self._toggleMobileSubNav(this);
      }
    });

    this.$globalSubNavHeading.click(function(e) {
      e.preventDefault();
      self._toggleMobileSubNav(this);
    });

    this.$mobileNavClose.click(function(e){
      e.preventDefault();
      self._closeMobileNav();
    });

    this.$mobileNavOverlay.click(function(e){
      e.preventDefault();
      self._closeMobileNav();
    });
  };

  /**
  * Set up events for desktop nav
  */
  GlobalNav.prototype._setUpDesktopInteraction = function() {
    var self = this;

    this.$globalNavClumpHeading
      .mouseenter(function(e) {
        window.clearTimeout(self.timeout);

        self.timeout = window.setTimeout(function() {
          self._sendHoverAnalytics($(e.target).text());
          self._openDesktopSubNav($(e.target).parents('[data-dough-nav-clump-heading]'));
        }, self.delay);
      });

    this.$globalNav
      .mouseleave(function() {
        window.clearTimeout(self.timeout);

        self.closeNavTimeout = window.setTimeout(function() {
          self._closeDesktopSubNav();
        }, self.delay);
      })
      .mouseenter(function() {
        window.clearTimeout(self.closeNavTimeout);
      });
  };

  GlobalNav.prototype._toggleMobileNav = function() {
    this.$globalNav.toggleClass('is-active');
    this.$mobileNavOverlay.toggleClass('is-active');

    if (this.$globalNavClumps.hasClass('is-active')) {
      this.$globalNavClumps.removeClass('is-active');
    }
  };

  GlobalNav.prototype._closeMobileNav = function() {
    this.$globalNav.removeClass('is-active');
    this.$mobileNavOverlay.removeClass('is-active');
    this.$globalNavClumps.removeClass('is-active');
    this.$globalNavClump.removeClass('is-active');
  };

  GlobalNav.prototype._toggleMobileSubNav = function(index) {
    $(index)
      .parents('[data-dough-nav-clump]').toggleClass('is-active')
      .parents('[data-dough-nav-clumps]').toggleClass('is-active');
  };

  GlobalNav.prototype._openDesktopSubNav = function(index) {
    if (!this.$globalNav.hasClass('is-active')) {
      this.$globalSubNav.removeClass('is-active');
      this.$globalNavClump.removeClass('is-active');
      this.$globalNavClumpHeading.attr('aria-expanded', 'false');

      $(index)
        .attr('aria-expanded', 'true')
        .parent('[data-dough-nav-clump]').addClass('is-active')
        .siblings('[data-dough-subnav]').addClass('is-active');
    }
  };

  GlobalNav.prototype._closeDesktopSubNav = function(index) {
    if (!this.$globalNav.hasClass('is-active')) {
      this.$globalSubNav.removeClass('is-active');
      this.$globalNavClump.removeClass('is-active');

      $(index)
        .siblings('[data-dough-nav-clump-heading]')
        .parents('[data-dough-nav-clump]').removeClass('is-active');
    }
  };

  GlobalNav.prototype._sendHoverAnalytics = function(label) {
    window.dataLayer.push({
      'event': 'gaEvent',
      'gaEventCat': 'Global Navigation',
      'gaEventAct': 'Hover',
      'gaEventLab': label
    });
  };

  return GlobalNav;
});
<commit_msg>TP: 7978, Comment: Removes focus from clump headings for mouse events - This is to avoid confusing behaviour for the user whereby mouse events serve no other purpose (there is no link to follow and the sub-menu is opened by the hover event). - Focus is retained when the user is navigating by keyboard (tab or left/right arrow) which is necessary because another keyboard action (enter or down arrow) is then required to open the sub-menu.<commit_after>define(['jquery', 'DoughBaseComponent', 'mediaQueries', 'utilities', 'common'], function($, DoughBaseComponent, mediaQueries, utilities, common) {
  'use strict';

  var GlobalNav;

  GlobalNav = function($el, config) {
    GlobalNav.baseConstructor.call(this, $el, config);

    this.$globalNav = this.$el;
    this.$mobileNavButton = $(document).find('[data-dough-mobile-nav-button]');
    this.$globalNavClumps = this.$el.find('[data-dough-nav-clumps]');
    this.$globalNavClump = this.$el.find('[data-dough-nav-clump]');
    this.$globalNavClumpHeading = this.$el.find('[data-dough-nav-clump-heading]');
    this.$globalSubNavHeading = this.$el.find('[data-dough-subnav-heading]');
    this.$globalSubNav = this.$el.find('[data-dough-subnav]');
    this.$mobileNavClose = this.$el.find('[data-dough-mobile-nav-close]');
    this.$mobileNavOverlay = $(document).find('[data-dough-mobile-nav-overlay]');
    this.delay = 250; // ms
  };

  /**
  * Inherit from base module, for shared methods and interface
  */
  DoughBaseComponent.extend(GlobalNav);
  GlobalNav.componentName = 'GlobalNav';

  /**
  * Initialize the component
  */
  GlobalNav.prototype.init = function(initialised) {
    this._initialisedSuccess(initialised);
    this._setUpMobileInteraction();
    this._setUpDesktopInteraction();
    this._setUpMobileAnimation();
    this._setUpKeyboardEvents();
    this.$globalNav.removeClass('uninitialised');

    $(window).on('resize', utilities.debounce($.proxy(this._setUpMobileAnimation, this), 100));

    return this;
  };

  /**
   * Set up keyboard events
   * Adds enhanced keyboard interation to nav
   */
  GlobalNav.prototype._setUpKeyboardEvents = function() {
    var self = this;

    this.$globalNav.keydown(function(e) {
      if (e.which === 32 || e.which === 40) {
        e.preventDefault();
      }
    });

    this.$globalNav.keyup(function(e) {
      var level;

      if ($(e.target).parents('[data-dough-subnav]').length > 0) {
        level = 'subnav';
      } else {
        level = 'top';
      }

      switch (e.which) {
        // tab
        case 9:
          if (level === 'top') {
            self._closeBlurredClumps(e.target);
          }
          break;
        // enter
        case 13:
          if (level === 'top') {
            e.preventDefault();
            self._openDesktopSubNav(e.target);
            self._moveFocusToSubNav(e.target);
          }
          break;
        // escape
        case 27:
          if (level === 'subnav') {
            self._closeDesktopSubNav($(e.target).parents('[data-dough-subnav]'));
            self._moveTopLevelFocus(e.target);
          }
          break;
        // spacebar
        case 32:
          e.preventDefault();

          if (level === 'top') {
            self._openDesktopSubNav(e.target);
          }
          break;
        // left arrow
        case 37:
          self._moveTopLevelFocus(e.target, 'left');

          if (level === 'subnav') {
            self._closeDesktopSubNav($(e.target).parents('[data-dough-subnav]'));
          }
          break;
        // up arrow
        case 38:
          if (level === 'subnav') {
            self._moveSubNavFocus(e.target, 'up');
          }
          break;
        // right arrow
        case 39:
          self._moveTopLevelFocus(e.target, 'right');

          if (level === 'subnav') {
            self._closeDesktopSubNav($(e.target).parents('[data-dough-subnav]'));
          }
          break;
        // down arrow
        case 40:
          if (level === 'top') {
            if ($(e.target).parents('[data-dough-nav-clump]').hasClass('is-active')) {
              self._moveFocusToSubNav(e.target);
            } else {
              self._openDesktopSubNav(e.target);
            }
          } else {
            self._moveSubNavFocus(e.target, 'down');
          }
          break;
      }
    });
  };

  /**
  * Moves focus to first link of subnav
  */
  GlobalNav.prototype._moveFocusToSubNav = function(el) {
    var firstLink = $(el).siblings('[data-dough-subnav]').find('[data-dough-subcategories]').find('a')[0];

    $(firstLink).focus();
  };

  /**
  * Moves focus of subnav up or down
  */
  GlobalNav.prototype._moveSubNavFocus = function(el, dir) {
    var subCatArray = $(el).parents('[data-dough-subcategories]').find('a');
    var index = $(subCatArray).index(el);

    if (dir === 'up') {
      if (index > 0) {
        subCatArray[index - 1].focus();
      } else {
        subCatArray[subCatArray.length - 1].focus();
      }
    } else {
      if (index < subCatArray.length - 1) {
        subCatArray[index + 1].focus();
      } else {
        subCatArray[0].focus();
      }
    }
  };

  /**
  * Moves focus of top level nav to left or right
  */
  GlobalNav.prototype._moveTopLevelFocus = function(el, dir) {
    var thisClump = $(el).parents('[data-dough-nav-clump]');
    var clumps = $(thisClump).siblings('[data-dough-nav-clump]');
    var firstClump = $(clumps).first();
    var lastClump = $(clumps).last();

    if (dir === 'left') {
      if ($(thisClump).prev('[data-dough-nav-clump]').length > 0) {
        $(thisClump).blur()
          .prev().children('[data-dough-nav-clump-heading]').focus();
      } else {
        $(lastClump).children('[data-dough-nav-clump-heading]').focus();
      }
    } else if (dir === 'right') {
      if ($(thisClump).next('[data-dough-nav-clump]').length > 0) {
        $(thisClump).blur()
          .next().children('[data-dough-nav-clump-heading]').focus();
      } else {
        $(firstClump).children('[data-dough-nav-clump-heading]').focus();
      }
    } else {
      $(thisClump).children('[data-dough-nav-clump-heading]').focus();
    }
  };

  /**
  * Closes all subnavs when top level focus shifts
  */
  GlobalNav.prototype._closeBlurredClumps = function(el) {
    this.$globalNavClump.not($(el).parents('[data-dough-nav-clump]')).removeClass('is-active');
  };

  /**
  * Ensure mobile nav not activated on resize or page load
  */
  GlobalNav.prototype._setUpMobileAnimation = function() {
    if (mediaQueries.atSmallViewport()) {
      this.$globalNav.removeClass('no-transition');
    } else {
      this.$globalNav.addClass('no-transition');
    }
  };

  /**
  * Set up events for mobile nav
  */
  GlobalNav.prototype._setUpMobileInteraction = function() {
    var self = this;

    this.$mobileNavButton.click(function(e) {
      e.preventDefault();
      self._toggleMobileNav();
    });

    this.$globalNavClumpHeading.click(function(e) {
      if (mediaQueries.atSmallViewport()) {
        e.preventDefault();
        self._toggleMobileSubNav(this);
      }
    });

    this.$globalSubNavHeading.click(function(e) {
      e.preventDefault();
      self._toggleMobileSubNav(this);
    });

    this.$mobileNavClose.click(function(e){
      e.preventDefault();
      self._closeMobileNav();
    });

    this.$mobileNavOverlay.click(function(e){
      e.preventDefault();
      self._closeMobileNav();
    });
  };

  /**
  * Set up events for desktop nav
  */
  GlobalNav.prototype._setUpDesktopInteraction = function() {
    var self = this;

    this.$globalNavClumpHeading
      .mouseenter(function(e) {
        window.clearTimeout(self.timeout);

        self.timeout = window.setTimeout(function() {
          self._sendHoverAnalytics($(e.target).text());
          self._openDesktopSubNav($(e.target).parents('[data-dough-nav-clump-heading]'));
        }, self.delay);
      })
      .mousedown(function(e) {
        e.preventDefault();
      });

    this.$globalNav
      .mouseleave(function() {
        window.clearTimeout(self.timeout);

        self.closeNavTimeout = window.setTimeout(function() {
          self._closeDesktopSubNav();
        }, self.delay);
      })
      .mouseenter(function() {
        window.clearTimeout(self.closeNavTimeout);
      });
  };

  GlobalNav.prototype._toggleMobileNav = function() {
    this.$globalNav.toggleClass('is-active');
    this.$mobileNavOverlay.toggleClass('is-active');

    if (this.$globalNavClumps.hasClass('is-active')) {
      this.$globalNavClumps.removeClass('is-active');
    }
  };

  GlobalNav.prototype._closeMobileNav = function() {
    this.$globalNav.removeClass('is-active');
    this.$mobileNavOverlay.removeClass('is-active');
    this.$globalNavClumps.removeClass('is-active');
    this.$globalNavClump.removeClass('is-active');
  };

  GlobalNav.prototype._toggleMobileSubNav = function(index) {
    $(index)
      .parents('[data-dough-nav-clump]').toggleClass('is-active')
      .parents('[data-dough-nav-clumps]').toggleClass('is-active');
  };

  GlobalNav.prototype._openDesktopSubNav = function(index) {
    if (!this.$globalNav.hasClass('is-active')) {
      this.$globalSubNav.removeClass('is-active');
      this.$globalNavClump.removeClass('is-active');
      this.$globalNavClumpHeading.attr('aria-expanded', 'false');

      $(index)
        .attr('aria-expanded', 'true')
        .parent('[data-dough-nav-clump]').addClass('is-active')
        .siblings('[data-dough-subnav]').addClass('is-active');
    }
  };

  GlobalNav.prototype._closeDesktopSubNav = function(index) {
    if (!this.$globalNav.hasClass('is-active')) {
      this.$globalSubNav.removeClass('is-active');
      this.$globalNavClump.removeClass('is-active');

      $(index)
        .siblings('[data-dough-nav-clump-heading]')
        .parents('[data-dough-nav-clump]').removeClass('is-active');
    }
  };

  GlobalNav.prototype._sendHoverAnalytics = function(label) {
    window.dataLayer.push({
      'event': 'gaEvent',
      'gaEventCat': 'Global Navigation',
      'gaEventAct': 'Hover',
      'gaEventLab': label
    });
  };

  return GlobalNav;
});
<|endoftext|>


<commit_before>/*
* jQuery Mobile Framework : "listview" plugin
* Copyright (c) jQuery Project
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*/
(function($, undefined ) {

$.widget( "mobile.listview", $.mobile.widget, {
	options: {
		theme: "c",
		countTheme: "c",
		headerTheme: "b",
		dividerTheme: "b",
		splitIcon: "arrow-r",
		splitTheme: "b",
		inset: false
	},

	_create: function() {
		var $list = this.element,
			o = this.options;

		// create listview markup
		$list
			.addClass( "ui-listview" )
			.attr( "role", "listbox" )

		if ( o.inset ) {
			$list.addClass( "ui-listview-inset ui-corner-all ui-shadow" );
		}

		$list.delegate( ".ui-li", "focusin", function() {
			$( this ).attr( "tabindex", "0" );
		});

		this._itemApply( $list, $list );

		this.refresh( true );

		//keyboard events for menu items
		$list.keydown(function( e ) {
			var target = $( e.target ),
				li = target.closest( "li" );

			// switch logic based on which key was pressed
			switch ( e.keyCode ) {
				// up or left arrow keys
				case 38:
					var prev = li.prev();

					// if there's a previous option, focus it
					if ( prev.length ) {
						target
							.blur()
							.attr( "tabindex", "-1" );

						prev.find( "a" ).first().focus();
					}

					return false;
				break;

				// down or right arrow keys
				case 40:
					var next = li.next();

					// if there's a next option, focus it
					if ( next.length ) {
						target
							.blur()
							.attr( "tabindex", "-1" );

						next.find( "a" ).first().focus();
					}

					return false;
				break;

				case 39:
					var a = li.find( "a.ui-li-link-alt" );

					if ( a.length ) {
						target.blur();
						a.first().focus();
					}

					return false;
				break;

				case 37:
					var a = li.find( "a.ui-link-inherit" );

					if ( a.length ) {
						target.blur();
						a.first().focus();
					}

					return false;
				break;

				// if enter or space is pressed, trigger click
				case 13:
				case 32:
					 target.trigger( "click" );

					 return false;
				break;
			}
		});

		// tapping the whole LI triggers click on the first link
		$list.delegate( "li", "click", function(event) {
			if ( !$( event.target ).closest( "a" ).length ) {
				$( this ).find( "a" ).first().trigger( "click" );
				return false;
			}
		});
	},

	_itemApply: function( $list, item ) {
		// TODO class has to be defined in markup
		item.find( ".ui-li-count" )
			.addClass( "ui-btn-up-" + ($list.data( "counttheme" ) || this.options.countTheme) + " ui-btn-corner-all" );

		item.find( "h1, h2, h3, h4, h5, h6" ).addClass( "ui-li-heading" );

		item.find( "p, dl" ).addClass( "ui-li-desc" );

		item.find( "img" ).addClass( "ui-li-thumb" ).each(function() {
			$( this ).closest( "li" )
				.addClass( $(this).is( ".ui-li-icon" ) ? "ui-li-has-icon" : "ui-li-has-thumb" );
		});

		var aside = item.find( ".ui-li-aside" );

		if ( aside.length ) {
            aside.each(function(i, el) {
			    $(el).prependTo( $(el).parent() ); //shift aside to front for css float
            });
		}

		if ( $.support.cssPseudoElement || !$.nodeName( item[0], "ol" ) ) {
			return;
		}
	},

	_removeCorners: function(li){
		li
			.add( li.find(".ui-btn-inner, .ui-li-link-alt, .ui-li-thumb") )
			.removeClass( "ui-corner-top ui-corner-bottom ui-corner-br ui-corner-bl ui-corner-tr ui-corner-tl" );
	},

	refresh: function( create ) {
		this._createSubPages();

		var o = this.options,
			$list = this.element,
			self = this,
			dividertheme = $list.data( "dividertheme" ) || o.dividerTheme,
			li = $list.children( "li" ),
			counter = $.support.cssPseudoElement || !$.nodeName( $list[0], "ol" ) ? 0 : 1;

		if ( counter ) {
			$list.find( ".ui-li-dec" ).remove();
		}

		li.attr({ "role": "option", "tabindex": "-1" });

		li.first().attr( "tabindex", "0" );

		li.each(function( pos ) {
			var item = $( this ),
				itemClass = "ui-li";

			// If we're creating the element, we update it regardless
			if ( !create && item.hasClass( "ui-li" ) ) {
				return;
			}

			var a = item.find( "a" );

			if ( a.length ) {
				item
					.buttonMarkup({
						wrapperEls: "div",
						shadow: false,
						corners: false,
						iconpos: "right",
						icon: a.length > 1 ? false : item.data("icon") || "arrow-r",
						theme: o.theme
					});

				a.first().addClass( "ui-link-inherit" );

				if ( a.length > 1 ) {
					itemClass += " ui-li-has-alt";

					var last = a.last(),
						splittheme = $list.data( "splittheme" ) || last.data( "theme" ) || o.splitTheme;

					last
						.attr( "title", last.text() )
						.addClass( "ui-li-link-alt" )
						.empty()
						.buttonMarkup({
							shadow: false,
							corners: false,
							theme: o.theme,
							icon: false,
							iconpos: false
						})
						.find( ".ui-btn-inner" )
							.append( $( "" ).buttonMarkup({
								shadow: true,
								corners: true,
								theme: splittheme,
								iconpos: "notext",
								icon: $list.data( "spliticon" ) || last.data( "icon" ) ||  o.splitIcon
							} ) );
				}

			} else if ( item.data( "role" ) === "list-divider" ) {
				itemClass += " ui-li-divider ui-btn ui-bar-" + dividertheme;
				item.attr( "role", "heading" );

				//reset counter when a divider heading is encountered
				if ( counter ) {
					counter = 1;
				}

			} else {
				itemClass += " ui-li-static ui-btn-up-" + o.theme;
			}

			if( o.inset ){
				if ( pos === 0 ) {
						itemClass += " ui-corner-top";

						item
							.add( item.find( ".ui-btn-inner" ) )
							.find( ".ui-li-link-alt" )
								.addClass( "ui-corner-tr" )
							.end()
							.find( ".ui-li-thumb" )
								.addClass( "ui-corner-tl" );

						self._removeCorners( item.next() );

				} else if ( pos === li.length - 1 ) {
						itemClass += " ui-corner-bottom";

						item
							.add( item.find( ".ui-btn-inner" ) )
							.find( ".ui-li-link-alt" )
								.addClass( "ui-corner-br" )
							.end()
							.find( ".ui-li-thumb" )
								.addClass( "ui-corner-bl" );

						self._removeCorners( item.prev() );
				}
			}

			if ( counter && itemClass.indexOf( "ui-li-divider" ) < 0 ) {
				item
					.find( ".ui-link-inherit" ).first()
					.addClass( "ui-li-jsnumbering" )
					.prepend( "" + (counter++) + ". " );
			}

			item.addClass( itemClass );

			if ( !create ) {
				self._itemApply( $list, item );
			}
		});
	},

	_createSubPages: function() {
		var parentList = this.element,
			parentPage = parentList.closest( ".ui-page" ),
			parentId = parentPage.attr( "id" ),
			o = this.options,
			persistentFooterID = parentPage.find( "[data-role='footer']" ).data( "id" );

		$( parentList.find( "ul, ol" ).toArray().reverse() ).each(function( i ) {
			var list = $( this ),
				parent = list.parent(),
				title = parent.contents()[ 0 ].nodeValue.split("\n")[0],
				id = parentId + "&" + $.mobile.subPageUrlKey + "=" + $.mobile.idStringEscape(title + " " + i),
				theme = list.data( "theme" ) || o.theme,
				countTheme = list.data( "counttheme" ) || parentList.data( "counttheme" ) || o.countTheme,
				newPage = list.wrap( "

" )
							.parent()
								.before( "

" + title + "

" )
								.after( persistentFooterID ? $( "
", { "data-role": "footer", "data-id": persistentFooterID, "class": "ui-footer-duplicate" } ) : "" )
								.parent()
									.attr({
										id: id,
										"data-theme": theme,
										"data-count-theme": countTheme
									})
									.appendTo( $.pageContainer );

				newPage.page();

			parent.html( "" + title + "" );
		}).listview();
	}
});

})( jQuery );
<commit_msg>moved the ID creation string escape function from core to listview. No other plugins use it.<commit_after>/*
* jQuery Mobile Framework : "listview" plugin
* Copyright (c) jQuery Project
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*/
(function($, undefined ) {

$.widget( "mobile.listview", $.mobile.widget, {
	options: {
		theme: "c",
		countTheme: "c",
		headerTheme: "b",
		dividerTheme: "b",
		splitIcon: "arrow-r",
		splitTheme: "b",
		inset: false
	},

	_create: function() {
		var $list = this.element,
			o = this.options;

		// create listview markup
		$list
			.addClass( "ui-listview" )
			.attr( "role", "listbox" )

		if ( o.inset ) {
			$list.addClass( "ui-listview-inset ui-corner-all ui-shadow" );
		}

		$list.delegate( ".ui-li", "focusin", function() {
			$( this ).attr( "tabindex", "0" );
		});

		this._itemApply( $list, $list );

		this.refresh( true );

		//keyboard events for menu items
		$list.keydown(function( e ) {
			var target = $( e.target ),
				li = target.closest( "li" );

			// switch logic based on which key was pressed
			switch ( e.keyCode ) {
				// up or left arrow keys
				case 38:
					var prev = li.prev();

					// if there's a previous option, focus it
					if ( prev.length ) {
						target
							.blur()
							.attr( "tabindex", "-1" );

						prev.find( "a" ).first().focus();
					}

					return false;
				break;

				// down or right arrow keys
				case 40:
					var next = li.next();

					// if there's a next option, focus it
					if ( next.length ) {
						target
							.blur()
							.attr( "tabindex", "-1" );

						next.find( "a" ).first().focus();
					}

					return false;
				break;

				case 39:
					var a = li.find( "a.ui-li-link-alt" );

					if ( a.length ) {
						target.blur();
						a.first().focus();
					}

					return false;
				break;

				case 37:
					var a = li.find( "a.ui-link-inherit" );

					if ( a.length ) {
						target.blur();
						a.first().focus();
					}

					return false;
				break;

				// if enter or space is pressed, trigger click
				case 13:
				case 32:
					 target.trigger( "click" );

					 return false;
				break;
			}
		});

		// tapping the whole LI triggers click on the first link
		$list.delegate( "li", "click", function(event) {
			if ( !$( event.target ).closest( "a" ).length ) {
				$( this ).find( "a" ).first().trigger( "click" );
				return false;
			}
		});
	},

	_itemApply: function( $list, item ) {
		// TODO class has to be defined in markup
		item.find( ".ui-li-count" )
			.addClass( "ui-btn-up-" + ($list.data( "counttheme" ) || this.options.countTheme) + " ui-btn-corner-all" );

		item.find( "h1, h2, h3, h4, h5, h6" ).addClass( "ui-li-heading" );

		item.find( "p, dl" ).addClass( "ui-li-desc" );

		item.find( "img" ).addClass( "ui-li-thumb" ).each(function() {
			$( this ).closest( "li" )
				.addClass( $(this).is( ".ui-li-icon" ) ? "ui-li-has-icon" : "ui-li-has-thumb" );
		});

		var aside = item.find( ".ui-li-aside" );

		if ( aside.length ) {
            aside.each(function(i, el) {
			    $(el).prependTo( $(el).parent() ); //shift aside to front for css float
            });
		}

		if ( $.support.cssPseudoElement || !$.nodeName( item[0], "ol" ) ) {
			return;
		}
	},

	_removeCorners: function(li){
		li
			.add( li.find(".ui-btn-inner, .ui-li-link-alt, .ui-li-thumb") )
			.removeClass( "ui-corner-top ui-corner-bottom ui-corner-br ui-corner-bl ui-corner-tr ui-corner-tl" );
	},

	refresh: function( create ) {
		this._createSubPages();

		var o = this.options,
			$list = this.element,
			self = this,
			dividertheme = $list.data( "dividertheme" ) || o.dividerTheme,
			li = $list.children( "li" ),
			counter = $.support.cssPseudoElement || !$.nodeName( $list[0], "ol" ) ? 0 : 1;

		if ( counter ) {
			$list.find( ".ui-li-dec" ).remove();
		}

		li.attr({ "role": "option", "tabindex": "-1" });

		li.first().attr( "tabindex", "0" );

		li.each(function( pos ) {
			var item = $( this ),
				itemClass = "ui-li";

			// If we're creating the element, we update it regardless
			if ( !create && item.hasClass( "ui-li" ) ) {
				return;
			}

			var a = item.find( "a" );

			if ( a.length ) {
				item
					.buttonMarkup({
						wrapperEls: "div",
						shadow: false,
						corners: false,
						iconpos: "right",
						icon: a.length > 1 ? false : item.data("icon") || "arrow-r",
						theme: o.theme
					});

				a.first().addClass( "ui-link-inherit" );

				if ( a.length > 1 ) {
					itemClass += " ui-li-has-alt";

					var last = a.last(),
						splittheme = $list.data( "splittheme" ) || last.data( "theme" ) || o.splitTheme;

					last
						.attr( "title", last.text() )
						.addClass( "ui-li-link-alt" )
						.empty()
						.buttonMarkup({
							shadow: false,
							corners: false,
							theme: o.theme,
							icon: false,
							iconpos: false
						})
						.find( ".ui-btn-inner" )
							.append( $( "" ).buttonMarkup({
								shadow: true,
								corners: true,
								theme: splittheme,
								iconpos: "notext",
								icon: $list.data( "spliticon" ) || last.data( "icon" ) ||  o.splitIcon
							} ) );
				}

			} else if ( item.data( "role" ) === "list-divider" ) {
				itemClass += " ui-li-divider ui-btn ui-bar-" + dividertheme;
				item.attr( "role", "heading" );

				//reset counter when a divider heading is encountered
				if ( counter ) {
					counter = 1;
				}

			} else {
				itemClass += " ui-li-static ui-btn-up-" + o.theme;
			}

			if( o.inset ){
				if ( pos === 0 ) {
						itemClass += " ui-corner-top";

						item
							.add( item.find( ".ui-btn-inner" ) )
							.find( ".ui-li-link-alt" )
								.addClass( "ui-corner-tr" )
							.end()
							.find( ".ui-li-thumb" )
								.addClass( "ui-corner-tl" );

						self._removeCorners( item.next() );

				} else if ( pos === li.length - 1 ) {
						itemClass += " ui-corner-bottom";

						item
							.add( item.find( ".ui-btn-inner" ) )
							.find( ".ui-li-link-alt" )
								.addClass( "ui-corner-br" )
							.end()
							.find( ".ui-li-thumb" )
								.addClass( "ui-corner-bl" );

						self._removeCorners( item.prev() );
				}
			}

			if ( counter && itemClass.indexOf( "ui-li-divider" ) < 0 ) {
				item
					.find( ".ui-link-inherit" ).first()
					.addClass( "ui-li-jsnumbering" )
					.prepend( "" + (counter++) + ". " );
			}

			item.addClass( itemClass );

			if ( !create ) {
				self._itemApply( $list, item );
			}
		});
	},

	//create a string for ID/subpage url creation
	_idStringEscape: function( str ){
		return str.replace(/[^a-zA-Z0-9]/g, '-');
	},

	_createSubPages: function() {
		var parentList = this.element,
			parentPage = parentList.closest( ".ui-page" ),
			parentId = parentPage.attr( "id" ),
			o = this.options,
			persistentFooterID = parentPage.find( "[data-role='footer']" ).data( "id" );

		$( parentList.find( "ul, ol" ).toArray().reverse() ).each(function( i ) {
			var list = $( this ),
				parent = list.parent(),
				title = parent.contents()[ 0 ].nodeValue.split("\n")[0],
				id = parentId + "&" + $.mobile.subPageUrlKey + "=" + self.idStringEscape(title + " " + i),
				theme = list.data( "theme" ) || o.theme,
				countTheme = list.data( "counttheme" ) || parentList.data( "counttheme" ) || o.countTheme,
				newPage = list.wrap( "

" )
							.parent()
								.before( "

" + title + "

" )
								.after( persistentFooterID ? $( "
", { "data-role": "footer", "data-id": persistentFooterID, "class": "ui-footer-duplicate" } ) : "" )
								.parent()
									.attr({
										id: id,
										"data-theme": theme,
										"data-count-theme": countTheme
									})
									.appendTo( $.pageContainer );

				newPage.page();

			parent.html( "" + title + "" );
		}).listview();
	}
});

})( jQuery );
<|endoftext|>


<commit_before>/*global jQuery,cyberDojo*/

var cyberDojo = (function(cd, $) {
  "use strict";

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.eachFilename = function(f) {
    cd.filenames().forEach(f);
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.filenames = function() {
    // Gets the filenames when on a kata/edit page.
    // The review/show page/dialog has to collect its filenames
    // in its own way.
    var filenames = [ ];
    const prefix = 'file_content_for_';
    $('textarea[id^=' + prefix + ']').each(function(_) {
      const id = $(this).attr('id');
      const filename = id.substr(prefix.length, id.length - prefix.length);
      filenames.push(filename);
    });
    return filenames;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.filenameAlreadyExists = function(filename) {
    return cd.inArray(filename, cd.filenames());
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const isSourceFile = function(filename) {
    var match = false;
    $.each(cd.extensionFilenames(), function(_, extension) {
      // Shell test frameworks (eg shunit2) use .sh as their
      // filename extension but we don't want cyber-dojo.sh
      // in the hiFilenames() above output in the filename-list.
      if (filename.endsWith(extension) && filename != 'cyber-dojo.sh') {
        match = true;
      }
    });
    return match;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.hiFilenames = function(filenames) {
    // Controls which filenames appear at the
    // top of the filename-list, above 'output'
    //
    // Used in three places.
    // 1. kata/edit page to help show filename list
    // 2. kata/edit page in alt-j alt-k hotkeys
    // 3. review/show page/dialog to help show filename list
    //
    var hi = [];
    $.each(filenames, function(_, filename) {
      if (isSourceFile(filename) || filename == 'instructions') {
        hi.push(filename);
      }
    });
    hi.sort();
    hi = hi.filter(item => item !== 'output')
    hi = hi.filter(item => item != 'cyber-dojo.sh')
    return hi;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.loFilenames = function(filenames) {
    // Controls which filenames appear at the
    // bottom of the filename list, below 'output'
    //
    // Used in three places.
    // 1. kata/edit page to help show filename-list
    // 2. kata/edit page in alt-j alt-k hotkeys
    // 3. review/show page/dialog to help show filename-list
    //
    var lo = [];
    $.each(filenames, function(_, filename) {
      if (!isSourceFile(filename) && filename != 'instructions') {
        lo.push(filename);
      }
    });
    lo.sort();
    lo = lo.filter(item => item !== 'output')
    return lo;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.sortedFilenames = function(filenames) {
    // Controls the order of files in the filename-list
    // Used in two places
    //
    // 1. kata/edit page to help show filename-list
    // 2. review/show page/dialog to help show filename-list
    //
    return [].concat(cd.hiFilenames(filenames), ['output'], cd.loFilenames(filenames));
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.rebuildFilenameList = function() {
    var filenames = cd.filenames();
    var filenameList = $('#filename-list');
    filenameList.empty();
    $.each(cd.sortedFilenames(filenames), function(_, filename) {
      filenameList.append(cd.makeFileListEntry(filename));
    });
    return filenames;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.makeFileListEntry = function(filename) {
    var div = $('<div>', {
      'class': 'filename',
      id: 'radio_' + filename,
      text: filename
    });
    if (cd.inArray(filename, cd.highlightFilenames())) {
      div.addClass('highlight');
    }
    div.click(function() { cd.loadFile(filename); });
    return div;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  return cd;

})(cyberDojo || {}, jQuery);
<commit_msg>refactoring; reduce scope of cd.makeFileListEntry()<commit_after>/*global jQuery,cyberDojo*/

var cyberDojo = (function(cd, $) {
  "use strict";

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.eachFilename = function(f) {
    cd.filenames().forEach(f);
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.filenames = function() {
    // Gets the filenames when on a kata/edit page.
    // The review/show page/dialog has to collect its filenames
    // in its own way.
    var filenames = [ ];
    const prefix = 'file_content_for_';
    $('textarea[id^=' + prefix + ']').each(function(_) {
      const id = $(this).attr('id');
      const filename = id.substr(prefix.length, id.length - prefix.length);
      filenames.push(filename);
    });
    return filenames;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.filenameAlreadyExists = function(filename) {
    return cd.inArray(filename, cd.filenames());
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const isSourceFile = function(filename) {
    var match = false;
    $.each(cd.extensionFilenames(), function(_, extension) {
      // Shell test frameworks (eg shunit2) use .sh as their
      // filename extension but we don't want cyber-dojo.sh
      // in the hiFilenames() above output in the filename-list.
      if (filename.endsWith(extension) && filename != 'cyber-dojo.sh') {
        match = true;
      }
    });
    return match;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.hiFilenames = function(filenames) {
    // Controls which filenames appear at the
    // top of the filename-list, above 'output'
    //
    // Used in three places.
    // 1. kata/edit page to help show filename list
    // 2. kata/edit page in alt-j alt-k hotkeys
    // 3. review/show page/dialog to help show filename list
    //
    var hi = [];
    $.each(filenames, function(_, filename) {
      if (isSourceFile(filename) || filename == 'instructions') {
        hi.push(filename);
      }
    });
    hi.sort();
    hi = hi.filter(item => item !== 'output')
    hi = hi.filter(item => item != 'cyber-dojo.sh')
    return hi;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.loFilenames = function(filenames) {
    // Controls which filenames appear at the
    // bottom of the filename list, below 'output'
    //
    // Used in three places.
    // 1. kata/edit page to help show filename-list
    // 2. kata/edit page in alt-j alt-k hotkeys
    // 3. review/show page/dialog to help show filename-list
    //
    var lo = [];
    $.each(filenames, function(_, filename) {
      if (!isSourceFile(filename) && filename != 'instructions') {
        lo.push(filename);
      }
    });
    lo.sort();
    lo = lo.filter(item => item !== 'output')
    return lo;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.sortedFilenames = function(filenames) {
    // Controls the order of files in the filename-list
    // Used in two places
    //
    // 1. kata/edit page to help show filename-list
    // 2. review/show page/dialog to help show filename-list
    //
    return [].concat(cd.hiFilenames(filenames), ['output'], cd.loFilenames(filenames));
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.rebuildFilenameList = function() {
    var filenames = cd.filenames();
    var filenameList = $('#filename-list');
    filenameList.empty();
    $.each(cd.sortedFilenames(filenames), function(_, filename) {
      filenameList.append(makeFileListEntry(filename));
    });
    return filenames;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  const makeFileListEntry = (filename) => {
    const div = $('<div>', {
      'class': 'filename',
      id: 'radio_' + filename,
      text: filename
    });
    if (cd.inArray(filename, cd.highlightFilenames())) {
      div.addClass('highlight');
    }
    div.click(function() { cd.loadFile(filename); });
    return div;
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  return cd;

})(cyberDojo || {}, jQuery);
<|endoftext|>


<commit_before>/*
* jQuery Mobile Framework : listview plugin
* Copyright (c) jQuery Project
* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses.
* Note: Code is in draft form and is subject to change
*/
(function( $ ) {

$.widget( "mobile.listview", $.mobile.widget, {
	options: {
		theme: "c",
		countTheme: "c",
		headerTheme: "b",
		dividerTheme: "b",
		splitIcon: "arrow-r",
		splitTheme: "b",
		inset: false
	},

	_create: function() {
		// create listview markup
		this.element
			.addClass( "ui-listview" )
			.attr( "role", "listbox" )

		if ( this.options.inset ) {
			this.element.addClass( "ui-listview-inset ui-shadow" );
		}

		this.refresh();

		//keyboard events for menu items
		this.element.keydown(function(event){
			//switch logic based on which key was pressed
			switch(event.keyCode){
				//up or left arrow keys
				case 38:
					//if there's a previous option, focus it
					if( $(event.target).closest('li').prev().length  ){
						$(event.target).blur().attr("tabindex","-1").closest('li').prev().find('a').eq(0).focus();
					}
					//prevent native scroll
					return false;
				break;
				//down or right arrow keys
				case 40:

					//if there's a next option, focus it
					if( $(event.target).closest('li').next().length ){
						$(event.target).blur().attr("tabindex","-1").closest('li').next().find('a').eq(0).focus();
					}
					//prevent native scroll
					return false;
				break;
				case 39:
					if( $(event.target).closest('li').find('a.ui-li-link-alt').length ){
						$(event.target).blur().closest('li').find('a.ui-li-link-alt').eq(0).focus();
					}
					return false;
				break;
				case 37:
					if( $(event.target).closest('li').find('a.ui-link-inherit').length ){
						$(event.target).blur().closest('li').find('a.ui-link-inherit').eq(0).focus();
					}
					return false;
				break;
				//if enter or space is pressed, trigger click
				case 13:
				case 32:
					 $(event.target).trigger('click'); //should trigger select
					 return false;
				break;
			}
		});

		//tapping the whole LI triggers ajaxClick on the first link
		this.element.delegate( "li:has(a)", "tap", function(event) {
			if( !$(event.target).closest('a').length ){
				$( this ).find( "a:first" ).trigger('click');
				return false;
			}
		});
	},

	refresh: function() {
		this._createSubPages();

		var o = this.options,
			dividertheme = this.element.data( "dividertheme" ) || o.dividerTheme,
			$list = this.element;
		this.element.find( "li")
			.eq(0)
			.attr("tabindex","0")
			.end()
			.filter(":not(.ui-li)" )
			.attr("role","option")
			.attr("tabindex","-1")
			.focus(function(){
				$(this).attr("tabindex","0");
			})
			.each(function() {
				var $li = $( this ).addClass( "ui-li" ),
					role = $li.data( "role" );

				if ( $li.is( ":has(img)" ) ) {
					if ($li.is( ":has(img.ui-li-icon)" )) {
						$li.addClass( "ui-li-has-icon" );
					} else {
						$li.addClass( "ui-li-has-thumb" );
					}
				}

				if ( $li.is( ":has(.ui-li-aside)" ) ) {
					var aside = $li.find('.ui-li-aside');
					aside.prependTo(aside.parent()); //shift aside to front for css float
				}

				if ( $li.find('a').length ) {
					$li
						.buttonMarkup({
							wrapperEls: "div",
							shadow: false,
							corners: false,
							iconpos: "right",
							icon: $(this).data("icon") || "arrow-r",
							theme: o.theme
						})
						.find( "a" ).eq( 0 )
							.addClass( "ui-link-inherit" );
				}
				else if( role == "list-divider" ){
					$li.addClass( "ui-li-divider ui-btn ui-bar-" + dividertheme ).attr( "role", "heading" );
				}
				else {
					$li.addClass( "ui-li-static ui-btn-up-" + o.theme );
				}

				// TODO class has to be defined in markup
				$li.find( ".ui-li-count" )
					.addClass( "ui-btn-up-" + ($list.data( "counttheme" ) || o.countTheme) + " ui-btn-corner-all" );

				$li.find( ":header" ).addClass( "ui-li-heading" );

				//for split buttons
				var $splitBtn = $li.find( "a" ).eq( 1 );
				if ( $splitBtn.length ) {
					$(this).addClass('ui-li-has-alt');
				}
				$splitBtn.each(function() {
					var a = $( this )
						.attr( "title", $( this ).text() )
						.addClass( "ui-li-link-alt" )
						.empty()
						.buttonMarkup({
							shadow: false,
							corners: false,
							theme: o.theme,
							icon: false,
							iconpos: false
						});
					a.find( ".ui-btn-inner" )
						.append( $( "" ).buttonMarkup({
							shadow: true,
							corners: true,
							theme: $list.data('splittheme') || a.data('theme') || o.splitTheme,
							iconpos: "notext",
							icon: $list.data('spliticon') || a.data('icon') ||  o.splitIcon
						} ) );

					//fix corners
					if ( o.inset ) {
						var closestLi = $( this ).closest( "li" );
						if ( closestLi.is( "li:first-child" ) ) {
							$( this ).addClass( "ui-corner-tr" );
						} else if ( closestLi.is( "li:last-child" ) ) {
							$( this ).addClass( "ui-corner-br" );
						}
					}
				});

				//remove corners before or after dividers
				var sides = ['top','bottom'];
				$.each( sides, function( i ){
					var side = sides[ i ];
					$li.filter( ".ui-corner-" + side ).each(function() {
						if ( $li[ i == 0 ? 'prev' : 'next' ]( ".ui-li-divider" ).length ) {
							$( this ).removeClass( "ui-corner-" + side );
						}
					});
				});
			})
			.find( "img")
				.addClass( "ui-li-thumb" )
			.end()
			.find( "p, ul, dl" )
				.addClass( "ui-li-desc" );

		// TODO remove these classes from any elements that may have gotten them in a previous call
		this.element
			.find( "img" )
				.filter( "li:first-child img" )
					.addClass( "ui-corner-tl" )
				.end()
				.filter( "li:last-child img" )
					.addClass( "ui-corner-bl" )
				.end();

		this._numberItems();
	},

	_createSubPages: function() {
		var parentId = this.element.closest( ".ui-page" ).attr( "id" ),
			o = this.options,
			parentList = this.element;
		$( this.element.find( "ul,ol" ).get().reverse() ).each(function( i ) {
			var list = $( this ),
				parent = list.parent(),
				title = parent.contents()[ 0 ].nodeValue.split("\n")[0],
				id = parentId + "&" + $.mobile.subPageUrlKey + "=" + $.mobile.idStringEscape(title + " " + i),
				theme = list.data( "theme" ) || o.theme,
				countTheme = list.data( "counttheme" ) || parentList.data( "counttheme" ) || o.countTheme,
				newPage = list.wrap( "

" )
							.parent()
								.before( "

" + title + "

" )
								.parent()
									.attr({
										id: id,
										"data-theme": theme,
										"data-count-theme": countTheme
									})
									.appendTo( $.pageContainer );

				newPage.page();

			parent.html( "" + title + "" );
		}).listview();
	},

	// JS fallback for auto-numbering for OL elements
	_numberItems: function() {
		if ( $.support.cssPseudoElement || !this.element.is( "ol" ) ) {
			return;
		}
		var counter = 1;
		this.element.find( ".ui-li-dec" ).remove();
		this.element.find( "li:visible" ).each(function() {
			if( $( this ).is( ".ui-li-divider" ) ) {
				//reset counter when a divider heading is encountered
				counter = 1;
			} else {
				$( this )
					.find( ".ui-link-inherit:first" )
					.addClass( "ui-li-jsnumbering" )
					.prepend( "" + (counter++) + ". " );
			}
		});
	}
});

})( jQuery );
<commit_msg>fixed up rounded corners on inset lists<commit_after>/*
* jQuery Mobile Framework : listview plugin
* Copyright (c) jQuery Project
* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses.
* Note: Code is in draft form and is subject to change
*/
(function( $ ) {

$.widget( "mobile.listview", $.mobile.widget, {
	options: {
		theme: "c",
		countTheme: "c",
		headerTheme: "b",
		dividerTheme: "b",
		splitIcon: "arrow-r",
		splitTheme: "b",
		inset: false
	},

	_create: function() {
		// create listview markup
		this.element
			.addClass( "ui-listview" )
			.attr( "role", "listbox" )

		if ( this.options.inset ) {
			this.element.addClass( "ui-listview-inset ui-corner-all ui-shadow" );
		}

		this.refresh();

		//keyboard events for menu items
		this.element.keydown(function(event){
			//switch logic based on which key was pressed
			switch(event.keyCode){
				//up or left arrow keys
				case 38:
					//if there's a previous option, focus it
					if( $(event.target).closest('li').prev().length  ){
						$(event.target).blur().attr("tabindex","-1").closest('li').prev().find('a').eq(0).focus();
					}
					//prevent native scroll
					return false;
				break;
				//down or right arrow keys
				case 40:

					//if there's a next option, focus it
					if( $(event.target).closest('li').next().length ){
						$(event.target).blur().attr("tabindex","-1").closest('li').next().find('a').eq(0).focus();
					}
					//prevent native scroll
					return false;
				break;
				case 39:
					if( $(event.target).closest('li').find('a.ui-li-link-alt').length ){
						$(event.target).blur().closest('li').find('a.ui-li-link-alt').eq(0).focus();
					}
					return false;
				break;
				case 37:
					if( $(event.target).closest('li').find('a.ui-link-inherit').length ){
						$(event.target).blur().closest('li').find('a.ui-link-inherit').eq(0).focus();
					}
					return false;
				break;
				//if enter or space is pressed, trigger click
				case 13:
				case 32:
					 $(event.target).trigger('click'); //should trigger select
					 return false;
				break;
			}
		});

		//tapping the whole LI triggers ajaxClick on the first link
		this.element.delegate( "li:has(a)", "tap", function(event) {
			if( !$(event.target).closest('a').length ){
				$( this ).find( "a:first" ).trigger('click');
				return false;
			}
		});
	},

	refresh: function() {
		this._createSubPages();

		var o = this.options,
			dividertheme = this.element.data( "dividertheme" ) || o.dividerTheme,
			$list = this.element;
		this.element.find( "li")
			.eq(0)
			.attr("tabindex","0")
			.end()
			.filter(":not(.ui-li)" )
			.attr("role","option")
			.attr("tabindex","-1")
			.focus(function(){
				$(this).attr("tabindex","0");
			})
			.find( "img")
				.addClass( "ui-li-thumb" )
			.end()
			.each(function() {
				var $li = $( this ).addClass( "ui-li" ),
					role = $li.data( "role" );

				if ( $li.is( ":has(img)" ) ) {
					if ($li.is( ":has(img.ui-li-icon)" )) {
						$li.addClass( "ui-li-has-icon" );
					} else {
						$li.addClass( "ui-li-has-thumb" );
					}
				}

				if ( $li.is( ":has(.ui-li-aside)" ) ) {
					var aside = $li.find('.ui-li-aside');
					aside.prependTo(aside.parent()); //shift aside to front for css float
				}

				if ( $li.find('a').length ) {
					$li
						.buttonMarkup({
							wrapperEls: "div",
							shadow: false,
							corners: false,
							iconpos: "right",
							icon: $(this).data("icon") || "arrow-r",
							theme: o.theme
						})
						.find( "a" ).eq( 0 )
							.addClass( "ui-link-inherit" );
				}
				else if( role == "list-divider" ){
					$li.addClass( "ui-li-divider ui-btn ui-bar-" + dividertheme ).attr( "role", "heading" );
				}
				else {
					$li.addClass( "ui-li-static ui-btn-up-" + o.theme );
				}

				// TODO class has to be defined in markup
				$li.find( ".ui-li-count" )
					.addClass( "ui-btn-up-" + ($list.data( "counttheme" ) || o.countTheme) + " ui-btn-corner-all" );

				$li.find( ":header" ).addClass( "ui-li-heading" );

				//for split buttons
				var $splitBtn = $li.find( "a" ).eq( 1 );
				if ( $splitBtn.length ) {
					$(this).addClass('ui-li-has-alt');
				}
				$splitBtn.each(function() {
					var a = $( this )
						.attr( "title", $( this ).text() )
						.addClass( "ui-li-link-alt" )
						.empty()
						.buttonMarkup({
							shadow: false,
							corners: false,
							theme: o.theme,
							icon: false,
							iconpos: false
						});
					a.find( ".ui-btn-inner" )
						.append( $( "" ).buttonMarkup({
							shadow: true,
							corners: true,
							theme: $list.data('splittheme') || a.data('theme') || o.splitTheme,
							iconpos: "notext",
							icon: $list.data('spliticon') || a.data('icon') ||  o.splitIcon
						} ) );
				});

				//fix corners
				if ( o.inset ) {
					var closestLi = $( this ).closest( "li" );
					if ( closestLi.is( "li:first-child" ) ) {
						closestLi
							.addClass('ui-corner-top')
							.find( ".ui-li-link-alt" )
								.addClass('ui-corner-tr')
							.end()
							.find( ".ui-li-thumb" )
								.addClass('ui-corner-tl')
					} else if ( closestLi.is( "li:last-child" ) ) {
						closestLi
							.addClass('ui-corner-bottom')
							.find( ".ui-li-link-alt" )
								.addClass('ui-corner-br')
							.end()
							.find( ".ui-li-thumb" )
								.addClass('ui-corner-bl')
					}
				}

			})
			.find( "p, ul, dl" )
				.addClass( "ui-li-desc" );

		// TODO remove these classes from any elements that may have gotten them in a previous call
		this.element
			.find( "img" )
				.filter( "li:first-child img" )
					.addClass( "ui-corner-tl" )
				.end()
				.filter( "li:last-child img" )
					.addClass( "ui-corner-bl" )
				.end();

		this._numberItems();
	},

	_createSubPages: function() {
		var parentId = this.element.closest( ".ui-page" ).attr( "id" ),
			o = this.options,
			parentList = this.element;
		$( this.element.find( "ul,ol" ).get().reverse() ).each(function( i ) {
			var list = $( this ),
				parent = list.parent(),
				title = parent.contents()[ 0 ].nodeValue.split("\n")[0],
				id = parentId + "&" + $.mobile.subPageUrlKey + "=" + $.mobile.idStringEscape(title + " " + i),
				theme = list.data( "theme" ) || o.theme,
				countTheme = list.data( "counttheme" ) || parentList.data( "counttheme" ) || o.countTheme,
				newPage = list.wrap( "

" )
							.parent()
								.before( "

" + title + "

" )
								.parent()
									.attr({
										id: id,
										"data-theme": theme,
										"data-count-theme": countTheme
									})
									.appendTo( $.pageContainer );

				newPage.page();

			parent.html( "" + title + "" );
		}).listview();
	},

	// JS fallback for auto-numbering for OL elements
	_numberItems: function() {
		if ( $.support.cssPseudoElement || !this.element.is( "ol" ) ) {
			return;
		}
		var counter = 1;
		this.element.find( ".ui-li-dec" ).remove();
		this.element.find( "li:visible" ).each(function() {
			if( $( this ).is( ".ui-li-divider" ) ) {
				//reset counter when a divider heading is encountered
				counter = 1;
			} else {
				$( this )
					.find( ".ui-link-inherit:first" )
					.addClass( "ui-li-jsnumbering" )
					.prepend( "" + (counter++) + ". " );
			}
		});
	}
});

})( jQuery );
<|endoftext|>


<commit_before>import { mount } from '@vue/test-utils'
import BAutocomplete from '@components/autocomplete/Autocomplete'

const findStringsStartingWith = (array, value) =>
    array.filter((x) => x.startsWith(value))

const DATA_LIST = [
    'Angular',
    'Angular 2',
    'Aurelia',
    'Backbone',
    'Ember',
    'jQuery',
    'Meteor',
    'Node.js',
    'Polymer',
    'React',
    'RxJS',
    'Vue.js'
]
const dropdownMenu = '.dropdown-menu'
let wrapper, $input, $dropdown, stubs

describe('BAutocomplete', () => {
    beforeEach(() => {
        stubs = {'b-icon': true}
        wrapper = mount(BAutocomplete, {
            propsData: {
                checkInfiniteScroll: true
            },
            stubs
        })

        $input = wrapper.find('input')
        $dropdown = wrapper.find(dropdownMenu)
    })

    it('is called', () => {
        expect(wrapper.name()).toBe('BAutocomplete')
        expect(wrapper.isVueInstance()).toBeTruthy()
    })

    it('render correctly', () => {
        expect(wrapper.html()).toMatchSnapshot()
    })

    it('has an input type', () => {
        expect(wrapper.contains('.control .input[type=text]')).toBeTruthy()
    })

    it('has a dropdown menu hidden by default', () => {
        expect(wrapper.contains(dropdownMenu)).toBeTruthy()
        expect($dropdown.isVisible()).toBeFalsy()
    })

    it('can apply a maximum height for the dropdown', () => {
        expect(wrapper.vm.contentStyle.maxHeight).toBeNull()

        const maxHeight = 200

        wrapper.setProps({ maxHeight })
        expect(wrapper.vm.contentStyle.maxHeight).toBe(`${maxHeight}px`)
        wrapper.setProps({ maxHeight: `${maxHeight}rem` })
        expect(wrapper.vm.contentStyle.maxHeight).toBe(`${maxHeight}rem`)
    })

    it('can select a value using v-model', () => {
        const value = DATA_LIST[0]
        wrapper.setProps({ value })
        expect(wrapper.vm.newValue).toBe(value)
    })

    it('can emit input, focus and blur events', async () => {
        const VALUE_TYPED = 'test'
        wrapper.setProps({ data: DATA_LIST })

        $input.trigger('focus')
        expect(wrapper.emitted()['focus']).toBeTruthy()
        $input.setValue(VALUE_TYPED)

        await wrapper.vm.$nextTick()

        const valueEmitted = wrapper.emitted()['input'][0]
        expect(valueEmitted).toContainEqual(VALUE_TYPED)

        $input.trigger('blur')
        expect(wrapper.emitted()['blur']).toBeTruthy()
    })

    it('can autocomplete with keydown', async () => {
        const VALUE_TYPED = 'Ang'
        wrapper.setProps({ data: DATA_LIST })

        $input.trigger('focus')
        $input.setValue(VALUE_TYPED)
        await wrapper.vm.$nextTick()

        expect($dropdown.isVisible()).toBeTruthy()

        const itemsInDropdowm = findStringsStartingWith(DATA_LIST, VALUE_TYPED)

        $input.trigger('keydown.down')
        $input.trigger('keydown.enter')
        await wrapper.vm.$nextTick()

        expect($input.element.value).toBe(itemsInDropdowm[0])
        expect($dropdown.isVisible()).toBeFalsy()

        $input.trigger('focus')
        $input.setValue(VALUE_TYPED)
        await wrapper.vm.$nextTick()

        expect($dropdown.isVisible()).toBeTruthy()

        $input.trigger('keydown.down')
        $input.trigger('keydown.down')
        $input.trigger('keydown.enter')
        await wrapper.vm.$nextTick()

        expect($input.element.value).toBe(itemsInDropdowm[1])
        expect($dropdown.isVisible()).toBeFalsy()
    })

    it('close dropdown on esc', () => {
        jest.useFakeTimers()
        wrapper.setProps({ data: DATA_LIST })

        wrapper.vm.isActive = true
        expect($dropdown.isVisible()).toBeTruthy()

        $input.trigger('keyup.esc')

        expect($dropdown.isVisible()).toBeFalsy()

        wrapper.vm.calcDropdownInViewportVertical = jest.fn(
            () => wrapper.vm.calcDropdownInViewportVertical
        )
        jest.runAllTimers()
        expect(wrapper.vm.calcDropdownInViewportVertical).toHaveBeenCalled()
        jest.useRealTimers()
    })

    it('close dropdown on click outside', () => {
        wrapper.setProps({ data: DATA_LIST })

        wrapper.vm.isActive = true
        expect($dropdown.isVisible()).toBeTruthy()

        window.document.body.click()
        expect($dropdown.isVisible()).toBeFalsy()
    })

    it('open dropdown on down key click', async () => {
        wrapper.vm.setHovered = jest.fn(() => wrapper.vm.setHovered)
        wrapper.setProps({
            data: DATA_LIST,
            dropdownPosition: 'bottom'
        })

        expect($dropdown.isVisible()).toBeFalsy()

        $input.trigger('focus')
        $input.trigger('keydown.down')
        await wrapper.vm.$nextTick()

        expect($dropdown.isVisible()).toBeTruthy()
        expect(wrapper.vm.setHovered).toHaveBeenCalled()
    })

    it('manages tab pressed as expected', async () => {
        // hovered is null
        $input.trigger('keydown.tab')
        expect($dropdown.isVisible()).toBeFalsy()

        // The first element will be hovered
        wrapper.setProps({
            openOnFocus: true,
            keepFirst: true
        })
        wrapper.setProps({
            data: DATA_LIST
        })
        // Set props in 2 steps to trigger Watch with keepFirst true so the 1st element is hovered

        $input.trigger('focus')
        await wrapper.vm.$nextTick()

        $input.trigger('keydown.tab')
        expect($input.element.value).toBe(DATA_LIST[0])
    })

    it('can be used with objects', async () => {
        const data = [
            {
                id: 1,
                name: 'Value 1'
            },
            {
                id: 2,
                name: 'Value 2'
            }
        ]
        wrapper.setProps({
            data,
            field: 'name'
        })

        wrapper.vm.isActive = true
        expect($dropdown.isVisible()).toBeTruthy()

        $input.trigger('keydown.enter')
        expect(wrapper.vm.hovered).toBeNull()

        $input.trigger('keydown.down')
        $input.trigger('keydown.enter')
        expect($input.element.value).toBe(data[0].name)
    })

    it('clears the value if clearOnSelect is used', async () => {
        wrapper.setProps({
            data: DATA_LIST,
            clearOnSelect: (val) => true
        })

        wrapper.vm.isActive = true
        expect($dropdown.isVisible()).toBeTruthy()

        $input.trigger('keydown.down')
        $input.trigger('keydown.enter')
        expect($input.element.value).toBe('')
    })

    it('can be used with a custom data formatter', async () => {
        wrapper.setProps({
            data: DATA_LIST,
            customFormatter: (val) => `${val} formatted`
        })

        wrapper.vm.isActive = true
        expect($dropdown.isVisible()).toBeTruthy()

        $input.trigger('keydown.down')
        $input.trigger('keydown.enter')
        expect($input.element.value).toBe(`${DATA_LIST[0]} formatted`)
    })

    it('can openOnFocus and keepFirst', async () => {
        wrapper.setProps({
            openOnFocus: true,
            keepFirst: true
        })

        expect($dropdown.isVisible()).toBeFalsy()

        $input.trigger('focus')
        expect(wrapper.vm.hovered).toBeNull()

        wrapper.setProps({
            data: DATA_LIST
        }) // Set props in 2 steps to trigger the Watch for data having keepFirst true

        $input.trigger('focus')
        await wrapper.vm.$nextTick()

        expect($dropdown.isVisible()).toBeTruthy()

        expect(wrapper.vm.hovered).toBe(DATA_LIST[0])
    })

    it('clear button does not exist when the search input is empty', () => {
        wrapper.setData({newValue: ''})
        wrapper.setProps({ clearable: true })
        const subject = wrapper.find('b-icon-stub').exists()

        expect(subject).toBeFalsy()
    })

    it('clear button exists when the search input is not empty and clearable property is true', () => {
        wrapper.setData({ newValue: 'Jquery' })
        wrapper.setProps({ clearable: true })
        const subject = wrapper.find('b-icon-stub').exists()

        expect(subject).toBeTruthy()
    })

    it('clears search input text when clear button gets clicked', () => {
        wrapper.setData({newValue: 'Jquery'})
        wrapper.setProps({ clearable: true })
        wrapper.find('b-icon-stub').trigger('click')
        const subject = wrapper.vm.newValue

        expect(subject).toEqual('')
    })

    it('clear button does not appear when clearable property is not set to true', () => {
        wrapper.setData({newValue: 'Jquery'})
        const subject = wrapper.find('b-icon-stub').exists()

        expect(subject).toBeFalsy()
    })

    it('can have a custom clickable right icon', () => {
        wrapper.setProps({
            iconRight: 'delete',
            iconRightClickable: true
        })
        const icon = wrapper.find('b-icon-stub')
        expect(icon.exists()).toBeTruthy()

        icon.trigger('click')
        expect(wrapper.emitted()['icon-right-click']).toBeTruthy()
    })

    it('reset events before destroy', () => {
        document.removeEventListener = jest.fn()
        window.removeEventListener = jest.fn()

        wrapper.destroy()

        expect(document.removeEventListener).toBeCalledWith('click', expect.any(Function))
        expect(window.removeEventListener).toBeCalledWith('resize', expect.any(Function))
    })
})
<commit_msg>Fix unit tests<commit_after>import { mount } from '@vue/test-utils'
import BAutocomplete from '@components/autocomplete/Autocomplete'

const findStringsStartingWith = (array, value) =>
    array.filter((x) => x.startsWith(value))

const DATA_LIST = [
    'Angular',
    'Angular 2',
    'Aurelia',
    'Backbone',
    'Ember',
    'jQuery',
    'Meteor',
    'Node.js',
    'Polymer',
    'React',
    'RxJS',
    'Vue.js'
]
const dropdownMenu = '.dropdown-menu'
let wrapper, $input, $dropdown, stubs

describe('BAutocomplete', () => {
    beforeEach(() => {
        stubs = {'b-icon': true}
        wrapper = mount(BAutocomplete, {
            propsData: {
                checkInfiniteScroll: true
            },
            stubs
        })

        $input = wrapper.find('input')
        $dropdown = wrapper.find(dropdownMenu)
    })

    it('is called', () => {
        expect(wrapper.name()).toBe('BAutocomplete')
        expect(wrapper.isVueInstance()).toBeTruthy()
    })

    it('render correctly', () => {
        expect(wrapper.html()).toMatchSnapshot()
    })

    it('has an input type', () => {
        expect(wrapper.contains('.control .input[type=text]')).toBeTruthy()
    })

    it('has a dropdown menu hidden by default', () => {
        expect(wrapper.contains(dropdownMenu)).toBeTruthy()
        expect($dropdown.isVisible()).toBeFalsy()
    })

    it('can apply a maximum height for the dropdown', () => {
        expect(wrapper.vm.contentStyle.maxHeight).toBeNull()

        const maxHeight = 200

        wrapper.setProps({ maxHeight })
        expect(wrapper.vm.contentStyle.maxHeight).toBe(`${maxHeight}px`)
        wrapper.setProps({ maxHeight: `${maxHeight}rem` })
        expect(wrapper.vm.contentStyle.maxHeight).toBe(`${maxHeight}rem`)
    })

    it('can select a value using v-model', () => {
        const value = DATA_LIST[0]
        wrapper.setProps({ value })
        expect(wrapper.vm.newValue).toBe(value)
    })

    it('can emit input, focus and blur events', async () => {
        const VALUE_TYPED = 'test'
        wrapper.setProps({ data: DATA_LIST })

        $input.trigger('focus')
        expect(wrapper.emitted()['focus']).toBeTruthy()
        $input.setValue(VALUE_TYPED)

        await wrapper.vm.$nextTick()

        const valueEmitted = wrapper.emitted()['input'][0]
        expect(valueEmitted).toContainEqual(VALUE_TYPED)

        $input.trigger('blur')
        expect(wrapper.emitted()['blur']).toBeTruthy()
    })

    it('can autocomplete with keydown', async () => {
        const VALUE_TYPED = 'Ang'
        wrapper.setProps({ data: DATA_LIST })

        $input.trigger('focus')
        $input.setValue(VALUE_TYPED)
        await wrapper.vm.$nextTick()

        expect($dropdown.isVisible()).toBeTruthy()

        const itemsInDropdowm = findStringsStartingWith(DATA_LIST, VALUE_TYPED)

        $input.trigger('keydown.down')
        $input.trigger('keydown.enter')
        await wrapper.vm.$nextTick()

        expect($input.element.value).toBe(itemsInDropdowm[0])
        expect($dropdown.isVisible()).toBeFalsy()

        $input.trigger('focus')
        $input.setValue(VALUE_TYPED)
        await wrapper.vm.$nextTick()

        expect($dropdown.isVisible()).toBeTruthy()

        $input.trigger('keydown.down')
        $input.trigger('keydown.down')
        $input.trigger('keydown.enter')
        await wrapper.vm.$nextTick()

        expect($input.element.value).toBe(itemsInDropdowm[1])
        expect($dropdown.isVisible()).toBeFalsy()
    })

    it('close dropdown on esc', () => {
        jest.useFakeTimers()
        wrapper.setProps({ data: DATA_LIST })

        wrapper.vm.isActive = true
        expect($dropdown.isVisible()).toBeTruthy()

        $input.trigger('keyup.esc')

        expect($dropdown.isVisible()).toBeFalsy()

        wrapper.vm.calcDropdownInViewportVertical = jest.fn(
            () => wrapper.vm.calcDropdownInViewportVertical
        )
        jest.runAllTimers()
        expect(wrapper.vm.calcDropdownInViewportVertical).toHaveBeenCalled()
        jest.useRealTimers()
    })

    it('close dropdown on click outside', () => {
        wrapper.setProps({ data: DATA_LIST })

        wrapper.vm.isActive = true
        expect($dropdown.isVisible()).toBeTruthy()

        window.document.body.click()
        expect($dropdown.isVisible()).toBeFalsy()
    })

    it('open dropdown on down key click', async () => {
        wrapper.vm.setHovered = jest.fn(() => wrapper.vm.setHovered)
        wrapper.setProps({
            data: DATA_LIST,
            dropdownPosition: 'bottom'
        })

        expect($dropdown.isVisible()).toBeFalsy()

        $input.trigger('focus')
        $input.trigger('keydown.down')
        await wrapper.vm.$nextTick()

        expect($dropdown.isVisible()).toBeTruthy()
    })

    it('manages tab pressed as expected', async () => {
        // hovered is null
        $input.trigger('keydown.tab')
        expect($dropdown.isVisible()).toBeFalsy()

        // The first element will be hovered
        wrapper.setProps({
            openOnFocus: true,
            keepFirst: true
        })
        wrapper.setProps({
            data: DATA_LIST
        })
        // Set props in 2 steps to trigger Watch with keepFirst true so the 1st element is hovered

        $input.trigger('focus')
        await wrapper.vm.$nextTick()

        $input.trigger('keydown.tab')
        expect($input.element.value).toBe(DATA_LIST[0])
    })

    it('can be used with objects', async () => {
        const data = [
            {
                id: 1,
                name: 'Value 1'
            },
            {
                id: 2,
                name: 'Value 2'
            }
        ]
        wrapper.setProps({
            data,
            field: 'name'
        })

        wrapper.vm.isActive = true
        expect($dropdown.isVisible()).toBeTruthy()

        $input.trigger('keydown.enter')
        expect(wrapper.vm.hovered).toBeNull()

        $input.trigger('keydown.down')
        $input.trigger('keydown.enter')
        expect($input.element.value).toBe(data[0].name)
    })

    it('clears the value if clearOnSelect is used', async () => {
        wrapper.setProps({
            data: DATA_LIST,
            clearOnSelect: (val) => true
        })

        wrapper.vm.isActive = true
        expect($dropdown.isVisible()).toBeTruthy()

        $input.trigger('keydown.down')
        $input.trigger('keydown.enter')
        expect($input.element.value).toBe('')
    })

    it('can be used with a custom data formatter', async () => {
        wrapper.setProps({
            data: DATA_LIST,
            customFormatter: (val) => `${val} formatted`
        })

        wrapper.vm.isActive = true
        expect($dropdown.isVisible()).toBeTruthy()

        $input.trigger('keydown.down')
        $input.trigger('keydown.enter')
        expect($input.element.value).toBe(`${DATA_LIST[0]} formatted`)
    })

    it('can openOnFocus and keepFirst', async () => {
        wrapper.setProps({
            openOnFocus: true,
            keepFirst: true
        })

        expect($dropdown.isVisible()).toBeFalsy()

        $input.trigger('focus')
        expect(wrapper.vm.hovered).toBeNull()

        wrapper.setProps({
            data: DATA_LIST
        }) // Set props in 2 steps to trigger the Watch for data having keepFirst true

        $input.trigger('focus')
        await wrapper.vm.$nextTick()

        expect($dropdown.isVisible()).toBeTruthy()

        expect(wrapper.vm.hovered).toBe(DATA_LIST[0])
    })

    it('clear button does not exist when the search input is empty', () => {
        wrapper.setData({newValue: ''})
        wrapper.setProps({ clearable: true })
        const subject = wrapper.find('b-icon-stub').exists()

        expect(subject).toBeFalsy()
    })

    it('clear button exists when the search input is not empty and clearable property is true', () => {
        wrapper.setData({ newValue: 'Jquery' })
        wrapper.setProps({ clearable: true })
        const subject = wrapper.find('b-icon-stub').exists()

        expect(subject).toBeTruthy()
    })

    it('clears search input text when clear button gets clicked', () => {
        wrapper.setData({newValue: 'Jquery'})
        wrapper.setProps({ clearable: true })
        wrapper.find('b-icon-stub').trigger('click')
        const subject = wrapper.vm.newValue

        expect(subject).toEqual('')
    })

    it('clear button does not appear when clearable property is not set to true', () => {
        wrapper.setData({newValue: 'Jquery'})
        const subject = wrapper.find('b-icon-stub').exists()

        expect(subject).toBeFalsy()
    })

    it('can have a custom clickable right icon', () => {
        wrapper.setProps({
            iconRight: 'delete',
            iconRightClickable: true
        })
        const icon = wrapper.find('b-icon-stub')
        expect(icon.exists()).toBeTruthy()

        icon.trigger('click')
        expect(wrapper.emitted()['icon-right-click']).toBeTruthy()
    })

    it('reset events before destroy', () => {
        document.removeEventListener = jest.fn()
        window.removeEventListener = jest.fn()

        wrapper.destroy()

        expect(document.removeEventListener).toBeCalledWith('click', expect.any(Function))
        expect(window.removeEventListener).toBeCalledWith('resize', expect.any(Function))
    })
})
<|endoftext|>


<commit_before>define('managers/objectmanager', [
    'c'
], function (
    C
) {
    return function () {
        var i = 0,
            len = 0,
            canvas = C.canvas,
            ctx = C.ctx,
            objects = [],
            update = function () {
                for (i = 0, len = objects.length; i < len; i++) {
                    // preventing errors
                    if (!objects[i]) {
                        console.log('Index ' + i + ' in objects returns null.');
                        continue;
                    }

                    if (objects[i].update)
                        objects[i].update();
                }
            },
            draw = function () {
                // Clear screen befor drawing
                ctx.clearRect(0, 0, C.canvasWidth, C.canvasHeight);
                for (i = 0, len = objects.length; i < len; i++)
                    if (objects[i].draw)
                        objects[i].draw();
            },
            add = function (object) {
                objects.push(object);
                //Keeping the amount of on screen objects low for performance
                if (objects.length > 150)
                    objects.shift();
                if (object.init)
                    object.init();
            },
            module = {
                add: add,
                update: update,
                draw: draw,
                /**
                 * Clears all objects from screen
                 */
                clearBalls: function () {
                    objects.length = 0;
                },
                toggleGravity: function () {
                    C.gravity = !C.gravity;
                    for (i = 0, len = objects.length; i < len; i++)
                        objects[i].toggleGravity();
                },
                toggleFriction: function () {
                    C.friction = !C.friction;
                    for (i = 0, len = objects.length; i < len; i++)
                        objects[i].toggleFriction();
                }
            };

        return module;
    };
});<commit_msg>double object limit<commit_after>define('managers/objectmanager', [
    'c'
], function (
    C
) {
    return function () {
        var i = 0,
            len = 0,
            canvas = C.canvas,
            ctx = C.ctx,
            objects = [],
            update = function () {
                for (i = 0, len = objects.length; i < len; i++) {
                    // preventing errors
                    if (!objects[i]) {
                        console.log('Index ' + i + ' in objects returns null.');
                        continue;
                    }

                    if (objects[i].update)
                        objects[i].update();
                }
            },
            draw = function () {
                // Clear screen befor drawing
                ctx.clearRect(0, 0, C.canvasWidth, C.canvasHeight);
                for (i = 0, len = objects.length; i < len; i++)
                    if (objects[i].draw)
                        objects[i].draw();
            },
            add = function (object) {
                objects.push(object);
                //Keeping the amount of on screen objects low for performance
                if (objects.length > 300)
                    objects.shift();
                if (object.init)
                    object.init();
            },
            module = {
                add: add,
                update: update,
                draw: draw,
                /**
                 * Clears all objects from screen
                 */
                clearBalls: function () {
                    objects.length = 0;
                },
                toggleGravity: function () {
                    C.gravity = !C.gravity;
                    for (i = 0, len = objects.length; i < len; i++)
                        objects[i].toggleGravity();
                },
                toggleFriction: function () {
                    C.friction = !C.friction;
                    for (i = 0, len = objects.length; i < len; i++)
                        objects[i].toggleFriction();
                }
            };

        return module;
    };
});<|endoftext|>