To-do list web application

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
4
down vote

favorite












I personally find it helpful to see what aspects of a program I already implemented and what features I need to add. Just for fun, I decided to write up a program to do that. This is the first program for which I tried to write clean looking code. Sorry for the long, overcomplicated code.



The way the program stores/retrieves data is weird and inefficient mainly because I don't know a better way.



The program is html and javascript communicating to a Python webserver. I also hosted the program on the webserver to make cross origin stuff easier to deal with. Unfortunately all the filenames are hardcoded into the program.



webserver.py:



from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
import SocketServer

startData = "###DATAGOESHERE###"
endData = "###ENDDATAGOESHERE###"

def readFile(fileName):
with open(fileName,'r') as f:
data = f.read()
f.close()
return data

def replaceWithData(string, data):
data = data.strip()
if data.strip() == "":
return string.replace(startData, "").replace(endData, "")
else:
start = string.find(startData)
end = string.find(endData)+len(endData)
string = string[:start]+data+string[end:]
return string

def writeFile(fileName, data):
with open(fileName,'w') as f:
f.write(data)
f.close()

def serveStaticApp(request, url):
data = readFile('data.html')
html = readFile('main.html')
html = replaceWithData(html, data)
request.wfile.write(html)

def serverStaticFile(request, url):
data = readFile(url)
request.wfile.write(data)

def saveData(request, url):
length = int(request.headers.getheader('content-length'))
data = request.rfile.read(length).strip()
writeFile('data.html', data)

funcMap =
"app": serveStaticApp,
"main.js":serverStaticFile,
"save": saveData,
"main.css": serverStaticFile,


def handleRequest(request):
request._set_headers()
for i in funcMap.keys():
if request.path[-len(i):] == i:
funcMap[i](request, i)
return
request.wfile.write("<html><body><h1>REQUEST!</h1></body></html>")

class S(BaseHTTPRequestHandler):
def _set_headers(self):
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()

def do_GET(self):
handleRequest(self)

def do_HEAD(self):
self._set_headers()

def do_POST(self):
handleRequest(self)


def run(server_class=HTTPServer, handler_class=S, port=8080):
server_address = ('localhost', port)
httpd = server_class(server_address, handler_class)
print 'Starting...'
httpd.serve_forever()

if __name__ == "__main__":
from sys import argv

if len(argv) == 2:
run(port=int(argv[1]))
else:
run()


main.html



<!DOCTYPE>
<html>
<head>
<link rel="stylesheet" href="main.css" type="text/css">
</head>
<body>
<div>
<span id="not-implemented-label">Not Implemented:</span>
<span id="implemented-label">Implemented:</span>
</div>
<span id="save">
###DATAGOESHERE###
<select id="not-implemented-select" size="2" multiple="true"></select>
<span id="controls">
<input type="button" value="<-" id="left-arrow-button">
<input type="button" value="LOAD" id="load-button">
<input type="button" value="->" id="right-arrow-button"><br>
<input type="button" value="DELETE" id="delete-button">
</span>
<select id="implemented-select" size="2" multiple="true"></select>
###ENDDATAGOESHERE###
</span>
<br>
<br>
<input type="text" id="title-input" placeholder="Title"><br>
<textarea id="description-input" placeholder="Description"></textarea>
<span id="save-controls">
<input type="button" value="SAVE OVER" id="save-over-button"><br>
<input type="button" value="SAVE NEW" id="save-new-button"><br>
<input type="button" value="CLEAR" id="clear-button">
</span>
<script src="functions.js"></script>
<script src="main.js"></script>
</body>
</html>


main.css



select
width:40vw;
height:60vh;


textarea
width:80vw;
height:30vh;

#title-input
width:80vw;

#controls
display:inline-block;
width:8em;
text-align: center;

#save-controls
display:inline-block;

#not-implemented-label
display:inline-block;
width:calc(40vw + 8em);

.description
display:none;



main.js



"use strict";
var mostRecentlySelectedOptions = ;
var allowTextOverrride = true;
var saveServerURL = "/save"

var cachedElements = ;
function gebi(id)
var cachedElement = cachedElements[id];
if(cachedElement === undefined)
cachedElement = document.getElementById(id);
cachedElements[id] = cachedElement;
return cachedElement;
else
return cachedElement;



function slice(collection)
return .slice.call(collection);


function moveOptions(options, toSelect)
options = options.slice();
for(var i = 0; i < options.length; i++)
toSelect.appendChild(options[i]);

saveToServer();


function setInputs(title, description)
allowTextOverrride = true;
gebi("title-input").value = title;
gebi("description-input").value = description;


function getInputs()
return
"title": gebi("title-input").value,
"description": gebi("description-input").value
;


function getSafeInputs()
var inputs = getInputs();
if(inputs.title.trim() == "")
inputs.title = "<untitled>";

inputs.title = inputs.title.trim();
inputs.description = inputs.description.trim();
return inputs;


function clearInputs()
setInputs("", "");


function moveSelectedOptions(toSelect)
moveOptions(mostRecentlySelectedOptions, toSelect);


function onSelectionChange(target)
mostRecentlySelectedOptions = slice(target.selectedOptions);
if(allowTextOverrride)
loadSelectedOption();



function deselectAllInSelect(selectElement)
var selected = selectElement.selectedOptions;
for(var i = 0; i < selected.length; i++)
selected[i].selected = false;



function saveText()
allowTextOverrride = true;
saveToServer();


function saveToServer()
var dataToSave = gebi("save").innerHTML;
var xhttp = new XMLHttpRequest();
xhttp.open("POST", saveServerURL, true);
xhttp.setRequestHeader("Content-type", "text/plain");
xhttp.send(dataToSave);


function removeOptions(options)
options = options.slice();
for(var i = 0; i < options.length; i++)
options[i].parentNode.removeChild(options[i]);

saveToServer();


function removeSelectedOptions()
removeOptions(mostRecentlySelectedOptions);


function setOptionData(option, title, description)
option.text = title;
option.setAttribute("data-description", description);


function getOptionData(option)
return
"title": option.text,
"description": option.getAttribute("data-description")
;


function createOption(title, description)
var option = document.createElement("option");
setOptionData(option, title, description);
return option;


function addOption(title, description, parentSelect)
var option = createOption(title, description);
parentSelect.add(option);
deselectAllInSelect(parentSelect);
option.selected = true;


function saveNew()
var inputData = getSafeInputs();
var parentSelect = gebi("not-implemented-select");
addOption(inputData.title, inputData.description, parentSelect);
saveText();


function loadSelectedOption()
if(mostRecentlySelectedOptions.length == 1)
var selectedOption = mostRecentlySelectedOptions[0];
var data = getOptionData(selectedOption);
setInputs(data.title, data.description);



function saveOverwrite()
if(mostRecentlySelectedOptions.length == 1)
var selectedOption = mostRecentlySelectedOptions[0];
var inputData = getSafeInputs();
setOptionData(selectedOption, inputData.title, inputData.description);
saveText();
else
saveNew();



function addEventListeners()
var notImplementedSelect = gebi("not-implemented-select");
var implementedSelect = gebi("implemented-select");

gebi("right-arrow-button").addEventListener("click", function()
moveSelectedOptions(implementedSelect);
);

gebi("left-arrow-button").addEventListener("click", function()
moveSelectedOptions(notImplementedSelect);
);

gebi("load-button").addEventListener("click", function()
loadSelectedOption();
allowTextOverrride = false;
);

gebi("delete-button").addEventListener("click", function()
removeSelectedOptions();
);

gebi("save-over-button").addEventListener("click", function()
saveOverwrite();
);

gebi("save-new-button").addEventListener("click", function()
saveNew();
);

gebi("clear-button").addEventListener("click", function()
clearInputs();
);

implementedSelect.addEventListener("change", function(event)
onSelectionChange(event.target);
);

implementedSelect.addEventListener("focus", function(event)
onSelectionChange(event.target);
);

notImplementedSelect.addEventListener("change", function(event)
onSelectionChange(event.target);
);

notImplementedSelect.addEventListener("focus", function(event)
onSelectionChange(event.target);
);

gebi("title-input").addEventListener("keypress", function()
allowTextOverrride = false;
);

gebi("description-input").addEventListener("keypress", function()
allowTextOverrride = false;
);

window.addEventListener("beforeunload", function()
//alert();
);


addEventListeners();


To run it, put all the files into the same directory and run the Python file. You also need a blank file named data.html in the same directory. Then visit http://localhost:8080/app to access the app.



You can put a small title for the task and a longer description of the task and click SAVE NEW. SAVE OVER will save over whatever one you have selected. The two arrow buttons move the task between columns. It is important to note that the webserver should not be run over the internet.



I'm particularly interesting in the following:



  • Are my variables/functions well named?

  • Are my functions doing too much or too little?

  • Are there cases that you can get unexpected behavior?

  • Do you feel that the code is easily extendable/maintainable?

  • What can I do about the long list of addEventListeners?






share|improve this question



























    up vote
    4
    down vote

    favorite












    I personally find it helpful to see what aspects of a program I already implemented and what features I need to add. Just for fun, I decided to write up a program to do that. This is the first program for which I tried to write clean looking code. Sorry for the long, overcomplicated code.



    The way the program stores/retrieves data is weird and inefficient mainly because I don't know a better way.



    The program is html and javascript communicating to a Python webserver. I also hosted the program on the webserver to make cross origin stuff easier to deal with. Unfortunately all the filenames are hardcoded into the program.



    webserver.py:



    from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
    import SocketServer

    startData = "###DATAGOESHERE###"
    endData = "###ENDDATAGOESHERE###"

    def readFile(fileName):
    with open(fileName,'r') as f:
    data = f.read()
    f.close()
    return data

    def replaceWithData(string, data):
    data = data.strip()
    if data.strip() == "":
    return string.replace(startData, "").replace(endData, "")
    else:
    start = string.find(startData)
    end = string.find(endData)+len(endData)
    string = string[:start]+data+string[end:]
    return string

    def writeFile(fileName, data):
    with open(fileName,'w') as f:
    f.write(data)
    f.close()

    def serveStaticApp(request, url):
    data = readFile('data.html')
    html = readFile('main.html')
    html = replaceWithData(html, data)
    request.wfile.write(html)

    def serverStaticFile(request, url):
    data = readFile(url)
    request.wfile.write(data)

    def saveData(request, url):
    length = int(request.headers.getheader('content-length'))
    data = request.rfile.read(length).strip()
    writeFile('data.html', data)

    funcMap =
    "app": serveStaticApp,
    "main.js":serverStaticFile,
    "save": saveData,
    "main.css": serverStaticFile,


    def handleRequest(request):
    request._set_headers()
    for i in funcMap.keys():
    if request.path[-len(i):] == i:
    funcMap[i](request, i)
    return
    request.wfile.write("<html><body><h1>REQUEST!</h1></body></html>")

    class S(BaseHTTPRequestHandler):
    def _set_headers(self):
    self.send_response(200)
    self.send_header('Content-type', 'text/html')
    self.end_headers()

    def do_GET(self):
    handleRequest(self)

    def do_HEAD(self):
    self._set_headers()

    def do_POST(self):
    handleRequest(self)


    def run(server_class=HTTPServer, handler_class=S, port=8080):
    server_address = ('localhost', port)
    httpd = server_class(server_address, handler_class)
    print 'Starting...'
    httpd.serve_forever()

    if __name__ == "__main__":
    from sys import argv

    if len(argv) == 2:
    run(port=int(argv[1]))
    else:
    run()


    main.html



    <!DOCTYPE>
    <html>
    <head>
    <link rel="stylesheet" href="main.css" type="text/css">
    </head>
    <body>
    <div>
    <span id="not-implemented-label">Not Implemented:</span>
    <span id="implemented-label">Implemented:</span>
    </div>
    <span id="save">
    ###DATAGOESHERE###
    <select id="not-implemented-select" size="2" multiple="true"></select>
    <span id="controls">
    <input type="button" value="<-" id="left-arrow-button">
    <input type="button" value="LOAD" id="load-button">
    <input type="button" value="->" id="right-arrow-button"><br>
    <input type="button" value="DELETE" id="delete-button">
    </span>
    <select id="implemented-select" size="2" multiple="true"></select>
    ###ENDDATAGOESHERE###
    </span>
    <br>
    <br>
    <input type="text" id="title-input" placeholder="Title"><br>
    <textarea id="description-input" placeholder="Description"></textarea>
    <span id="save-controls">
    <input type="button" value="SAVE OVER" id="save-over-button"><br>
    <input type="button" value="SAVE NEW" id="save-new-button"><br>
    <input type="button" value="CLEAR" id="clear-button">
    </span>
    <script src="functions.js"></script>
    <script src="main.js"></script>
    </body>
    </html>


    main.css



    select
    width:40vw;
    height:60vh;


    textarea
    width:80vw;
    height:30vh;

    #title-input
    width:80vw;

    #controls
    display:inline-block;
    width:8em;
    text-align: center;

    #save-controls
    display:inline-block;

    #not-implemented-label
    display:inline-block;
    width:calc(40vw + 8em);

    .description
    display:none;



    main.js



    "use strict";
    var mostRecentlySelectedOptions = ;
    var allowTextOverrride = true;
    var saveServerURL = "/save"

    var cachedElements = ;
    function gebi(id)
    var cachedElement = cachedElements[id];
    if(cachedElement === undefined)
    cachedElement = document.getElementById(id);
    cachedElements[id] = cachedElement;
    return cachedElement;
    else
    return cachedElement;



    function slice(collection)
    return .slice.call(collection);


    function moveOptions(options, toSelect)
    options = options.slice();
    for(var i = 0; i < options.length; i++)
    toSelect.appendChild(options[i]);

    saveToServer();


    function setInputs(title, description)
    allowTextOverrride = true;
    gebi("title-input").value = title;
    gebi("description-input").value = description;


    function getInputs()
    return
    "title": gebi("title-input").value,
    "description": gebi("description-input").value
    ;


    function getSafeInputs()
    var inputs = getInputs();
    if(inputs.title.trim() == "")
    inputs.title = "<untitled>";

    inputs.title = inputs.title.trim();
    inputs.description = inputs.description.trim();
    return inputs;


    function clearInputs()
    setInputs("", "");


    function moveSelectedOptions(toSelect)
    moveOptions(mostRecentlySelectedOptions, toSelect);


    function onSelectionChange(target)
    mostRecentlySelectedOptions = slice(target.selectedOptions);
    if(allowTextOverrride)
    loadSelectedOption();



    function deselectAllInSelect(selectElement)
    var selected = selectElement.selectedOptions;
    for(var i = 0; i < selected.length; i++)
    selected[i].selected = false;



    function saveText()
    allowTextOverrride = true;
    saveToServer();


    function saveToServer()
    var dataToSave = gebi("save").innerHTML;
    var xhttp = new XMLHttpRequest();
    xhttp.open("POST", saveServerURL, true);
    xhttp.setRequestHeader("Content-type", "text/plain");
    xhttp.send(dataToSave);


    function removeOptions(options)
    options = options.slice();
    for(var i = 0; i < options.length; i++)
    options[i].parentNode.removeChild(options[i]);

    saveToServer();


    function removeSelectedOptions()
    removeOptions(mostRecentlySelectedOptions);


    function setOptionData(option, title, description)
    option.text = title;
    option.setAttribute("data-description", description);


    function getOptionData(option)
    return
    "title": option.text,
    "description": option.getAttribute("data-description")
    ;


    function createOption(title, description)
    var option = document.createElement("option");
    setOptionData(option, title, description);
    return option;


    function addOption(title, description, parentSelect)
    var option = createOption(title, description);
    parentSelect.add(option);
    deselectAllInSelect(parentSelect);
    option.selected = true;


    function saveNew()
    var inputData = getSafeInputs();
    var parentSelect = gebi("not-implemented-select");
    addOption(inputData.title, inputData.description, parentSelect);
    saveText();


    function loadSelectedOption()
    if(mostRecentlySelectedOptions.length == 1)
    var selectedOption = mostRecentlySelectedOptions[0];
    var data = getOptionData(selectedOption);
    setInputs(data.title, data.description);



    function saveOverwrite()
    if(mostRecentlySelectedOptions.length == 1)
    var selectedOption = mostRecentlySelectedOptions[0];
    var inputData = getSafeInputs();
    setOptionData(selectedOption, inputData.title, inputData.description);
    saveText();
    else
    saveNew();



    function addEventListeners()
    var notImplementedSelect = gebi("not-implemented-select");
    var implementedSelect = gebi("implemented-select");

    gebi("right-arrow-button").addEventListener("click", function()
    moveSelectedOptions(implementedSelect);
    );

    gebi("left-arrow-button").addEventListener("click", function()
    moveSelectedOptions(notImplementedSelect);
    );

    gebi("load-button").addEventListener("click", function()
    loadSelectedOption();
    allowTextOverrride = false;
    );

    gebi("delete-button").addEventListener("click", function()
    removeSelectedOptions();
    );

    gebi("save-over-button").addEventListener("click", function()
    saveOverwrite();
    );

    gebi("save-new-button").addEventListener("click", function()
    saveNew();
    );

    gebi("clear-button").addEventListener("click", function()
    clearInputs();
    );

    implementedSelect.addEventListener("change", function(event)
    onSelectionChange(event.target);
    );

    implementedSelect.addEventListener("focus", function(event)
    onSelectionChange(event.target);
    );

    notImplementedSelect.addEventListener("change", function(event)
    onSelectionChange(event.target);
    );

    notImplementedSelect.addEventListener("focus", function(event)
    onSelectionChange(event.target);
    );

    gebi("title-input").addEventListener("keypress", function()
    allowTextOverrride = false;
    );

    gebi("description-input").addEventListener("keypress", function()
    allowTextOverrride = false;
    );

    window.addEventListener("beforeunload", function()
    //alert();
    );


    addEventListeners();


    To run it, put all the files into the same directory and run the Python file. You also need a blank file named data.html in the same directory. Then visit http://localhost:8080/app to access the app.



    You can put a small title for the task and a longer description of the task and click SAVE NEW. SAVE OVER will save over whatever one you have selected. The two arrow buttons move the task between columns. It is important to note that the webserver should not be run over the internet.



    I'm particularly interesting in the following:



    • Are my variables/functions well named?

    • Are my functions doing too much or too little?

    • Are there cases that you can get unexpected behavior?

    • Do you feel that the code is easily extendable/maintainable?

    • What can I do about the long list of addEventListeners?






    share|improve this question























      up vote
      4
      down vote

      favorite









      up vote
      4
      down vote

      favorite











      I personally find it helpful to see what aspects of a program I already implemented and what features I need to add. Just for fun, I decided to write up a program to do that. This is the first program for which I tried to write clean looking code. Sorry for the long, overcomplicated code.



      The way the program stores/retrieves data is weird and inefficient mainly because I don't know a better way.



      The program is html and javascript communicating to a Python webserver. I also hosted the program on the webserver to make cross origin stuff easier to deal with. Unfortunately all the filenames are hardcoded into the program.



      webserver.py:



      from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
      import SocketServer

      startData = "###DATAGOESHERE###"
      endData = "###ENDDATAGOESHERE###"

      def readFile(fileName):
      with open(fileName,'r') as f:
      data = f.read()
      f.close()
      return data

      def replaceWithData(string, data):
      data = data.strip()
      if data.strip() == "":
      return string.replace(startData, "").replace(endData, "")
      else:
      start = string.find(startData)
      end = string.find(endData)+len(endData)
      string = string[:start]+data+string[end:]
      return string

      def writeFile(fileName, data):
      with open(fileName,'w') as f:
      f.write(data)
      f.close()

      def serveStaticApp(request, url):
      data = readFile('data.html')
      html = readFile('main.html')
      html = replaceWithData(html, data)
      request.wfile.write(html)

      def serverStaticFile(request, url):
      data = readFile(url)
      request.wfile.write(data)

      def saveData(request, url):
      length = int(request.headers.getheader('content-length'))
      data = request.rfile.read(length).strip()
      writeFile('data.html', data)

      funcMap =
      "app": serveStaticApp,
      "main.js":serverStaticFile,
      "save": saveData,
      "main.css": serverStaticFile,


      def handleRequest(request):
      request._set_headers()
      for i in funcMap.keys():
      if request.path[-len(i):] == i:
      funcMap[i](request, i)
      return
      request.wfile.write("<html><body><h1>REQUEST!</h1></body></html>")

      class S(BaseHTTPRequestHandler):
      def _set_headers(self):
      self.send_response(200)
      self.send_header('Content-type', 'text/html')
      self.end_headers()

      def do_GET(self):
      handleRequest(self)

      def do_HEAD(self):
      self._set_headers()

      def do_POST(self):
      handleRequest(self)


      def run(server_class=HTTPServer, handler_class=S, port=8080):
      server_address = ('localhost', port)
      httpd = server_class(server_address, handler_class)
      print 'Starting...'
      httpd.serve_forever()

      if __name__ == "__main__":
      from sys import argv

      if len(argv) == 2:
      run(port=int(argv[1]))
      else:
      run()


      main.html



      <!DOCTYPE>
      <html>
      <head>
      <link rel="stylesheet" href="main.css" type="text/css">
      </head>
      <body>
      <div>
      <span id="not-implemented-label">Not Implemented:</span>
      <span id="implemented-label">Implemented:</span>
      </div>
      <span id="save">
      ###DATAGOESHERE###
      <select id="not-implemented-select" size="2" multiple="true"></select>
      <span id="controls">
      <input type="button" value="<-" id="left-arrow-button">
      <input type="button" value="LOAD" id="load-button">
      <input type="button" value="->" id="right-arrow-button"><br>
      <input type="button" value="DELETE" id="delete-button">
      </span>
      <select id="implemented-select" size="2" multiple="true"></select>
      ###ENDDATAGOESHERE###
      </span>
      <br>
      <br>
      <input type="text" id="title-input" placeholder="Title"><br>
      <textarea id="description-input" placeholder="Description"></textarea>
      <span id="save-controls">
      <input type="button" value="SAVE OVER" id="save-over-button"><br>
      <input type="button" value="SAVE NEW" id="save-new-button"><br>
      <input type="button" value="CLEAR" id="clear-button">
      </span>
      <script src="functions.js"></script>
      <script src="main.js"></script>
      </body>
      </html>


      main.css



      select
      width:40vw;
      height:60vh;


      textarea
      width:80vw;
      height:30vh;

      #title-input
      width:80vw;

      #controls
      display:inline-block;
      width:8em;
      text-align: center;

      #save-controls
      display:inline-block;

      #not-implemented-label
      display:inline-block;
      width:calc(40vw + 8em);

      .description
      display:none;



      main.js



      "use strict";
      var mostRecentlySelectedOptions = ;
      var allowTextOverrride = true;
      var saveServerURL = "/save"

      var cachedElements = ;
      function gebi(id)
      var cachedElement = cachedElements[id];
      if(cachedElement === undefined)
      cachedElement = document.getElementById(id);
      cachedElements[id] = cachedElement;
      return cachedElement;
      else
      return cachedElement;



      function slice(collection)
      return .slice.call(collection);


      function moveOptions(options, toSelect)
      options = options.slice();
      for(var i = 0; i < options.length; i++)
      toSelect.appendChild(options[i]);

      saveToServer();


      function setInputs(title, description)
      allowTextOverrride = true;
      gebi("title-input").value = title;
      gebi("description-input").value = description;


      function getInputs()
      return
      "title": gebi("title-input").value,
      "description": gebi("description-input").value
      ;


      function getSafeInputs()
      var inputs = getInputs();
      if(inputs.title.trim() == "")
      inputs.title = "<untitled>";

      inputs.title = inputs.title.trim();
      inputs.description = inputs.description.trim();
      return inputs;


      function clearInputs()
      setInputs("", "");


      function moveSelectedOptions(toSelect)
      moveOptions(mostRecentlySelectedOptions, toSelect);


      function onSelectionChange(target)
      mostRecentlySelectedOptions = slice(target.selectedOptions);
      if(allowTextOverrride)
      loadSelectedOption();



      function deselectAllInSelect(selectElement)
      var selected = selectElement.selectedOptions;
      for(var i = 0; i < selected.length; i++)
      selected[i].selected = false;



      function saveText()
      allowTextOverrride = true;
      saveToServer();


      function saveToServer()
      var dataToSave = gebi("save").innerHTML;
      var xhttp = new XMLHttpRequest();
      xhttp.open("POST", saveServerURL, true);
      xhttp.setRequestHeader("Content-type", "text/plain");
      xhttp.send(dataToSave);


      function removeOptions(options)
      options = options.slice();
      for(var i = 0; i < options.length; i++)
      options[i].parentNode.removeChild(options[i]);

      saveToServer();


      function removeSelectedOptions()
      removeOptions(mostRecentlySelectedOptions);


      function setOptionData(option, title, description)
      option.text = title;
      option.setAttribute("data-description", description);


      function getOptionData(option)
      return
      "title": option.text,
      "description": option.getAttribute("data-description")
      ;


      function createOption(title, description)
      var option = document.createElement("option");
      setOptionData(option, title, description);
      return option;


      function addOption(title, description, parentSelect)
      var option = createOption(title, description);
      parentSelect.add(option);
      deselectAllInSelect(parentSelect);
      option.selected = true;


      function saveNew()
      var inputData = getSafeInputs();
      var parentSelect = gebi("not-implemented-select");
      addOption(inputData.title, inputData.description, parentSelect);
      saveText();


      function loadSelectedOption()
      if(mostRecentlySelectedOptions.length == 1)
      var selectedOption = mostRecentlySelectedOptions[0];
      var data = getOptionData(selectedOption);
      setInputs(data.title, data.description);



      function saveOverwrite()
      if(mostRecentlySelectedOptions.length == 1)
      var selectedOption = mostRecentlySelectedOptions[0];
      var inputData = getSafeInputs();
      setOptionData(selectedOption, inputData.title, inputData.description);
      saveText();
      else
      saveNew();



      function addEventListeners()
      var notImplementedSelect = gebi("not-implemented-select");
      var implementedSelect = gebi("implemented-select");

      gebi("right-arrow-button").addEventListener("click", function()
      moveSelectedOptions(implementedSelect);
      );

      gebi("left-arrow-button").addEventListener("click", function()
      moveSelectedOptions(notImplementedSelect);
      );

      gebi("load-button").addEventListener("click", function()
      loadSelectedOption();
      allowTextOverrride = false;
      );

      gebi("delete-button").addEventListener("click", function()
      removeSelectedOptions();
      );

      gebi("save-over-button").addEventListener("click", function()
      saveOverwrite();
      );

      gebi("save-new-button").addEventListener("click", function()
      saveNew();
      );

      gebi("clear-button").addEventListener("click", function()
      clearInputs();
      );

      implementedSelect.addEventListener("change", function(event)
      onSelectionChange(event.target);
      );

      implementedSelect.addEventListener("focus", function(event)
      onSelectionChange(event.target);
      );

      notImplementedSelect.addEventListener("change", function(event)
      onSelectionChange(event.target);
      );

      notImplementedSelect.addEventListener("focus", function(event)
      onSelectionChange(event.target);
      );

      gebi("title-input").addEventListener("keypress", function()
      allowTextOverrride = false;
      );

      gebi("description-input").addEventListener("keypress", function()
      allowTextOverrride = false;
      );

      window.addEventListener("beforeunload", function()
      //alert();
      );


      addEventListeners();


      To run it, put all the files into the same directory and run the Python file. You also need a blank file named data.html in the same directory. Then visit http://localhost:8080/app to access the app.



      You can put a small title for the task and a longer description of the task and click SAVE NEW. SAVE OVER will save over whatever one you have selected. The two arrow buttons move the task between columns. It is important to note that the webserver should not be run over the internet.



      I'm particularly interesting in the following:



      • Are my variables/functions well named?

      • Are my functions doing too much or too little?

      • Are there cases that you can get unexpected behavior?

      • Do you feel that the code is easily extendable/maintainable?

      • What can I do about the long list of addEventListeners?






      share|improve this question













      I personally find it helpful to see what aspects of a program I already implemented and what features I need to add. Just for fun, I decided to write up a program to do that. This is the first program for which I tried to write clean looking code. Sorry for the long, overcomplicated code.



      The way the program stores/retrieves data is weird and inefficient mainly because I don't know a better way.



      The program is html and javascript communicating to a Python webserver. I also hosted the program on the webserver to make cross origin stuff easier to deal with. Unfortunately all the filenames are hardcoded into the program.



      webserver.py:



      from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
      import SocketServer

      startData = "###DATAGOESHERE###"
      endData = "###ENDDATAGOESHERE###"

      def readFile(fileName):
      with open(fileName,'r') as f:
      data = f.read()
      f.close()
      return data

      def replaceWithData(string, data):
      data = data.strip()
      if data.strip() == "":
      return string.replace(startData, "").replace(endData, "")
      else:
      start = string.find(startData)
      end = string.find(endData)+len(endData)
      string = string[:start]+data+string[end:]
      return string

      def writeFile(fileName, data):
      with open(fileName,'w') as f:
      f.write(data)
      f.close()

      def serveStaticApp(request, url):
      data = readFile('data.html')
      html = readFile('main.html')
      html = replaceWithData(html, data)
      request.wfile.write(html)

      def serverStaticFile(request, url):
      data = readFile(url)
      request.wfile.write(data)

      def saveData(request, url):
      length = int(request.headers.getheader('content-length'))
      data = request.rfile.read(length).strip()
      writeFile('data.html', data)

      funcMap =
      "app": serveStaticApp,
      "main.js":serverStaticFile,
      "save": saveData,
      "main.css": serverStaticFile,


      def handleRequest(request):
      request._set_headers()
      for i in funcMap.keys():
      if request.path[-len(i):] == i:
      funcMap[i](request, i)
      return
      request.wfile.write("<html><body><h1>REQUEST!</h1></body></html>")

      class S(BaseHTTPRequestHandler):
      def _set_headers(self):
      self.send_response(200)
      self.send_header('Content-type', 'text/html')
      self.end_headers()

      def do_GET(self):
      handleRequest(self)

      def do_HEAD(self):
      self._set_headers()

      def do_POST(self):
      handleRequest(self)


      def run(server_class=HTTPServer, handler_class=S, port=8080):
      server_address = ('localhost', port)
      httpd = server_class(server_address, handler_class)
      print 'Starting...'
      httpd.serve_forever()

      if __name__ == "__main__":
      from sys import argv

      if len(argv) == 2:
      run(port=int(argv[1]))
      else:
      run()


      main.html



      <!DOCTYPE>
      <html>
      <head>
      <link rel="stylesheet" href="main.css" type="text/css">
      </head>
      <body>
      <div>
      <span id="not-implemented-label">Not Implemented:</span>
      <span id="implemented-label">Implemented:</span>
      </div>
      <span id="save">
      ###DATAGOESHERE###
      <select id="not-implemented-select" size="2" multiple="true"></select>
      <span id="controls">
      <input type="button" value="<-" id="left-arrow-button">
      <input type="button" value="LOAD" id="load-button">
      <input type="button" value="->" id="right-arrow-button"><br>
      <input type="button" value="DELETE" id="delete-button">
      </span>
      <select id="implemented-select" size="2" multiple="true"></select>
      ###ENDDATAGOESHERE###
      </span>
      <br>
      <br>
      <input type="text" id="title-input" placeholder="Title"><br>
      <textarea id="description-input" placeholder="Description"></textarea>
      <span id="save-controls">
      <input type="button" value="SAVE OVER" id="save-over-button"><br>
      <input type="button" value="SAVE NEW" id="save-new-button"><br>
      <input type="button" value="CLEAR" id="clear-button">
      </span>
      <script src="functions.js"></script>
      <script src="main.js"></script>
      </body>
      </html>


      main.css



      select
      width:40vw;
      height:60vh;


      textarea
      width:80vw;
      height:30vh;

      #title-input
      width:80vw;

      #controls
      display:inline-block;
      width:8em;
      text-align: center;

      #save-controls
      display:inline-block;

      #not-implemented-label
      display:inline-block;
      width:calc(40vw + 8em);

      .description
      display:none;



      main.js



      "use strict";
      var mostRecentlySelectedOptions = ;
      var allowTextOverrride = true;
      var saveServerURL = "/save"

      var cachedElements = ;
      function gebi(id)
      var cachedElement = cachedElements[id];
      if(cachedElement === undefined)
      cachedElement = document.getElementById(id);
      cachedElements[id] = cachedElement;
      return cachedElement;
      else
      return cachedElement;



      function slice(collection)
      return .slice.call(collection);


      function moveOptions(options, toSelect)
      options = options.slice();
      for(var i = 0; i < options.length; i++)
      toSelect.appendChild(options[i]);

      saveToServer();


      function setInputs(title, description)
      allowTextOverrride = true;
      gebi("title-input").value = title;
      gebi("description-input").value = description;


      function getInputs()
      return
      "title": gebi("title-input").value,
      "description": gebi("description-input").value
      ;


      function getSafeInputs()
      var inputs = getInputs();
      if(inputs.title.trim() == "")
      inputs.title = "<untitled>";

      inputs.title = inputs.title.trim();
      inputs.description = inputs.description.trim();
      return inputs;


      function clearInputs()
      setInputs("", "");


      function moveSelectedOptions(toSelect)
      moveOptions(mostRecentlySelectedOptions, toSelect);


      function onSelectionChange(target)
      mostRecentlySelectedOptions = slice(target.selectedOptions);
      if(allowTextOverrride)
      loadSelectedOption();



      function deselectAllInSelect(selectElement)
      var selected = selectElement.selectedOptions;
      for(var i = 0; i < selected.length; i++)
      selected[i].selected = false;



      function saveText()
      allowTextOverrride = true;
      saveToServer();


      function saveToServer()
      var dataToSave = gebi("save").innerHTML;
      var xhttp = new XMLHttpRequest();
      xhttp.open("POST", saveServerURL, true);
      xhttp.setRequestHeader("Content-type", "text/plain");
      xhttp.send(dataToSave);


      function removeOptions(options)
      options = options.slice();
      for(var i = 0; i < options.length; i++)
      options[i].parentNode.removeChild(options[i]);

      saveToServer();


      function removeSelectedOptions()
      removeOptions(mostRecentlySelectedOptions);


      function setOptionData(option, title, description)
      option.text = title;
      option.setAttribute("data-description", description);


      function getOptionData(option)
      return
      "title": option.text,
      "description": option.getAttribute("data-description")
      ;


      function createOption(title, description)
      var option = document.createElement("option");
      setOptionData(option, title, description);
      return option;


      function addOption(title, description, parentSelect)
      var option = createOption(title, description);
      parentSelect.add(option);
      deselectAllInSelect(parentSelect);
      option.selected = true;


      function saveNew()
      var inputData = getSafeInputs();
      var parentSelect = gebi("not-implemented-select");
      addOption(inputData.title, inputData.description, parentSelect);
      saveText();


      function loadSelectedOption()
      if(mostRecentlySelectedOptions.length == 1)
      var selectedOption = mostRecentlySelectedOptions[0];
      var data = getOptionData(selectedOption);
      setInputs(data.title, data.description);



      function saveOverwrite()
      if(mostRecentlySelectedOptions.length == 1)
      var selectedOption = mostRecentlySelectedOptions[0];
      var inputData = getSafeInputs();
      setOptionData(selectedOption, inputData.title, inputData.description);
      saveText();
      else
      saveNew();



      function addEventListeners()
      var notImplementedSelect = gebi("not-implemented-select");
      var implementedSelect = gebi("implemented-select");

      gebi("right-arrow-button").addEventListener("click", function()
      moveSelectedOptions(implementedSelect);
      );

      gebi("left-arrow-button").addEventListener("click", function()
      moveSelectedOptions(notImplementedSelect);
      );

      gebi("load-button").addEventListener("click", function()
      loadSelectedOption();
      allowTextOverrride = false;
      );

      gebi("delete-button").addEventListener("click", function()
      removeSelectedOptions();
      );

      gebi("save-over-button").addEventListener("click", function()
      saveOverwrite();
      );

      gebi("save-new-button").addEventListener("click", function()
      saveNew();
      );

      gebi("clear-button").addEventListener("click", function()
      clearInputs();
      );

      implementedSelect.addEventListener("change", function(event)
      onSelectionChange(event.target);
      );

      implementedSelect.addEventListener("focus", function(event)
      onSelectionChange(event.target);
      );

      notImplementedSelect.addEventListener("change", function(event)
      onSelectionChange(event.target);
      );

      notImplementedSelect.addEventListener("focus", function(event)
      onSelectionChange(event.target);
      );

      gebi("title-input").addEventListener("keypress", function()
      allowTextOverrride = false;
      );

      gebi("description-input").addEventListener("keypress", function()
      allowTextOverrride = false;
      );

      window.addEventListener("beforeunload", function()
      //alert();
      );


      addEventListeners();


      To run it, put all the files into the same directory and run the Python file. You also need a blank file named data.html in the same directory. Then visit http://localhost:8080/app to access the app.



      You can put a small title for the task and a longer description of the task and click SAVE NEW. SAVE OVER will save over whatever one you have selected. The two arrow buttons move the task between columns. It is important to note that the webserver should not be run over the internet.



      I'm particularly interesting in the following:



      • Are my variables/functions well named?

      • Are my functions doing too much or too little?

      • Are there cases that you can get unexpected behavior?

      • Do you feel that the code is easily extendable/maintainable?

      • What can I do about the long list of addEventListeners?








      share|improve this question












      share|improve this question




      share|improve this question








      edited Mar 2 at 15:44









      200_success

      123k14142399




      123k14142399









      asked Mar 2 at 8:45









      jkd

      18527




      18527

























          active

          oldest

          votes











          Your Answer




          StackExchange.ifUsing("editor", function ()
          return StackExchange.using("mathjaxEditing", function ()
          StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
          StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
          );
          );
          , "mathjax-editing");

          StackExchange.ifUsing("editor", function ()
          StackExchange.using("externalEditor", function ()
          StackExchange.using("snippets", function ()
          StackExchange.snippets.init();
          );
          );
          , "code-snippets");

          StackExchange.ready(function()
          var channelOptions =
          tags: "".split(" "),
          id: "196"
          ;
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function()
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled)
          StackExchange.using("snippets", function()
          createEditor();
          );

          else
          createEditor();

          );

          function createEditor()
          StackExchange.prepareEditor(
          heartbeatType: 'answer',
          convertImagesToLinks: false,
          noModals: false,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          );



          );








           

          draft saved


          draft discarded


















          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f188659%2fto-do-list-web-application%23new-answer', 'question_page');

          );

          Post as a guest



































          active

          oldest

          votes













          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes










           

          draft saved


          draft discarded


























           


          draft saved


          draft discarded














          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f188659%2fto-do-list-web-application%23new-answer', 'question_page');

          );

          Post as a guest













































































          Popular posts from this blog

          Greedy Best First Search implementation in Rust

          Function to Return a JSON Like Objects Using VBA Collections and Arrays

          C++11 CLH Lock Implementation