(2023.05.24)mod_harbour support SSE.
發表於 : 2023-05-24, 22:09
Server-Sent Events (SSE) : 允許網頁從伺服器端取得更新資料.
參考網址: https://www.w3schools.com/html/html5_se ... events.asp
當我們要從伺服器端抓取資料,又不想用一般的進度表顯示,SSE 是一種好方法.
mod_harbour 有沒有支援 SSE 呢?
經過反覆測試下,終於完成 SSE 功能.
SSE is a good way when we want to fetch data from the server and don't want to use the usual progress meter.
Does mod_harbour support SSE? After repeated testing, the SSE function is finally completed.
javascript:
prg:
參考網址: https://www.w3schools.com/html/html5_se ... events.asp
當我們要從伺服器端抓取資料,又不想用一般的進度表顯示,SSE 是一種好方法.
mod_harbour 有沒有支援 SSE 呢?
經過反覆測試下,終於完成 SSE 功能.
SSE is a good way when we want to fetch data from the server and don't want to use the usual progress meter.
Does mod_harbour support SSE? After repeated testing, the SSE function is finally completed.
javascript:
代碼: 選擇全部
let param=self.url+'?func=9&help='+(self.lHelp ? 1 : 0 )+'&progress=1&data='+JSON.stringify(data); // object into url parameter string, because SSE can only do that!!
source = new EventSource(param,{
withCredentials: false // Whether to allow cross domain requests, default: false
});
// Handle postback events 'message'
source.addEventListener('message',function(e){
let o=JSON.parse(e.data); // analyze
if(o.lEnd == 0){ // lEnd=1:End
// Screen display progress processing
let per=Math.round( o.step/o.total*100,2 ); // calculate percentage
$('#progress').val(per);
$('#progress_down').text( per+'%' );
}else{
source.close(); // Close SSE!! Don't send data back!!
// download file
if(o.err_no != 0){
w2alert('Failed to read data!!'+o.err_msg, 'Error!!').ok(event => {
w2popup.close();
});
return;
}
// After processing, receive the excel data
let $a = $("<a>");
// Because there will be problems when sending '/' in the backend, so move this keyword to the front to deal with it!!
$a.attr("href",'data:application\/vnd.ms-excel;base64,'+o.errmsg);
$("body").append($a);
$a.attr("download",self.cXlsName);
$a[0].click();
$a.remove();
w2popup.close();
}
});
// Handle the event on start 'open'
source.addEventListener('open', function(e) {
//
});
// Events when errors are handled 'error'
source.addEventListener('error', function(e) {
if(e.target.readyState == EventSource.CLOSED){
w2alert('connect be close!!','error!!');
source.close(); // stop SSE
}else{
console.log('error!!: '+dump(e,2));
}
});
// Handle system status events 'status'
source.addEventListener('status', function(e) {
console.log('System status is now: ' + e.data);
});
代碼: 選擇全部
METHOD printData() CLASS TPER
LOCAL hRes := {;
'err_no'=>0; // 錯誤碼
,'errmsg'=>''; // 錯誤訊息
,'errtil'=>''; // 錯誤抬頭
,'lEnd'=>0; // 結束?<*0:No/1:Yes>
,'step'=>0; // 目前進度指標(every)
,'total'=>0; // 總筆數(total)
}
//
LOCAL hParam := hb_hash(),;
hPostData := hb_hash(),;
hSortData := hb_hash(),;
hData := hb_hash(),;
cDef := '',;
hObj := hb_hash(),;
nObjCount := 0,;
cMethod := AP_Method(),;
nProgress := 0,;
lClientStop := .F. // 前端中斷?
//
LOCAL i := 0,;
j := 0,;
k := 0,;
aHD := {},;
nRow := 0
//
LOCAL PR_NUM1 := '',;
PR_NUM2 := '',;
PR_NAM := '',;
cWhere := '',;
cQry := '',;
oQry := NIL,;
nTolRec := 0
//
LOCAL oExcel := NIL,;
oBook := NIL,;
oSheet := NIL,;
oFnt1, oFnt2, oFnt3,;
oFmtR, oFmtL, oFmt0, oFmt1, oFmt2, oFmt3
// 送出 SSE 專用表頭
AP_SetContentType( "Cache-Control: no-cache" ) // 關閉快取
AP_SetContentType( "text/event-stream;charset=UTF-8" ) // 送出表頭格式
// 取得資料傳遞方式
IF cMethod == 'POST'
hParam := AP_PostPairs()
ELSE
hParam := AP_GetPairs()
ENDIF
// 取得參數 progress: 是否顯示進度表<*0:No/1:Yes>
IF hb_HHasKey(hParam, 'progress')
nProgress := Val( hParam['progress'])
ELSE
nProgress := 0
ENDIF
//
DO WHILE .T.
// 接收參數: 換頁方式處理方式,直接利用 data 帶過來
hPostData={; // 設定預設值: array
"PR_NUM1"=>"";
,"PR_NUM2"=>"";
,"PR_NAM"=>"";
,"OUT_TYPE"=>0; // 輸出類型:<*0:Excel/1:PDF>
}
hSortData := {; // 設定預設值: array
["field"=>"r.PR_NUM"; // 排序欄位:用陣列包住,模擬 w2grid.load()
,"direction"=>"asc"]; // 排序方是
}
cDef := hb_jsonencode({; // 預設參數: array to json string
"postData"=>hPostData;
,"sortData"=>hSortData;
})
IF hb_HHasKey(hParam, 'data')
cData := hParam['data']
ELSE
cData := cDef
ENDIF
hObj := hb_jsondecode(cData) // 將字串資料轉為 json 物件
nObjCount := Len( hb_hKeys(hObj)) // 取得 hObj 裡面 keys 數量
IF nObjCount < 2
hRes['err_no'] := -1
hRes['errmsg'] := "參數資料有問題!!"
hRes['errtil'] := "錯誤!!"+ProcName()+"("+hb_ntos(ProcLine())+")"
EXIT
ELSE
hPostData := hObj['postData']
hSortData := hObj['sortData']
ENDIF
// 從物件中取得傳遞參數
PR_NUM1 := hPostData['PR_NUM1'] // 員工編號-1
PR_NUM2 := hPostData['PR_NUM2'] // 員工編號-2
PR_NAM := hPostData['PR_NAM' ] // 員工姓名
// 整理資料
PR_NUM1 := AllTrim( PR_NUM1 )
PR_NUM2 := AllTrim( PR_NUM2 )
PR_NAM := AllTrim( PR_NAM )
//-----------------------
// 檢查資料
//-----------------------
//-----------------------
// 組合 $where
//-----------------------
cWhere := ""
IF ! Empty(PR_NUM1)
cWHERE += IF( ! Empty(cWHERE), ' AND ', '' )
cWHERE +="r.PR_NUM>='"+AllTrim( PR_NUM1 )+"'"
ENDIF
IF ! Empty(PR_NUM2)
cWHERE += IF( ! Empty(cWHERE), ' AND ', '' )
cWHERE += "r.PR_NUM<='"+AllTrim( PR_NUM2 )+"z'"
ENDIF
IF ! Empty(PR_NAM)
cWHERE += IF( ! Empty(cWHERE), ' AND ', '' )
cWHERE += "r.PR_NAM LIKE '%"+AllTrim( PR_NAM )+"%'"
ENDIF
IF Empty(cWHERE)
cWHERE := "1>0"
ENDIF
// 抓取資料
cQry := "SELECT r.* "
cQry += "FROM "+::db:table['per']+" r "
cQry += "WHERE "+cWHERE+" "
cQry += "ORDER BY "+hSortData[1,'field']+" "+Upper( hSortData[1, 'direction'])
oQry := ::db:Qry(cQry)
IF ::db:err_no <> 0
hRes['err_no'] := ::db:err_no
hRes['errmsg'] := ::db:err_msg
hRes['errtil'] := "錯誤!!"+ProcName()+"("+hb_ntos( ProcLine() )+")"
EXIT
ENDIF
nTolRec := oQry:RecCount() // 總筆數
//
oExcel := TLibXL():New()
oBook := oExcel:CreateXMLBook() // .xlsx oExcel:CreateXMLBook() // .xlsx
oBook:setLocale('UTF-8') // 'en_US.UTF-8': 不能加 'en_US' 不然輸出資料會是亂碼
oSheet := oBook:AddSheet( '員工基本資料' )
// Font
oBook:setDefaultFont('細明體',12) // 設定整體字型
oFnt1 := oBook:AddFont(); oFnt1:SetName('細明體'); oFnt1:SetSize(12)
oFnt2 := oBook:AddFont(); oFnt2:SetName('細明體'); oFnt2:SetSize(16)
// Format
oFmtR := oBook:AddFormat(); oFmtR:setAlignH( ALIGNH_RIGHT ) // 水平靠右
oFmtL := oBook:AddFormat(); oFmtL:setAlignH( ALIGNH_LEFT ) // 水平靠左
oFmt1 := oBook:AddFormat(); oFmt1:setNumFormat(NUMFORMAT_TEXT )
oFmt2 := oBook:AddFormat(); oFmt1:setNumFormat(NUMFORMAT_NUMBER ) // ex: 1000
oFmt3 := oBook:AddFormat(); oFmt1:setNumFormat(NUMFORMAT_NUMBER_D2 ) // ex: 1000.00
//
aHD := Array( 2, 3 )
aHD[01] := { "員工編號" , 10, 'C' }
aHD[02] := { "員工姓名" , 12, 'C' }
// 抓取對應 grid 資料
i := 0
nPer := ceiling( Round( nTolRec/20, 2 )) // 無條件進位取整數, /25 等於四筆顯示一次
// 回傳進度表資料[progress(完成百分比)、count(目前進度指標)、total(總筆數)]
IF nProgress == 1
hRes['step' ] := i // 目前進度
hRes['total'] := nTolRec // 總筆數
::sendMsg( hb_jsonencode( hRes ) ) // 將訊息傳回前端
ENDIF
//
nRow := 1
//
DO WHILE ! oQry:Eof()
IF i == 0
oSheet:writeStr( nRow, 1, '產品基本資料')
nRow += 1
// 輸出表頭
FOR j := 1 To Len(aHD)
oSheet:SetCol(j, j, aHD[j, 2], oFmtL )
oSheet:writeStr( nRow, j, aHD[j, 1] )
NEXT j
ENDIF
i++
// 回傳進度表資料
IF nProgress == 1
hRes['step' ] := i // 目前進度
hRes['total'] := nTolRec // 總數
::sendMsg( hb_jsonencode( hRes ) ) // 送資料到前端
ENDIF
// 利用迴圈輸出資料
j := i + 1
nRow++
//
oSheet:writeStr( nRow, 1, RTrim( oQry:FieldGet("PR_NUM")))
oSheet:writeStr( nRow, 2, RTrim( oQry:FieldGet("PR_NAM")))
//
oQry:Skip()
//
ENDDO // end: while(...)
oQry:End()
//
IF lClientStop
oBook:End()
oExcel:End() // 先關閉
EXIT // 前端中斷
ELSE
oBook:Save( ::cMainDir+"per.xlsx" )
oBook:End()
oExcel:End() // 先關閉
ENDIF
EXIT
ENDDO // end: while(...)
//
IF lClientStop
RETURN // 使用者中斷,離開,不用再送什麼資料回前端!!
ENDIF
hRes['lEnd'] := 1 // 利用此變數通知前端處理完畢!! 產生 Excel 檔案
IF hRes['err_no'] == 0
// 輸出
hRes['errmsg'] := hb_base64Encode( memoRead(::cMainDir+'per.xlsx') ) // 做 base64 編碼回傳
fErase(::cMainDir+'per.xlsx')
ENDIF
::sendMsg( hb_jsonencode( hRes )) // 將資料送至前端
//
RETURN NIL // $this->printData()
METHOD sendMsg(cRes) CLASS TPER
AP_SetContentType( "text/event-stream;charset=UTF-8" ) // 送出表頭格式
AP_RWRITE( e"event: message\n" )
AP_RWRITE( e"id: "+hb_ntos( int( hb_TToMSec( hb_dateTime() )))+e"\n" )
AP_RWRITE( e"retry: 15000\n" ) // 一個整數值,指示斷開連接時的重新連接時間
AP_RWRITE( e"data: "+cRes+e"\n" )
AP_RWRITE( e"\n" ) // 跟上面 hb_eol() 組合成兩個 \n \n 代表送出完畢!!
// 測試用,暫停 SLEEP_SEC 秒數
::Delay(200)
RETURN NIL // end: this->sebdMsg()