// TODO: https://codereview.stackexchange.com/questions/240011/js-schedule-conflict-detection-algorithm-logic-length
// List with handle
Sortable.create(dashboard, {
handle: '.fa-bars',
animation: 150
});
// To Instantiate & Control the Modal
var modal = document.getElementById("modal");
var addSchedule = document.getElementById('add');
var scheduleCount = 0;
var trackedSchedule;
addSchedule.addEventListener('click', function (event) {
scheduleCount += 1;
// console.log(event.target.id); // Anomoly Here
trackedSchedule = `schedule-${scheduleCount}`;
console.log(trackedSchedule);
// Change modal to load with default values (prefill name with scheduleCount)
// Change Text to Read "Create Schedule" instead of "Edit Schedule"
modal.style.display = "block";
});
var span = document.getElementsByClassName("close")[0];
span.onclick = function() {
modal.style.display = "none";
scheduleCount -= 1;
}
window.onclick = function(event) {
if (event.target == modal) {
modal.style.display = "none";
scheduleCount -= 1;
}
}
function getDate() {
var today = new Date();
var dd = String(today.getDate()).padStart(2, '0');
var mm = String(today.getMonth() + 1).padStart(2, '0');
var yyyy = today.getFullYear();
today = `${yyyy}-${mm}-${dd}`;
return today;
}
var savedSchedules = {};
var data = [];
var layout = {
showlegend: false,
xaxis: {range: ['2020-01-01 00:00:00', '2020-01-01 23:59:59'],
showgrid: false,
zeroline: false,
showline: true,
tickformat: '%H:%M:%S'
},
yaxis: {rangemode: 'tozero',
range: [-0.75, 6.5],
showline: true,
zeroline: false,
tickvals: [0, 1, 2, 3, 4, 5, 6],
ticktext: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
}
};
// To Save the Schedule Data from the Modal
var scheduleName = document.getElementById('schedule-name');
var warningMsg = document.getElementById('warning');
var saveButton = document.getElementById('submit-schedule');
var overwriteButton = document.getElementById('overwrite-schedule');
var sunday = document.getElementById('Sunday');
var monday = document.getElementById('Monday');
var tuesday = document.getElementById('Tuesday');
var wednesday = document.getElementById('Wednesday');
var thursday = document.getElementById('Thursday');
var friday = document.getElementById('Friday');
var saturday = document.getElementById('Saturday');
var daysOfWeek = {'Sunday': sunday,
'Monday': monday,
'Tuesday': tuesday,
'Wednesday': wednesday,
'Thursday': thursday,
'Friday': friday,
'Saturday': saturday}
var startTime;
var endTime;
document.getElementById('startTime').addEventListener("input", function (event) {
startTime = event.target.value;
});
document.getElementById('endTime').addEventListener("input", function (event) {
endTime = event.target.value;
});
function editSchedule(event) {
console.log(event.target.id);
// Init Modal from saved dict
schedule = savedSchedules[event.target.id];
Object.entries(schedule).forEach(function([key, value]) {
if (value) {
if (key != 'Name') {
var day = document.getElementById(key);
day.checked = true;
}
else {
scheduleName.value = value;
}
var startTime_ = document.getElementById('startTime');
var endTime_ = document.getElementById('endTime');
startTime_.value = value.StartTime;
endTime_.value = value.EndTime;
}
});
modal.style.display = 'block';
};
// Need to validate logical comparators for provided time format?
function checkForConflict(c1s, c1e, c2s, c2e) { // case 1 start time, case 1 end time, case 2 start time, case 2 end time
// if (c1 intersects c2) or (c2 intersects c1) or (c2 contained in c1) or (c1 contained in c2) or (anything else?)
if ( (c1s >= c2s && c1e >= c2s) || (c1s <= c2s && c1e <= c2e) || (c1s <= c2s && c1e >= c2e) || (c1s >= c2s && c1e <= c2e) ) {
return true;
} else {return false};
}
saveButton.onclick = function() {
// Disable save if not all reqd fields filled -> https://stackoverflow.com/questions/39880389/disable-button-until-fields-are-full-pure-js
// while (!scheduleName || !startTime || !endTime || !(sunday.checked&&monday.checked&&tuesday.checked&&wednesday.checked&&thursday.checked&&friday.checked&&saturday.checked)) {
// saveButton.disabled = true;
// }
// Check that fields are filled
if (!scheduleName || !startTime || !endTime || !sunday.checked&&!monday.checked&&!tuesday.checked&&!wednesday.checked&&!thursday.checked&&!friday.checked&&!saturday.checked) {
warningMsg.textContent = "All required fields must contain data";
}
// Try to Save the Schedule
else {
// Check for Schedule Conflicts
var conflictData;
if (savedSchedules && Object.keys(savedSchedules).length >= 1) {
console.log("At least one schedule detected");
// Loop through saved schedules (key = 'schedule-x')
Object.entries(savedSchedules).forEach(function([key, value]) {
schedule_ = savedSchedules[key];
// Loop through the schedule (key_ = 'Name', 'Sunday', ...)
Object.entries(schedule_).forEach(function([key_, value_]) {
// Only check if weekday has a time value and weekdays that are selected
if (key_ != 'Name' && value_ && daysOfWeek[key_].checked) {
// if there is conflict -> store to variable
var conflict = checkForConflict(value_.StartTime, value_.EndTime, startTime, endTime);
if (conflict) {
console.log('Conflict Detected');
conflictData = [key_, `Start: ${value_.StartTime}/${startTime}`, `End: ${value_.EndTime}/${endTime}`];
}
}
});
});
}
// Alert User of Conflict
if (conflictData) {
console.log(conflictData);
warningMsg.textContent = `There is a scheduling conflict ${conflictData}`;
overwriteButton.style.display = 'block';
// Handle Conflict Resolution -> Overwrite Schedule, etc
}
// Save the Schedule
else {
var dict = {
Name: scheduleName.value,
Sunday: sunday.checked && startTime && endTime ? {StartTime: startTime, EndTime: endTime} : null,
Monday: monday.checked && startTime && endTime ? {StartTime: startTime, EndTime: endTime} : null,
Tuesday: tuesday.checked && startTime && endTime ? {StartTime: startTime, EndTime: endTime} : null,
Wednesday: wednesday.checked && startTime && endTime ? {StartTime: startTime, EndTime: endTime} : null,
Thursday: thursday.checked && startTime && endTime ? {StartTime: startTime, EndTime: endTime} : null,
Friday: friday.checked && startTime && endTime ? {StartTime: startTime, EndTime: endTime} : null,
Saturday: saturday.checked && startTime && endTime ? {StartTime: startTime, EndTime: endTime} : null,
}
savedSchedules[trackedSchedule] = dict;
console.log(savedSchedules);
// Add to the graph
var randomColor = Math.floor(Math.random()*16777215).toString(16); // Make color indicative of temperature
Object.entries(dict).forEach(function([key, value]) {
if (key != 'Name' && value) {
data.push({
x: [`${getDate()} ${value.StartTime}`, `${getDate()} ${value.EndTime}`],
y: [key, key],
hovertext: dict.Name,
type: 'scatter',
mode: 'markers',
marker: {
size: 30,
color: [randomColor, randomColor],
}
});
data.push({
x: [`${getDate()} ${value.StartTime}`, `${getDate()} ${value.EndTime}`],
y: [key, key],
hovertext: scheduleName.value,
type: 'scatter',
mode: "lines",
line: {
width: 30,
color: randomColor,
},
});
};
});
Plotly.newPlot('graph', data, layout);
// Create the Dashboard Entry
var newLine = document.createElement('div');
newLine.classList.add('list-group-item');
var newButton = document.createElement('div');
newButton.classList.add('add-button');
newButton.id = trackedSchedule;
newButton.addEventListener('click', editSchedule);
var newTemp = document.createElement('a');
var newName = document.createElement('a');
newName.textContent = scheduleName.value;
newName.classList.add('name-font');
var newWeekSet = document.createElement('div');
newWeekSet.classList.add('right-align');
Object.entries(dict).forEach(function([key, value]) {
if (key != 'Name') {
var newDay = document.createElement('label')
// newDay.classList.add('day-font');
newDay.style.display = 'inline-block';
newDay.style.margin = '5px';
newDay.style.fontStyle = 'italic';
var newDayIcon;
newDay.textContent = key.charAt(0);
if (value) {
newDayIcon = document.createElement('i');
newDayIcon.classList.add('far', 'fa-check-circle', 'fa-xs');
// Why doesn't class work for this?
newDayIcon.style.display = 'block';
newDayIcon.style.color = 'Blue';
}
else {
newDayIcon = document.createElement('i');
newDayIcon.classList.add('far', 'fa-times-circle', 'fa-xs');
// Why doesn't class work for this?
newDayIcon.style.display = 'block';
newDayIcon.style.color = 'Orange';
}
newDay.append(newDayIcon);
newWeekSet.appendChild(newDay);
};
});
var newMoveIcon = document.createElement('i');
newMoveIcon.classList.add('fas', 'fa-bars');
newMoveIcon.style.display = 'inline-block';
newMoveIcon.style.paddingTop = '10px';
newMoveIcon.style.paddingLeft = '20px'
newLine.appendChild(newButton);
newLine.appendChild(newName);
newLine.appendChild(newWeekSet);
newWeekSet.appendChild(newMoveIcon);
dashboard.appendChild(newLine);
modal.style.display = "none";
}
}
}@import url(https://fonts.googleapis.com/css?family=Open+Sans:700,300);
body {
background: white;
font-family: 'Open Sans', Helvetica, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.add-button {
display: inline-block;
height: 50px;
width: 50px;
color: orange;
padding-left: 18px;
padding-top: 10px;
background: white;
border: 2px solid orange;
border-radius: 25px;
cursor: pointer;
}
.name-font {
font-size: 2em;
padding-left: 35px;
}
.submit-button {
display: inline-block;
border: 1px solid orange;
border-radius: 5px;
height: 40px;
background-color: white;
cursor: pointer;
}
.submit-button:hover {
background-color: #ffd796
}
.clickable {
cursor: pointer;
}
.right-align {
float: right;
}
/* The Modal (background) */
.modal {
position: fixed;
display: none;
padding-left: 75%;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(255,255,255,0.5);
}
/* Modal Content */
.modal-content {
padding: 20px;
border: 2px solid orange;
height: 100%;
overflow: auto;
}
/* The Close Button */
.close {
display: inline;
color: gray;
float: right;
}
.close:hover,
.close:focus {
color: #000;
text-decoration: none;
cursor: pointer;
}
/* Checkboxes */
.form-group {
display: block;
margin-bottom: 5px;
}
.form-group input {
padding: 0;
height: initial;
width: initial;
margin-bottom: 0;
display: none;
cursor: pointer;
}
.form-group label {
position: relative;
cursor: pointer;
}
.form-group label:before {
content:'';
-webkit-appearance: none;
background-color: transparent;
border: 2px solid orange;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05), inset 0px -15px 10px -12px rgba(0, 0, 0, 0.05);
padding: 10px;
display: inline-block;
position: relative;
vertical-align: middle;
cursor: pointer;
margin-right: 5px;
}
.form-group input:checked + label:after {
content: '';
display: block;
position: absolute;
top: 4px;
left: 9px;
width: 6px;
height: 14px;
border: solid orange;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
/* Clocklet */
.labels {
display: block;
}
.clock {
display: block;
z-index: 10000;
}
/* minute - dial, hand, selected tick, hovered tick */
.clocklet-color-example .clocklet-plate {
box-shadow: 1px 2px 4px 0 rgba(0, 0, 0, 0.2);
border: 1px solid gray;
}
.clocklet-color-example[data-clocklet-placement="bottom"] {
background-color: transparent;
border: none;
box-shadow: none;
}
.clocklet-color-example .clocklet-dial--minute { background-color: orange; color: white;}
.clocklet-color-example .clocklet-hand--minute { background-color: white; border: 1px solid gray; width: 3px;}
.clocklet-color-example .clocklet-tick--minute.clocklet-tick--selected { background-color: white; color: orange;}
.clocklet-color-example.clocklet--hoverable:not(.clocklet--dragging) .clocklet-tick--minute:hover { background-color: white; color: orange;}
/* hour - dial, hand, selected tick, hovered tick */
.clocklet-color-example .clocklet-dial--hour { background-color: white; }
.clocklet-color-example .clocklet-hand--hour { background-color: white; border: 1px solid gray; width: 3px;}
.clocklet-color-example .clocklet-tick--hour.clocklet-tick--selected { background-color: orange; }
.clocklet-color-example.clocklet--hoverable:not(.clocklet--dragging) .clocklet-tick--hour:hover { background-color: orange; }
/* hand origin */
.clocklet-color-example .clocklet-hand-origin { background-color: gray; }
/* ampm */
.clocklet-color-example .clocklet-ampm::before { background-color: orange; }
.clocklet-color-example .clocklet-ampm:hover::before { background-color: white; color: orange; border: 1px solid orange;}
.clocklet-color-example .clocklet-ampm[data-clocklet-ampm="pm"]::before { background-color: orange; }
.clocklet-color-example .clocklet-ampm[data-clocklet-ampm="pm"]:hover::before { background-color: white; } Add Schedule
Edit Schedule
Start Time
End Time
Sunday
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
Create Schedule
Overwrite Schedule第一篇文章-我希望我理解社会的目的/意图。我正在尝试创建一个使用Js的暖通空调管理WebUI的温度调度器(尽管您不会在代码中找到任何关于设置温度的内容)。代码评审的目的与调度冲突算法&代码相关联。也就是说,在某一周或某一时间(或房间)的某一天,绝对不应出现重复的温度设定。
我希望看看是否有任何改进我的时间表冲突检测算法的建议--我并不完全满意它,因为在我看来,逻辑很难理解。找到代码(很多遗漏,所以如果有什么不清楚的地方让我知道)和CodePen链接(没有遗漏)
var scheduleName = document.getElementById('schedule-name');
var warningMsg = document.getElementById('warning');
var saveButton = document.getElementById('submit-schedule');
var sunday = document.getElementById('Sunday'); // checkbox
var monday = document.getElementById('Monday'); // checkbox
var tuesday = document.getElementById('Tuesday'); // checkbox
var wednesday = document.getElementById('Wednesday'); // checkbox
var thursday = document.getElementById('Thursday'); // checkbox
var friday = document.getElementById('Friday'); // checkbox
var saturday = document.getElementById('Saturday'); // checkbox
var daysOfWeek = {'Sunday': sunday,
'Monday': monday,
'Tuesday': tuesday,
'Wednesday': wednesday,
'Thursday': thursday,
'Friday': friday,
'Saturday': saturday}
function checkForConflict(c1s, c1e, c2s, c2e) { // case 1 start time, case 1 end time, case 2 start time, case 2 end time
// if (c1 intersects c2) or (c2 intersects c1) or (c2 contained in c1) or (c1 contained in c2) or (anything else?)
if ( (c1s >= c2s && c1e >= c2s) || (c1s <= c2s && c1e <= c2e) || (c1s <= c2s && c1e >= c2e) || (c1s >= c2s && c1e <= c2e) ) {
return true;
} else {return false};
}
saveButton.onclick = function() {
// Check that fields are filled
if (!scheduleName || !startTime || !endTime || !sunday.checked&&!monday.checked&&!tuesday.checked&&!wednesday.checked&&!thursday.checked&&!friday.checked&&!saturday.checked) {
warningMsg.textContent = "All required fields must contain data";
}
// Try to Save the Schedule
else {
// Check for Schedule Conflicts
var conflictData;
if (savedSchedules && Object.keys(savedSchedules).length >= 1) {
console.log("At least one schedule detected");
// Loop through saved schedules (key = 'schedule-x')
Object.entries(savedSchedules).forEach(function([key, value]) {
schedule_ = savedSchedules[key];
// Loop through the schedule (key_ = 'Name', 'Sunday', ...)
Object.entries(schedule_).forEach(function([key_, value_]) {
// Only check if weekday has a time value and weekdays that are selected
if (key_ != 'Name' && value_ && daysOfWeek[key_].checked) {
// if there is conflict -> store to variable
var conflict = checkForConflict(value_.StartTime, value_.EndTime, startTime, endTime);
if (conflict) {
console.log('Conflict Detected');
conflictData = [key_, `Start: ${value_.StartTime}/${startTime}`, `End: ${value_.EndTime}/${endTime}`];
}
}
});
});
}
// Alert User of Conflict
if (conflictData) {
console.log(conflictData);
warningMsg.textContent = `There is a scheduling conflict ${conflictData}: - Cannot proceed...`;
// Handle Conflict Resolution -> Overwrite Schedule, etc
}
// Save the Schedule
else {...}savedSchedules的格式如下所示:
{
schedule-1: {
Name: ,
Sunday: null,
Monday: null,
Tuesday: null,
Wednesday: null,
Thursday: null,
Friday: null,
Saturday: {StartTime: 12:55 A.M., EndTime: 1:35 P.M.}
},
schedule-2: {...},
...
}对我来说,// Check for Schedule Conflicts块在逻辑和长度上都很简洁。我希望第二组更有经验的眼睛可以确认这是相当合理的逻辑/结构,或者提供一个更好的选择。
链接到CodePen
发布于 2020-04-06 00:10:49
您对复选框的检索有点冗长。考虑使用单个选择器来获得所有的选择器,而不是单独选择每个选择器。
const daysOfWeek = {};
for (const input of document.querySelectorAll('.form-group > input[type="checkbox"]') {
daysOfWeek[input.id] = input;
}然后,与其检查每个单独的变量以查看是否检查了它,不如迭代该对象以查看是否检查了任何值。
为了避免缩进,请考虑在遇到错误时尽早使用return,而不是在没有错误时使用非常大的else块。
您的savedSchedules是一个从未重新分配过的对象:
var savedSchedules = {};因此,它将永远是真实的- if (savedSchedules &&检查是多余的,因为它永远是真实的。
也不需要事先检查对象有多少个键,只需遍历所有这些键就行了。如果没有,那么就不会发现任何冲突。这样Object.keys(savedSchedules).length >= 1就可以被删除。
Object.entries's回调接受键和值的条目数组参数。由于您已经将该值作为变量,因此不需要通过遍历[key]来再次选择它:
Object.entries(savedSchedules).forEach(function([key, value]) {
schedule_ = savedSchedules[key];简化为
Object.entries(savedSchedules).forEach(function([key, schedule_]) {但是,由于您实际上并没有使用计划名称( key),所以最好删除它,而使用Object.values:
Object.values(savedSchedules).forEach(function(schedule_) {但是,既然你试图找出是否有冲突,当发现问题时,短路就更合适了。考虑使用for循环代替。当发现问题时,告诉用户它和return。
变量名后面的下划线令人困惑--它们与我所知道的约定不匹配。看起来,它们是为了区分单个计划的对象和当天的对象值以及当天的对象值。我认为应该在变量名中显式地注意到这一点,例如schedule、day和dayObj。
而且,由于您使用的是Object.entries,您的环境支持ES6 -在ES6中,您应该在可能的情况下总用const声明变量,而不是var (只有在需要重新分配时才声明let )。
当您深入到验证阶段并与value_.StartTime变量名称进行比较时,D24变量名(来自输入)有点不清楚。也许可以调用输入变量inputStartTime (endTime也是这样)来减少可能的混淆?
冲突检查器是一个不错的代码块,可能应该在它自己的函数中使用。这使您可以从单击监听器中看到完整的单击过程的概要,而不必滚动大量代码才能获得大致的概念。
通常应该避免分配给onclick。如果只执行一次,它就可以工作,但是如果任何其他代码都遵循相同的错误做法,并且试图做同样的事情,您以前的侦听器就会被覆盖。最好始终使用addEventListener。
全文:
const daysOfWeek = {};
for (const input of document.querySelectorAll('.form-group > input[type="checkbox"]')) {
daysOfWeek[input.id] = input;
}
const getConflicts = () => {
for (const schedule of Object.values(savedSchedules)) {
// Loop through the schedule (key_ = 'Name', 'Sunday', ...)
for (const [day, dayObj] of Object.entries(schedule)) {
// Only check if weekday has a time value and weekdays that are selected
if (day === 'Name' || !dayObj || !daysOfWeek[day].checked) {
continue;
}
// if there is conflict, return it
const conflict = checkForConflict(dayObj.StartTime, dayObj.EndTime, inputStartTime, inputEndTime);
if (conflict) {
console.log('Conflict Detected');
const conflictData = [day, `Start: ${dayObj.StartTime}/${inputStartTime}`, `End: ${dayObj.EndTime}/${inputEndTime}`];
return `There is a scheduling conflict ${conflictData}: - Cannot proceed...`;
}
}
}
};
saveButton.addEventListener('click', () => {
// Check that fields are filled
if (!scheduleName || !inputStartTime || !inputEndTime || Object.values(daysOfWeek).every(input => !input.checked)) {
warningMsg.textContent = 'All required fields must contain data';
return;
}
// Check for Schedule Conflicts
const conflictsMessage = getConflicts();
if (conflictsMessage) {
// Handle Conflict Resolution -> Overwrite Schedule, etc
// Probably CALL A FUNCTION HERE, don't write it all inside this click listener
return;
}
// Save the Schedule
// ...
});schedule对象的生成没有显示,但是最好不必执行if (day === 'Name'检查,而不是将日程名称与日期键组合在一起,考虑将天放在一个完全独立的属性中,例如:
{
scheduleName: 'schedule-1',
days: {
Sunday: ...
Monday: ...https://codereview.stackexchange.com/questions/240011
复制相似问题