├── .gitignore
├── Gruntfile.js
├── LICENSE
├── README.md
├── data.csv
├── package.json
└── public
├── css
└── spreadsheet.css
├── index.html
└── js
├── components
├── application.js
├── cell.js
├── row.js
├── spreadsheet.js
└── toolbar.js
├── dispatchers
└── spreadsheet.js
├── entities
├── cell.js
└── formula.js
├── libs
└── filesaver.js
├── main.js
├── mixins
└── cell.js
└── stores
└── spreadsheet.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # Compiled binary addons (http://nodejs.org/api/addons.html)
20 | build/Release
21 |
22 | # Dependency directory
23 | # Commenting this out is preferred by some people, see
24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
25 | node_modules
26 |
27 | # Users Environment Variables
28 | .lock-wscript
29 | public/js/main.dist.js
30 | npm-debug.log
31 | .DS_Store
32 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | module.exports = function( grunt ){
2 | grunt.initConfig({
3 | browserify: {
4 | options: {
5 | transform: [ require('grunt-react').browserify ]
6 | },
7 | app: {
8 | src: 'public/js/main.js',
9 | dest: 'public/js/main.dist.js'
10 | }
11 | },
12 |
13 | connect: {
14 | server: {
15 | options: {
16 | port: 3000,
17 | base: 'public',
18 | livereload: true
19 | }
20 | }
21 | },
22 |
23 | watch: {
24 | js: {
25 | files: ['public/**/**/*.js', 'public/**/*.js', '!public/js/main.dist.js'],
26 | tasks: ['browserify']
27 | },
28 | livereload: {
29 | options: {
30 | livereload: true
31 | },
32 | files: [
33 | 'public/**/**/*.js',
34 | 'public/**/*.js',
35 | 'public/css/*.css',
36 | '!public/js/main.dist.js',
37 | 'public/index.html'
38 | ]
39 | }
40 |
41 | }
42 | });
43 |
44 | grunt.loadNpmTasks('grunt-browserify');
45 | grunt.loadNpmTasks('grunt-contrib-watch');
46 | grunt.loadNpmTasks('grunt-contrib-connect');
47 |
48 | grunt.registerTask( 'dev', ['browserify', 'connect', 'watch'] )
49 | grunt.registerTask('default', ['browserify']);
50 | };
51 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Carlos Villuendas Zambrana
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | react-spreadsheet
2 | =================
3 |
4 | Spreadsheet as a reactJS component.
5 |
6 | ### Install
7 |
8 | Dependencies:
9 |
10 | * nodeJS 0.10
11 | * npm 1.4
12 | * grunt 0.4
13 |
14 | #### How to install
15 |
16 | $ git clone https://github.com/carlosvillu/react-spreadsheet
17 | $ cd react-spreadsheet
18 | $ npm install
19 | $ grunt dev
20 |
21 | Open your browser in http://localhost:3000
22 |
23 | ### Description
24 |
25 | The goal of this project is to build an online spreadsheet. It is a 40x40 grid with editable cells. Cells adapt to the data entered by the user.
26 |
27 | When clicking a cell, the background of the cell turns blue, indicating it has been selected. Double click turns the background orange, indicating it can be edited.
28 |
29 | Cell editing allows the user to enter numeric values or strings, as well as formulas. Formulas must have the following format: =(row number, column number) {op} (row number, column number)...
30 |
31 | =A2+B8 this will add up the contents of the two respective cells
32 |
33 | Editing ends when clicking any different cell. For formulas, the grid shows "Formula result".
34 |
35 | #### Saving a file
36 |
37 | The app saves files in csv format (comma-separated spreadsheet). To save a file click on the download icon (leftmost icon on the toolbar) and an alert will ask you to specify the name of the file. The default name was set as `spreadsheet.csv`.
38 |
39 | Formulas, and not results, are saved in the csv file.
40 |
41 | #### Loading
42 |
43 | To load an existing csv file, simply drag a file onto the grid. The grid will show the exact number of rows and columns with the original data.
44 |
45 | ### Architecture
46 |
47 | The development is web-component oriented. Thus, the spreadhsheet is a single component that can be used in the following way, ``. On the basis of this, it is very simple to create a multi-tab app with several spreadsheets, each with its own store.
48 |
49 | I used ReactJS framework for this design, following Flux's development philosophy.
50 |
51 | The main actors are
52 |
53 | * *Store*: saves the state of the full spreadsheet at any given point. If changes are made in the store, it emits a chain event to update the view.
54 | * *View*: consists of n row views, which, in turn, consist in m cell views.
55 | * *Entity*: there is a single basic entity: the cell. It contains its data and state and knows what to show, depending on its state (editing or non-editing).
56 | * *Dispatcher*: directs the information flow from the view to the store, so that the view never communicates directly with the store. This allows one to keep separate view events from data state-changes.
57 |
58 | ### To do
59 |
60 |
61 | [](https://bitdeli.com/free "Bitdeli Badge")
62 |
63 |
--------------------------------------------------------------------------------
/data.csv:
--------------------------------------------------------------------------------
1 | 0,1,2,3,4,5,6,7,8,9
2 | 10,11,12,13,14,15,16,17,18,19
3 | 20,21,22,23,24,25,26,27,28,29
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-spreadsheet",
3 | "version": "1.0.0",
4 | "description": "Spreadsheet like ReactJS component",
5 | "main": "Gruntfile.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/carlosvillu/react-spreadsheet"
12 | },
13 | "keywords": [
14 | "reactjs",
15 | "web",
16 | "component",
17 | "spreadsheet"
18 | ],
19 | "author": "Carlos Villuendas",
20 | "license": "MIT",
21 | "bugs": {
22 | "url": "https://github.com/carlosvillu/react-spreadsheet/issues"
23 | },
24 | "homepage": "https://github.com/carlosvillu/react-spreadsheet",
25 | "devDependencies": {
26 | "grunt": "^0.4.5",
27 | "grunt-browserify": "^3.2.0",
28 | "grunt-contrib-connect": "^0.8.0",
29 | "grunt-contrib-watch": "^0.6.1",
30 | "grunt-react": "^0.10.0"
31 | },
32 | "dependencies": {
33 | "backbone": "^1.1.2",
34 | "debug": "^2.1.0",
35 | "drag-drop": "^2.0.0",
36 | "flux": "^2.0.1",
37 | "jquery": "^2.1.1",
38 | "lodash": "^2.4.1",
39 | "mathjs": "^1.1.1",
40 | "react": "^0.12.0",
41 | "underscore": "^1.7.0"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/public/css/spreadsheet.css:
--------------------------------------------------------------------------------
1 | [contenteditable]:focus {
2 | outline: 0px solid transparent;
3 | }
4 | .cell {
5 | min-height: 20px;
6 | min-width: 40px;
7 | }
8 | .cell.selected {
9 | background: rgba(169, 208, 221, 0.490196);
10 | }
11 | .cell.selected.active {
12 | background: antiquewhite;
13 | }
14 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |