2016年10月16日 星期日

Reactive Native 初體驗

話說IONIC2 Angular2 的組合仍然是當前火紅的HyBrid APP, 但開發行動裝置的語言工具又多了一款React Native 可直接呼叫原生的API,而且語法近似乎HTML5 CSS3,所以讓我嘗鮮看看,找到一款完美的跨平台語法是每個人的夢想。

一、建立react native 的專案


react-native init AwesomeProject
cd AwesomeProject
react-native run-android

二、TabNavigator 範例:

適用於IOSAndroid Navigator Tab (下方的工具列表上,可切換的頁籤),因為是第三方元件提供的,所以要先安裝

npm install react-native-tab-navigator --save

範例: index.android.js 建立有兩個頁籤的TAB,分別是頁面Featured Search

首先開始使用react 要先import 基本元件:

import React, { Component,PropTypes } from 'react';
import {
AppRegistry, //用來預設畫面
StyleSheet, //如同css一樣定義文字大小、排版
Text, //顯示文字
View //每個畫面==>screen
} from 'react-native';
import TabNavigator from 'react-native-tab-navigator'; //引用外部的元件,所以要import

TabNavigator 的第三方元件可以自訂新增tab 頁籤,每頁籤可引用自訂react component 控件,例如以下引用兩個【Featured】和【Search】在不同component控件

import React, { Component,PropTypes } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View
} from 'react-native';
import TabNavigator from 'react-native-tab-navigator';

var Featured = require('./Featured');
var ListViewBasics = require('./ListViewDemo');

class AwesomeProject extends Component {
constructor(props) {
super(props);
this.state = {
selectedTab: 'Featured' //預設為Featured
};
}

render() {
return (
<TabNavigator>
    <TabNavigator.Item selected={this.state.selectedTab === 'Featured'}
      title="Featured" onPress={() => this.setState({ selectedTab: 'Featured' })}>
         <Featured />//零組件
   </TabNavigator.Item>
   <TabNavigator.Item selected={this.state.selectedTab === 'ListViewBasics'}
        title="ListViewBasics" onPress={() => this.setState({ selectedTab: 'ListViewBasics' })}>
        <ListViewBasics />   //零組件
   </TabNavigator.Item>
</TabNavigator>
);
}
}

AppRegistry.registerComponent('AwesomeProject', () => AwesomeProject); //用來當作預設頁的元件

設定 StyleSheet
const styles = StyleSheet.create({
container: {
    flex: 1, //用來在各種不同螢幕大小,元件layout與邊框距離
    justifyContent: 'center', //置中處理
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
},
welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
},
instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
},
});

三、Navigator (頁面的轉換路徑)
Navigator 元件允許頁面轉向到另一個新的頁面,接續上面的案例中,Featured是獨立的控件,要能導覽至下一頁MyScene,按下back就回上一頁,所以要先import Navigator元件和MyScene 控件,才能將畫面導覽至MyScene

Featured.js
'use strict';
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View,
Navigator
} from 'react-native';
import MyScene from './MyScene'; //元件,要自已實作
var styles = StyleSheet.create({
    description: {
    fontSize: 20,
    backgroundColor: 'white'
},
container: {
   flex: 1,
   justifyContent: 'center',
   alignItems: 'center'
}
});
class Feature extends Component {
render() {
return (
<Navigator
initialRoute={{title:"my Initial Scene", index:0}} //傳給下一頁的參數值(Title,index)
renderScene={(route,navigator)=>{
return <MyScene title={route.title}
onForward={()=>{
const nextIndex=route.index+1;
    navigator.push({
    title:"Scene_"+nextIndex,
    index:nextIndex,
});
}}
onBack={() => {
if (route.index > 0) {
     navigator.pop();
}
}}
/>
}}
/>
);
}
}
module.exports = Feature;

名稱 MyScene的自訂屬性 onForward onBack,傳入函式用來控制導覽下一頁 navigator.push 的動作和上一頁 navigator.pop()的動作。

MyScene.js
import React, { Component,PropTypes } from 'react';
import { View, Text, Navigator,StyleSheet,TouchableHighlight } from 'react-native';

var styles = StyleSheet.create({
toolbar:{
backgroundColor:'#81c04d',
paddingTop:30,
paddingBottom:10,
flexDirection:'row' //用來控制以列(由左->)的排列
},
toolbarButton:{
width: 50,
color:'#fff',
textAlign:'center'
},
toolbarTitle:{
color:'#fff',
textAlign:'center',
fontWeight:'bold',
flex:1
}
});

class MyScene extends Component {

static PropTypes={
title: PropTypes.string.isRequired,
onForward:PropTypes.func.isRequired,
onBack:PropTypes.func.isRequired
}
render() {
return (
<View style={styles.toolbar}>
<TouchableHighlight onPress={this.props.onBack}>
<Text style={styles.toolbarButton}>back</Text>
</TouchableHighlight>
<Text style={styles.toolbarTitle}> {this.props.title}</Text>
<TouchableHighlight onPress={this.props.onForward}>
<Text style={styles.toolbarButton}>next</Text>
</TouchableHighlight>
</View>
)
}
}
module.exports=MyScene;

<TouchableHighlight> 控制允許點選按鈕範圍,所以如上所示 back next是按鈕,各分別按下後呼叫不同的事件產生, {this.props.onBack}{this.props.OnFoward} 調用MyScene傳入參數的OnFoward OnBack 的方法。



ListViewDemo.js

import React, { Component } from 'react';
import { AppRegistry, ListView,ScrollView,Image,TouchableOpacity,Text, View, StyleSheet } from 'react-native';

//自訂圖片的檔案路徑
var THUMB_URLS = [
require('./imgs/like.png'),
require('./imgs/dislike.png'),
require('./imgs/call.png'),
require('./imgs/fist.png'),
require('./imgs/bandaged.png'),
require('./imgs/flowers.png'),
require('./imgs/heart.png'),
require('./imgs/liking.png'),
require('./imgs/party.png'),
require('./imgs/poke.png'),
require('./imgs/superlike.png'),
require('./imgs/victory.png'),
];

class ListViewDemo extends Component {

constructor(props) {
super(props);
const ds = new ListView.DataSource({
    rowHasChanged: (r1, r2) => r1 !== r2,
    sectionHeaderHasChanged: (s1, s2) => s1 !== s2
});
this.state = {
dataSource: ds.cloneWithRowsAndSections(this.getRows()), //取得getRows()繫結資料
};
}
getRows(){
  let dataObj = {}
  let section = '测試1'
  dataObj[section] = []
  for (let i=0;i<10;i++){
     let data = {
     name:''+i+'',
     num:i
  }
   dataObj[section][i] = data
  }
  section = '测試2'
  dataObj[section] = []
  for (let i=0;i<10;i++){
     let data = {
     name:''+i+'',
     num:i
   }
  dataObj[section][i] = data
 }
 return dataObj
}
//rowData傳入的資料列(每筆資料物件,分組ID, 資列列索引, 是否選中的狀態)
renderRow(rowData,sectionID,rowID,highlightRow){
var imgSource = THUMB_URLS[rowID];
return (
  <View style={styles.rowItem}>
    <View style={styles.rowItemLeft}>
        <Image style={styles.thumb} source={imgSource} resizeMode={"cover"} />
    </View>
    <View style={styles.rowItemRight}>
        <Text style={styles.rowItemText}>{rowData.num}</Text>  
    </View>
  </View>
  )
}
onEndReached(e){
//當資料列執行最後一列的時候,執行此區塊
}
renderSectionHeader(sectionData, sectionID){ //用來顯示資料列的分組的標題(一維陣列的值)
return(
<View style={styles.rowTite}>
    <Text>{sectionID}</Text>
</View>
)
}
onChangeVisibleRows(visibleRows, changedRows){
//當資料列有變化時,呼叫此區塊
}
render() {
return (
<ListView
   style={styles.body}
   onEndReached = {this.onEndReached}
   onEndReachedThreshold = {20}
   renderSectionHeader = {this.renderSectionHeader}
   onChangeVisibleRows = {this.onChangeVisibleRows}
   dataSource={this.state.dataSource}
   renderRow={this.renderRow} />
   );
  }
}

var styles = StyleSheet.create({
body:{
flex:1,
},
rowItem:{
    flex:1,
    height:50,
    flexDirection:'row',
    justifyContent:'center',
    alignItems:'center',
    borderBottomWidth:1,
    borderBottomColor:'#ddd',
},
rowTite:{
    height:30,
    alignItems:'center',
    justifyContent:'center',
    backgroundColor:'#ccc',
},
rowItemLeft:{
   flex:1,
   borderRightColor:'#ccc',
},
rowItemRight:{
  flex:3,
},
rowItemText:{
  textAlign:'center'
},
thumb: {
  width: 30,
  height:30
},
});
module.exports = ListViewDemo;

RenderRow(rowData,sectionID,rowID,highlightRow)
rowData : 每筆資料列視為物件,可以呼叫出每個物件的屬性 ex: rowData.name
sectionID : 分組的ID
rowID : 顯示資料列的索引值,從0開始
highlightRow : 是否該資料列被選中



reactNative 的相關指令:

執行Android 的compile : react-native run-android
建議安裝Android 的模擬器,因為作者無法實機連線到react native ,搞了很久…


後來只好將 apk 安裝在實機上面測試  …  搜尋 \專案路徑 \android\app\build\outputs\apk\ app-debug.apk 透過網路 或USB直接安裝在手機上

最後文件中找到這樣一篇文章

A common issue is that the packager is not started automatically when you run react-native run-android. You can start it manually using react-native start.

下指令 : react-native start   (開始預設PORT 8081)



然後啟動APP就成功囉~謝謝各位


三、參考文獻


2016年9月12日 星期一

MongoDB進階教學

用 MongoDB 開發程式也面臨到許多難題,雖然mongo是適用物件儲存時非常具有彈性,但也有它的缺點,例如型別檢查是非常不嚴格的,如果資料欄位型態儲存失敗,也不會有任何的提示訊息,需要特別注意,因為MongoDB不支援某些SQL的型別,所以要先轉換型別,所以在LINQ上需要轉換型別時,可以參考以下的範例

var oders = (from b in realTimeDb.v_orderinfo4back
select new
{
CREATEDDATE = b.CREATEDDATE,
COLORNAME = b.colorname,
COST = b.COST,
deliverytypename = b.deliverytypename,
ITEMID = b.ITEMID,
}).AsEnumerable().Select(q=> new vOrderInfo4Back() {
CREATEDDATE = q.CREATEDDATE,
COLORNAME = q.COLORNAME,
COST = Convert.ToDouble(q.COST),
DELIVERYTYPENAME =q.deliverytypename,
ITEMID = Convert.ToDouble(q.ITEMID),
});
注意: SQLDeciaml型別,但mongodb 並沒有deciaml會被轉換成string,所以mongodb在型態檢查是不嚴格

建立mongoDB的索引
//-----add index------//
var keyCreateDates = MongoDB.Driver.Builders<vOrderInfo4Back>.IndexKeys.Ascending("CREATEDDATE");
_db.vOrderInfo4Back.Indexes.CreateOne(keyCreateDates);

var keyOrderid = MongoDB.Driver.Builders<vOrderInfo4Back>.IndexKeys.Ascending("ORDERID");
_db.vOrderInfo4Back.Indexes.CreateOne(keyOrderid);

var keySALENO = MongoDB.Driver.Builders<vOrderInfo4Back>.IndexKeys.Ascending("SALENO");
_db.vOrderInfo4Back.Indexes.CreateOne(keySALENO);


注意: MongoDB 會自動新增Collection 【如同SQL的資料表】,並不需要事先開table 儲存資料,所以如果要在程式中建立索引亦可以加快搜尋速度。

建立mongoDB的儲存模型
public class vOrderInfo4Back {
/// <summary>
/// 訂單日期
/// </summary>
public DateTime? CREATEDDATE { get; set; }
/// <summary>
/// 訂單編號
/// </summary>
public double ORDERID { get; set; }
/// <summary>
/// 項次
/// </summary>
public double ITEMID { get; set; }

/// <summary>
/// 顏色
/// </summary>
public string COLORNAME { get; set; }

/// <summary>
/// 成本
/// </summary>
public double? COST { get; set; }

/// <summary>
/// 配送方式
/// </summary>
public string DELIVERYTYPENAME { get; set; }
}
因為MongoDB十分有彈性,只有在儲存的model 增加欄位,儲存至mongoDB會自動增加欄位,並不用特別針對資料庫增加欄位,這點是NO SQL 的好處之一,只需著重程式開發上是否會影響之前的程式。

因為MongoDB 讀取時間會依預設的標準時間為主(US),所以要加上BsonDateTimeOptions 關鍵字 或者 註明當地時間 [BsonDateTimeOptions(kind=dateTimekind.Local)]
/// <summary>
/// 訂單日期
/// </summary>
[BsonDateTimeOptions]
public DateTime CREATEDDATE { get; set; }


注意:如果要讀取MongoDB日期欄位,需要加註 [BsonDateTimeOptions]在欄位的上方

單筆寫入MongoDB的範例:
var _db = new MallDbContext(); //是自己新增的物件,用來取得MongoDB的資料庫
_db.vOrderInfo4Back.InsertOne(new vOrderInfo4Back() {
COLORNAME = "",
COST=0,
ITEMID=1,
SALENO=1,
DELIVERYTYPENAME="測試"
});
_db.vOrderInfo4Back : 自己新增的物件回傳vorderInfo4Backcollection

如果有多筆資料要一次寫入MongoDB時,需要用InsertMany 可以加快mongo寫入的速度
_db.vOrderInfo4Back.InsertMany(orderList);

查詢MonoLINQ語法
var _db = new MallDbContext();
var vOrders=
_db.GetCollection<vOrderInfo4Back>("vOrderInfo4Back").AsQueryable<vOrderInfo4Back>();
//支援LINEQ語法
var orders = from q in vOrders where (string.IsNullOrEmpty(inputModel.productName) || q.SALENAME.Contains(inputModel.productName ?? ""))
&& q.CREATEDDATE >= startDate
&& q.CREATEDDATE <= endDate
&& q.SUPPLIERID == doubleFugoID
orderby q.CREATEDDATE descending
select q;


C#查詢Mongo原生語法
var builder = Builders<vOrderInfo4Back>.Filter;
var filter = builder.Gte(q => q.CREATEDDATE, startDate) & builder.Lte(q => q.CREATEDDATE, endDate) //GTE 如同>=LTE 如同<=

//商品名稱 (LIKE '% productName%' )
if (!string.IsNullOrWhiteSpace(inputModel.productName))
{
filter = filter & builder.Regex(q => q.SALENAME, "/.*" + inputModel.productName + ".*/");
}
// supplier
if (!string.IsNullOrWhiteSpace(fugoID))
{
filter = filter & builder.Eq(q => q.SUPPLIERID, doubleFUgoID);
}

//前台分類
if (categories2.Count > 0)
{
filter = filter & builder.In(q => q.SCLASSCODE, categories2);
}
var vOrders = _db.GetCollection<vOrderInfo4Back>("vOrderInfo4Back").Find(filter)

以上是我對MongoDB的基本瞭解,如果有任何補充的地方,還希望大家多多指教~ 感謝大家