星期一, 9月 21, 2020

即時預覽圖片、多檔上傳 (with no bootstrap, no JQuery)


程式碼

<!DOCTYPE html>

<html lang=zh-Hant>

<head>

<title>preview picture with no bootstrap no jQuery</title>

    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">

<meta http-equiv="Content-Type" content="text/html; charset=utf-8">

</head>

<style>  

#OriginInput{

border:1px solid red;

height:30px;  

opacity:100; /* 必須為 0,覆蓋的「綠色圖片按鈕」才有作用 */

  width:90px;

        cursor:pointer;


}  

    #click_me{/*這是圖片,來當覆蓋按鈕*/

    margin-top:0px;

    margin-left:-60px;

        opacity:100; /* 0:不顯示 */

        /* cursor:pointer; 没作用,被 #OriginInput 強制取代 */

    }


</style>  

<body>

(1) 三種方式取得上傳圖片資料:二進位碼字串、UTF-8字串、Base64編碼字串。<br />

    (2) 上傳圖檔絕對路徑不正確,基本是瀏覽器之安全設定問題<br />

    (3) 綠色圖片游標變手型時,點按可上傳圖檔 但CSS要先設定 OriginInput.opacity = 0<br />

    (4) 預覽圖片可依需求動態調整大小<br />

    (5) 本程式已有多檔上傳功能,略改寫就能達到<br />

    <br />

    

    <div id="result" style="border:1px solid red;">測試結果區</div>

<form action="後台程式名.php" method="post" enctype="multipart/form-data">

<input id="PretendInput" type="text" readonly >

<input id="OriginInput" type="file"  multiple accept="image/gif, image/jpeg, image/png"  onchange="setFilePath()" >

<img id="click_me" src="https://via.placeholder.com/100x35/00FF00/" ><br />

</form>

</body>

<script>

function setFilePath(){


//--- 取得單一檔案路徑 + 檔名 - 第1種正確 => 

        var oOriginInput=document.getElementById("OriginInput");

        var oFiles=document.getElementById("OriginInput").files[0];

        if(oFiles && oFiles.name.trim().length > 4){

            let reader = new FileReader();


/*

            //-- 開始:用二進位字串顯示圖片資料 (讀出圖片16進位資料)

reader.readAsBinaryString(oFiles);

reader.onload = function(e) {

let vData = e.target.result;

                document.getElementById("result").innerHTML = null;

document.getElementById("result").innerHTML = vData;

};

            //-- 結束

*/

/*

            //-- 開始:用UTF-8字串顯示圖片資料 (讀出圖片16進位資料,呈現很多都無字型碼)

reader.readAsText(oFiles,'UTF-8');

reader.onload = function (e) {

let vData = e.target.result;

                document.getElementById("result").innerHTML = null;

document.getElementById("result").innerHTML = vData;

};

            //-- 結束

*/


            //-- 開始:解譯Base64編碼,直接預覽圖片 (讀出圖片16進位資料)

            reader.readAsDataURL(oFiles);

            reader.onload = function (e) {

                let vData = e.target.result;

                

                //let vData = document.getElementById("OriginInput").result; <= 錯誤語法

                //說明:(1) e.target == document.getElementById("OriginInput"), 但是 input

                //         没有 result 屬性。

                //     (2) 判斷 e.target 應該是最早取得結果值的物件,而 OriginInput 則是較後期才

                //         取得上傳的資料(或由e.target 把部份值塞給 OriginInput 的屬性變數)。

                

                //塞入完整「檔案路徑 + 檔名」

                document.getElementById("PretendInput").value=oOriginInput.value;

                            

                //預覽檔名

                document.getElementById("result").innerHTML =null;

                document.getElementById("result").innerHTML =oFiles.name + " <br /> ";

                

                //預覽圖片 (以下這段程式中的 vData 資料不是字串,是base64之圖片編碼)

                document.getElementById("result").innerHTML += "<img src='" + vData + "' style='width:120px;height:120px;'  alt='" + oFiles.name + "' />";

            };         

        //-- 結束


        }else{

        document.getElementById("PretendInput").value="";

        }



    

/*

        //--- 第2種 -- 取得單一「檔案路徑 + 檔名」之結果不正確 => C:\fakepath\sa.png

        var oFilePath=document.getElementById("OriginInput");       

        if(oFilePath.value.trim().length > 0){

document.getElementById("PretendInput").value=oFilePath.value;

alert("取得檔案路徑及檔名:" + oFilePath.value);

        }else{

        document.getElementById("PretendInput").value="";

        }

*/        


/*

        //--- 取得檔案名稱(適合多檔上傳架構)

        var oFiles=document.getElementById("OriginInput").files;

        var j=oFiles.length;

//console.log("共 " + j + " 個檔名");


        for(let i=0;i<j;i++){

if (oFiles[i]){

//alert("取得第 " + parseInt(i+1) + " 個檔名:" + oFiles[i].name.trim());

                

//以下三個有反應

console.log("取得第 " + parseInt(i+1) + " 個 name:" + oFiles[i].name.trim());

console.log("取得第 " + parseInt(i+1) + " 個 type:" + oFiles[i].type.trim());

console.log("取得第 " + parseInt(i+1) + " 個 size:" + oFiles[i].size);

                

//以下二個 undefined                

console.log("取得第 " + parseInt(i+1) + " 個 tmp_name:" + oFiles[i].tmp_name);

console.log("取得第 " + parseInt(i+1) + " 個 error:" + oFiles[i].error);

}

}

*/


/*

        //--- 取得檔案名稱(適合單檔上傳架構)

        var oFiles=document.getElementById("OriginInput").files;

        

//if(oFiles && oFiles.length>0 && sFileName.length > 0){

        if(oFiles){

        let sFileName=oFiles[0].name.trim();

        document.getElementById("PretendInput").value=sFileName;

        alert("取得檔案名稱:" + sFileName);

        }else{//清空檔名

        document.getElementById("PretendInput").value="";

        }

*/

}  

