2

I am currently trying to auto-create bullet points in a tree hierarchy formation where, when you press tab, you would go to the sub-point of the main point.

It is meant to be similar to the Outliner in Microsoft Word. However, I am struggling what approach I should make to this as currently from the code below, I can only create a list of points but I want to be able to indent some of the points where they would move to the right along with the bullet point.

function list() {
  let ul = document.getElementById("ul");
  let li = document.createElement("li");
  ul.appendChild(li);
}
<div id="ul" contenteditable="true" class="editor" draggable="true" onfocus="list()" style="font-weight:normal;">
</div>
Run_Script
  • 2,487
  • 2
  • 15
  • 30

2 Answers2

1

If I got you right all you would need is to set a padding on the parent ul, which funny enough they are already set, if you want to further indent it, you could add even more padding to it, but from your example it is not clear enough, you are not setting an innerHtml or innerText to the li, on the other hand, if you want to focus a div, you should probably set a tabindex to it.

EDIT: I did not catch before that you were using a div with the id of ul, still you can add an inner child li, but it doesn't make much sense, if you still decide to go that way you can style each li child to have the same margin left and also set the before attribute to have the '*'

EDIT 2:

Initial markup:

<div class="outline-editor">
  <button>+ Add child</button>
  <input type="text" name="content" id="add-text" />
  <ul>
    Items go here:
  </ul>
</div>

<script>
  const button = document.querySelector('button')
  const input = document.querySelector('input')
  const ul = document.querySelector('ul')

  function addChild(li) {
    let ul = li.querySelector('ul');
    if (!ul) {
      ul = document.createElement('ul')
      li.appendChild(ul)
    }

    let childLi = document.createElement('li')
    let childButton = document.createElement('button')
    childButton.innerText = '+ Add child'

    let childSpan = document.createElement('span')
    childSpan.innerText = input.value;
    childButton.addEventListener('click', () => {
      addChild(childLi)
    })
    childLi.style.marginLeft = '15px'
    childLi.appendChild(childSpan)
    childLi.appendChild(childButton)
    ul.appendChild(childLi)
  }

  function handleClick(e) {
    let li = document.createElement('li')
    let childButton = document.createElement('button')
    childButton.innerText = '+ Add child'

    let childSpan = document.createElement('span')
    childSpan.innerText = input.value;
    childButton.addEventListener('click', () => {
      addChild(li)
    })
    li.style.marginLeft = '15px'
    li.appendChild(childSpan)
    li.appendChild(childButton)
    ul.appendChild(li)
  }

  button.addEventListener('click', handleClick)
</script>

You can check the behaviour on the codepen:

https://codepen.io/jenaro94/pen/RwNwyBr

Of course you can further style each li element and you should probably use a modal to check what the user writes as a child to each li.

EDIT 3:

Ok this is mhy last and final edit, hope this works well for you.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>
  <body>
    <style>
  .outline-editor {
    margin: 100px auto;
  }

  ul {
    position: relative;
  }

  button {
    border: 1px solid black;
  }

  input {
    border: none;
  }
</style>

<div class="outline-editor">
  <ul id="top-level">
    Items go here:

    <li>
      <input type="text" name="content" id="add-text" />
    </li>
  </ul>
</div>

<script>
  const input = document.querySelector('#add-text')
  const ul = document.querySelector('#top-level')

  function addChild(li) {
    let ul = li.tagName === 'UL' ? li : li.querySelector('ul')
    if (!ul) {
      ul = document.createElement('ul')
      li.appendChild(ul)
    }

    let childLi = document.createElement('li')

    let text = document.createElement('input')
    text.type = 'text'
    text.rows = 1
    text.cols = 10
    text.focus()
    text.addEventListener('keydown', e => {
      e.preventDefault()
      if (e.keyCode === 9) {
        addChild(childLi)
      } else if (e.keyCode === 13) {
        addChild(li)
      }
    })
    childLi.appendChild(text)
    ul.appendChild(childLi)
  }

  function handleKeyDown(e) {
    e.preventDefault();
    e.stopPropagation();
    let li = document.createElement('li')
    if (e.keyCode === 9) {
      addChild(this.parentElement)
    } else if (e.keyCode === 13) {
      addChild(this.parentElement.parentElement)
    }
  }

  input.addEventListener('keydown', handleKeyDown)
</script>
  </body>
</html>
  • yeah sorry it's not very clear. I see what you mean. What do you think would be the most suitable way of doing this. – Emre Karayalcin Dec 02 '19 at 20:01
  • Well if you could explain better what the purpose of everything is, maybe I could help you better, for starters, if you are going to be making a list, I would use a ul or a ol tag, on the other side, what do you want to insert in the li tags? Is there text in there? Is this a classic to do list app? Why don't you use the onclick event instead of the focus event? Try and make your question answer all those things and I will try to help you – Jenaro Calviño Dec 02 '19 at 20:13
  • My aim is to create an outline editor so when the user writes a sentence or a paragraph. The user is then able to add points onto that specific sentence they previously entered. My task describes this as a tree hierarchy. I am specifically not able to use jQuery. – Emre Karayalcin Dec 02 '19 at 20:25
  • Ok now, I will change my answer to try and fit your request – Jenaro Calviño Dec 02 '19 at 20:49
  • Thats great. I see what you have done there. However, you might of misunderstood me or I did not explain it clear enough. I was looking at it as more of a notepad like so when you press tab you move onto the secondary point and when you press enter you go back to the main points. – Emre Karayalcin Dec 02 '19 at 21:01
  • Thank you for your help really appreciated. some sections were very useful. – Emre Karayalcin Dec 02 '19 at 21:52
0

I can add a new level on a tab key-press, but it cannot keep track of the nodes it has created inside...

const TAB_KEY = 9;
const ENTER_KEY = 13;

let editor = document.querySelector('.editor');

editor.appendChild(document.createElement('li')); // Add initial (for visibility)

editor.addEventListener('focus', (e) => {
  //let ul = e.target;
  //let li = document.createElement('li');
  //ul.appendChild(li);
});

editor.addEventListener('keydown', (e) => {
  var code = e.keyCode || e.which;
  if (code == TAB_KEY) {
    e.preventDefault(); // Stop treating it like an actual tab
    let parent = e.target;
    let ul = document.createElement('ul');
    let li = document.createElement('li');
    ul.appendChild(li);
    parent.appendChild(ul);
    moveCursorToEnd(li);
  } else if (code == ENTER_KEY) {
    e.preventDefault(); // Stop treating it like an actual line feed
    let parent = e.target;
    let li = document.createElement('li');
    parent.appendChild(li);
    moveCursorToEnd(li);
  }
});

// See: https://stackoverflow.com/a/55811159/1762224
function moveCursorToEnd(el) {
  el.focus();
  document.execCommand('selectAll', false, null);
  document.getSelection().collapseToEnd();
}
.editor {
  font-weight: normal
}
<div class="editor" contenteditable="true" draggable="true"></div>
Mr. Polywhirl
  • 42,981
  • 12
  • 84
  • 132
  • Hi, thats exactly what I was looking for. The problem is I have the javascript in a separate file to the HTML so I link it to the HTML through src. However, it seems to still not recognise it. – Emre Karayalcin Dec 02 '19 at 20:55