Hacker News new | past | comments | ask | show | jobs | submit login
Show HN: A TO-DO app that fits inside a single tweet (ruky.me)
51 points by rukshn on Dec 21, 2020 | hide | past | favorite | 53 comments



My version is even shorter and has a closing <body>-tag and even <html>-tags for the swag:

  <html><body><script src="https://cdn.example.com/js/todoapp.min.js"></script><script>new TodoApp({});</script></body></html>
It's obviously just a concept and doesn't work yet (example.com, durr!) but it's in the same spirit. It only relies on a general purpose todo app library. Default options of course! (Which fortunately make it mount on body)


I golfed it down and fixed a few bugs for you:

    <html><meta http-equiv="refresh" content="1; URL=http://todomvc.com/examples/backbone/" />


Woah, that's amazing! Put this on your resume and then change your Twitter bio to "rockstar JS developer".


rockstar ninja assassin js developer


These terms are outdated. Please replace with ‘data scientist’.


Ha ha I almost gave up and wanted to do the same

But then I realized I’ll be able to figure it out

It’s done for the challenge :)


I think you did it pretty well. Your codes pretty readable despite the constraints. Maybe just let the people know what you would've added or changed if you had a bit more space :)


229 characters using plain JavaScript and proper forms and buttons, for somewhat better accessibility:

  document.write("<form onsubmit='return l.insertAdjacentHTML(`beforeend`,`<li>${x.value.replace(/&/,`&amp;`).replace(/</g,`&lt;`)} <button onclick=this.parentNode.remove()></button>`),x.value=``,!1'><input id=x></form><ul id=l>")
This even leaves enough space in the tweet for <input type=submit value=Add> if you wanted a button beside the initial text box.

As you see, it’d be shorter as straight HTML without the “body must be empty, you can only write inside a <script> tag” requirement, as you could just drop the document.write("…") wrapping.


Nice use of real <form> and submit handling!

Note that HN apparently ate your "delete" character(s) (happened to me with "recycle" sign as well).

Props for HTML escaping, was not in task, as well as separate delete buttons. Note though that entities in the document.write are transformed, so the "html escaping" is broken in effect; either double escaping or codepoints should make it work.

    `<li>${x.value.replace(/\u0026/g,`\u0026amp;`).replace(/</g,`\u0026lt;`
Or, you know, the mighty glorious ancient deprecated "example" element, that is still very well supported. Provided you are OK with danger of '</xmp>'and will not prevent it, result would be quite terse:

    `<li><xmp>${x.value}</xmp>`.


I used U+2718 HEAVY BALLOT X and forgot that it’d be trashed.

On the double escaping thing, I thought of it earlier on and was careful to make sure it did the right thing, but then I transformed the code in such a way that that it ended up being read by the HTML parser again, and neglected to switch it to double escaping, which was rather careless of me. \u0026 can be more briefly written as &amp; (saving one character) or \x26 (saving two). I suppose this bumps my 229 up to 235 (the first of the three ampersands will be parsed correctly unescaped), and brings closer the possibility that DOM methods rather than crazy HTML strings may be more suitable (I think it was ending up something like ten or fifteen characters cheaper this way, even with escaping the value this long way).

I’m not willing to use <xmp> because it leaves a plausible injection attack open. In fact, I’d probably exploit it with my very first TODO item, “fix <xmp>…</xmp> injection attack”!

I like the way you’re thinking on these things. Not many people think of things like the double encoding, and although I didn’t think of <xmp> this time, I’ve definitely thought of it before on other similar things (and used it for fun on hard-coded values, but rejected it if user input is involved).

----

Taking a tip from the original challenger’s solution, I’ve refined my not-using-serialised-HTML solution down to 209 characters, once you replace \u2718 with the actual character which HN eats. Here presented with the necessary wrapping in a data: URI for convenience (<body> now being necessary):

  data:text/html;charset=utf-8,<body><script>d=document;e=(p,e,c)=>(p.append(e=d.createElement(e)),c&&e.append(c),e);x=e(f=e(b=d.body,"form"),"input");f.onsubmit=i=>(e(i=e(l,"li",x.value+" "),"button","\u2718").onclick=_=>i.remove(),x.value="",!1);l=e(b,"ul")</script>


Thanks. And I'm glad xmp still lives in developers' minds.

But whoa, this is approach is really impressive. That "e" function for creation and appending elements is witty -- I wouldn't think it is possible to have such clean and terse JS element "factory" that in effect outperforms (in character count metric) literal HTML content.

(I must confess it took me a while to wrap my head around it, so if anyone is struggling like me, this is the above code unrolled:)

    var Doc = document;
    var FormElem;
    var BodyElem;
    var AppendElem =
    (
     aParentElem
     // reference
     ,
     aTagElemRet
     // tag name for new child, will become child itself
     ,
     aContent
     // (text) content for new child
    ) =>
    (
     aParentElem.append(aTagElemRet = Doc.createElement(aTagElemRet))
     // aTagElemRet tag name (string) becomes HTMLElement
     // and is immediately appended at the end of reference element
     ,
     // comma operator evaluates both sides, returns right
     aContent && aTagElemRet.append(aContent)
     // if third argument is truthy, put it into the new HTMLElement
     ,
     aTagElemRet
     // expression results in this so arrow function returns the new HTMLElement
    );
    var InputElem = AppendElem
     (
      FormElem = AppendElem
       (
        BodyElem = Doc.body, // = parent
        "form" // = tagName
       ), // = parent
      "input" //= tagName
      // no content
     );
    var ListElem = AppendElem(
     BodyElem, // = parent
     "ol" // = tagName
    );
    FormElem.onsubmit =
     pParamLI =>
     // event handler gets SubmitEvent object as a sole argument
     // but it is not needed, so parameter will be reassigned to
     // newly created LI element
     (
      AppendElem(
       pParamLI = AppendElem(
        ListElem, // = parent
        "li", // = tagName
        InputElem.value + " " // = (text) content
       ), // = parent
       "button", // = tagName
       "×" // = content
      ).onclick = _ => pParamLI.remove(), !1
      // inline handler attached to created button
      // ClickEvent passed in _ is not used
      // return !1 what evaluates to false
      // inline handler returning false prevents default action
      // in this case form submission
      // button is in default type, so "submit" would be dispatched
      // on its form otherwise
     );


Funny thing, if you make the most straightforward no dependency version in "Vanilla HTML+CSS+JS" you'll easily get below 250 characters, but it will break the last rule

> Starting HTML body should be empty except the <script>

Because you'll have no <script> tag. To fulfill this, you'll have to unnecessarily wrap the whole thing in script, like:

    <script>document.write(`<style>:checked+*{text-decoration:line-through}#t{display:none}</style><p id="t"><input type="checkbox"><input><div id="f"></div><p><button onclick="f.appendChild(t.cloneNode(true)).id=''">+</button><button onclick="f.innerHTML=''">×</button>`)</script>
(277 chars, golfable even further, but I'm not going to replace button elements with something inappropriate.)


Ah wow the :checked + * {text-decoration:line-through} is pretty smart! No need for JS in that case. Your reply inspired me for a new version:

https://twitter.com/FPresencia/status/1341037189409296384

https://jsfiddle.net/franciscop/6p9xwv7e/

<script>b=u("body").append(u("<input>").on("keyup",e=>{t=e.currentTarget;if(13==e.keyCode){b.append(`<p><label><input type="checkbox"> <span>`+t.value);t.value=''}})).append(u('<button>Clear').on('click',e=>u('p').remove()))</script><style>:checked+*{text-decoration:line-through}


Am I missing something or did everyone forget <s> is a thing in html?


Nope, could be made with <strike> as well, but swapping element tags takes more characters than toggling a class. In the case of UmbrellaJS I made a .wrap() method, but since there is no .unwrap() again it takes more than toggling classes.


Ah, fair. The challenge itself didn't mention toggling (just crossing tasks off), so you could probably get away with it; But that's just the golfer in me talking I guess.


I also thought of using the <s> tag, but I didn’t use it because it is considered to be an outdated tag

But yes that is totally possible


I am not very familiar with JS but doesn't loading a framework externally kind of defeat the purpose?


I wouldn't say so, since it's a general purpose framework. In most languages you use a framework to build GUI applications. Working with the DOM in pure JS is pretty verbose.


Op here: you can’t do it with plain js Plus it’s ok to use frameworks according to the tweet


I don't mean to make this into a code golf thread, but saying it "can't" be done in plain JS was too enticing not to try it. Does this meet the requirements?

<body><script>let win=window;document.body.innerHTML=`<input id="a"/><b id="b"></b><ul id="c"></ul>`,win.b.onclick=a=>{let b=win.c.appendChild(document.createElement("p"));b.innerText=win.a.value,win.a.value="",b.onclick=()=>win.c.removeChild(b)};</script>

JS Fiddle: https://jsfiddle.net/2z0bus8d/

While I think there's always the standard caveat around code golf in JS (you're building on a vast set of APIs and an often resource hungry platform), reaching for a framework really doesn't feel in the spirit of this kind of puzzle.