</script>

</html>

星期日, 9月 20, 2020

即時預覽圖片、單檔上傳 (with bootstrap & JQuery)








原始程式碼:

<!DOCTYPE html>

<html lang=zh-Hant>

<head>

<title>picture upload with bootstrap and jQuery</title>

    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">

<meta http-equiv="Content-Type" content="text/html; charset=utf-8">

<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css" rel="stylesheet" type="text/css">

<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.6/umd/popper.min.js" ></script>

<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/js/bootstrap.min.js"></script>

<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" ></script>

</head>

<style>

  .image-preview-wrapper{

    display: block;

    max-width: 310px;

    max-height: 310px;

    width: 100%;

    border: 2px solid #cccccc;

    margin: 0 auto;

    position: relative;

    cursor: pointer;

  }


  .spinner-wrapper {

    opacity: 0;

    margin: 0;

    padding: 0;

    left: 50%;

    top: 50%;

    transform: translate(-50%, -50%);

  }


  .opacity-1 {

    opacity: 1;

  }

  

  #idSubmit{

  cursor:pointer;

  }

  

  #file-uploader, #idFilePath{

  cursor:pointer;

  }


</style>


<body>

<br /><br />

<div class="row">

<div class="col-12 col-md-6 mx-auto">

  <a style="float:left" href="https://pjchender.blogspot.com/2019/01/js-javascript-input-file-upload-file.html">程式來源</a>

</div></div>            

<br /><br />

    

<label class="text-center mb-5 image-preview-wrapper" for="file-uploader">

<img src="https://via.placeholder.com/500x300?text=click to upload" alt="image-placehoder" class="img-thumbnail" data-target="image-preview">

    <!-- loading --->

<div class="spinner-wrapper position-absolute" data-target="spinner">

<div class="spinner-border text-secondary" role="status">

<span class="sr-only">Loading...</span>

</div>

</div>

</label>


  

<div class="row">

<div class="col-12 col-md-6 mx-auto">

<div class="custom-file">

<form action="後台程式名.php" method="post" enctype="multipart/form-data">

<input type="file" class="custom-file-input"  id="file-uploader" multiple accept="image/gif, image/jpeg, image/png" data-target="file-uploader" onchange="setFilePath()" >

<label class="custom-file-label"  id="idFilePath">點按上傳檔案</label>

                    <br /><br />

                    <input type="submit" id="idSubmit" value="Submit">

</form>

</div>

</div>

</div>


</body>


<script>

function setFilePath(){

    document.getElementById("idFilePath").innerText=document.getElementById("file-uploader").value;

    }

</script>


<script>

// STEP 1: select element and register change event

const imagePreview = document.querySelector('[data-target="image-preview"]');

const spinner = document.querySelector('[data-target="spinner"]');

const fileUploader = document.querySelector('[data-target="file-uploader"]');

fileUploader.addEventListener("change", handleFileUpload);


async function handleFileUpload(e) {

  try {

const file = e.target.files[0];

setUploading(true);

if (!file) return;


const beforeUploadCheck = await beforeUpload(file);

if (!beforeUploadCheck.isValid) throw beforeUploadCheck.errorMessages;


const arrayBuffer = await getArrayBuffer(file);

const response = await uploadFileAJAX(arrayBuffer);

alert("File Uploaded Success");

showPreviewImage(file);

  } catch (error) {

alert(error);

console.log("Catch Error: ", error);

  } finally {

e.target.value = '';  // reset input file

setUploading(false);

  }

}


// STEP 2: showPreviewImage with createObjectURL

// If you prefer Base64 image, use "FileReader.readAsDataURL"

function showPreviewImage(fileObj) {

  const image = URL.createObjectURL(fileObj);

  imagePreview.src = image;

}


// STEP 3: change file object into ArrayBuffer

function getArrayBuffer(fileObj) {

  return new Promise((resolve, reject) => {

const reader = new FileReader();

// Get ArrayBuffer when FileReader on load

reader.addEventListener("load", () => {

  resolve(reader.result);

});


// Get Error when FileReader on error

reader.addEventListener("error", () => {

  reject("error occurred in getArrayBuffer");

});


// read the blob object as ArrayBuffer

// if you nedd Base64, use reader.readAsDataURL

reader.readAsArrayBuffer(fileObj);

  });

}


// STEP 4: upload file throguth AJAX

// - use "new Uint8Array()"" to change ArrayBuffer into TypedArray

// - TypedArray is not a truely Array,

//   use "Array.from()" to change it into Array

function uploadFileAJAX(arrayBuffer) {

  // correct it to your own API endpoint

  return fetch("https://jsonplaceholder.typicode.com/posts/", {

headers: {

  version: 1,

  "content-type": "application/json"

},

method: "POST",

body: JSON.stringify({

  imageId: 1,

  icon: Array.from(new Uint8Array(arrayBuffer))

})

  })

.then(res => {

  if (!res.ok) {

throw res.statusText;

  }

  return res.json();

})

.then(data => data)

.catch(err => console.log("err", err));

}


// STEP 5: Create before upload checker if needed

function beforeUpload(fileObject) {

  return new Promise(resolve => {

const validFileTypes = ["image/jpeg", "image/png"];

const isValidFileType = validFileTypes.includes(fileObject.type);

let errorMessages = [];


if (!isValidFileType) {

  errorMessages.push("You can only upload JPG or PNG file!");

}


const isValidFileSize = fileObject.size / 1024 / 1024 < 2;

if (!isValidFileSize) {

  errorMessages.push("Image must smaller than 2MB!");

}


resolve({

  isValid: isValidFileType && isValidFileSize,

  errorMessages: errorMessages.join("\n")

});

  });

}


function setUploading(isUploading) {

  if (isUploading === true) {

spinner.classList.add("opacity-1");

  } else {

spinner.classList.remove("opacity-1");

  }

}

</script>

</html>

星期四, 5月 14, 2020

Chrome Extensions 在Linux 當作獨立程式執行:以LINE為例


目的:Chrome Extensions 擴充程式,在Linux 可當成獨立程式執行

用途:1、不必開啟Chrome 就能直接執行
   2、可同時執行多支程式。這代表若有三個 LINE ID, 則能同時登入。

範例:以「 LINE 」的 Chrome 擴充程式為範例


步驟一:
  雖便找一款有GUI的檔案管理工具,然後找Chrome的執行檔(/opt/google/
  chrome/google-chrome)或任意一個檔案,做「傳送(Send To)」至桌
  面(Desktop)的動作。

  這個動作實際是在做「軟捷徑(soft link)」。


步驟二:
  用任一款文字編輯器打開這個桌面上的「軟捷徑目標」,輸入以下資料
            [Desktop Entry]
            Encoding=UTF-8
            Version=1.0
            Comment=不必開啟chrome 就能直接執行LINE
            Type=Application
            Exec=/opt/google/chrome/google-chrome --app='chrome-extension://ophjlpahpchlmihnnnihgmmeilfjmjjc/index.html'
            Icon=virtualbox-vbox.png
            Name=LINE of Chrome Extensions
            Name[en_US.UTF-8]=LINE of Chrome Extensions

  說明1:
    上述「Icon」可自行訂喜歡的圖像,筆者是對VirtualBox做軟捷徑的
    傳送,所以它才呈現VirtualBox預設的圖像名稱「virtualbox-vbox.png」

  說明2:
    程式執行字串(Exec)中的「ophjlpahpchlmihnnnihgmmeilfjmjjc」
    是LINE 在 Chrome Store 中的ID

星期二, 3月 10, 2020

Android 11 特色/功能

來源

日前就已經不小心提前曝光的Android 11開發者預覽版本,稍早終於正式向開發者提供下載預覽,支援Pixel 2系列之後機種安裝測試 (對,第一代Pixel已經不支援了)。而Google也預計在今年5月開放beta版本,屆時應該就會開放一般使用者加入體驗測試,並且會在秋季釋出正式版本,理論上也會配合新款Pixel 5系列一同推出。

而在Android 11中,Google自然加強針對5G網路傳輸支援,另外也釋出新版可計算網路傳輸流量的API,讓開發者能在服務中標明當前使用網路傳輸量。另外,在螢幕設計原生支援部分也加入螢幕開孔設計,以及像華為Mate 30系列採用明顯側邊曲面設計的螢幕規格,無需硬體廠商另外撰寫硬體描述設置。

另外,在隱私與權限部分則是增加單次使用的位置存取、麥克風存取與相機權限存取設定,同時也會自動阻擋app重複權限請求,若使用者連續兩次選擇拒絕授權,系統就會限制app持續顯示權限存取請求訊息,避免造成使用干擾。

其他部分則包含加入可懸浮畫面上的聊天訊息氣泡,並且讓複製貼上功能變得更加直覺,另外也重新加入螢幕錄製功能,而針對去年推出的Pixel 4系列手機也改善Motion Sense浮空手勢操作功能。

至於在裝置上的人工智慧運算應用,Google此次也在Android 11加入全新Neural Networks API 1.3版本,藉此加快裝置端的人工智慧運算效率與運算廣度。

此次更新也包含針對Google Play安全強化、自動建議連接訊號較強的Wi-Fi網路、加強Call Screen電話接聽功能、支援HEIF圖像顯示,以及相機拍攝過程中自動關閉即時訊息通知、來電鈴聲或鬧鐘聲響,並且改善多媒體內容或連接HDMI輸出播放延遲。

Google接下來預期會在Google I/O 2020期間說明Android 11更具體細節,並且會在今年秋季釋出正式版本。

星期四, 1月 02, 2020

清單 - 區塊鏈可能用到的技術

