Skip to content

Instantly share code, notes, and snippets.

@gregory-yet
Last active October 17, 2018 15:10
Show Gist options
  • Save gregory-yet/3eba47b5ef7fd0658c5f4f9e53d14a2d to your computer and use it in GitHub Desktop.
Save gregory-yet/3eba47b5ef7fd0658c5f4f9e53d14a2d to your computer and use it in GitHub Desktop.
Planification autorisé / bloqué (ex. contrôle parental freebox)
<html lang="fr">
<head>
<meta charset="utf-8">
<title>Planification</title>
<!-- Bootstrap core CSS -->
<link href="http://localhost/planification/assets/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="http://localhost/planification/assets/utilities/TimeSheet/TimeSheet.css">
</head>
<body id="planification">
<div class="container py-5">
<div class="mb-5">
<a href="#" class="btn btn-success" role="button" id="savePlanification">Enregistrer</a>
<a href="#" class="btn btn-primary" role="button" id="enablePlanification">Autorisé</a>
<a href="#" class="btn btn-danger" role="button" id="disablePlanification">Bloqué</a>
</div>
<table class="table">
<thead></thead>
<tbody id="planning"></tbody>
</table>
</div>
<script>
var siteUrl = "http://localhost/planification/";
</script>
<script type="text/javascript" src="http://localhost/planification/assets/utilities/jquery-3.2.1.min.js"></script>
<script type="text/javascript" src="http://localhost/planification/assets/bootstrap/js/popper.min.js"></script>
<script type="text/javascript" src="http://localhost/planification/assets/bootstrap/js/bootstrap.min.js"></script>
<script type="text/javascript" src="http://localhost/planification/assets/utilities/TimeSheet/TimeSheet.js"></script>
<script type="text/javascript">
var hours = [];
for(var i = 0; i < 24; i++){
var hour = i.toString().padStart(2, 0);
var hourNext = i+1 === 24 ? '00' : (i+1).toString().padStart(2, 0);
hours.push({name: hour + 'h', title: hour + ':00 - ' + hourNext + ':00'});
}
var sheetData = [];
for(var i = 0; i < 7; i++){
for(var j = 0; j < 24; j++){
if(typeof sheetData[i] === "undefined"){
sheetData[i] = [1];
}
else {
sheetData[i].push(1);
}
}
}
var sheet = $('#planning').TimeSheet({
data: {
dimensions : [7,24],
colHead : hours,
rowHead : [{name:"Lundi"},{name:"Mardi"}, {name:"Mercredi"}, {name:"Jeudi"}, {name:"Vendredi"}, {name:"Samedi"}, {name:"Dimanche"}],
sheetHead : {name:"Planning"},
sheetData : sheetData
}
});
$('#enablePlanification, #disablePlanification').click(function(e){
e.preventDefault();
var mode = $(this).attr('id') === 'enablePlanification' ? true : false;
sheet.setMode(mode);
});
$('#savePlanification').click(function(e){
var states = sheet.getSheetStates();
var disabled = [];
for(var i = 0; i < 7; i++){
for(var j = 0; j < 24; j++){
if(states[i][j] === 0) disabled.push({day: i + 1, hour: j, });
}
}
console.log(disabled);
});
</script>
</body>
</html>
tbody.TimeSheet{
-webkit-user-select:none;
-moz-user-select : none;
user-select : none;
border : #cccccc 1px solid;
color : #757a7d;
font-size: 12px;
cursor:default;
border-collapse: collapse;
}
.TimeSheet td{
padding: .75rem 10px !important;
border : #e3e3e3 1px solid;
}
.TimeSheet-cell {
background-color: #dc3545;
}
.TimeSheet-cell-selected{
background-color: rgba(0, 123, 255, 0.65);
}
.TimeSheet-cell-selecting{
background-color: #dae0ef !important;
}
.TimeSheet-rowHead, .TimeSheet-colHead{
background-color: #f5f5f5;
}
.TimeSheet-rowHead{
padding:3px 10px;
border-left:none !important;
}
.TimeSheet-colHead{
padding: 6px 3px 6px 3px;
border-top:none !important;
}
.TimeSheet-head{
text-align: center;
border-top:none !important;
border-left:none !important;
}
.TimeSheet .rightMost{
border-right:none !important;
}
.TimeSheet .bottomMost{
border-bottom:none !important;
}
.TimeSheet-remarkHead{
text-align: center;
border-top:none !important;
border-right:none !important;
}
.TimeSheet-remark{
padding : 0 5px;
border-right:none !important;
}
.TimeSheet-remarkHead, .TimeSheet-remark{
max-width: 120px;
background-color: #fbf0ed;
white-space:nowrap;
overflow:hidden;
text-overflow:ellipsis;
border-left-color : #cccccc !important;
}
/**
* TimeSheet.js [v1.0]
* Li Bin
*/
(function ($) {
/*
* 表格中的单元格类
* */
var CSheetCell = function(opt){
/*
* opt : {
* state : 0 or 1,
* toggleCallback : function(curState){...}
* settingCallback : function(){...}
* }
*
* */
var cellPrivate = $.extend({
state : 0,
toggleCallback : false,
settingCallback : false
},opt);
/*反向切换单元格状态*/
this.toggle = function(){
cellPrivate.state = cellPrivate.state>0 ? cellPrivate.state-1 : cellPrivate.state+1;
if(cellPrivate.toggleCallback){
cellPrivate.toggleCallback(cellPrivate.state);
}
}
/*
* 设置单元格状态
* state : 0 or 1
* */
this.set = function(state){
cellPrivate.state = state==0 ? 0 : 1;
if(cellPrivate.settingCallback){
cellPrivate.settingCallback();
}
}
/*
* 获取单元格状态
* */
this.get = function(){
return cellPrivate.state;
}
}
/*
* 表格类
* */
var CSheet = function(opt){
this.mode = true;
/*
* opt : {
* dimensions : [8,9], [行数,列数]
* sheetData : [[0,1,1,0,0],[...],[...],...] sheet数据,二维数组,索引(a,b),a-行下标,b-列下标,每个cell只有0,1两态,与dimensions对应
* toggleCallback : function(){..}
* settingCallback : function(){..}
* }
*
* */
var sheetPrivate = $.extend({
dimensions : undefined,
sheetData : undefined,
toggleCallback : false,
settingCallback : false
},opt);
sheetPrivate.cells = [];
/*
* 初始化表格中的所有单元格
* */
sheetPrivate.initCells = function(){
var rowNum = sheetPrivate.dimensions[0];
var colNum = sheetPrivate.dimensions[1];
if(sheetPrivate.dimensions.length==2 && rowNum>0 && colNum>0){
for(var row= 0,curRow = []; row<rowNum; ++row){
curRow = [];
for(var col=0; col<colNum; ++col){
curRow.push(new CSheetCell({
state : sheetPrivate.sheetData ? (sheetPrivate.sheetData[row]?sheetPrivate.sheetData[row][col]:0) : 0
}));
}
sheetPrivate.cells.push(curRow);
}
}else{
throw new Error("CSheet : wrong dimensions");
}
}
/*
* 对给定的表各区域进行 toggle 或 set操作
* */
sheetPrivate.areaOperate = function(area,opt){
/*
* area : {
* startCell : [2,1],
* endCell : [7,6]
* }
* opt : {
* type:"set" or "toggle",
* state : 0 or 1 if type is set
* }
* */
var rowCount = sheetPrivate.cells.length;
var colCount = sheetPrivate.cells[0] ? sheetPrivate.cells[0].length : 0;
var operationArea = $.extend({
startCell : [0,0],
endCell : [rowCount-1,colCount-1]
},area);
var isSheetEmpty = rowCount==0 || colCount==0;
var isAreaValid = operationArea.startCell[0]>=0 && operationArea.endCell[0]<=rowCount-1 &&
operationArea.startCell[1]>=0 && operationArea.endCell[1]<=colCount-1 && //operationArea不能超越sheet的边界
operationArea.startCell[0]<=operationArea.endCell[0] && operationArea.startCell[1]<=operationArea.endCell[1]; //startCell必须居于endCell的左上方,或与之重合
if(!isAreaValid){
throw new Error("CSheet : operation area is invalid");
}else if(!isSheetEmpty){
for(var row=operationArea.startCell[0]; row<=operationArea.endCell[0]; ++row){
for(var col=operationArea.startCell[1]; col<=operationArea.endCell[1]; ++col){
if(opt.type=="toggle"){
sheetPrivate.cells[row][col].toggle();
}else if(opt.type=="set"){
sheetPrivate.cells[row][col].set(opt.state);
}
}
}
}
}
sheetPrivate.initCells();
/*
* 对表格的指定区域进行状态反向切换
* toggleArea : {
* startCell : [2,1],
* endCell : [7,6]
* }
*
* */
this.toggle = function(toggleArea){
sheetPrivate.areaOperate(toggleArea,{type:"toggle"});
if(sheetPrivate.toggleCallback){
sheetPrivate.toggleCallback();
}
}
/*
* 对表格的指定区域进行状态设置
* state : 0 or 1
* settingArea : {
* startCell : [2,1],
* endCell : [7,6]
* }
* */
this.set = function(state,settingArea){
sheetPrivate.areaOperate(settingArea,{type:"set",state:state});
if(sheetPrivate.settingCallback){
sheetPrivate.settingCallback();
}
}
/*
* 获取指定单元格的状态
* cellIndex : [2,3]
* @return : 0 or 1
* */
this.getCellState = function(cellIndex){
return sheetPrivate.cells[cellIndex[0]][cellIndex[1]].get();
}
/*
* 获取指定行所有单元格的状态
* row : 2
* @return : [1,0,...,1]
* */
this.getRowStates = function(row){
var rowStates = [];
for(var col=0; col<sheetPrivate.dimensions[1]; ++col){
rowStates.push(sheetPrivate.cells[row][col].get());
}
return rowStates;
}
/*
* 获取所有单元格的状态
* @return : [[1,0,...,1],[1,0,...,1],...,[1,0,...,1]]
* */
this.getSheetStates = function(){
var sheetStates = [];
for(var row= 0,rowStates = []; row<sheetPrivate.dimensions[0]; ++row){
rowStates = [];
for(var col=0; col<sheetPrivate.dimensions[1]; ++col){
rowStates.push(sheetPrivate.cells[row][col].get());
}
sheetStates.push(rowStates);
}
return sheetStates;
}
}
$.fn.TimeSheet = function(opt){
/*
* 说明 :
*
* TimeSheet 应该被绑定在 TBODY 元素上,其子元素有如下默认class:
*
* 表头 ---- class: .TimeSheet-head
* 列表头 ---- class: .TimeSheet-colHead
* 行表头 ---- class: .TimeSheet-rowHead
* 单元格 ---- class: .TimeSheet-cell
*
* 用户可在传入的sheetClass下将元素的默认样式覆盖
* sheetClass将被赋予 TBODY 元素
*
*
* opt :
* {
* data : {
* dimensions : [7,8],
* colHead : [{name:"name1",title:"",style:"width,background,color,font"},{name:"name2",title:"",style:"width,background,color,font"},...]
* rowHead : [{name:"name1",title:"",style:"height,background,color,font"},{name:"name2",title:"",style:"height,background,color,font"},...]
* sheetHead : {name:"headName",style:"width,height,background,color,font"}
* sheetData : [[0,1,1,0,0],[...],[...],...] sheet数据,二维数组,行主序,索引(a,b),a-行下标,b-列下标,每个cell只有0,1两态,与dimensions对应
* },
*
* sheetClass : "",
* start : function(ev){...}
<<<<<<< HEAD
* end : function(ev, selectedArea){...}
=======
* end : function(ev,selectedArea){...}
>>>>>>> b2f70472ecb97bbf9ecdc7f205ea10c8e5e7b929
* remarks : false
* }
*
*/
var thisSheet = $(this);
if(!thisSheet.is("TBODY")){
throw new Error("TimeSheet needs to be bound on a TBODY element");
}
var sheetOption = $.extend({
data: {},
sheetClass: "",
start: false,
end : false,
remarks : null
}, opt);
if(!sheetOption.data.dimensions || sheetOption.data.dimensions.length!==2 || sheetOption.data.dimensions[0]<0 || sheetOption.data.dimensions[1]<0){
throw new Error("TimeSheet : wrong dimensions");
}
var operationArea = {
startCell : undefined,
endCell : undefined
};
var sheetModel = new CSheet({
dimensions : sheetOption.data.dimensions,
sheetData : sheetOption.data.sheetData ? sheetOption.data.sheetData : undefined
});
/*
* 表格初始化
* */
var initSheet = function(){
thisSheet.html("");
thisSheet.addClass("TimeSheet");
if(sheetOption.sheetClass){
thisSheet.addClass(sheetOption.sheetClass);
}
initColHeads();
initRows();
repaintSheet();
};
/*
* 初始化每一列的顶部表头
* */
var initColHeads = function(){
var colHeadHtml = '<tr>';
for(var i=0,curColHead=''; i<=sheetOption.data.dimensions[1]; ++i){
if(i===0){
curColHead = '<td class="TimeSheet-head" style="'+(sheetOption.data.sheetHead.style?sheetOption.data.sheetHead.style:'')+'">'+sheetOption.data.sheetHead.name+'</td>';
}else{
curColHead = '<td title="'+(sheetOption.data.colHead[i-1].title ? sheetOption.data.colHead[i-1].title:"")+'" data-col="'+(i-1)+'" class="TimeSheet-colHead '+(i===sheetOption.data.dimensions[1]?'rightMost':'')+'" style="'+(sheetOption.data.colHead[i-1].style ? sheetOption.data.colHead[i-1].style : '')+'">'+sheetOption.data.colHead[i-1].name+'</td>';
}
colHeadHtml += curColHead;
}
if(sheetOption.remarks){
colHeadHtml += '<td class="TimeSheet-remarkHead">'+sheetOption.remarks.title+'</td>';
}
colHeadHtml += '</tr>';
thisSheet.append(colHeadHtml);
};
/*
* 初始化每一行
* */
var initRows = function(){
for(var row=0,curRowHtml=''; row<sheetOption.data.dimensions[0]; ++row){
curRowHtml='<tr class="TimeSheet-row">'
for(var col= 0, curCell=''; col<=sheetOption.data.dimensions[1]; ++col){
if(col===0){
curCell = '<td title="'+(sheetOption.data.rowHead[row].title ? sheetOption.data.rowHead[row].title:"")+'"class="TimeSheet-rowHead '+(row===sheetOption.data.dimensions[0]-1?'bottomMost ':' ')+'" style="'+(sheetOption.data.rowHead[row].style ? sheetOption.data.rowHead[row].style : '')+'">'+sheetOption.data.rowHead[row].name+'</td>';
}else{
curCell = '<td class="TimeSheet-cell '+(row===sheetOption.data.dimensions[0]-1?'bottomMost ':' ')+(col===sheetOption.data.dimensions[1]?'rightMost':'')+'" data-row="'+row+'" data-col="'+(col-1)+'"></td>';
}
curRowHtml += curCell;
}
if(sheetOption.remarks){
curRowHtml += '<td class="TimeSheet-remark '+(row===sheetOption.data.dimensions[0]-1?'bottomMost ':' ')+'">'+sheetOption.remarks.default+'</td>';
}
curRowHtml += '</tr>';
thisSheet.append(curRowHtml);
}
};
/*
* 比较两个单元格谁更靠近左上角
* cell1:[2,3]
* cell2:[4,5]
* @return:{
topLeft : cell1,
bottomRight : cell2
}
* */
var cellCompare = function(cell1,cell2){ //check which cell is more top-left
var sum1 = cell1[0] + cell1[1];
var sum2 = cell2[0] + cell2[1];
if((cell1[0]-cell2[0])*(cell1[1]-cell2[1])<0){
return {
topLeft : cell1[0]<cell2[0] ? [cell1[0],cell2[1]] : [cell2[0],cell1[1]],
bottomRight : cell1[0]<cell2[0] ? [cell2[0],cell1[1]] : [cell1[0],cell2[1]]
};
}
return {
topLeft : sum1<=sum2 ? cell1 : cell2,
bottomRight : sum1>sum2 ? cell1 : cell2
};
};
/*
* 刷新表格
* */
var repaintSheet = function(){
var sheetStates = sheetModel.getSheetStates();
thisSheet.find(".TimeSheet-row").each(function(row,rowDom){
var curRow = $(rowDom);
curRow.find(".TimeSheet-cell").each(function(col,cellDom){
var curCell = $(cellDom);
if(sheetStates[row][col]===1){
curCell.addClass("TimeSheet-cell-selected");
}else if(sheetStates[row][col]===0){
curCell.removeClass("TimeSheet-cell-selected");
}
});
});
};
/*
* 移除所有单元格的 TimeSheet-cell-selecting 类
* */
var removeSelecting = function(){
thisSheet.find(".TimeSheet-cell-selecting").removeClass("TimeSheet-cell-selecting");
};
/*
* 清空备注栏
* */
var cleanRemark = function(){
thisSheet.find(".TimeSheet-remark").each(function(idx,ele){
var curDom = $(ele);
curDom.prop("title","");
curDom.html(sheetOption.remarks.default);
});
};
/*
* 鼠标开始做选择操作
* startCel : [1,4]
* */
var startSelecting = function(ev,startCel){
operationArea.startCell = startCel;
if(sheetOption.start){
sheetOption.start(ev);
}
};
/*
* 鼠标在选择操作过程中
* topLeftCell : [1,4], 鼠标选择区域的左上角
* bottomRightCell : [3,9] 鼠标选择区域的右下角
* */
var duringSelecting = function(ev,topLeftCell,bottomRightCell){
var curDom = $(ev.currentTarget);
if(isSelecting && curDom.hasClass("TimeSheet-cell") || isColSelecting && curDom.hasClass("TimeSheet-colHead")){
removeSelecting();
for(var row=topLeftCell[0]; row<=bottomRightCell[0]; ++row){
for(var col=topLeftCell[1]; col<=bottomRightCell[1]; ++col){
$($(thisSheet.find(".TimeSheet-row")[row]).find(".TimeSheet-cell")[col]).addClass("TimeSheet-cell-selecting");
}
}
}
};
/*
* 选择操作完成后
* targetArea : {
* topLeft : [1,2],
* bottomRight: [3,8]
* }
* */
var afterSelecting = function(ev,targetArea){
var curDom = $(ev.currentTarget);
var key = $(ev.which);
var targetState = undefined;
if(sheetModel.mode === true) targetState = 1; // autorisé
else targetState = 0; // bloqué
if(isSelecting && curDom.hasClass("TimeSheet-cell") || isColSelecting && curDom.hasClass("TimeSheet-colHead")){
sheetModel.set(targetState,{
startCell : targetArea.topLeft,
endCell : targetArea.bottomRight
});
removeSelecting();
repaintSheet();
if(sheetOption.end){
sheetOption.end(ev,targetArea);
}
}else{
removeSelecting();
}
isSelecting = false;
isColSelecting = false;
operationArea = {
startCell : undefined,
endCell : undefined
}
};
var isSelecting = false; /*鼠标在表格区域做选择*/
var isColSelecting = false; /*鼠标在列表头区域做选择*/
var eventBinding = function(){
/*防止重复绑定*/
thisSheet.undelegate(".umsSheetEvent");
/*表格开始选择*/
thisSheet.delegate(".TimeSheet-cell","mousedown.umsSheetEvent",function(ev){
var curCell = $(ev.currentTarget);
var startCell = [curCell.data("row"),curCell.data("col")];
isSelecting = true;
startSelecting(ev,startCell);
});
/*表格选择完成*/
thisSheet.delegate(".TimeSheet-cell","mouseup.umsSheetEvent",function(ev){
if(!operationArea.startCell){
return;
}
var curCell = $(ev.currentTarget);
var endCell = [curCell.data("row"),curCell.data("col")];
var correctedCells = cellCompare(operationArea.startCell,endCell);
afterSelecting(ev,correctedCells);
});
/*表格正在选择*/
thisSheet.delegate(".TimeSheet-cell","mouseover.umsSheetEvent",function(ev){
if(!isSelecting){
return;
}
var curCell = $(ev.currentTarget);
var curCellIndex = [curCell.data("row"),curCell.data("col")];
var correctedCells = cellCompare(operationArea.startCell,curCellIndex);
var topLeftCell = correctedCells.topLeft;
var bottomRightCell = correctedCells.bottomRight;
duringSelecting(ev,topLeftCell,bottomRightCell);
});
/*列表头开始选择*/
thisSheet.delegate(".TimeSheet-colHead","mousedown.umsSheetEvent",function(ev){
var curColHead = $(ev.currentTarget);
var startCell = [0,curColHead.data("col")];
isColSelecting = true;
startSelecting(ev,startCell);
});
/*列表头选择完成*/
thisSheet.delegate(".TimeSheet-colHead","mouseup.umsSheetEvent",function(ev){
if(!operationArea.startCell){
return;
}
var curColHead = $(ev.currentTarget);
var endCell = [sheetOption.data.dimensions[0]-1,curColHead.data("col")];
var correctedCells = cellCompare(operationArea.startCell,endCell);
afterSelecting(ev,correctedCells);
});
/*列表头正在选择*/
thisSheet.delegate(".TimeSheet-colHead","mouseover.umsSheetEvent",function(ev){
if(!isColSelecting){
return;
}
var curColHead = $(ev.currentTarget);
var curCellIndex = [sheetOption.data.dimensions[0]-1,curColHead.data("col")];
var correctedCells = cellCompare(operationArea.startCell,curCellIndex);
var topLeftCell = correctedCells.topLeft;
var bottomRightCell = correctedCells.bottomRight;
duringSelecting(ev,topLeftCell,bottomRightCell);
});
/*表格禁止鼠标右键菜单*/
thisSheet.delegate("td","contextmenu.umsSheetEvent",function(ev){
return false;
});
};
initSheet();
eventBinding();
var publicAPI = {
/*
* 获取单元格状态
* cellIndex :[1,2]
* @return : 0 or 1
* */
getCellState : function(cellIndex){
return sheetModel.getCellState(cellIndex);
},
/*
* 获取某行所有单元格状态
* row :2
* @return : [1,0,0,...,0,1]
* */
getRowStates : function(row){
return sheetModel.getRowStates(row);
},
/*
* 获取表格所有单元格状态
* @return : [[1,0,0,...,0,1],[1,0,0,...,0,1],...,[1,0,0,...,0,1]]
* */
getSheetStates : function(){
return sheetModel.getSheetStates();
},
/*
* 设置某行的说明文字
* row : 2,
* html : 说明
* */
setRemark : function(row,html){
if($.trim(html)!==''){
$(thisSheet.find(".TimeSheet-row")[row]).find(".TimeSheet-remark").prop("title",html).html(html);
}
},
/*
* 重置表格
* */
clean : function(){
sheetModel.set(0,{});
repaintSheet();
cleanRemark();
},
/*
* 获取 default remark
* */
getDefaultRemark : function(){
return sheetOption.remarks.default;
},
/*
* 使表格不可操作
* */
disable : function(){
thisSheet.undelegate(".umsSheetEvent");
},
/*
* 使表格可操作
* */
enable : function(){
eventBinding();
},
/*
* 判断表格是否所有单元格状态都是1
* @return : true or false
* */
isFull : function(){
for(var row=0; row<sheetOption.data.dimensions[0]; ++row){
for(var col=0; col<sheetOption.data.dimensions[1]; ++col){
if(sheetModel.getCellState([row,col])===0){
return false;
}
}
}
return true;
},
/*
* Mode de sélection (ajouter ou enlève)
* @return : true or false
* */
setMode: function(mode){
sheetModel.mode = mode;
return sheetModel.mode;
},
};
return publicAPI;
}
})(jQuery);
@gregory-yet
Copy link
Author

Tableau avec jours et heures, drag & select pour autoriser / bloquer des plages horaires en cliquant sur le bouton associé, puis un bouton "enregistrer" pour récupérer les valeurs bloquées (on estime que les autres plages horaires non récupérées sont automatiquement autorisées).

English short version
Timetable with allow and disallow buttons, you can drag and select time slot with buttons and save values.

Preview
timetable time slot planification contrôle parental

Credits : https://github.com/lbbc1117/TimeSheet

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment