Skip to content

Instantly share code, notes, and snippets.

@coodoo
Last active August 29, 2015 14:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save coodoo/149587de1b9e6f858b28 to your computer and use it in GitHub Desktop.
Save coodoo/149587de1b9e6f858b28 to your computer and use it in GitHub Desktop.
// 選出 UI 元素
var refreshButton = document.querySelector('.refresh');
var closeButton1 = document.querySelector('.close1');
var closeButton2 = document.querySelector('.close2');
var closeButton3 = document.querySelector('.close3');
// 偵聽 ui 元素的 click 事件並生成 observable
var refreshClickStream = Rx.Observable.fromEvent(refreshButton, 'click');
var close1ClickStream = Rx.Observable.fromEvent(closeButton1, 'click');
var close2ClickStream = Rx.Observable.fromEvent(closeButton2, 'click');
var close3ClickStream = Rx.Observable.fromEvent(closeButton3, 'click');
var zoken = '76de43f9a5bdb95c268d8b5b0a2548d03da32c2f';
// 基於 refreshClickStream 之上做 startWith 與 map 處理後
// 返還一個新的 stream 叫 requestStream
//
// 原理是:
// 每次按下滑鼠,會觸發一個 click item,進入 clickStream 內
// scheduler 會定時處理這個 item,也就是跑 .map() 處理
//
// 特別之處是下面用 startWith() 在初始化時先塞一個 click 事件進去
// 好讓 clickStream 內有值可處理
var requestStream = refreshClickStream
// 由於 refreshClickStream[] 裏原本是空無一物
// 因此用 startWith() 往裏面先塞一個物件,
// 也就是模擬一個 click 事件,進而觸發第一次 $.getJSON 請求
// 注意 'startup click' 可以是任何字串,這兩個字本身無特別意義
.startWith('startup click')
// 每一個 click event 都轉換為一個 requestURL
.map(function() {
// 這個值是 github api 需要的
var randomOffset = Math.floor(Math.random()*500);
// 這裏返還 requestUrl 出去
return 'https://api.github.com/users?since=' + randomOffset + '&access_token=' + zoken;
});
// 將每個 requestUrl 轉成真正的 $.getJSON() 呼叫
// 然後對 requestStream 做 flatMap 處理後
// 返還一個新的 stream 叫 responseStream
var responseStream = requestStream
// 依序走訪 requestStream 中每個元素
// 此例中它是取得每個元素的 url,然後發出 $.getJSON() 操作
// 並且得回一個 promise 物件,
// 這裏的問題是: promise stream of json objects 不方便取得裏面真正的資料
// 因此用 flatMap() 將 promise stream 剝開,即可直接存取裏面 server 返還的真正資料
// 注意跑完 flatMap() 後,照慣例一樣是返還一個新 stream,名稱為 responseStream
.flatMap(function (requestUrl) {
return Rx.Observable.fromPromise($.getJSON(requestUrl));
});
// 這支是 util
// 傳入一個 clickStream,在它身上掛各種 operator 後
// 返還一個新的 suggestionStream 出去
//
// 傳入:clickStream
// 返還:suggestionStream
// 轉換:
// - startWith: 給入一個初始物件供啟動
// - combineLatest: 監控 closeClickStream 與 responseStream 任一有變化時,就各取一個新值透過 suggestionStream 返還
// -
function createSuggestionStream(closeClickStream) {
// 以傳入的 closeClickStream 為基礎,
// 對它做 startWith, combineLatest, merge 等處理後
// 產生一個新的 stream 返還出去, 名稱叫 suggestionStream
return closeClickStream
// 往 closeClickStream 裏塞第一筆資料讓它有東西可跑
// 原因:因為下面用 combineLatest() 要求兩個 stream 都至少要有一筆資料
// 因此如果從來沒按過 close button,就永遠不會有產出,因此他模擬一次 click
.startWith('startup click')
// 也就同時監控 closeClickStream 與 responseSteram 兩個 stream
// 只要其中一個有變化,就各取一個最新值,放入 suggestionStream 內交出去
// 實際上它這裏的用意只是:每當 close 鈕被 click 時,就從 responseStream 裏取一個新值
.combineLatest( responseStream,
// 因此只要有 click or 新的response 回來,就會跑下面這個 fn
function(click, listUsers) {
console.log( '一百取一' );
// github 每次返還 100 個用戶,它從中隨機選一個
return listUsers[Math.floor(Math.random()*listUsers.length)];
}
)
// 重要:
// ↑ 至此是將 clickStream 與 responseSteam 合併為一個 stream (假設稱為 comboStream)
// ↓ 然後與另一個 refreshClickStream 做合併,生成一個新的 stream (名稱為 suggestionStream) 返還出去
//
// 重要關念為
// - 雖然 merge 了,但 comboStream 與 refreshClickStream 仍然是獨立存在的
// - merge 的意義只是說,以上兩者任何一個有變動,都會在新的 suggestionStream 上發出事件
// - 想像成是兩個 data pool, A & B,合併後放入 C 這個 pool,方便外界只要針對 C 做偵聽即可
// - 因此這樣做的好處只是用戶不需分別向 comboStream 與 refreshClickStream 註冊偵聽
// - 這是為何 @huang47 改寫後將它拆成兩段,就必需分為兩次偵聽的原因
//---------------------------------------------------------------------------------
// 將 suggestionStream 與另一個 stream 合併
.merge(
// merge 是將上面在處理的 suggestionStream 與一個新的 stream 合併
//
// 只有當按下 refresh 鈕時,才會觸發 refrshClickStream 運行
// 也才會進入這段,故意將 result 全設為 null,目地是要讓 ui 清空
// 這裏返還後,整支 suggestionStream 的內容就變成 null 了,因此 ui 就會清空
//
console.log( '\n\t跑清空 result' );
return null;
})
)
// 至此前面一連串的操作已生成一個新 stream 準備要返還(也就是 pool C)
// 這行是在這個新的 stream 前面塞一筆初始資料,正好是 null 值
// 這樣會讓 ui 上第一個推薦人選變成空白
.startWith(null);
}
// 一開始就跑這支
// 為三個空座位各建立一個 source stream 來供應推薦人選
var suggestion1Stream = createSuggestionStream(close1ClickStream);
var suggestion2Stream = createSuggestionStream(close2ClickStream);
var suggestion3Stream = createSuggestionStream(close3ClickStream);
//========================================================================
//
// @huang47 改寫版 http://jsfiddle.net/8jFJH/117/
// 重點在移除 merge 操作,讓兩個 stream 獨立存在,也分別做 subscribe()
// 這樣只是程式碼比較長,但較易閱讀,也好維護
refreshClickStream
.subscribe(function () {
['.suggestion1', '.suggestion2', '.suggestion3']
.map(function (s) { return document.querySelector(s); })
.forEach(function (el) {
el.style.visibility = 'hidden';
})
});
//--------------------------------------------------------------
// Rendering
// 這支只是 util,分別更新每個空位的推薦人選
function renderSuggestion(suggestedUser, selector) {
var suggestionEl = document.querySelector(selector);
if (suggestedUser === null) {
suggestionEl.style.visibility = 'hidden';
} else {
suggestionEl.style.visibility = 'visible';
var usernameEl = suggestionEl.querySelector('.username');
usernameEl.href = suggestedUser.html_url;
usernameEl.textContent = suggestedUser.login;
var imgEl = suggestionEl.querySelector('img');
imgEl.src = "";
imgEl.src = suggestedUser.avatar_url;
}
}
//
//----------------------------------------------------------------
// 前面做完處理後,這裏才開始收割,偵聽每次處理完的結果
suggestion1Stream.subscribe(function (suggestedUser) {
console.log( 'ui 事件: ', arguments );
renderSuggestion(suggestedUser, '.suggestion1');
});
// suggestion2Stream.subscribe(function (suggestedUser) {
// renderSuggestion(suggestedUser, '.suggestion2');
// });
// suggestion3Stream.subscribe(function (suggestedUser) {
// renderSuggestion(suggestedUser, '.suggestion3');
// });
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment