Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add cycle.js #12

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions implementations/cyclejs-4.1.1/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"presets": ["es2015"],
"plugins": ["transform-object-rest-spread"]
}
7 changes: 7 additions & 0 deletions implementations/cyclejs-4.1.1/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
; editorconfig.org
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = LF
5 changes: 5 additions & 0 deletions implementations/cyclejs-4.1.1/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.idea/
ignore/
node_modules/
npm-debug.log
js
22 changes: 22 additions & 0 deletions implementations/cyclejs-4.1.1/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
The MIT License (MIT)

Copyright (c) 2014 André Staltz

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

17 changes: 17 additions & 0 deletions implementations/cyclejs-4.1.1/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
TodoMVC in Cycle.js
===================

To build the app run:

```
npm install
npm run build
```

TodoMVC example implemented in [Cycle.js](http://cycle.js.org).

[Open the app]( http://cycle.js.org/todomvc-cycle/ )

- - -

To see a version of this codebase using Immutable.js, [click here](https://github.com/cyclejs/todomvc-cycle/pull/9/files).
19 changes: 19 additions & 0 deletions implementations/cyclejs-4.1.1/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en" data-framework="cycle">
<head>
<meta charset="utf-8">
<title>Cycle • TodoMVC</title>
<link href="node_modules/todomvc-common/base.css" rel="stylesheet">
<link href="node_modules/todomvc-app-css/index.css" rel="stylesheet">
</head>
<body>
<section class="todoapp"></section>
<footer class="info">
<p>Double-click to edit a todo</p>
<p><a href="http://github.com/cyclejs/todomvc-cycle">Source code</a></p>
<p>Created by <a href="http://andre.staltz.com">Andre Staltz</a></p>
</footer>
<script src="node_modules/todomvc-common/base.js"></script>
<script src="js/app.js"></script>
</body>
</html>
51 changes: 51 additions & 0 deletions implementations/cyclejs-4.1.1/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{
"name": "todomvc-cycle",
"version": "0.0.0",
"author": "Andre Staltz",
"repository": {
"type": "git",
"url": "git@github.com:staltz/todomvc-cycle.git"
},
"license": "MIT",
"private": true,
"contributors": [
{
"name": "Frederik Krautwald"
},
{
"name": "Kahlil Lechelt",
"email": "hello@kahlil.info"
}
],
"dependencies": {
"@cycle/dom": "12.2.5",
"@cycle/history": "^4.0.0",
"@cycle/isolate": "1.4.x",
"@cycle/storage": "3.0.0-rc3",
"@cycle/xstream-run": "3.1.0",
"history": "^3.0.0",
"todomvc-app-css": "2.0.3",
"todomvc-common": "1.0.1",
"xstream": "5.2.1"
},
"devDependencies": {
"babel-plugin-transform-object-rest-spread": "^6.6.5",
"babel-preset-es2015": "^6.3.13",
"babel-register": "^6.4.3",
"babelify": "^7.2.0",
"browserify": "12.0.1",
"live-server": "^0.9.0",
"mkdirp": "^0.5.1",
"npm-run-all": "^1.4.0",
"uglify-js": "2.6.1",
"watchify": "^3.6.1"
},
"scripts": {
"build-debug": "mkdirp js && browserify src/app.js -t babelify --outfile js/app.js",
"watch:js": "mkdirp js && watchify src/app.js -t babelify --outfile js/app.js -dv",
"serve": "live-server ./",
"uglify": "uglifyjs js/app.js -o js/app.min.js",
"build": "npm run build-debug && npm run uglify",
"start": "npm-run-all --parallel watch:js serve"
}
}
28 changes: 28 additions & 0 deletions implementations/cyclejs-4.1.1/src/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {run} from '@cycle/xstream-run';
import {makeDOMDriver} from '@cycle/dom';
import {makeHistoryDriver} from '@cycle/history'
import {createHistory} from 'history';
import storageDriver from '@cycle/storage';
// THE MAIN FUNCTION
// This is the todo list component.
import TaskList from './components/TaskList/index';

const main = TaskList;

// THE ENTRY POINT
// This is where the whole story starts.
// `run` receives a main function and an object
// with the drivers.
run(main, {
// THE DOM DRIVER
// `makeDOMDriver(container)` from Cycle DOM returns a
// driver function to interact with the DOM.
DOM: makeDOMDriver('.todoapp', {transposition: true}),
// THE HISTORY DRIVER
// A driver to interact with browser history
History: makeHistoryDriver(createHistory(), {capture: true}),
// THE STORAGE DRIVER
// The storage driver which can be used to access values for
// local- and sessionStorage keys as streams.
storage: storageDriver
});
19 changes: 19 additions & 0 deletions implementations/cyclejs-4.1.1/src/components/Task/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import intent from './intent';
import model from './model';
import view from './view';

// THE TODO ITEM FUNCTION
// This is a simple todo item component,
// structured with the MVI-pattern.
function Task({DOM, props$}) {
let action$ = intent(DOM);
let state$ = model(props$, action$);
let vtree$ = view(state$);

return {
DOM: vtree$,
action$,
};
}

export default Task;
36 changes: 36 additions & 0 deletions implementations/cyclejs-4.1.1/src/components/Task/intent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import xs from 'xstream';
import {ENTER_KEY, ESC_KEY} from '../../utils';

// THE TODO ITEM INTENT
// This intent function returns a stream of all the different,
// actions that can be taken on a todo.
function intent(DOMSource) {
// THE INTENT MERGE
// Merge all actions into one stream.
return xs.merge(
// THE DESTROY ACTION STREAM
DOMSource.select('.destroy').events('click')
.mapTo({type: 'destroy'}),

// THE TOGGLE ACTION STREAM
DOMSource.select('.toggle').events('change')
.mapTo({type: 'toggle'}),

// THE START EDIT ACTION STREAM
DOMSource.select('label').events('dblclick')
.mapTo({type: 'startEdit'}),

// THE ESC KEY ACTION STREAM
DOMSource.select('.edit').events('keyup')
.filter(ev => ev.keyCode === ESC_KEY)
.mapTo({type: 'cancelEdit'}),

// THE ENTER KEY ACTION STREAM
DOMSource.select('.edit').events('keyup')
.filter(ev => ev.keyCode === ENTER_KEY)
.compose(s => xs.merge(s, DOMSource.select('.edit').events('blur', true)))
.map(ev => ({title: ev.target.value, type: 'doneEdit'}))
);
}

export default intent;
27 changes: 27 additions & 0 deletions implementations/cyclejs-4.1.1/src/components/Task/model.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import xs from 'xstream';

function model(props$, action$) {
// THE SANITIZED PROPERTIES
// If the list item has no data set it as empty and not completed.
let sanitizedProps$ = props$.startWith({title: '', completed: false});

// THE EDITING STREAM
// Create a stream that emits booleans that represent the
// "is editing" state.
let editing$ =
xs.merge(
action$.filter(a => a.type === 'startEdit').mapTo(true),
action$.filter(a => a.type === 'doneEdit').mapTo(false),
action$.filter(a => a.type === 'cancelEdit').mapTo(false)
)
.startWith(false);

return xs.combine(sanitizedProps$, editing$)
.map(([{title, completed}, editing]) => ({
title,
isCompleted: completed,
isEditing: editing,
}));
}

export default model;
34 changes: 34 additions & 0 deletions implementations/cyclejs-4.1.1/src/components/Task/view.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {button, div, input, label, li} from '@cycle/dom';

function view(state$) {
return state$.map(({title, isCompleted, isEditing}) => {
let todoRootClasses = {
completed: isCompleted,
editing: isEditing,
};

return li('.todoRoot', {class: todoRootClasses}, [
div('.view', [
input('.toggle', {
props: {type: 'checkbox', checked: isCompleted},
}),
label(title),
button('.destroy')
]),
input('.edit', {
props: {type: 'text'},
hook: {
update: (oldVNode, {elm}) => {
elm.value = title;
if (isEditing) {
elm.focus();
elm.selectionStart = elm.value.length;
}
}
}
})
]);
});
}

export default view;
99 changes: 99 additions & 0 deletions implementations/cyclejs-4.1.1/src/components/TaskList/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import xs from 'xstream';
import isolate from '@cycle/isolate'
import intent from './intent';
import model from './model';
import view from './view';
import deserialize from './storage-source';
import serialize from './storage-sink';
import Task from '../Task/index';

// AMEND STATE WITH CHILDREN
// This function creates the projection function
// for the map function below.
function amendStateWithChildren(DOMSource) {
return function (todosData) {
return {
...todosData,
// The list property is the only one being amended.
// We map over the array in the list property to
// enhance them with the actual todo item data flow components.
list: todosData.list.map(data => {
// Turn the data item into an Observable
let props$ = xs.of(data);
// Create scoped todo item dataflow component.
let todoItem = isolate(Task)({DOM: DOMSource, props$});
debugger;

// Return the new data item for the list property array.
return {
...data,
// This is a new property containing the DOM- and action stream of
// the todo item.
todoItem: {
DOM: todoItem.DOM,
action$: todoItem.action$.map(ev => ({...ev, id: data.id}))
}
};
}),
};
};
}

// THE TASKLIST COMPONENT
// This is the TaskList component which is being exported below.
function TaskList(sources) {
// THE LOCALSTORAGE STREAM
// Here we create a localStorage stream that only streams
// the first value read from localStorage in order to
// supply the application with initial state.
let localStorage$ = sources.storage.local.getItem('todos-cycle').take(1);
// THE INITIAL TODO DATA
// The `deserialize` function takes the serialized JSON stored in localStorage
// and turns it into a stream sending a JSON object.
let sourceTodosData$ = deserialize(localStorage$);
// THE PROXY ITEM ACTION STREAM
// We create a stream as a proxy for all the actions from each task.
let proxyItemAction$ = xs.create();
// THE INTENT (MVI PATTERN)
// Pass relevant sources to the intent function, which set up
// streams that model the users actions.
let action$ = intent(sources.DOM, sources.History, proxyItemAction$);
// THE MODEL (MVI PATTERN)
// Actions get passed to the model function which transforms the data
// coming through and prepares the data for the view.
let state$ = model(action$, sourceTodosData$);
// AMEND STATE WITH CHILDREN
let amendedState$ = state$
.map(amendStateWithChildren(sources.DOM))
.remember();
// A STREAM OF ALL ACTIONS ON ALL TASKS
// Each todo item has an action stream. All those action streams are being
// merged into a stream of all actions. Below this stream is passed into
// the proxyItemAction$ that we passed to the intent function above.
// This is how the intent on all the todo items flows back through the intent
// function of the list and can be handled in the model function of the list.
let itemAction$ = amendedState$
.map(({list}) => xs.merge(...list.map(i => i.todoItem.action$)))
.flatten();
// PASS REAL ITEM ACTIONS TO PROXY
// The item actions are passed to the proxy object.
proxyItemAction$.imitate(itemAction$);
// THE VIEW (MVI PATTERN)
// We render state as markup for the DOM.
let vdom$ = view(amendedState$);
// WRITE TO LOCALSTORAGE
// The latest state is written to localStorage.
let storage$ = serialize(state$).map((state) => ({
key: 'todos-cycle', value: state
}));
// COMPLETE THE CYCLE
// Write the virtual dom stream to the DOM and write the
// storage stream to localStorage.
let sinks = {
DOM: vdom$,
storage: storage$,
};
return sinks;
}

export default TaskList;
Loading