F7 + VUE = TODO

by Nikolay Kuznetsov

HTML

<link rel="stylesheet" href="https://framework7.io/dist/css/framework7.ios.colors.min.css">
<link rel="stylesheet" href="https://framework7.io/dist/css/framework7.ios.min.css">
<script src="https://framework7.io/dist/js/framework7.min.js"></script>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div class="views">
<div class="view view-main">
<div class="pages">
<div data-page="home" class="page navbar-fixed" id="app">
<div class="navbar">
<div class="navbar-inner">
<div class="left"> </div>
<div class="center">F7 + VUE = ToDo</div>
<div class="right"><a href="" @click="addTodoUI" class="link">Add Task</a></div>
</div>
</div>
<div class="page-content">
<div class="content-block-title">Todo Filters</div>
<div class="content-block buttons-row">
<a href="#" class="button" :class="{ active: visibility == 'all' }" @click="setFilter('all')">All ({{todos.length}})</a>
<a href="#" class="button" :class="{ active: visibility == 'active' }" @click="setFilter('active')">Active ({{remaining}})</a>
<a href="#" class="button" :class="{ active: visibility == 'completed' }" @click="setFilter('completed')">Complete ({{todos.length - remaining}})</a>
</div>
<div class="list-block">
<ul>
<li v-for="(todo,index) in filteredTodos" :key="todo.id" class="swipeout" @deleted="deleteTodo(index)">
<div class="swipeout-content">
<label class="label-checkbox item-content" :class="{completed: todo.done}">
<input type="checkbox" name="ks-checkbox" v-model="todo.done">
<div class="item-media"><i class="icon icon-form-checkbox"></i></div>
<div class="item-inner">
<div class="item-title">{{todo.title}}</div>
</div>
</label>
</div>
<div class="swipeout-actions-right">
<a href="#" class="swipeout-delete">Delete</a>
</div>
</li>

</ul>
<div class="list-block-label">
Swipe left to delete task
</div>
</div>
<div class="content-block buttons-row">
<a href="#" class="button" @click="completeAll()">Complete all</a>
<a href="#" class="button" @click="removeCompleted()">Remove...

CSS

label.completed {
	color: #d9d9d9;
	text-decoration: line-through;
}

.view-left {
  border-right: 1px solid #b2b2b2;
}

.panel-left .page-content {
  padding: 0;
}

.panel-left .page {
  margin-top: 44px;
  padding-bottom: 44px;
}

.panel-left .navbar {
  position: relative;
  margin-bottom: -44px;
}

body.with-panel-right-reveal .panel-overlay {
  margin-left: 0;
}

@media (min-width: 569px) {
  .panel.panel-left.panel-cover {
    width: 320px;
    left: -320px;
  }
  body.with-panel-left-cover .panel-left {
    -webkit-transform: translate3d(320px, 0, 0);
    -ms-transform: translate3d(320px, 0, 0);
    transform: translate3d(320px, 0, 0);
  }
}

@media (min-width: 769px) {
  body .panel.panel-left.panel-cover {
    left: 0;
    width: 320px;
    display: block;
  }
  body.with-panel-left-cover .panel-left {
    -webkit-transform: translate3d(0px, 0, 0);
    -ms-transform: translate3d(0px, 0, 0);
    transform: translate3d(0px, 0, 0);
  }
  .view-main {
    float: right;
    width: -webkit-calc(100% - 320px);
    width: -moz-calc(100% - 320px);
    width: -ms-calc(100% - 320px);
    width: calc(100% - 320px);
  }
  .view-main .navbar .open-panel {
    display: none;
  }
}

i.tabbar-demo-icon-1 {
  width: 30px;
  height: 30px;
  background-image: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30' fill='%23929292'><g><circle cx='15.2' cy='8.8' r='1.5'/><polygon points='16.7,12.3 16.7,11.7 16.7,11.3 13.7,11.3 13.7,12.3 14.7,12.3 14.7,20.3 13.7,20.3 13.7,21.3 17.7,21.3 17.7,20.3 16.7,20.3'/><path d='M15.2,2.3C8.3,2.3,2.7,7.9,2.7,14.8s5.6,12.5,12.5,12.5c6.9,0,12.5-5.6,12.5-12.5S22.1,2.3,15.2,2.3z M15.2,26.3 c-6.3,0-11.5-5.2-11.5-11.5S8.8,3.3,15.2,3.3s11.5,5.2,11.5,11.5S21.5,26.3,15.2,26.3z'/></g></svg>");
}

.active i.tabbar-demo-icon-1 {
  background-image: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' viewBox='0 0 30 30' fill='%23007aff'><path...

JavaScript

var myApp = new Framework7({
 modalTitle: 'F7 + VUE'
});
var $$ = Dom7;

var mainView = myApp.addView('.view-main', {
  dynamicNavbar: true,
  animateNavBackIcon: true,
  scrollTopOnStatusbarClick: true,
});

var STORAGE_KEY = 'f7-vue-todo';
var todoStorage = {
  load: function () {
    var todos = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]')
    return todos
  },
  save: function (todos) {
    localStorage.setItem(STORAGE_KEY, JSON.stringify(todos))
  }
}


// visibility filters
var filters = {
  all: function (todos) {
    return todos
  },
  active: function (todos) {
    return todos.filter(function (todo) {
      return !todo.done
    })
  },
  completed: function (todos) {
    return todos.filter(function (todo) {
      return todo.done
    })
  }
}

var app = new Vue({
  // element to mount to
  el: '#app',
  // initial data
  data: {
  	todos:todoStorage.load(),
 		visibility: 'all'  
   },
   watch: {
    todos: {
      handler: function (todos) {
        todoStorage.save(todos)
      },
      deep: true
    }
  },
  
  computed: {
  	  remaining: function () {
      		return filters.active(this.todos).length
    	},
      
      filteredTodos: function () {
      	return filters[this.visibility](this.todos)
    	},
  
  },

  // methods
  methods: {
  	setFilter: function(filter){
    	this.visibility = filter;
      
    },
  	addTodoUI: function(event) {
        myApp.prompt('Add task ' , function (value) {
       	//console.log(app)
       	if(value){
					app.addTodo(value);
       }
    });
    },
    addTodo: function (value) {
        this.todos.push({"id": Date.now(),"title": value, "done": false });    
    },
    deleteTodo: function (todo) {
         this.todos.splice(todo, 1);
    },
    removeCompleted: function(){
    	 this.todos = filters.active(this.todos);
       this.visibility = "all";
    },
    completeAll: function(){
        this.todos.forEach(function (todo) {
          todo.done = true;
        });
        
    }
  }
})