我的待辦事項工具的設計和搭建(IFE前端2015春季 任務3)

這是我幾個月以前的項目做品,花了至關的時間去完善。博客人氣不高,但拿代碼的人很多,因此一直處於保密狀態。沒有公開代碼。但若是對你有幫助,並能提出指導意見的,我將十分感謝。javascript

IFE前端2015春季 任務3

綜合練習

任務描述

參考設計稿實現一個簡單的我的任務管理系統:以下圖css

設計稿

任務需求描述:html

  • 最左側爲任務分類列表,支持查看全部任務或者查看某個分類下的全部任務
    • 初始時有一個默認分類,進入頁面時默認選中默認分類
    • 分類支持多層級別。
    • 分類支持增長分類、刪除分類兩個操做在左側分類最下方有添加操做,點擊後彈出浮層讓輸入新分類的名稱,新分類將會被添加到當前選中的分類下。浮層能夠爲自行設計實現,也能夠直接使用prompt。當鼠標hover過某一個分類時,右側會出現刪除按鈕,點擊後,彈出確認是否刪除的浮層,確認後刪除掉該分類。彈出的確認浮層能夠自行設計實現,也能夠直接使用confirm。不能爲默認分類添加子分類,也不能刪除默認分類
    • 每個分類名字後顯示一個當前分類下的未完成任務總數量。
  • 中間列爲任務列表,用於顯示當前選中分類下的全部未完成任務
    • 任務列表按日期(升序或者降序,自行設定)進行聚類
    • 用不一樣的字體顏色或者圖標來標示任務的狀態,任務狀態有兩張:已完成未完成
    • 下方顯示新增任務的按鈕,點擊後,右側列會變成新增任務編輯界面。
    • 單擊某個任務後,會在右側顯示該任務的詳細信息。
    • 在任務列表的上方有任務篩選項,能夠選擇在任務列表中顯示全部任務,或者只顯示已完成或者未完成的任務。
  • 右側爲任務詳細描述部分
    • 第一行顯示任務標題,對於未完成的任務,在標題行的右側會有完成任務的操做按鈕及編輯任務的按鈕。
    • 點擊完成任務按鈕時,彈出確認是否確認完成的浮層,確認後該任務完成,更新中間列任務的狀態。彈出的確認浮層能夠自行設計實現,也能夠直接使用confirm
    • 點擊編輯任務操做後,右側變動爲編輯窗口。
  • 新增及編輯任務窗口描述
    • 有3個輸入框:分別是標題輸入框,完成日期輸入框及內容輸入框
    • 標題輸入框:輸入標題,爲單行,須要自行設定一個標題輸入限制的規則(如字數),並給出良好提示。
    • 日期輸入框:單行輸入框,按照要求格式輸入日期,如yyyy-mm-dd
    • 內容輸入框:多行輸入框,自行設定一個內容輸入的限制(如字數),並給出良好提示。
    • 確認按鈕:確認新增或修改。
    • 取消按鈕:取消新增或修改。

任務實現要求:前端

  • 整個界面的高度和寬度始終保持和瀏覽器窗口大小一致。當窗口變化高寬時,界面中的內容自適應變化。
  • 左側列表和中間列表保持一個固定寬度(自行設定),右側自適應。
  • 須要自行設定一個最小寬度和最小高度,當瀏覽器窗口小於最小值時,界面內容的高度和寬度再也不跟隨變化,容許瀏覽器出現滾動條。
  • 經過本地存儲來做爲任務數據的保存方式。
  • 不使用任何類庫及框架。
  • 儘量符合代碼規範的要求。
  • 瀏覽器兼容性要求:Chrome、IE8+。

注意java

該設計稿僅爲線框原型示意圖,全部的視覺設計不須要嚴格按照示意圖。若是有設計能力的同窗,歡迎實現得更加美觀,若是沒有,也能夠按照線框圖實現。如下內容能夠自行發揮:git

  • 背景顏色
  • 字體大小、顏色、行高
  • 線框粗細、顏色
  • 圖標、圖片
  • 高寬、內外邊距

解決方案

整個環境應該經過後端的交互實現。可是簡單地實現就是ajax方法。github

項目要求不用任何類庫框架,可是任務2中的$d類庫是本身寫的。能夠檢驗$d類庫的可靠性,因此用了也問題不大。ajax

待辦事項列表是一個至關典型的數據結構,再設計數據結構時,顯然應該用面向對象的思路觸發操做。spring

基本樣式和交互

第一個問題就是高度自填充。json

和寬度同樣,一個元素要在父級高度有數值時才能設定百分比高度。

分類列表

分類列表的方法應該是ul-li體系

  • ul.classify-list
    • li
      • h3.title-list
        • a.title1:點擊標籤,包含分類一級標題,點擊時給h3加上激活樣式。
        • a.close:關閉刪除按鈕(正常時隱藏,鼠標劃過期顯示)
      • ul classify-list2
        • li (如下是二級分類標題結構)

其中特殊分類是「默認分類」,不能刪除

點擊標題出現激活樣式:

我以爲這隻須要考慮當前點選邏輯,當點擊了二級分類,再點擊其它一級分類時,激活樣式顯示在所點擊的一級分類上。原來的二級分類激活樣式消失。

$('.title1').on('click',function(){
        $('.title-list').removeClass('classify-active');
        $(this.parentNode).addClass('classify-active');
    });

    $('.title2').on('click',function(){
        $('.title-list').removeClass('classify-active');
        $('.title-list',this.parentNode.parentNode.parentNode.parentNode).addClass('classify-active');

        $('.title-list2').removeClass('classify-active2');
        $(this.parentNode).addClass('classify-active2');
    });

注:兩次點擊的效果不一樣,因此考慮寫一個toggle方法。

//toggle方法:
$d.prototype.toggle=function(_event){
    var _arguments=Array.prototype.slice.call(arguments).slice(1,arguments.length);//把toggle的arguments轉化爲數組存起來,以便在其它函數中能夠調用。
    //console.log(_arguments);
    //私有計數器,計數器會被一組對象所享用。
    function addToggle(obj){
        var count=0;
        addEvent(obj,_event,function(){
            _arguments[count++%_arguments.length].call(obj);
        });
    }

    each(this.objs,function(item,index){
        addToggle(item);
    });
};

    //使用示例:
    $('.title1').toggle('click',function(){
        $('.classify-list2',this.parentNode.parentNode).obj.style.display='block';
    },function(){
        $('.classify-list2',this.parentNode.parentNode).obj.style.display='none';
    });

而後再寫一個hover方法

//hover方法
$d.prototype.hover=function(fnover,fnout){
    var i=0;
    //對於返回器數組的內容
    each(this.objs,function(item,index){
        addEvent(item,'mouseover',fnover);
        addEvent(item,'mouseout',fnout);
    });
    return this;
};

//使用示例
$('.title-list').hover(function(){
        if($('.classify-close',this.parentNode).obj){
            $('.classify-close',this.parentNode).move({'opacity':100});
        }
    },function(){
        if($('.classify-close',this.parentNode).obj){
            $('.classify-close',this.parentNode).move({'opacity':0});
        }
    });

還有一個狀態,若是點擊某個分類,下面沒有子分類,就什麼都不顯示

$('.title1').toggle('click',function(){
        if($('.classify-list2',this.parentNode.parentNode).obj){
            $('.classify-list2',this.parentNode.parentNode).obj.style.display='block';
        }
    },function(){
        if($('.classify-list2',this.parentNode.parentNode).obj){
            $('.classify-list2',this.parentNode.parentNode).obj.style.display='none';
        }
    });

基本邏輯以下

待辦事項列表

篩選欄有三個按鈕和一個搜索框,其中,這三個按鈕應該擁有激活狀態

$('.todo-btn').on('click',function(){
        $('.todo-btn').removeClass('todo-btn-active');
        $(this).addClass('todo-btn-active');
    });

後面的基本結構是這樣的——已完成和未完成都應該以不一樣的樣式顯示

<div class="todo-content">
                    <ul class="todo-date">
                        <span>2017-1-24</span>
                        <li class="completed"><a href="javascript:;">任務1</a></li>
                        <li class="uncompleted"><a href="javascript:;">任務2</a></li>
                        <li class="completed"><a href="javascript:;">任務3</a></li>
                    </ul>

                    <ul class="todo-date">
                        <span>2017-1-25</span>
                        <li class="completed"><a href="javascript:;">任務1</a></li>
                        <li class="completed"><a href="javascript:;">任務2</a></li>
                        <li class="uncompleted"><a href="javascript:;">任務3</a></li>
                    </ul>
                </div>

界面大體是這個樣子

要求篩選欄經過keyUp事件輸入或點擊按鈕,下面的框動態顯示結果。

這些交互是經過數據特性來設置的,因此不必在這裏寫。

主體顯示區

相似Ps畫板。注意畫板去容許出現垂直滾動條。

<div class="content">
                <div class="content-outer">
                    <div class="content-info">
                        <div class="content-header">
                            <h3>待辦事項標題</h3>
                            <a href="javascript:;">編輯</a>
                        </div>
                        <div class="content-substract">
                            任務日期:2017-1-25
                        </div>
                    </div>
                    
                    <div class="content-content">
                        <div class="content-paper">
                            <h4>啊!今天是個好日子</h4>
                            <p>完成task3的設計和樣式實現。</p>
                        </div>
                    </div>
                </div>

            </div>

佈局樣式

.content{
    width: auto;
    height: inherit;
    padding-left: 512px;
}
.content-outer{
    height: 100%;
    position: relative;
}
.content-info{
    height: 91px;
}
.content-content{
    position: absolute;
    width: 100%;
    top:91px;
    bottom: 0;
    background: #402516;
    overflow-y: scroll;
}

利用絕對定位的方式實現畫板區(.content-content)的高度自適應,而後.paper經過固定的margin實現區域延伸。

那麼整個界面就出來了。

前端組件開發

嚴格點說說「前端組件開發」這個名字並不許確。這裏只涉及了本項目中組件的控制邏輯,並不展現數據結構部分的邏輯。

靜態的模態彈窗

給分類列表和任務欄添加一個「添加」按鈕,要求添加時彈出一個模態彈窗。

彈窗提供最基本的功能是:一個輸入框,自定義你的分類名或任務名,一個取消按鈕,一個肯定按按鈕。

模態彈窗是由兩個部分組成

  • 遮罩層(黑色,半透明)
  • 彈窗體

採用的是動態建立的方式能夠給指定的彈窗添加id,兩個都是用絕對定位實現。

<div class="add-mask"></div>
            <div id="(自定義)" class="add">
                <div class="add-title">
                    <h4>添加內容</h4>
                </div>
                <div class="add-content">
                    <span>名稱:</span>
                    <input type="text" name="" value="">
                    <div class="btns">
                        <button id="exit" type="button">取消</button>
                        <button id="submit" type="button">肯定</button>
                    </div>
                </div>
            </div>

這個應該直接放到body標籤結束前。

組件結構

寫一個面向對象的組件,能夠想象它的調用過程是怎樣的:

// 以添加分類爲例:
var addCategoryModal=new Modal();

// 初始化
addCategoryModal.init({
  //這裏放配置
});
// 生成窗口
categoryModal.create();

new 出一個新的組件,而後進行初始化,傳入必要的參數,若是不傳配置,組件有自身的配置。

function Modal(){
    this.settings={
        // 這裏放默認的配置
    };
}

轉入的配置疊加能夠經過一個擴展函數來實現:

function extend(obj1,obj2){
    for(var attr in obj2){
        obj1[attr]=obj2[attr];
    }
}
// ...
//這裏是以自定義的option配置覆蓋內部配置
Modal.prototype.init=function(option){
    extend(this.settings,option);
};

那麼這個框架就搭建起來了。

彈窗須要哪些配置?

在這個項目中,只須要指定彈窗提示內容title和彈窗類型type(這裏就三個,一個是目錄addCtategory,另外一個是任務addMission,最後一個是通用提示框tips)就能夠了。

其中,type將成爲模態彈窗頂層容器的id值。

組件實現

生成窗口無非是給DOM追加一個節點。

Modal.prototype.create=function(){
    var oDialog=document.createElement('div');

    oDialog.className='add';
    oDialog.id=this.settings.type;
    if(this.settings.type=='tips'){
        oDialog.innerHTML =
            '<div class="add-title">'+
                '<h4>信息提示</h4>'+
            '</div>'+
            '<div class="add-content">'+
                '<span>'+this.settings.tips+'</span>'+
                '<div class="btns">'+
                    '<button id="exit" type="button">我知道了</button>'+
                '</div>'+
            '</div>';
    }else{
        oDialog.innerHTML =
            '<div class="add-title">'+
                '<h4>添加內容</h4>'+
            '</div>'+
            '<div class="add-content">'+
                '<span>'+this.settings.title+'名稱:</span>'+
                '<input class="input" type="text" value="">'+
                '<div class="btns">'+
                    '<button id="exit" type="button">取消</button>'+
                    '<button class="submit" type="button">肯定</button>'+
                '</div>'+
            '</div>';
    }

    // 顯示效果
    document.body.appendChild(oDialog);
    $('.add-mask').obj.style.display='block';

    //彈窗位置指定,絕對居中
    var clientWidth=document.documentElement.clientWidth;
    var clientHeight=document.documentElement.clientHeight;

    oDialog.style.left=(clientWidth)/2-175+'px';
    oDialog.style.top=(clientHeight)/2-75+'px';

    //關閉按鈕
    function remove(){
        document.body.removeChild(oDialog);
        $('.add-mask').obj.style.display='none';
        $(this).un('click',remove);
    }
    $('#exit').on('click',remove);
};

好了。咱們給一個#addCategory的按鈕添加點擊事件:

$('#addCategory').on('click',function(){
        var categoryModal=new Modal();
        categoryModal.init(
            {
                type:newCategory,
                title:'目錄'
            }
        );
        categoryModal.create();
    });

效果就出來了:

組件完善

要讓這個組件具備基本的功能,還須要寫遮罩層,取消按鈕等。

注意:如下效果所有在create方法中完成

遮罩

遮罩(.mask):遮罩是一個隱藏的,不須要動態顯示。

.add-mask{
    position: absolute;
    left: 0;
    top:0;
    right: 0;
    bottom:0;
    background: rgba(0,0,0,0.5);
    z-index: 99;/*注意.add的層級應該大於99*/
}
<div class="add-mask" style="display:none;"></div>

而後添加一個顯示效果:

// 顯示效果
    document.body.appendChild(oDialog);
    $('.add-mask').obj.style.display='block';
取消按鈕(#exit)

本着清理乾淨的精神,除了把oDialog從document中清掉。

//關閉按鈕
    function remove(){
        document.body.removeChild(oDialog);
        $('.add-mask').obj.style.display='none';
    }
    $('#exit').on('click',remove);

那麼取消就寫完了。

肯定按鈕

肯定按鈕也能夠寫一個手動關閉彈窗的方法:

Modal.prototype.exit=function(){
    document.body.removeChild($('.add').obj);
    $('.add-mask').obj.style.display='none';
}

實際上

到此能夠認爲,這個靜態的模態彈窗完成。

效果:

markdown組件

雖然任務要求不用任何框架,可是咱們的需求在當前來講已經開始超越了任務自己的需求,不用jQuery勉強能夠接受,可是前端渲染你的content部份內容,marke.js顯然是最好的選擇。關於marked.js的用法,能夠參照marked.js簡易手冊

實際上這已是第三次在項目中用到mark.js,用起來水到渠成。

固然不想作任何處理的話,也能夠跳過這節。

引入marked.js和highlight.js

如今把它拖進來。並引用一個基本能搭配當前頁面風格的樣式庫。

<link rel="stylesheet" type="text/css" href="css/css.css"/>
    <link rel="stylesheet" type="text/css" href="css/solarized-dark.css"/>


    <script type="text/javascript" src="js/dQuery.js"></script>

    <script type="text/javascript" src="js/marked.js"></script>
    <script type="text/javascript" src="js/highlight.pack.js"></script>
    <script >hljs.initHighlightingOnLoad();</script>

    <script type="text/javascript" src="js/js.js"></script>

而後:

// 渲染頁面模塊
    var rendererMD = new marked.Renderer();
    marked.setOptions({
        renderer: rendererMD,
        highlight: function (code,a,c) {
            return hljs.highlightAuto(code).value;
        },
        gfm: true,
        tables: true,
        breaks: false,
        pedantic: false,
        sanitize: false,
        smartLists: true,
        smartypants: false
    });

    //用於測試效果
    $('.content-paper').obj.innerHTML=marked('# 完成markdown模塊開發\n---\nRendered by **marked**.\n\n```javascript\nfunction(){\n  console.log("Hello!Marked.js!");\n}\n```\n這是響應式圖片測試:\n![](http://images2015.cnblogs.com/blog/1011161/201701/1011161-20170127184909206-861797658.png)\n1\. 傳進去前端的代碼結構必須符合樣式庫的要求。\n2\. 我要把頁面的代碼通通是現貨高亮顯示——好比這樣`alert(Hello!)`');
重寫樣式庫

儘管有了樣式庫的支持,可是這個樣式庫只是定義了配色。而瀏覽器默認的樣式被當初的css-reset給幹掉了。

markdown最經常使用的效果就是代碼高亮,搭配圖片顯示,

在過去的項目(Node.js博客搭建)中,我已經使用了marked.js重寫了一個還算漂亮的樣式庫(基於marked.js樣式庫和bootstrap樣式庫code和pre部分)。如今把重寫CSS的要點簡單概括如左:

  • 響應式圖片
.content-paper img{
  display: block;
  max-width: 100%;
  height: auto;
  border: 1px solid #ccc;
}
  • 列表效果(其實也包括ol-li)
.content-paper ul li{
  list-style: disc;
  margin-left: 15px;
}
.content-paper ol li{
  list-style: decimal;
  margin-left: 15px;
}
  • 文本間距,行間距,好比,p標記,h1-h6的間距等等。大小最好用em和百分比顯示,好比個人p標記字體大小爲1.05em

效果以下:

那麼效果馬上有了。

搜索(過濾)組件

搜索組件只作一件事情:根據代辦事項列表窗(ul.todo-content)中的文本節點,經過監聽文本輸入框(input.search)的內容,綁定keyUp事件綁定,查找數據集。

若是按照封裝對象的思路來寫,一個是監聽模塊,一個是顯示模塊。爲了方便起見,給各自的元素加上同名id。

思路

就實現上來講彷佛很簡單,查找#todo-content裏面的文本節點,而後轉化爲數組:

// 搜索組件
function Search(listener,shower){
    this.listener=$(listener);
    this.shower=$(shower);
}
Search.prototype.filter=function(){
    var value=this.listener.obj.value;
    var content=this.shower.obj.innerText;
    console.log(content.split('\n'));
};

$(funciton(){
  $('#search').on('keyup',function(){
        var search=new Search('#search','#todo-content');
        search.filter();
    });
});

然而不幸的事情發生了:

竟然把任務日期打出來了。此外還有一個空文本。

由於html代碼結構是這樣的:

<div id='todo-content' class="todo-content">
                    <ul class="todo-date">
                        <span>2017-1-24</span>
                        <li class="completed"><a href="javascript:;">任務1</a></li>
                        <li class="uncompleted"><a href="javascript:;">任務2</a></li>
                        <li class="completed"><a href="javascript:;">任務3</a></li>
                    </ul>

                    <ul class="todo-date">
                        <span>2017-1-25</span>
                        <li class="completed"><a href="javascript:;">任務1</a></li>
                        <li class="completed"><a href="javascript:;">任務2</a></li>
                        <li class="uncompleted"><a href="javascript:;">任務3</a></li>
                    </ul>
                </div>

既然這樣,就查找var search=new Search('#search','#todo-content li');把,而後對li對象作一個for循環。沒有的就設置display爲none:

// 搜索組件
function Search(listener,shower){
    this.listener=$(listener);
    this.shower=$(shower);
}

Search.prototype.filter=function(){
    var value=this.listener.obj.value;

    var content=[];
    for(var i=0;i<this.shower.objs.length;i++){
        this.shower.objs[i].style.display='block';
        content.push(this.shower.objs[i]);
        if(this.shower.objs[i].innerText.indexOf(value)==-1){
            this.shower.objs[i].style.display='none';
        }
    }
};

// 調用
var search=new Search('#search','#todo-content li');
$('#search').on('keyup',function(){
    search.filter();
});

效果:

其它組件的實現

目前搜索組件有一個很大的問題,就是沒法實現數據的雙向綁定。

輸入框搜索組件是獨立的判斷條件。下面的三個按鈕是公用一套判斷信息。

思路是活用html元素的data屬性。給全部節點添加data-searchdata-query兩個屬性,全部html元素初始的兩個屬性都是true。當不一樣的按鈕被點選,就執行query方法把符合條件的元素的data-xxx設置爲true。而後再進行渲染render,兩個屬性都爲true的纔不給添加.hide樣式(hide的樣式就是display爲none)。

// 搜索組件
function Search(listener,shower){
    this.listener=$(listener);
    this.shower=$(shower);
    this.key='all';
}

Search.prototype.filter=function(){
    var value=this.listener.obj.value;
    // 先所有設置爲true
    for(var j=0;j<this.shower.objs.length;j++){
        this.shower.objs[j].setAttribute('data-search', "true");
    }
    //綁定當前按鈕的搜索條件
    this.query(this.key);


    for(var i=0;i<this.shower.objs.length;i++){
        if(this.shower.objs[i].innerText.indexOf(value)==-1){
            this.shower.objs[i].setAttribute('data-search', 'false');
        }
    }

    this.renderer();

};

Search.prototype.query=function(key){
    this.key=key;
    for(var j=0;j<this.shower.objs.length;j++){
            //this.shower.objs[i].style.display='block';
        this.shower.objs[j].setAttribute('data-key',"true");
    }
    this.renderer();


    for(var i=0;i<this.shower.objs.length;i++){
        this.shower.objs[i].setAttribute('data-key',"true");
        if(key!=='all'){
            if(this.shower.objs[i].className!==key){
                this.shower.objs[i].setAttribute('data-key',"false");
            }
        }
    }
    this.renderer();
};
// 最後是渲染方法
Search.prototype.renderer=function(){
    for(var i=0;i<this.shower.objs.length;i++){
        var a=this.shower.objs[i].getAttribute('data-search');
        var b=this.shower.objs[i].getAttribute('data-key');
        if(a=="true"&&b=="true"){
            $(this.shower.objs[i]).removeClass('hide');
        }else{
            $(this.shower.objs[i]).addClass('hide');
        }
    }
};

那麼搜索機制就幾行

var search=new Search('#search','#todo-content li');
    $('#search').on('keyup',function(){
        search.filter();
    });

    $('#completed').on('click',function(){
        search.query('completed');
    });

    $('#all').on('click',function(){
        search.query('all');
    });

    $('#uncompleted').on('click',function(){
        search.query('uncompleted');
    });

最終效果:

數據可視化

數據可視化是個大坑。

基本邏輯是:

  • 分類模塊從後端獲取數據根據數據進行分類展現
    • 當分類被點選,則暴露該分類下的一級任務信息給任務模塊,
    • 點擊建立模塊,根據當前層級,建立一個平級的分類,若是沒有點選,則建立一個一級分類。
  • 任務模塊根據暴露出來的信息,按照「建立日期」的邏輯從新分類並進行排列
    • 篩選組件查找暴露出來的信息,按照篩選規則從新排列
    • 根據分類區塊的點選結果,暴露一個當前選擇的任務給右側的信息內容模塊
  • 右側信息內容模塊根據任務模塊暴露出來的信息,用markdown渲染內容並進行顯示。

顯然用面向對象的思路是最好的。

原始數據結構的設計

涉及無級樹的設計。

縱觀前面的邏輯,每一個數據須要哪些特性?

一個好的數據結構,前端拿到以後渲染也是方便的。不妨直觀一點,用數組+對象的方式來組織信息。

var json=[
  {
    "categoryName":一級目錄名,
    "id":惟一的流水號或是時間戳
    "missions"(該目錄下屬的任務):[
      {
      "id":任務id
        "title":任務名,
        "createTime":推送時間,
        "isCompleted":是否完成,
        "content":任務的文本內容
      },
      //...
    ],// 沒有則爲空數組[]
    
    "list"(該目錄下屬的直接子分類):[
      {
        "categoryName":二級目錄名,
        "id":...
        。。。
      }
    ]//沒有則爲空數組[]。
  },
  {
    "categoryName":一級目錄名2
    "mission":[
      //...
    ],
    "list":[
      //...
    ]
  },
  //...
]

對沒有使用真正後端支持的的前端渲染來講,處理這樣的數據是十分之麻煩的。

接下來就是渲染。

渲染分類模塊

多級分類的ul以下:

構造一個對象:

/*遞歸實現獲取無級樹數據並生成DOM結構*/
function Data(data){
    this.data=data;
}

Data.prototype.renderTree=function(selector){
    var _this=this;
    var result='';
    (function getTree(_data){
        var obj=_data;
        for(var i=0;i<obj.length;i++){
            var str='';
            if(obj==_this.data){//若是是頂層一級標題則用較大的顯示
                str=
                '<li class="lv1"><h3 class="title-list">'+
                    '<a '+'data-id="'+obj[i]["id"]+'"'+' class="title1" href="javascript:;"><img src="images/dirs.png"> '+obj[i]["categoryName"]+'</a>'+
                    '<a class="classify-close" href="javascript:;"><img src="images/close.png"></a>'+
                '</h3>';
            }else{
                str='<li>'+
                    '<h4 class="title-list2">'+
                        '<a '+'data-id="'+obj[i]["id"]+'"'+' class="title2" href="javascript:;"><img src="images/dir.png" alt=""> '+obj[i]["categoryName"]+'</a>'+
                        '<a class="classify-close2" href="javascript:;"><img src="images/close.png"></a>'+
                    '</h4>';
            }



            result+=str;
            if(obj[i]["list"]!==[]){
                //注意:此處表面還未結束
                result+='<ul class="classify-list2">';
                getTree(obj[i]["list"]);
                result+='</ul></li>';
            }else{
                result+='</li>';
            }


        }
    })(_this.data);

    $(selector).obj.innerHTML=result;
};

好比,我要在ul#categories下渲染數據:

var _data=new Data(json);
_data.renderTree('#categories');
動態交互的改進

還記得動態交互吧。以前的DOM操做極其噁心(出現了連續4個parentNode),並且是寫死的,如今實現一個根據標籤查找第一個祖先class名的函數:

function getAcient(target,className){
    //console.log(target.parentNode.classList);
    var check=false;
    for(var i=0;i<target.parentNode.classList.length;i++){
        if(target.parentNode.classList[i]==className){
            check=true;
            break;
        }
    }

    if(check){
        return target.parentNode;
    }else{
        return getAcient(target.parentNode,className);
    }
}

// 好比說,getAcient(document.getElementById('li1'),'ul1')
// 表示查找一個#li1的元素最近的、class名包括.ul的祖先。

有了它,以前的噁心寫法大多能夠取代了。

點擊li.lv1下的任何a,都響應內容

$('.lv1').delegate('a','click',function(){
        $('.title-list').removeClass('classify-active');
        $('.title-list2').removeClass('classify-active2');
        // 頂層加類
        $('h3',getAcient(this,'lv1')).addClass('classify-active');
        if(this.parentNode.className!=="title-list"){
            $(this.parentNode).addClass('classify-active2');
        }
    });

    $('.title2').on('click',function(){
        $('.title-list').removeClass('classify-active');
        $('.title-list2').removeClass('classify-active2');
        $(this.parentNode).addClass('classify-active2');
    });

如今反觀toggle,添加數據時展現很是不直觀,爲了代碼的簡潔,因此刪掉。

接下來把全部涉及效果的函數封裝爲Data的一個方法,每次執行renderTree()方法,就渲染一次交互效果。

Data.prototype.renderCategoryEfect=function(){
    $('.title2').on('click',function(){
        $('.title-list').removeClass('classify-active');
        $('.title-list2').removeClass('classify-active2');
        $(this.parentNode).addClass('classify-active2');
    });

    // $('.title2').toggle('click',function(){
    //  if($('.classify-list2',this.parentNode.parentNode).obj){
    //      $('.classify-list2',this.parentNode.parentNode).obj.style.display='block';
    //  }
    // 
    // },function(){
    //  if($('.classify-list2',this.parentNode.parentNode).obj){
    //      $('.classify-list2',this.parentNode.parentNode).obj.style.display='none';
    //  }
    // });


    // $('.title1').toggle('click',function(){
    //  if($('.classify-list2',this.parentNode.parentNode).obj){
    //      $('.classify-list2',this.parentNode.parentNode).obj.style.display='block';
    //  }
    // },function(){
    //  if($('.classify-list2',this.parentNode.parentNode).obj){
    //      $('.classify-list2',this.parentNode.parentNode).obj.style.display='none';
    //  }
    // });

    $('.title-list2').hover(function(){
        if($('.classify-close2',this.parentNode).obj){
            $('.classify-close2',this.parentNode).move({'opacity':100});
        }
    },function(){
        if($('.classify-close2',this.parentNode).obj){
            $('.classify-close2',this.parentNode).move({'opacity':0});
        }
    });


    $('.title-list').hover(function(){
        if($('.classify-close',this.parentNode).obj){
            $('.classify-close',this.parentNode).move({'opacity':100});
        }
    },function(){
        if($('.classify-close',this.parentNode).obj){
            $('.classify-close',this.parentNode).move({'opacity':0});
        }
    });
};

這裏沒有把delegate監聽事件寫進去,由於這涉及到其它對象的交互。

通過這一步,至少檯面上的代碼已經大大簡化了。

添加數據

當點擊添加分類,出來一個模態彈窗,在模態彈窗輸入內容。則添加一個目錄到相應的數據結構下:

固然是push方法.

var obj={
                "categoryName":value,//經過輸入框獲取到的數據
                "id":Date.parse(new Date()),
                "missions":[],
                "list":[]
            };

這須要id值。

Data.prototype.setCategoryActiveId=function(id){
    this.category.id=id;
};

當分類目錄下的信息被點選,就從對應的a標記獲取data-id值。

查找data-id值,不然把data-id設爲null.

寫一個Data對象的addCategory方法。把它添加到點擊事件中。

Data.prototype.addCategory=function(id,category){
    var data=this.data;
    var arr=[];
    if(id==null){
        arr=data;
    }else{
        (function findPositon(_id,_data){
            for(var i=0;i<_data.length;i++){
                console.log(_data[i]["id"])
                if(_data[i]["id"]==_id){
                    arr=_data[i]["list"];
                }

                if(_data[i]["list"]!==[]){
                    findPositon(_id,_data[i]["list"]);
                }
            }
        })(id,this.data);
    }
    console.log(arr);
    arr.push(category);
};

而後在監聽事件中,寫一個方法當點擊時把對應a的data-id值存起來:

Data.prototype.setCategoryActiveId=function(id){
    this.category.id=id;
};



//。。。
//經過事件代理監聽數據
    $('.lv1').delegate('a','click',function(){
        $('.title-list').removeClass('classify-active');
        $('.title-list2').removeClass('classify-active2');
        // 頂層加類
        $('h3',getAcient(this,'lv1')).addClass('classify-active');
        if(this.parentNode.className!=="title-list"){
            $(this.parentNode).addClass('classify-active2');
        }
        dataRenderer.setCategoryActiveId(this.getAttribute('data-id'));
    });

注意,每次渲染後內容都會丟失,

因此添加分類概括起來作這麼幾件事:

$('#newCategory .submit').on('click',function(){
            // 獲取激活的a標記的id(在你點選時已經存在了`.category.id`裏)
            var idName=dataRenderer.category.id;
            //獲取數據
            var value=$('#newCategory .input').obj.value;
            //構造目錄信息,mission和list固然是空的。
            var obj={
                "categoryName":value,
                "id":Date.parse(new Date()),
                "missions":[],
                "list":[]
            };
            //添加進去!
            dataRenderer.addCategory(idName,obj);
            //根據更新後的數據執行渲染
            dataRenderer.renderTree('#categories');
            // 添加基本效果。
            dataRenderer.renderCategoryEfect();
            // 事件監聽,不作這一步的話就再沒法更新信息
            $('.lv1').delegate('a','click',function(){
                $('.title-list').removeClass('classify-active');
                $('.title-list2').removeClass('classify-active2');
                // 頂層加類
                $('h3',getAcient(this,'lv1')).addClass('classify-active');
                if(this.parentNode.className!=="title-list"){
                    $(this.parentNode).addClass('classify-active2');
                }

                //把a標記的data-id值拿到手
                dataRenderer.setCategoryActiveId(this.getAttribute('data-id'));
            });
            // 模態彈窗關閉
            document.body.removeChild($('.add').obj);
            $('.add-mask').obj.style.display='none';
        });
    });

通過無數次失敗的嘗試和換位思考,目錄樹的結果終於出來了:

原本只想作二級目錄就夠了。如今終於實現多級目錄了

分類的刪除

漫長而糾結的分類模塊尚未結束,可是思路已經愈來愈清晰了。接下來要作的是點擊x,刪除分類。

經過dom查找(這個關閉按鈕的父級的第一個元素),能夠獲得這個分類下的id值。而後寫一個方法,找到該id目錄所在的引用位置,將它用splice抹掉!(不能用filter去重)

方法的核心是一個遞歸,一個循環。

//根據id值刪除分類:
Data.prototype.deleteCategory=function(id){
    var _this=this;
    var parentDataArr=[];//描述待刪除數據所在的數組。
    var childData={};//描述待刪除對象
    (function findPosition(_id,_data){
        for(var i=0;i<_data.length;i++){
            //console.log(_data[i]["id"])
            if(_data[i]["id"]==_id){
                parentDataArr=_data;
                childData=_data[i];
            }

            if(_data[i]["list"]!==[]){
                findPosition(_id,_data[i]["list"]);
            }
        }
    })(id,_this.data);

    for(var i=0;i<parentDataArr.length;i++){
        if(parentDataArr[i]==childData){
            parentDataArr.splice(i,1);
        }
    }
};

怎麼調用呢?

主要是渲染後再次綁定——寫一個的函數吧!

function close(){
        $('.classify-close2').on('click',function(){
            // 獲取id值
            var dataId=this.parentNode.childNodes[0].getAttribute('data-id');
            // 從數據中刪除該id所在的目錄
            dataRenderer.deleteCategory(dataId);
            // 渲染
            dataRenderer.renderTree('#categories');
            dataRenderer.renderCategoryEffect();
            //再次綁定事件
            close();
        });

        $('.classify-close').on('click',function(){
            var dataId=this.parentNode.childNodes[0].getAttribute('data-id');
            dataRenderer.deleteCategory(dataId);

            dataRenderer.renderTree('#categories');
            dataRenderer.renderCategoryEffect();

            close();
        });

        $('.lv1').delegate('a','click',function(){
            $('.title-list').removeClass('classify-active');
            $('.title-list2').removeClass('classify-active2');

            $('h3',getAcient(this,'lv1')).addClass('classify-active');
            if(this.parentNode.className!=="title-list"){
                $(this.parentNode).addClass('classify-active2');
            }

            dataRenderer.setCategoryActiveId(this.getAttribute('data-id'));
        });

    };

    close();

這個close函數之因此不作成執行函數,由於在添加時還須要再調用一次。如今close函數已經包含了事件代理,delegate代理在添加目錄後就能夠刪掉了。

var dataRenderer=new Data(json);
    // 渲染目錄樹
    dataRenderer.renderTree('#categories');
    dataRenderer.renderCategoryEffect();

    // 刪除分類邏輯
    function close(){
        $('.classify-close2').on('click',function(){
            // 獲取id值
            var dataId=this.parentNode.childNodes[0].getAttribute('data-id');
            // 從數據中刪除該id所在的目錄
            dataRenderer.deleteCategory(dataId);
            // 渲染
            dataRenderer.renderTree('#categories');
            dataRenderer.renderCategoryEffect();
            //再次綁定事件
            close();
        });

        $('.classify-close').on('click',function(){
            console.log(1);
            var dataId=this.parentNode.childNodes[0].getAttribute('data-id');
            dataRenderer.deleteCategory(dataId);

            dataRenderer.renderTree('#categories');
            dataRenderer.renderCategoryEffect();

            close();
        });
        // 事件代理
        $('.lv1').delegate('a','click',function(){
            $('.title-list').removeClass('classify-active');
            $('.title-list2').removeClass('classify-active2');

            $('h3',getAcient(this,'lv1')).addClass('classify-active');
            if(this.parentNode.className!=="title-list"){
                $(this.parentNode).addClass('classify-active2');
            }

            dataRenderer.setCategoryActiveId(this.getAttribute('data-id'));
        });
    }

    close();

    // 添加分類邏輯
    $('#addCategory').on('click',function(){
        var categoryModal=new Modal();
        categoryModal.init(
            {
                type:'newCategory',
                title:'目錄'
            }
        );
        categoryModal.create();

        // 添加分類
        $('#newCategory .submit').on('click',function(){
            var idName=dataRenderer.category.id;
            var value=$('#newCategory .input').obj.value;

            var obj={
                "categoryName":value,
                "id":Date.parse(new Date()),
                "missions":[],
                "list":[]
            };

            dataRenderer.addCategory(idName,obj);
            dataRenderer.renderTree('#categories');
            dataRenderer.renderCategoryEffect();
            // 綁定刪除分類
            close();
            // 把當前激活的id設置爲null,這是細節處理
            dataRenderer.setCategoryActiveId(null);
            // 模態彈窗關閉
            categoryModal.exit();
        });
    });

效果就出來了,可是仍是有一個細節問題。

經過點擊,就自動獲取了目錄元素的id值,可是當我想建立一級目錄時怎麼辦?

我讓點擊全部分類,就Data對象的id值設爲null。

Data.prototype.clearCategoryId=function(){
    $(this.category.id+' *').removeClass('classify-active');
    $(this.category.id+' *').removeClass('classify-active2');
    this.category.id=null;
};

而後在刪除時處理掉。

默認分類

根據需求,默認分類不可不可刪除(沒有刪除按鈕,天然刪除不了),不能添加子分類(添加分類時出現錯誤提示),但旗下任務能夠添加任務內容。其實就是一個判斷的事情。

實際上這是一個特殊的分類數據結構。就把它的id值設置爲0吧!

好比:

var json =
    [
        {
            "id":0,
            "categoryName":"默認分類(不可操做子分類)",
            "missions":[
                {
                    "title":"默認分類示例",
                    "createTime":"1970-1-1",
                    "isCompleted":"true",
                    "content":"完成默認分類說明的撰寫"
                },
                // ...

            ],
            "list":[]
        },
      // ...

用前面設計的方法足夠渲染出默認分類了。

首先,在renderTree方法中判斷id值,若是爲‘0’,就不渲染刪除按鈕

Data.prototype.renderTree=function(selector){
    var _this=this;
    var result='';
    (function getTree(_data){
        var obj=_data;
        for(var i=0;i<obj.length;i++){
            var str='';
            if(obj==_this.data){//若是是頂層一級標題則用較大的字體
                if(obj[i]["id"]=='0'){//id爲0只可能在設計數據的第一層顯示
                    str=
                    '<li class="lv1"><h3 class="title-list">'+
                        '<a '+'data-id="'+obj[i]["id"]+'"'+' class="title1" href="javascript:;"><img src="images/dirs.png"> '+obj[i]["categoryName"]+'</a>'+
                    '</h3>';
                }else{
                    str=
                    '<li class="lv1"><h3 class="title-list">'+
                        '<a '+'data-id="'+obj[i]["id"]+'"'+' class="title1" href="javascript:;"><img src="images/dirs.png"> '+obj[i]["categoryName"]+'</a>'+
                        '<a class="classify-close" href="javascript:;"><img src="images/close.png"></a>'+
                    '</h3>';
                }
              // 後文略

其次點擊分類添加時,判斷id值是否爲‘0’,是的話就渲染彈出框:

// 添加分類邏輯
    $('#addCategory').on('click',function(){
        var categoryModal=new Modal();
        var idName=dataRenderer.category.id;

        if(idName=='0'){
            categoryModal.init(
                {
                    type:'tips',
                    tips:'不能爲默認目錄添加子分類!'
                }
            );
            categoryModal.create();
        }else{
            categoryModal.init(
                {
                    type:'newCategory',
                    title:'目錄'
                }
            );
            categoryModal.create();
        }
      // 後文略

效果:

任務欄的渲染

Data對象暴露任務內容給中間的任務欄

獲取了Data.category.id以後,就把數據集的mission獲取到了。

這個方法獨立出來意義不大,只是寫出來測試用:

Data.prototype.findMission=function(id){
    var _this=this;
    var arr=[];

    (function findPosition(_id,_data){
        //console.log(_data);
        for(var i=0;i<_data.length;i++){
            //console.log(_data[i]["id"])
            if(_data[i]["id"]==_id){
                arr=_data[i]["missions"];
            }

            if(_data[i]["list"]!==[]){
                findPosition(_id,_data[i]["list"]);
            }
        }
    })(id,_this.data);
    return arr;
};

而後在事件代理中加上這麼一句:

console.log(dataRenderer.findMission(this.getAttribute('data-id')));

,每次點擊目錄標題,就在console看到了該分類下的任務內容了!。

根據內容組織信息渲染

中間列爲任務列表,用於顯示當前選中分類下的全部未完成任務。

這在React.js中小菜一疊。可是若是不用框架,會要麻煩些。

正常來講由上至下渲染是最好的。

當沒有頭緒時,把React的思路套進來是不錯的選擇。

傳進來數據,先作一個日期分類的數組。查詢數組中是否存在該日期。沒有則把該對象生成一個ul信息後追加到數組,不然追加到數組的對應的元素中:

Data.prototype.renderMissions=function(selector){
    $(selector).obj.innerHTML='';
    //獲取原始數組
    var categoryId=this.category.id;
    var _this=this;
    var data=[];
    (function findPosition(_id,_data){
        for(var i=0;i<_data.length;i++){
            //console.log(_data[i]["id"])
            if(_data[i]["id"]==_id){
                data=_data[i]["missions"];
            }

            if(_data[i]["list"]!==[]){
                findPosition(_id,_data[i]["list"]);
            }
        }
    })(categoryId,_this.data);
    this.missions.arr=data;//data是存到對象裏方便其它方法調用。
    //對數組進行處理
    var arr=[];
    if(data.length!==0){// 拿到的data數據有多是空數組,空數組之間不相互相等,因此就用長度判斷
        for(var i=0;i<data.length;i++){
            // 先生成li數據:一個數據名每個關閉按鈕
            var li=document.createElement('li');
            li.innerHTML='<a href="javascript:;">'+data[i]["title"]+'</a><a class="mission-close" href="javascript:;"><img src="images/close.png" alt="delete"></a>';
            // 搜索組件需求
            li.setAttribute('data-key', 'true');
            li.setAttribute('data-search',"true");
          
            if(data[i]["isCompleted"]){
                li.className='completed';
            }else{
                li.className='uncompleted';
            }

            var bCheck=true;
            for(var j=0;j<arr.length;j++){
                if(arr[j].getAttribute('data-date')==data[i]["createTime"]){
                    arr[j].appendChild(li);
                    bCheck=false;
                    break;
                }
            }
            // 若是找不到,就要追加新ul
            if(bCheck){
                var ul=document.createElement('ul');
                ul.className='todo-date';
                ul.innerHTML = '<span>'+data[i]["createTime"]+'</span>';
                ul.setAttribute('data-date', data[i]["createTime"]);
                ul.appendChild(li);
                arr.push(ul);
            }
        }
        // 最後再經過循環把該ul添加到指定容器
        arr.forEach(function(item,index){
            $(selector).obj.appendChild(item);
        });
      
        // 內容渲染完了,須要在這裏綁定效果,好比鼠標懸停效果,刪除邏輯等。
      
      
    }else{// 若是是空數組就渲染提示信息
        $(selector).obj.innerHTML='<p style="margin-top:20px;text-align:center;color:#666;">該分類下尚未任何任務!</p>';
    }

};
// ...
// 在delegate中調用:
dataRenderer.renderMissions('#todo-content');

效果:

增刪任務

增長任務基本邏輯是:找到當前任務所屬的分類下的missions數組(咱們在執行任務渲染時已經把它加到Date.missions.arr裏面了),追加一個任務信息以下:

{
  "id":Date.parse(new Date()),
  "createTime":friendlyDate(),
  "title":你設定的名字,
  "isCompleted":false,
  "content":''
}

其中,日期要轉化爲友好的格式(xxxx-y-z):

function friendlyDate(){
  var cDate=new Date().toLocaleDateString().replace(/\//g,'-');
  return cDate;
}

增長任務須要考慮的問題是:若是我什麼都任務沒點選,目錄信息this.missions.arr是一個空對象。若是我刪除了一個分類

刪除任務的交互更加複雜一些,首先得有一個相似任務中的關閉按鈕,當鼠標懸停在相應的li標記時,按鈕顯示。當點擊這個按鈕,便可獲取該任務的id值,而後在this.minssions.arr中查找該id所在的任務對象,刪除之,最後渲染之。

在這一步,不須要考慮目錄的問題。

綜上,這兩個方法這樣寫:

Data.prototype.deleteMission=function(id){
    var arr=this.missions.arr;
    for(var i=0;i<arr.length;i++){
        if(arr[i]["id"]==id){
            arr.splice(i,1);
        }
    }
    this.renderMissions('#todo-content');
};

Data.prototype.addMission=function(option){
    var arr=this.missions.arr;

    arr.push(option);
    this.renderMissions('#todo-content');
};

那麼怎麼調用呢?和任務樹邏輯相似,甚至還要簡單一點:

$('#addMission').on('click',function(){
        var missionCategory=dataRenderer.missions.arr;
        var missionModal=new Modal();

        if(missionCategory===null){
            missionModal.init({
                type:'tips',
                tips:'你尚未選擇一個分類!'
            });
            missionModal.create();
        }else{
            //console.log(missionId);
            missionModal.init(
                {
                    type:'newMission',
                    title:'任務'
                }
            );
            missionModal.create();
            $('#newMission .submit').on('click',function(){
                var value=$('#newMission .input').obj.value;
                var option={
                  "id":Date.parse(new Date()),
                  "createTime":friendlyDate(),
                  "title":value,
                  "isCompleted":false,
                  "content":''
                };
                dataRenderer.addMission(option);
                missionModal.exit();
            });
        }
    });
分類-任務區的交互邏輯

如今已經寫了不少個方法。能夠考慮怎麼寫更加方便友好。

初始的任務區應該根據Data.category.id進行渲染。若是什麼目錄都沒有點選,那麼就不該該顯示目錄相關的內容。

也就是說,每次目錄id值改變,都須要執行Data.renderMissions方法。

既然那麼麻煩,不如把renderMissions方法寫到內容裏面算了!這在軟件設計中是一個值的考慮的問題。但考慮「高內聚」的原則,這些邏輯仍是得在主要代碼中體現出來,因此不刪除。

好比,我要點擊「全部分類」,要作4件事:

$('#category-all').on('click',function(){
        dataRenderer.clearCategoryId();
        dataRenderer.missions.arr=null;
        dataRenderer.renderMissions('#todo-content');
        search.clear();
    });

數據的流向應該是清理id,觸發當前分類爲null,觸發渲染任務區。

同時,還要把搜索組件裏的key清理爲'all'.

第二個,當在搜索欄沒有清空時刪除任務分類,會是什麼狀態?

天然是清理輸入框的數據,把全部按鈕的激活樣式設置爲激活。

當搜索框還有內容時刪除任務,也要清理輸入框,全部按鈕的樣式設置爲激活。

第三點,任務樹追加到網頁的DOM結構以後,都要對效果進行綁定。

Data.prototype.renderCategoryEffect=function(){
    // 添加懸停效果
    $('.mission-close').hover(function(){
        $(this).move({
            'opacity':100
        });
    },function(){
        $(this).move({
            'opacity':0
        });
    });

    $('.mission-close').on('click',function(){
        var missionId=this.parentNode.childNodes[0].getAttribute('data-missionId');
        _this.deleteMission(missionId);
        $('.todo-btn').removeClass('todo-btn-active');
        $('#all').addClass('todo-btn-active');
        $('#search').obj.value='';
        _this.missions.id=null;
    });
    // 激活樣式
    $('#todo-content').delegate('a','click',function(){
        if(this.className!=='mission-close'){
            $('#todo-content a').removeClass('missions-active');
            $(this).addClass('missions-active');
        }else{

        }
    });
};

這一段能夠按做爲Data對象渲染任務樹時的內部方法。

綜合以上,就是:

  • 每次在渲染任務樹時,都把搜索組件的內容初始化。
  • 每次目錄id值改變,都須要執行Data.renderMissions方法。
  • 渲染任務樹後須要綁定幾個功能按鈕(刪除按鈕,)

放一個效果:

任務內容區

讓咱們結束繁雜的任務渲染流程,到任務內容的渲染上來吧!

點擊任務標題獲取內容

當前的任務的a標記都綁定了一個對應的id值。寫一個getContent方法來獲取整個任務具體對象:

Data.prototype.getContent=function(id){
    var arr=this.missions.arr;
    console.log(arr);
    for(var i=0;i<arr.length;i++){
        if(id==arr[i]["id"]){
            return arr[i];
        }
    }
};

如今要來獲取這個id任務下的內容。

// 激活樣式
    $('#todo-content').delegate('a','click',function(){
        if(this.className!=='mission-close'){
            $('#todo-content a').removeClass('missions-active');
            $(this).addClass('missions-active');

            // 如下是內容顯示區
            var idName=this.getAttribute('data-missionid');
            var content=dataRenderer.getContent(idName);
            console.log(content.content);
        }
    });

那還要不要寫一個渲染方法呢?

答案是不要再折騰了。直接使用marked.js吧!

// 激活樣式
    $('#todo-content').delegate('a','click',function(){
        if(this.className!=='mission-close'){
            $('#todo-content a').removeClass('missions-active');
            $(this).addClass('missions-active');

            // 如下是內容顯示區
            var idName=this.getAttribute('data-missionid');
            var content=dataRenderer.getContent(idName);
            $('.content-substract').obj.innerHTML=content.createTime;
            $('.content-header h3').obj.innerHTML=content.title;
            $('.content-paper').obj.innerHTML=marked(content.content);
        }
    });
任務內容編輯欄

以前作了數據各類展現,但還沒作過數據修改的功能。

修改的邏輯是:點擊編輯按鈕——>編輯按鈕隱藏,提交按鈕出現——>出現任務編輯欄——>在編輯欄輸入數據——>點擊保存——>提交按鈕隱藏,編輯按鈕出現——>查找該任務內容的引用地址,修改該地址下的數據爲文本框輸入的內容。

markdown編輯時要求所見即所得。因此有一個編輯預覽窗口,經過keyup事件傳進去渲染出markdown效果。

$('#edit').on('click',function(){
        var idName=dataRenderer.missions.id;
        var content=dataRenderer.getContent(idName);

        var str=
        '標題 <input id="content-title" value='+content.title+' type="text"/><br><p style="line-height:30px; font-size:16px">內容</p><textarea id="content-edit" rows="16" cols="80">'+content.content+'</textarea>'+
        '<p style="line-height:30px; font-size:16px">效果預覽:</p><div class="edit-view"></div>';

        $('.content-paper').obj.innerHTML=str;

        this.style.display='none';
        $('#content-submit').obj.style.display='block';
        // 實時預覽
        $('#content-edit').on('keyup',function(){
            $('.edit-view').obj.innerHTML = marked(this.value);
        });

    });

    $('#content-submit').on('click',function(){
        var idName=dataRenderer.missions.id;
        var content=dataRenderer.getContent(idName);
        var value=$('#content-edit').obj.value;
        var title=$('#content-title').obj.value;


        content.content=value;
        content.title=title;
        $('#edit').obj.style.display='block';
        this.style.display='none';
        
        $('.content-substract').obj.innerHTML=content.createTime;
        $('.content-header h3').obj.innerHTML=content.title;
        $('.content-paper').obj.innerHTML=marked(content.content);
        $('.missions-active').obj.innerText=title;
    });

標記已完成

初始建立的任務內容都是標記爲未完成的。如今要完成一個功能就是點擊我已完成按鈕,該任務變爲已經完成。

$('#hascompleted').on('click',function(){
        var idName=dataRenderer.missions.id;
        var content=dataRenderer.getContent(idName);

        content.isCompleted=true;
        $($('.missions-active').obj.parentNode).removeClass('uncompleted');
        $($('.missions-active').obj.parentNode).addClass('completed');

    });
任務內容與分類-任務區的交互邏輯

這個項目一大半的時間其實都在思考數據結構和交互

只有當點擊任務區時,纔出現任務內容,當任務樹從新渲染,任務內容區的視圖就從新刷新爲歡迎頁面。

當歡迎頁面呈現時,不容許出現編輯按鈕

歡迎頁面其實就是一篇簡單的說明文檔。

再好比說,當渲染任務內容時,我已完成按鈕要根據isCompleted進行渲染。

本地儲存

本地儲存依賴localStorage,

localStorage是一個對象,可是它能接受的儲存是字符串,因此json數據必須事先經過json檢測。

在文檔的開頭:

var data=null;
    if(localStorage.djtaoTodo){
        data=eval(localStorage.djtaoTodo);
        console.log('old');
    }else{
        console.log('new');
        localStorage.djtaoTodo=JSON.stringify(json);
        data=json;
    }

var dataRenderer=new Data(data);
...

而後在網頁刷新或關閉時,把dataRenderer的data數據存到localStorage的目錄中。

window.onunload=function(){
    localStorage.djtaoTodo=JSON.stringify(dataRenderer.data);
};

這樣本地儲存的問題就解決了。至此,待辦事項列表的項目算是完成。

相關文章
相關標籤/搜索