> I don't mean to make this into a code golf thread

Too late. Golfing notes:

• Your `win` variable is unnecessary: window is approximately the global object (look into globalThis for the subtleties of how window isn’t quite the global object), so you can drop your “win.” every time, e.g. win.c.removeChild(b) can be just c.removeChild(b).

• parent.appendChild(child) → parent.append(child) and parent.removeChild(child) → child.remove(), using newer, shorter DOM methods.

• Skip quotes on attribute values, and trailing slashes are 100% superfluous in HTML (they’re explicitly ignored, as an XHTML compatibility mechanism; I reckon they’re actually a slightly bad thing rather than harmless).


And this is why code golf is fun regardless of language!

The first two are new to me, so thank you, I'll read up. The third succinctly explains a quirk I've never bothered to check the legitimacy of, so thank you again.

With your changes, that brings us way below the character limit, sitting at 208 characters with the <body> and <script> tags the challenge dictates.

I had to rejig things to use parent.append(child) as it doesn't return the element, but using the child.remove() method directly means there's no parameter and therefore one less character there, so it all balances out. I also had to rename the event variable as it caused a scoping conflict for the variable `a`.

With all that space left over you could certainly add in the clear all and strike-through behaviour properly. Maybe even some real state!

Updated fiddle: https://jsfiddle.net/2z0bus8d/1/


<script>x=document;v='innerHTML';o='onclick';x.body[v]="<input id=a><b id=b>X</b><p id=d>clear<ul id=c>",b[o]=e=>{let b=x.createElement("li");c.append(b);b[v]=a.value,a.value="",b[o]=()=>b.classList.toggle('y');d[o]=()=>c[v]=''};</script><style>.y{text-decoration:line-through}

Combining yours & the strike thru example, managed to get both strike thru & clear added in 277 chars. If i could save another char could close the p which would be nice :p

https://jsfiddle.net/hysjgkcm/

Down to 270 https://jsfiddle.net/vr7246jk/1/


<body><script>x=document;x.write`<input id=a><b id=b></b><p id=d>🆑<ul id=c>`,b[o='onclick']=b=>{b=x.createElement`li`;c.append(b);b.v=b[v='innerHTML']=a.value,a.value="",b[o]=e=>b[v]=(b[v]==b.v?'<strike>':'')+b.v;d[o]=e=>c[v]=''}</script>

https://jsfiddle.net/eqym2ac0/

Working with shrew. 240 char with script/html or 217 with as JS.


No JS-in-HTML golfing is complete until you cram your script into onload attribute:

    --<body><script>…</script>
    ++<body onload="…">
(But again, would not fulfill the requirements of "sole script in body" :] )


Ah yes, I’d completely forgotten that appendChild returns the child element and append doesn’t, because it’s so long since I actually had cause to use the appendChild return value!


This has to be the best solution I have seen, I feel it ticks all boxes and better yet it doesn’t use any external libraries like I did

I found it difficult manipulate DOM with just js without hitting the limit, well done sir, you deserve a cookie


Care to elaborate? There are some nifty tricks when it comes to golfing JS.


Well sorry I was in a hurry when I typed the above comment

Well the body can’t have any dom elements according to the challenger

That means I had to create the dom elements as well and I was unable to figure out a way within the given character limit


I wonder if document.write() would work for you. The old-fashioned APIs from before the DOM era (e.g innerHTML, innerText) generally seem to be a lot shorter although they're still a bit verbose.


Yes I guess that was the right way all along, I saw a comment on this thread using innerHtml and write


JS is ok, it’s the DOM APIs that are verbose. I mean this is just to handle the enter key:

    f.addEventListener('keypress',e=>{if(e.keyCode==000) etc()})


Just a fun note: You can replace this

    f.addEventListener('keypress',e=>{...})
With the older but essentially equivalent

    f.onkeypress=e=>{...}
and save 18 characters. You can even use `onkeyup` and save 3 more characters.


Exactly these were the challenges I faced when using plain js


Then that means it doesn't fit.


Hello!,

I'm the original owner of this tweet & new to HN. I never expected this to go crazy at this scale. A big thank you to @rukshn for sharing it here.

My apologies for the narrow requirement definition (had to limit it to 280 chars :)

What I intended was to build the UI from scratch & the logic all within JS.

Learnt a lot from all these contributions & keep 'em coming.

Since everybody is into plain JS, here's my version:

279 chars, No HTML injection/document.write(), can add, strike-through & clear the whole list

a=document;x=a.body;b=(d,t)=>{return d.appendChild(a.createElement(t))};c=(e,v)=>{e.innerHTML=v;return e};i=b(x,"input");c(b(x,"a"),"+").onclick=_=>{c(b(o,"li"),i.value).onclick=(e)=>{e.target.style.textDecoration="line-through"}};o=b(x,"ol");c(b(x,"a"),"X").onclick=_=>{c(o,'')}

https://jsbin.com/weyijorila/edit?html,output


I like the createElement/appendChild-and-return-child and set-innerHTML-and-return-child combinations, they’re just the right level of repetition for minification.

As it stands, the use of innerHTML is bad, because it allows an injection attack (which amuses me given you said “no HTML injection”—though I know what you meant by that); you should use innerText instead. (You should always think very carefully before setting innerHTML or doing other similar tricks like the insertAdjacentHTML that I used in mine.)

There’s also some low-hanging fruit for golfing here:

• appendChild can be replaced with append (this sheds IE support, which I think is reasonable).

• You have arrow functions containing only a return statement; there’s a form that returns the expression: `a => { return b; }` can be written as just `a => b`. Also often in such golfing exercises if you have a function that has no return value, you can just drop the braces and let it return a value anyway, for two characters of savings.

• I said I liked the b and c functions, but it turns out that merging them ends up saving 7 characters in this case, because all but one of the invocations uses both.

Beyond that, I object strenuously to the use of <a onclick=…>+</a> for what is functionally a button, as it’s completely inaccessible to screen readers and the likes (it’s not focusable, isn’t announced as anything, and it doesn’t respond to the keyboard). Use <button> instead.

Here’s where I am on yours, 253 characters:

  a=document;b=(d,t,v)=>(d.append(d=a.createElement(t)),d.innerText=v,d);i=b(x=a.body,"input");b(x,"button","+").onclick=_=>b(o,"li",i.value).onclick=(e)=>e.target.style.textDecoration="line-through";o=b(x,"ol");b(x,"button","X").onclick=_=>o.innerText=""


Nice finishing touch. I think this is the shortest it could get in this approach with semantic tags.

I just learnt about insertAdjacentHTML & last variable return of arrow functions. (not using widely due to compatibility)

Also, you're absolutely right about XSS & Button. I thought innerText only removing text, keeping the <li></li> :)

So, learnt some new stuff. Thank you!

In your code, the <ol> initially outputs "undefined". So we need to change it to o=b(x,"ol","") there.

Final code: a=document;b=(d,t,v)=>(d.append(d=a.createElement(t)),d.innerText=v,d);i=b(x=a.body,"input");b(x,"button","+").onclick=_=>b(o,"li",i.value).onclick=(e)=>e.target.style.textDecoration="line-through";o=b(x,"ol","");b(x,"button","X").onclick=_=>o.innerText=""


> last variable return of arrow functions. (not using widely due to compatibility)

It’s not last variable return such as you get in expression-oriented languages like Ruby or Rust; rather, it’s that if what follows the fat arrow is not a curly brace, it’s parsed as an expression, which is made to be the value returned. So `a => { a }` is the void function, but `a => { return a }` and `a => a` are identity functions. Expressed as an approximate grammar, it’s

  arrow-function = arrow-function-parameters "=>" ( "{" *statement "}" | expression-minus-object-literals )
All forms of arrow functions were introduced at the same time, in ES6 (roughly meaning “everything since but excluding IE”). `a => { return a; }` has identical browser support to `a => a`, so if you use arrow functions you might as well switch to the expression form.


Welcome to HN community @dumindaxsb :]

Thank you for making my Sunday an awesome one


its very "on brand" for a js code golf submission to include a js webframework. kinda funny


Using Vue.js for this task is like pouring a jug of milk with a crane.


I’m open for better answers Clearly I failed in plain js I’d love to see a plain js solution


I wouldn't say so. People are using Vue.js in lieu of jQuery these days.


Code golf is generally the most interesting when the rules are specific enough that every entry has the same constraints.

In this case, "to-do app" and "general purpose library" are so general that it becomes a game of semantics. The winner will have the broadest definition of "general purpose" and narrowest definition of "app".

I don't think we should criticize the person who posted this. It was a silly prompt that was guaranteed to get controversial submissions that people would pick apart.


Here is my attempt! Please note that in the original tweet the "code" including the 3rd party library seems to be excluded so I also excluded it:

https://jsfiddle.net/franciscop/gexfdq1y/

https://twitter.com/FPresencia/status/1340952793352310785

Notably, and differently from the solution of the person originally issuing the challenge and from this HN entry of Rukshan answering to the challenge, I solved it including the three constrains:

• Esc to clear the list

• Enter to add item

• Click an item to cross/uncross it

Here is a cleaned-up and explained version of the same:

https://jsfiddle.net/franciscop/x1sc7rk9/


My solution in 155 chars, vanilla JS

<script>document.write(`<input id=i><a${o=" onclick="}'u.${H="innerHTML"}+="<li${o}${H}=${H}.strike()>"+i.value'>+<a${o}u.${H}="">x</a><ol id=u>`)</script>

Demo: https://codepen.io/xem/pen/dypVzaw?editors=1100

Cheers :)


Awesome What are the $ and H, is it some sort of JQuery? Can you explain :)


The "ungolfed" code looks like this:

<input id=i> <a onclick='u.innerHTML+="<li onclick=innerHTML=innerHTML.strike()>"+i.value'>+ <a onclick=u.innerHTML="">X</a> <ol id=u>

There are 4 occurrences of "innerHTML" and 3 occurrences of " onclick='" so I replaced those two substrings with H and o.

The whole HTML is rendered using an ES6 template literal (document.write(` ... `))

Everytime a ${H} or a ${o} is present in the template, the value of H or o is copied here.

It's a native feature of JS since ES6.

Besides that, the second <a> implicitly closes the first <a>, so no need to use </a>. The id's are global, that's why we can access i.value or u.innerHTML directly. In onclick, "this" (the current element) is implicit. Finally, the strikethrough is done using the (deprecated but still working) .strike() String method.


I think it should have been an absolute no-brainer to avoid external libraries. I'm rather disappointed in the challenge tweet for even allowing that.


Yes it would have been more interesting and made me think harder without using a shortcut with a framework


This is an interesting problem in the same way that many other codegolf problems are interesting. The question isn’t about solving something that’s directly applicable to everyday life, but instead it’s exploring what is possible in the language/framework provided as guidelines for the competition. So as long as we take it at face value it’s a fun and entertaining thought exercise.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: