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

Use immutable data structures #9

Closed
wants to merge 2 commits into from
Closed
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
12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@
"license": "MIT",
"private": true,
"devDependencies": {
"babelify": "^6.0.2",
"browserify": "^10.0.0",
"cyclejs": "0.21.1",
"immutable": "^3.7.3",
"todomvc-app-css": "^1.0.0",
"todomvc-common": "^1.0.1",
"babel": "^5.1.13",
"babelify": "^6.0.2",
"browserify": "^10.0.0"
"uglifyjs": "^2.4.10"
},
"scripts": {
"build-debug": "mkdir -p js && browserify src/app.js -t babelify --outfile js/app.js",
"uglify": "uglifyjs js/app.js -o js/app.min.js",
"build": "npm run build-debug && npm run uglify"
"build-debug": "browserify src/app.js -d -t babelify > js/app.js",
"build": "browserify src/app.js -t babelify | uglifyjs -c > js/app.js"
}
}
99 changes: 42 additions & 57 deletions src/models/todos.js
Original file line number Diff line number Diff line change
@@ -1,88 +1,73 @@
import Cycle from 'cyclejs';
import {Map} from 'immutable';
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if it's good practice to have Map conflicting with the native one in new JS engines ...

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there's no conflict, a module is a new scope: it's not overwritten


function getFilterFn(route) {
switch (route) {
case '/active': return (task => task.completed === false);
case '/completed': return (task => task.completed === true);
case '/active': return (x => !x.get('completed'));
case '/completed': return (x => x.get('completed'));
default: return () => true; // allow anything
}
}

function determineFilter(todosData, route) {
todosData.filter = route.replace('/', '').trim();
todosData.filterFn = getFilterFn(route);
return todosData;
}

function searchTodoIndex(todosList, todoid) {
let top = todosList.length;
let bottom = 0;
let pointerId;
let index;
while (true) { // binary search
index = bottom + ((top - bottom) >> 1);
pointerId = todosList[index].id;
if (pointerId === todoid) {
return index;
} else if (pointerId < todoid) {
bottom = index;
} else if (pointerId > todoid) {
top = index;
}
}
return todosData
.set('filter', route.replace('/', '').trim())
.set('filterFn', getFilterFn(route));
}

function makeModification$(intent) {
let clearInputMod$ = intent.clearInput$.map(() => (todosData) => {
todosData.input = '';
return todosData;
let clearInputMod$ = intent.clearInput$.map(() => todosData => {
return todosData.set('input', '');
});

let insertTodoMod$ = intent.insertTodo$.map((todoTitle) => (todosData) => {
let lastId = todosData.list.length > 0 ?
todosData.list[todosData.list.length - 1].id :
0;
todosData.list.push({
let insertTodoMod$ = intent.insertTodo$.map(todoTitle => todosData => {
let list = todosData.get('list');
let lastId = list.size ? list.last().get('id') : 0;

let todo = Map({
id: lastId + 1,
title: todoTitle,
completed: false
});
todosData.input = '';
return todosData;

return todosData.update('list', list => list.push(todo).set('input', ''));
});

let editTodoMod$ = intent.editTodo$.map((evdata) => (todosData) => {
let todoIndex = searchTodoIndex(todosData.list, evdata.id);
todosData.list[todoIndex].title = evdata.content;
return todosData;
let editTodoMod$ = intent.editTodo$.map(evdata => todosData => {
let todoIndex = todosData.get('list')
.findIndex(x => x.get('id') === evdata.id);

return todosData.update('list', list =>
list.update(todoIndex, x => x.set('title', evdata.content))
);
});

let toggleTodoMod$ = intent.toggleTodo$.map((todoid) => (todosData) => {
let todoIndex = searchTodoIndex(todosData.list, todoid);
let previousCompleted = todosData.list[todoIndex].completed;
todosData.list[todoIndex].completed = !previousCompleted;
return todosData;
let toggleTodoMod$ = intent.toggleTodo$.map(todoId => todosData => {
let todoIndex = todosData.get('list')
.findIndex(x => x.get('id') === todoId);

return todosData.update('list', list =>
list.update(todoIndex, x => x.set('completed', !x.get('completed')))
);
});

let toggleAllMod$ = intent.toggleAll$.map(() => (todosData) => {
let allAreCompleted = todosData.list
.reduce((x, y) => x && y.completed, true);
todosData.list.forEach((todoData) => {
todoData.completed = allAreCompleted ? false : true;
});
return todosData;
let toggleAllMod$ = intent.toggleAll$.map(() => todosData => {
let state = todosData.get('list').some(x => !x.get('completed'));
return todosData.update('list', list =>
list.map(x => x.set('completed', state))
);
});

let deleteTodoMod$ = intent.deleteTodo$.map((todoid) => (todosData) => {
let todoIndex = searchTodoIndex(todosData.list, todoid);
todosData.list.splice(todoIndex, 1);
return todosData;
let deleteTodoMod$ = intent.deleteTodo$.map(todoId => todosData => {
return todosData.update('list', list =>
list.filter(x => x.get('id') !== todoId)
);
});

let deleteCompletedsMod$ = intent.deleteCompleteds$.map(() => (todosData) => {
todosData.list = todosData.list
.filter(todoData => todoData.completed === false);
return todosData
let deleteCompletedsMod$ = intent.deleteCompleteds$.map(() => todosData => {
return todosData.update('list', list =>
list.filter(x => !x.get('completed'))
);
});

return Cycle.Rx.Observable.merge(
Expand Down
10 changes: 3 additions & 7 deletions src/sinks/local-storage.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
export default function localStorageSink(todosData) {

// Observe all todos data and save them to localStorage
let savedTodosData = {
list: todosData.list.map(todoData =>
({
title: todoData.title,
completed: todoData.completed,
id: todoData.id
})
)
list: todosData.get('list').filter(x => x ).toJS()
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sometimes I got null values in the list. Don't know if this was a testing issue or something about immutable.js I haven't discovered yet..
That's why I filter the values here ...

};

localStorage.setItem('todos-cycle', JSON.stringify(savedTodosData))
};
20 changes: 4 additions & 16 deletions src/sources/todos.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,16 @@
import Cycle from 'cyclejs';
import {fromJS} from 'immutable';

function merge() {
let result = {};
for (let i = 0; i < arguments.length; i++) {
let object = arguments[i];
for (let key in object) {
if (object.hasOwnProperty(key)) {
result[key] = object[key];
}
}
}
return result;
}

let defaultTodosData = {
let defaultTodosData = fromJS({
list: [],
input: '',
filter: '',
filterFn: () => true // allow anything
};
});

let storedTodosData = JSON.parse(localStorage.getItem('todos-cycle')) || {};

let initialTodosData = merge(defaultTodosData, storedTodosData);
let initialTodosData = defaultTodosData.merge(storedTodosData);

export default {
todosData$: Cycle.Rx.Observable.just(initialTodosData)
Expand Down
30 changes: 17 additions & 13 deletions src/views/todos.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ function vrenderHeader(todosData) {
h('h1', 'todos'),
h('input#new-todo', {
type: 'text',
value: propHook(elem => { elem.value = todosData.input; }),
value: propHook(elem => elem.value = todosData.get('input')),
attributes: {
placeholder: 'What needs to be done?'
},
Expand All @@ -18,16 +18,19 @@ function vrenderHeader(todosData) {
}

function vrenderMainSection(todosData) {
let allCompleted = todosData.list.reduce((x, y) => x && y.completed, true);
let list = todosData.get('list');
let allCompleted = list.every(x => x.get('completed'));

return h('section#main', {
style: {'display': todosData.list.length ? '' : 'none'}
style: {'display': list.size ? '' : 'none'}
}, [
h('input#toggle-all', {
type: 'checkbox',
checked: allCompleted
}),
h('ul#todo-list', todosData.list
.filter(todosData.filterFn)
h('ul#todo-list', list
.filter(todosData.get('filterFn'))
.toJS()
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if it is better to case to JS before the map or after.
Doesn't matter that much I guess. My thought was that it might be more performant this way.

.map(todoData =>
h('todo-item.todo-item', {
key: todoData.id,
Expand All @@ -41,30 +44,31 @@ function vrenderMainSection(todosData) {
}

function vrenderFooter(todosData) {
let amountCompleted = todosData.list
.filter(todoData => todoData.completed)
.length;
let amountActive = todosData.list.length - amountCompleted;
let list = todosData.get('list');
let filter = todosData.get('filter');
let amountCompleted = list.count(x => x.get('completed'));
let amountActive = list.size - amountCompleted;

return h('footer#footer', {
style: {'display': todosData.list.length ? '' : 'none'}
style: {'display': list.size ? '' : 'none'}
}, [
h('span#todo-count', [
h('strong', String(amountActive)),
' item' + (amountActive !== 1 ? 's' : '') + ' left'
]),
h('ul#filters', [
h('li', [
h('a' + (todosData.filter === '' ? '.selected' : ''), {
h('a' + (filter === '' ? '.selected' : ''), {
attributes: {'href': '#/'}
}, 'All')
]),
h('li', [
h('a' + (todosData.filter === 'active' ? '.selected' : ''), {
h('a' + (filter === 'active' ? '.selected' : ''), {
attributes: {'href': '#/active'}
}, 'Active')
]),
h('li', [
h('a' + (todosData.filter === 'completed' ? '.selected' : ''), {
h('a' + (filter === 'completed' ? '.selected' : ''), {
attributes: {'href': '#/completed'}
}, 'Completed')
])
Expand Down