聯(lián)系人詳情

2018-05-25 23:28 更新

前面已經(jīng)完成了聯(lián)系人列表,然而似乎與 Cordova 沒啥關(guān)系,隨便找個(gè) Web 服務(wù)器就可以跑。那么標(biāo)題上的 Cordova 就只是一個(gè)簡單的 Web 窗口而已么?

當(dāng)然不是,目前我們的數(shù)據(jù)是保存在 json 文件中的,以后會(huì)放在數(shù)據(jù)庫中,以便于實(shí)現(xiàn)數(shù)據(jù)的增刪改。數(shù)據(jù)庫選用 SQLite,操作 SQLite 必須使用 Android 提供的一些 API,這時(shí)候就需要通過 Cordova 的插件來實(shí)現(xiàn)了。甚至再往后添加二維碼等功能,都需要用到 Cordova。但是 Cordova 的 API 參與了開發(fā)之后,調(diào)試時(shí)就要部署在手機(jī)上進(jìn)行了,操作多有不變,所以先處理前端不需要 Cordova 的部分,待前端成熟之后再來連接 Cordova。

詳情頁面

聯(lián)系人列表完成之后,現(xiàn)在開始考慮詳情頁面。之所以需要詳情頁面,是因?yàn)檫€有一些不便于在列表中展示的信息(列表項(xiàng)顯示范圍有限),所以再在測(cè)試數(shù)據(jù)中添加更多的字段,比如性別、城市等

規(guī)劃

詳情頁面寫在 detail.html 中,對(duì)應(yīng)的在 js 目錄中創(chuàng)建一個(gè) detail.jsx 保存 React 組件和腳本。樣式表暫時(shí)仍然使用 index.css,在里面添加詳情頁面所需要的樣式。所以新的 www 目錄結(jié)構(gòu)會(huì)是這樣

clipboard.png

詳情頁面仍然需要顯示一個(gè)頁頭,標(biāo)題就是姓名。之后的內(nèi)容仍然用列表顯示。用 HTML 表示大概會(huì)像這樣

<ul>
    <li>
        <span class="label">姓名</span>
        <span>張三</span>
    </li>
    <li>
        <span class="label">電話</span>
        <span>13801234567</span>
    </li>
    <li>
        <span class="label">性別</span>
        <span>男</span>
    </li>
    <li>
        <span class="label">城市</span>
        <span>四川省綿陽市</span>
    </li>
</ul>

代碼

detail.html

detail.html 的代碼和 index.html 幾乎一樣,唯一的區(qū)別是

    <script type="text/jsx" src="js/index.jsx"></script>

換成了

    <script type="text/jsx" src="js/detail.jsx"></script>

detail.jsx

detail.jsx 中主要定義 3 個(gè)組件

  1. Detail 組件渲染聯(lián)系人信息詳情列表
  2. DetailItem 組件渲染聯(lián)系某一項(xiàng)信息的內(nèi)容
  3. Page 組件渲染頁面內(nèi)容,包括頁頭和 Detail 組件等

因?yàn)槭庆o態(tài)頁面,不能處理 GET 或 POST 參數(shù),所以由地址欄的 HASH 傳遞聯(lián)系人的 ID 參數(shù),再在頁面中通過 AJAX 獲取數(shù)據(jù),篩選出該詳情頁面需要顯示的聯(lián)系人信息。而數(shù)據(jù)獲取就由 Page 組件處理(處理過程參考 index.jsx 中的 Page 組件。

之后由 Page 通過屬性方式向 Detail 傳遞聯(lián)系人數(shù)據(jù);而 Detail 則拆分?jǐn)?shù)據(jù)項(xiàng),仍然通過 props 方式,逐項(xiàng)向 DetailItem 傳遞數(shù)據(jù)。

在容錯(cuò)方面,為了簡化處理過程,如果沒能取得聯(lián)系人數(shù)據(jù),則用一個(gè)姓名叫“查無此人”的默認(rèn)的聯(lián)系人數(shù)據(jù)代替。

定義 Detail 組件

var Detail = React.createClass({
    render: function() {
        var person = this.props.person;


        return (
            <A.List data-id={person.id}>
                <DetailItem label="姓名" value={person.name} />
                <DetailItem label="電話" value={person.tel} />
                <DetailItem label="性別" value={person.isMan ? "男" : "女"} />
                <DetailItem label="城市" value={person.city} />
            </A.List>
        );
    }
});

注意,這里在 <A.List> 中傳入了一個(gè) data-id 屬性,這個(gè)屬性在作為 React 對(duì)象屬性的同時(shí),也會(huì)以標(biāo)簽屬性形式渲染到 HTML 中——React 會(huì)把 data- 前綴的屬性直接渲染為 HTML 標(biāo)簽屬性。

DetailItem 組件

var DetailItem = React.createClass({
    render: function() {
        return (
            <A.ListItem>
                <span className="label">{this.props.label}</span>
                <span>{this.props.value}</span>
            </A.ListItem>
        );
    }
});

這個(gè)組件沒什么懸念,只是直接把 label 的文本顯示出來而已。

Page 組件

var Page = React.createClass({
    defaultPerson: {
        id: "0000",
        name: "查無此人",
        phone: "00000000000"
    },
    getInitialState: function() {
        return {
            person: null
        };
    },
    componentDidMount: function() {
        $.getJSON("/js/data.json").then(function(data) {
            if (this.isMounted()) {
                this.setState({
                    person: data.filter(function(p) {
                        return "#" + p.id === window.location.hash;
                    })[0]
                });
            }
        }.bind(this));
    },
    render: function() {
        var person = this.state.person || this.defaultPerson;


        return (
            <div>
                <A.Header title={person.name} />
                <Detail person={person} />
            </div>
        );
    }
});

Page 組件和之前列表頁面的 Page 組件一樣,在 componentDidMount() 中通過 AJAX 加載數(shù)據(jù)。之后,通過 window.location.hash 按 ID 精確查找聯(lián)系人,設(shè)置到 state 中。

顯示的時(shí)候,如果沒有找到 this.state.person,則使用默認(rèn)的“查無此人”聯(lián)系人信息。

渲染 Page

最后當(dāng)然不能忘了渲染根組件:Page。

React.render(<Page />, document.body);

這時(shí)候,通過 http://localhost/detail.html#1001 這個(gè)地址,已經(jīng)可以看到數(shù)據(jù)顯示出來,只不過由于沒有添加樣式,還不夠美觀。

clipboard.png

在列表頁面上添加到詳情頁面的連接

按照 Amazi UI React 文檔,在列表頁面上添加到詳情頁面的連接,只需要在 Person 組件中為 <A.ListItem> 添加 href 屬性即可,

<A.ListItem className="person" href={"detail.html#" + this.props.id}>
</A.ListItem>

然后添加之后顯示出來的效果卻大大出乎意料。主要原因是 Amaze UI 將 li>a 的 display 設(shè)置為 block了,所以帶鏈接的電話圖標(biāo)會(huì)顯示在下一行。當(dāng)然通過修改 CSS 是可以解決的,但是我想用另一種方法來解決:點(diǎn)擊事件。

列表項(xiàng)上的點(diǎn)擊事件

React 是支付事件處理的,在其 Event System 一章中說明了事件處理的方式和注意事項(xiàng)。React 可以處理的事件分幾大類共計(jì)數(shù)十個(gè),都在 Event System 中列舉出來了。這里需要用到的是 onClick 事件。

注意,下面說的內(nèi)容不是在 detail.jsx 而是在 index.jsx 中

首先為 Person 組件定義一個(gè)處理函數(shù),用于處理列表項(xiàng)被點(diǎn)擊后的動(dòng)作:設(shè)置 window.location.href 跳轉(zhuǎn)到詳情頁。

var Person = React.createClass({
    handleClick: function(event) {
        window.location.href = "detail.html#" + this.props.id;
    },
    render() { ... }
});

可以看到,數(shù)據(jù)來源仍然可以是 this.props,同理,也可以是 this.state。

之后在 <A.ListItem> 中綁定事件處理函數(shù)

     render: function() {
        var link = "tel:" + this.props.tel;
        return (
            <A.ListItem className="person" onClick={this.handleClick}>
            ...
            </A.ListItem>
        );
    }

注意到 onClick={this.handleClick} 的寫法,聯(lián)想到在 HTML 標(biāo)簽屬性中綁定事件處理函數(shù)的情況,函數(shù)在執(zhí)行時(shí) this 指針會(huì)變?yōu)槿謱?duì)象 (window)。這里是不是需要 bind(this) 呢?

不需要!

React 已經(jīng)處理了 this 的問題,所以這里只需要簡單綁定 this.handleClick,而在 handleClick 中使用 this 就是當(dāng)前組件對(duì)象,不會(huì)是全局對(duì)象。

如果有強(qiáng)迫癥,可能還需要給 <A.ListItem> 加個(gè)內(nèi)聯(lián)樣式:style={{ cursor: "pointer" }},不過我認(rèn)為沒必要,因?yàn)槲覀兊哪繕?biāo)是手機(jī) App,指哪點(diǎn)哪,完全沒有 hover 一說。

可點(diǎn)擊的詳情電話

要讓電話可點(diǎn)擊只需要加 <a href="tel:xxxxxx"> 即可。但是在詳情頁,每個(gè)數(shù)據(jù)項(xiàng)都是這個(gè)結(jié)構(gòu):

<ListItem>
    <span>{label}</span>
    <span>{value}</span>
</ListItem>

如果要加鏈接,就會(huì)變成下面的結(jié)構(gòu)

<ListItem>
    <span>{label}></span>
    <span>
        <a href={href}>{value}</a>
    </span>
</ListItem>

然后再在 Detail 中使用組件的時(shí)候多傳入一個(gè) href 參數(shù)。

說起來,兩個(gè) DetailItem 中的區(qū)別只有第 2 個(gè)的 <span> 中的內(nèi)容那一點(diǎn),有沒有辦法可以重用呢?——可以試試 mixins,因?yàn)閺?Reusable Components 的理解,mixins 有點(diǎn)像繼承。

嘗試(第 1 次失?。?/h3>

var DetailItem = React.createClass({
    getValueContent: function() {
        return this.props.value;
    },
    render: function() {
        return (
            <A.ListItem className="person-detail-item">
                <span className="label">{this.props.label}</span>
                <span>{this.getValueContent()}</span>
            </A.ListItem>
        );
    }
});

var DetailLinkItem = React.createClass({
    mixins: [DetailItem],
    getValueContent: function() {
        return <a href={this.props.href}>{this.props.value}</a>;
    }
});

結(jié)果失敗了。再次閱讀 Reusable Components 中的示例,然后發(fā)現(xiàn) mixins 數(shù)組中應(yīng)該是一個(gè)原型對(duì)象更貼切,嘗試

第 2 次嘗試(再次失敗)

var DetailLinkItem = React.createClass({
    mixins: [DetailItem.prototype],
    getValueContent: function() {
        return <a href={this.props.href}>{this.props.value}</a>;
    }
});

這次是因?yàn)橹貜?fù)定義了 constructor。DetailItem.prototype 中有一個(gè) constructor,而 React.createClass() 又定義了一個(gè)??磥聿荒芡祽?,只能定義個(gè)獨(dú)立對(duì)象了

終于成功的嘗試

var detailBase = {
    render: function() {
        return (
            <A.ListItem className="person-detail-item">
                <span className="label">{this.props.label}</span>
                <span>{this.getValueContent()}</span>
            </A.ListItem>
        );
    }
};


var DetailItem = React.createClass({
    mixins: [detailBase],
    getValueContent: function() {
        return this.props.value
    }
});


var DetailLinkItem = React.createClass({
    mixins: [detailBase],
    getValueContent: function() {
        return <a href={this.props.href}>{this.props.value}</a>;
    }
});

補(bǔ)充一下 Detail 中的變更

這個(gè)變更是在第 1 次嘗試的時(shí)候就修改了的,主要是處理“電話”那一項(xiàng)時(shí)用 <DetailLinkItem> 代替了 <DetailItem>。

var Detail = React.createClass({
    render: function() {
        var person = this.props.person;


        return (
            <A.List className="person-detail" data-id={person.id}>
                <DetailItem label="姓名" value={person.name} />
                <DetailLinkItem label="電話" value={person.tel} href={"tel:" + person.tel} />
                <DetailItem label="性別" value={person.isMan ? "男" : "女"} />
                <DetailItem label="城市" value={person.city} />
            </A.List>
        );
    }
});

美化 Detail

美化通常放在最后,但不代表不需要,現(xiàn)在來加 className 和 CSS。

從效果上來看,主要是需要把 label 和后面的內(nèi)容區(qū)分開來,順便參照列表頁面的列表樣式,做一些細(xì)節(jié)上的美化。

先為 <A.List> 添加 className="person-detail",再為 <A.ListItem> 添加 className="person-detail-item"className="label" 是有先見之明早就加了的。最后修改 CSS,去掉 li 的類限定,再加上 ul.person-detailli.person-detail-item 的樣式

ul.person-list {
    margin-top: 0;
}


li {
    padding: 3px 6px;
}


li>.person-icon {
    margin-right: 6px;
}


li>.person-phone {
    margin-top: 4px;
}


ul.person-detail li {
    margin-bottom: 4px;
    border-top: 0;
}


li.person-detail-item .label {
    margin-right: 8px;
    padding: 2px 5px;
    background-color: #41afff;
    border-radius: 3px;
    color: white;
}

最終效果還是比較令人滿意的

clipboard.png

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)