◆ 前端支付錢包採「React Native Crypto」
◎基本技能
  ・React Native iOS and/or Android
 ・Redux, Redux Thunk, Reselect, keychain, lodash, gRPC
  ・rn-nodeify, bignumber.js, async-storage, emitter(events),
   javascript-state-machine, i18next
 ・Socket.io
  ・Node.js
 ・Git flow
  ・Object oriented concept (flow)

◎虛擬幣知識
 ・Bitcoin wallet with BIP49, BIP39 etc.
  ・Hashing, NaCl, encryption library usage

◎單元測試
  ・Jest, Babel preset, enzyme

◎產業知識
  ・Payment gateway knowledge is preferred
 ・Cryptocurrency knowledge, specially Bitcoin

◎可加分項目
  ・electron.js
  ・CI/CD
  ・虛擬幣知識, 特別是比特幣為基本
  ・Objective-c or Android Java


◆ 網頁後端使用Node.js
・虛擬幣知識, 特別是比特幣為基本
・lodash, socket.io, bitcoinjs-lib, buffer, bignumber.js, emitter(events), javascript-state-machine
・Git flow, Babel preset
・encryption library usage
・Unit test with gulp/jest. Usage of flow-bin and babel preset
・Web security conscious
・Object oriented concept
・Redis, Dockerfile
・Bitcoin's BIP49, BIP39
・NaCl, Hashing Domain knowhow
・SQLite, MySQL C++14


◆ 後端支付閘道用Node.js
・支付匝道的專業知識經驗
・其餘與「網頁後端使用Node.js」相同


◆ 採用Golang開發應用的知識
・熟悉 SQLite/MySQL 或 key/value 資料庫 相關使用能力
・熟悉基本密碼學(加解密、對稱、非對稱、Hash)
・熟悉基本區塊鏈概念(如比特幣)
・具備 concurrency ( goroutine、chan、chan token )應用能力
・具備 context、mutex、Sync 應用能力
・使用 Git, 版控流程融合 Github 和 Git flow 的能力。
・基本 Linux 作業環境系統操作
・ethereum 程式閱讀/開發

星期四, 12月 26, 2019

AI 學習地圖

國網中心之AI 學習地圖


AI 概略知識課程

一般概略知識的AI授課課程表:

週別 主題課程 內容大綱
1AI課程簡介
2資料分析與統計學1. 敘述性統計與機率分佈
2. 參數估計與假設檢定
3. 探索性資料分析與資料視覺化
3手把手資料分析實務
4機器學習與演算法概論1. 機器學習概論
2. 非監督式學習方法
3. 監督式學習方法
4. 學習理論、泛化與特徵重要性
5手把手機器學習實務
6深度學習入門1. 深度學習簡介
2. 深度學習實務技巧與前瞻技術
3. 對抗式學習入門
4. 強化學習入門
7電腦視覺1. CNN 原理簡介
2. 代表性 CNN 模型
3. CNN 於電腦視覺之應用與實際案例
8文字探勘與自然語言處理1. 文字能挖出什麼有價值的資訊
2. 如何進行文字探勘
3. 文字探勘與自然語言處理的實務應用
9語音與音樂訊號處理
10手把手深度學習實務
11推薦系統與聊天機器人1. 關聯式推薦 (association rule)
2. 內容推薦 (content-based recommendation)
3. 協同過濾推薦 (collaborative filtering)
4. 深廣學習 (wide & deep learning) 推薦系統
5. 傳統聊天機器人 vs. 深度聊天機器人
12社群媒體與社交網路分析1. 社群分析可以為組織帶來什麼好處?
2. 社群數據分析分享
13人工智慧的金融應用
14人工智慧開發環境建置
151. 深度學習在實作上的各種挑戰與困難
2. 從今天起打造第一支人工智慧團隊
3. 在引入 AI 之前,企業必須知道的資料分析與機器學習實務

16成果驗收

星期六, 12月 14, 2019

清單 - Web 各類範例

編輯

◆ CSS Only
  ◇ 水平線
   ・Div 底框當水平線及其色系

  ◇ 文字相關
   ・文字間距(字字之間,不是列列之間)

  ◇ 背景相關
   ・CSS background 3個應用範例


◆ Javascript 為主
  ◇ CSS
   ・從Javascript 修改 CSS


◆ 綜合 (css, html, javascript 等)
  ◇ Upload - 圖片
   ・即時預覽圖片、多檔上傳 (with no bootstrap, no JQuery)
   ・即時預覽圖片、單檔上傳 (with bootstrap & JQuery)

  ◇ Share to
   ・Web Share to 語法 (LINE, Facebook)

  ◇ 表格 (table)
   ・表格第一列及第一行固定不動

星期二, 11月 26, 2019

判斷上網設備(手機,桌機),並動態導向至可不同網頁

◆ 程式目的: 判斷上網設備(手機,桌機)並動態導向至不同網頁

◆ 注意事項: 以下程式必須放在HTML head > meta > titile 之後的第一時間執行

◆ split("/")技術細節說明

  ※web url sample: https://a.b.c/web_root/language/real_root/sub_folder
  ※web_root == folder of: desktop, mobile, ftp, ...
  ※language == en, tw, cn, ...
  ※real_root == index.html, index.php, folder name of project, css, imgs, js, ...
  
  aUrl[0] == https:
  aUrl[1] == null
  aUrl[2] == a.b.c
  aUrl[3] == web_root
  ...

◆ Javascript Source Code
<script>
  // web url sample: https://a.b.c/web_root/language/real_root/sub_folder
  var sOriginUrl = document.URL;
  var aUrl = sOriginUrl.split("/");

  if (aUrl[3] === "desktop" && (aUrl[4] === "" || typeof aUrl[4] == "undefined")){
    window.location = "/desktop/<language>";
  } else if (aUrl[3] === "mobile" && (aUrl[4] === "" || typeof aUrl[4] == "undefined")){
    window.location = "/mobile/<language>";
  }

  var gotoNewWebLocation = function() {
    var w = window,
      d = document,
      b = d.getElementsByTagName("body")[0],
      e = d.documentElement,
      x = e.clientWidth || b.clientWidth,
      y = e.clientHeight || b.clientHeight;
    
    // 舊手機規格以「768px-1」為判斷依據。Bootstrap 4 已改為「1200px-1」
    // 本人建議,仍須考量幾個手機螢幕規格的判斷點:
    //     1440x810, 1600x900, 1920x1080, 2560x1440
    // 其中,以 1920 x 1080 為門檻(人類視覺只要超過1920px就無法分辨出差別)
    
    // 注意 n == 所要判斷字串的字數,若是en, tw 則是 2
    if (x <= 767) { // or 1079
      if (aUrl[3] == "desktop" && aUrl[4].substr(0,n) == "<language>" ) window.location = "/mobile/<language>";
    } else {
      if (aUrl[3] == "mobile" && aUrl[4].substr(0,n) == "<language>") window.location = "/desktop/<language>";
    }
  };
  gotoNewWebLocation();
  
  if (window.addEventListener) {
    window.addEventListener("resize", gotoNewWebLocation);
    window.addEventListener("orientationchange", gotoNewWebLocation);
  } else if (window.attachEvent) { // 相容IE事件
    window.attachEvent("onresize", gotoNewWebLocation);
  }
</script>

星期日, 11月 17, 2019

清單 - Web 開發注意事項

編輯

※資料還不多,未來再連結至不同清單

◆ CSS
 ⦁ 某些宣告出現無法覆蓋現象,要注意是否有先使用「優先權語法」
  宣告,例如「 !important 」。

 ⦁ display
   ・display == flex or inline-flex 時
    - align-items  : 父層有設定,則子層設定無效
    - justify-content : 子層設定有設定,則父層設定無效


◆ RWD
 ⦁ 判斷上網設備(手機、桌機),並動態導向至可不同網頁