Skip to content

Instantly share code, notes, and snippets.

@mmaltsev
Last active March 2, 2018 14:38
Show Gist options
  • Save mmaltsev/65b77a4b865d7d98bcd187c79b66ccb2 to your computer and use it in GitHub Desktop.
Save mmaltsev/65b77a4b865d7d98bcd187c79b66ccb2 to your computer and use it in GitHub Desktop.
Linear Programming Table
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<style>
.tables-wrapper { display: flex; flex-direction: row; margin: 15px; }
.table-column { display: flex; flex-direction: column; }
.table-input-false { width: 80px; border: 0; height: 20px; padding: 0; background-color: inherit; }
.table-input-true { width: 40px; border: 0; height: 20px; padding: 0; background-color: inherit; }
.table-input-true:nth-child(even) { border-left: 1px solid lightgray; }
.side-tables { margin-top: 4px; }
.header { font-size: 16px; font-weight: 800; text-align: center; }
.prod-cons { margin-top: 20px; }
.prof-ingr { text-align: center; }
table { border-collapse: collapse; border: 1px solid gray; }
tr:nth-child(even) { background-color: #f8f8f8; }
td { border-bottom: 1px solid gray; }
#delColId, #delRowId { width: 40px; }
.menu { display: flex; flex-direction: row; border: 1px solid gray; padding: 10px; width: 420px; }
.menu-column { display: flex; flex-direction: column; margin: 0 10px; height: 60px; justify-content: space-around; }
.menu-delete { display: flex; flex-direction: row; }
button { cursor: pointer; }
#addConst { margin-bottom: 10px; }
.const-input { height: 24px; width: 400px; font-size: 20px; font-weight: 800; border: 0; border-bottom: 1px solid lightgray; outline: none; }
.cross { font-size: 16px; font-family: cursive; cursor: pointer; }
.add-const { margin: 0 0 10px 10px; }
.add-const-button { margin-left: 10px; }
</style>
</head>
<body onload="drawTables()">
<h2>Linear Programming</h2>
<div class="menu">
<div class="menu-column">
<button onclick="addRow()">add row</button>
<button onclick="addColumn()">add column</button>
</div>
<div class="menu-column">
<div>
<button onclick="deleteRow()">delete row #</button><input type="number" id="delRowId" min="0" />
</div>
<div class="menu-delete">
<button onclick="deleteColumn()">delete column #</button><input type="number" id="delColId" min="0" />
</div>
</div>
<div class="menu-column">
<button onclick="saveAsJson()">Save as JSON</button>
<button onclick="sendData()">Submit data</button>
</div>
</div>
<div class="tables-wrapper">
<div class="table-column">
<span class="header prod-cons">Product</span>
<table id="prod" border="1" class="side-tables"></table>
</div>
<div class="table-column">
<span class="header prof-ingr">Ingredient</span>
<table id="ingr" border="1"></table>
<table id="mtx" border="1"></table>
<table id="cons" border="1"></table>
<span class="header prof-ingr">Constraints</span>
</div>
<div class="table-column">
<span class="header prod-cons">Profit</span>
<table id="prof" border="1" class="side-tables"></table>
</div>
</div>
<h3>Additional Constraints:</h3>
<div id="addConst"></div>
<button class="add-const-button" onclick="addConstraint()">Add Constraint</button>
<script>
function sendData() {
// POST request for sending data object
let obj = tablesToJSON()
console.log(obj)
$.post('/submit_data', obj)
.done((reponse) => {
console.log(reponse)
})
}
function fetchData(dataId) {
// POST request for fetching data object based on id
$.post('/fetch_data', { id: dataId })
.done((data) => {
dataToTables(data)
})
.fail(() => {
dummyData = {
mtx: [[0, 1], [3, 4]],
prod: [['prod0'], ['prod1']],
prof: [[10], [9]],
ingr: [['ingr0', 'ingr1']],
cons: [[['2', '3'], ['6', '8']]],
addConst: ['0 < 12', 'trtr'],
}
dataToTables(dummyData)
alert('dummy data is drawn')
})
}
function drawTables(dataId = -1) {
if (dataId === -1) {
// Initialization: add 3x3 matrix
addRow()
addRow()
addColumn()
addConstraint()
} else {
// Fetch predefined matrices
fetchData(dataId)
}
}
function addColumn() {
let { mtx, mtxRows, prod, prof, ingr, cons, rowsNum } = getTableValues()
let colsNum = mtxRows[0].getElementsByTagName('td').length
// Push a cell to every row in the mtx and fill it with text input
for (let i = 0; i < rowsNum; i++) {
insertCell(mtxRows[i], colsNum, 'mtx')
}
// Push a cell to ingr and cons and fill it with text input
let ingrRow = ingr.getElementsByTagName('tr')[0]
insertCell(ingrRow, colsNum, 'ingr')
let consRow = cons.getElementsByTagName('tr')[0]
insertCell(consRow, colsNum, 'cons', true)
document.getElementById('delColId').value = colsNum
}
function addRow() {
let { mtx, mtxRows, prod, prof, ingr, cons, rowsNum } = getTableValues()
if (rowsNum > 0) {
// Push a row to the mtx, add cells to the row and fill them with text input
let colsNum = mtxRows[0].getElementsByTagName('td').length
let newMtxRow = mtx.insertRow(rowsNum)
for (let i = 0; i < colsNum; i++) {
insertCell(newMtxRow, i, 'mtx')
}
// Push a row to prod and to prof, add one cell to each row and fill it with text input
insertCell(prod.insertRow(rowsNum), 0, 'prod')
insertCell(prof.insertRow(rowsNum), 0, 'prof')
} else {
// If mtx is empty, initialize all matrixes by adding a row
insertCell(mtx.insertRow(0), 0, 'mtx')
insertCell(prod.insertRow(0), 0, 'prod')
insertCell(prof.insertRow(0), 0, 'prof')
insertCell(ingr.insertRow(0), 0, 'ingr')
insertCell(cons.insertRow(0), 0, 'cons')
}
document.getElementById('delRowId').value = rowsNum
}
function deleteColumn() {
let { mtx, mtxRows, prod, prof, ingr, cons, rowsNum } = getTableValues()
let colsNum = mtxRows[0].getElementsByTagName('td').length
let delColId = document.getElementById('delColId').value || colsNum - 1
// If there are more than one column left -> delete, if no -> error
if (colsNum > 1 && delColId < colsNum) {
// Delete a cell from every mtx row
for (let row of mtxRows) {
row.deleteCell(delColId)
}
// Delete a cell from ingr and from cons
let ingrRows = ingr.getElementsByTagName('tr')
ingrRows[0].deleteCell(delColId)
let consRows = cons.getElementsByTagName('tr')
consRows[0].deleteCell(delColId)
document.getElementById('delColId').value = colsNum - 2
} else {
alert('cannot delete a column!')
}
}
function deleteRow() {
let { mtx, mtxRows, prod, prof, ingr, cons, rowsNum } = getTableValues()
let delRowId = document.getElementById('delRowId').value || rowsNum - 1
// If there are more than one row left -> delete, if no -> error
if (rowsNum > 1 && delRowId < rowsNum) {
mtx.deleteRow(delRowId)
prod.deleteRow(delRowId)
prof.deleteRow(delRowId)
document.getElementById('delRowId').value = rowsNum - 2
} else {
alert('cannot delete a row!')
}
}
function insertCell(row, cellId, type) {
let { inputType, inputValue, isDouble } = getTableAttr(cellId, type)
// Insert a new cell to the specified row
let cell = row.insertCell(cellId)
let input = document.createElement('input')
input.type = inputType
input.value = inputValue
input.onclick = function() { this.select() }
input.className = 'table-input-' + isDouble
cell.appendChild(input)
// If it is Constraints -> add one more input for max value
if (isDouble) {
let input2 = document.createElement('input')
input2.type = inputType
input2.value = inputValue
input2.onclick = function() { this.select() }
input2.className = 'table-input-' + isDouble
cell.appendChild(input2)
}
}
function getTableAttr(cellId, type) {
// Helper for getting all widely used table attributes
let inputType = 'text'
let inputValue = ''
let isDouble = false
if (type === 'mtx') {
inputType = 'number'
inputValue = 0
isDouble = false
} else if (type === 'prod') {
let rowId = document.getElementById('prod').getElementsByTagName('tr').length - 1
inputType = 'text'
inputValue = 'prod' + rowId
isDouble = false
} else if (type === 'prof') {
inputType = 'number'
inputValue = 0
isDouble = false
} else if (type === 'ingr') {
inputType = 'text'
inputValue = 'ingr' + cellId
isDouble = false
} else if (type === 'cons') {
inputType = 'text'
inputValue = 'None'
isDouble = true
}
return { inputType, inputValue, isDouble }
}
function dataToTables(data) {
// Convert JSON object to HTML tables and additional constraints inputs
for (let arr in data) {
let { inputType, inputValue, isDouble } = getTableAttr(0, arr)
if (data.hasOwnProperty(arr) && arr !== 'addConst') {
let table = document.getElementById(arr)
let tableHTML = '<table>'
for (let row of data[arr]) {
tableHTML += '<tr>'
for (let cell of row) {
tableHTML += '<td>'
if (Array.isArray(cell)) {
for (let val of cell) {
tableHTML += '<input value="' + val +
'" class="table-input-' + isDouble +
'" type="' + inputType + '" onclick="select()" />'
}
} else {
tableHTML += '<input value="' + cell +
'" class="table-input-' + isDouble +
'" type="' + inputType + '" onclick="select()" />'
}
tableHTML += '</td>'
}
tableHTML += '</tr>'
}
tableHTML += '</table>'
table.innerHTML = tableHTML
} else if (arr === 'addConst') {
for (constraint of data[arr]) {
addConstraint(constraint)
}
}
}
document.getElementById('delRowId').value = data['mtx'].length - 1
document.getElementById('delColId').value = data['mtx'][0].length - 1
}
function tablesToJSON() {
// Extract tables' data and put to JSON object
let tableNames = ['mtx', 'prod', 'prof', 'ingr', 'cons']
let obj = {}
for (tableName of tableNames) {
obj[tableName] = Array.prototype.map.call(document.querySelectorAll('#' + tableName + ' tr'), function (tr) {
return Array.prototype.map.call(tr.querySelectorAll('td'), function (td) {
return Array.prototype.map.call(td.querySelectorAll('input'), input => input.value)
})
})
}
obj['addConst'] = []
let addConst = document.getElementById('addConst')
let inputs = addConst.getElementsByTagName('input')
for (let input of inputs) {
if (input.value !== '') {
obj['addConst'].push(input.value)
}
}
return obj
}
function saveAsJson() {
// Create an HTML link and click on it to download the file
let jsonObj = JSON.stringify(tablesToJSON())
let a = document.createElement('a')
let file = new Blob([jsonObj], {type: 'text/plain'})
a.href = URL.createObjectURL(file)
a.download = 'file.json'
a.click()
}
function getTableValues() {
// Extract all useful matrices' values
let mtx = document.getElementById('mtx')
let mtxRows = mtx.getElementsByTagName('tr')
return {
mtx,
mtxRows,
prod: document.getElementById('prod'),
prof: document.getElementById('prof'),
ingr: document.getElementById('ingr'),
cons: document.getElementById('cons'),
rowsNum: mtxRows.length,
}
}
function addConstraint(value = '') {
// Add a new constraint as an input text
let addConst = document.getElementById('addConst')
let constNum = addConst.getElementsByTagName('div').length
let div = document.createElement('div')
div.id = 'const' + constNum
div.className = 'add-const'
div.innerHTML = '<input type="text" class="const-input" value="' +
value + '" /> <span onclick="deleteConst(' +
constNum +')" class="cross">x</span>'
addConst.appendChild(div)
}
function deleteConst(constId) {
document.getElementById('const' + constId).innerHTML = ''
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment