Strange javascript variable scope issue

昨天在測試 Web App 於 iPad 上運作狀況時發生一個意外的狀況,相同的程式在 Windows 的 Google Chrome, Mozilla Firefox 都正常運作,但到了 iPad 上 Apple Safari, Google Chrome, Mozilla Firefox 卻全數掛掉,經過 debug 後發現一個奇特的狀況:變數的 scope 在不同環境(Browsers on Windows vs Browsers on iOS) 居然有不同範圍!?

<!-- 簡化示意 -->
<script defer>const foo={ 'bar': true };</script>
<script src="app.js" type="module"></script>

以上同樣的程式碼在 Windows 下的兩個瀏覽器,app.js 都能存取到變數 foo,但到了 iOS 上面,三個瀏覽器都拋出 undefined,因此直覺就判定是 variable scope 的問題,所以將 foo 宣告由 const foo 改成 window.foo,將其附加到 window 物件後 app.js 就能正常地直接以變數名稱 foo 存取(不需要以 window.foo),若是 module 載入的 js 不能直接存取 foo 的話並沒有什麼問題 (非全域變數),但不同平台結果不同這就有些問題了…

分類: 射茶包, 軟體開發 | 標籤: | 在〈Strange javascript variable scope issue〉中留言功能已關閉

調整 Bootstrap 4 按鈕的顏色 (Change Bootstrap 4 button color)

Bootstrap 4 的按鈕配色個人覺得比較硬,想改回 Bootstrap 3 的按鈕配色於是就用覆蓋樣式的方式依照 Boostrap 4 的樣式設定調整顏色。

( LESS 格式,因為 WordPress bug 把 & 給 escape 了,複製後得把 &amp; 改回 & )

#bundle() {
	.btnStyle {
		color: @color;
		background-color: @background-color;
		border-color: @border-color;
		&:hover {
			color: @color;
			background-color: @background-color-hover;
			border-color: @border-color-hover;
		}
		&.focus, &:focus {
			box-shadow: @box-shadow;
		}
		&.disabled, &:disabled {
			color: @color;
			background-color: @background-color;
			border-color: @border-color;
		}
		&:not(:disabled):not(.disabled).active
		, &:not(:disabled):not(.disabled):active
		, .show > &.dropdown-toggle {
			color: @color;
			background-color: @background-color-hover;
			border-color: @border-color-hover;
		}
		&:not(:disabled):not(.disabled):active:focus
		, &:not(:disabled):not(.disabled).active:focus
		, .show > &.dropdown-toggle:focus {
			box-shadow: @box-shadow;
		}
	}
}

.btn-primary {
	@color: #fff;
	@background-color: #0275d8;
	@border-color: #0275d8;
	@background-color-hover: #025aa5;
	@border-color-hover: #01549b;
	@box-shadow: 0 0 0 .2rem rgba(2, 117, 216, .5);
	#bundle.btnStyle();
}

.btn-secondary {
	@color: #292b2c;
	@background-color: #fff;
	@border-color: #ccc;
	@background-color-hover: #e6e6e6;
	@border-color-hover: #adadad;
	@box-shadow: 0 0 0 .2rem rgba(204, 204, 204, .5);
	#bundle.btnStyle();
}

.btn-info {
	@color: #fff;
	@background-color: #5bc0de;
	@border-color: #5bc0de;
	@background-color-hover: #31b0d5;
	@border-color-hover: #2aabd2;
	@box-shadow: 0 0 0 .2rem rgba(91, 192, 222, .5);
	#bundle.btnStyle();
}

.btn-success {
	@color: #fff;
	@background-color: #5cb85c;
	@border-color: #5cb85c;
	@background-color-hover: #449d44;
	@border-color-hover: #449d44;
	@box-shadow: 0 0 0 .2rem rgba(92, 184, 92, .5);
	#bundle.btnStyle();
}

.btn-warning {
	@color: #fff;
	@background-color: #f0ad4e;
	@border-color: #f0ad4e;
	@background-color-hover: #ec971f;
	@border-color-hover: #eb9316;
	@box-shadow: 0 0 0 .2rem rgba(240, 173, 78, .5);
	#bundle.btnStyle();
}

.btn-danger {
	@color: #fff;
	@background-color: #d9534f;
	@border-color: #d9534f;
	@background-color-hover: #c9302c;
	@border-color-hover: #c12e2a;
	@box-shadow: 0 0 0 .2rem rgba(217, 83, 79, .5);
	#bundle.btnStyle();
}

.btn-light {
	@color: #212529;
	@background-color: #eceeec;
	@border-color: #eceeec;
	@background-color-hover: #d8dcd8;
	@border-color-hover: #d1d6d1;
	@box-shadow: 0 0 0 .2rem rgba(206, 208, 207, 0.5);
	#bundle.btnStyle();
}

.btn-dark {
	@color: #fff;
	@background-color: #343a40;
	@border-color: #343a40;
	@background-color-hover: #23272b;
	@border-color-hover: #1d2124;
	@box-shadow: 0 0 0 .2rem rgba(82, 88, 93, 0.5);
	#bundle.btnStyle();
}
 
分類: Web technology | 在〈調整 Bootstrap 4 按鈕的顏色 (Change Bootstrap 4 button color)〉中留言功能已關閉

用 PowerShell 自動備份 Windows 10 的焦點影像

Title: Windows Spotlight auto backup script by PowerShell

Windows 10 的鎖定畫面如果背景選用 "Winodws 焦點" 的話會每天變換影像,系統下載的這些影像並不會一直保留而是會刪除舊的,因此若是想要將好看的風景保留下來的話必須自己備份,於是我就寫一小段碼來自動化備份這些風景。

把以下程式碼存檔後用工作排程器安排每天執行或登入時執行即可,記得把 $to 的路徑改成自己的備份路徑:

$from = "$env:USERPROFILE\AppData\Local\Packages\Microsoft.Windows.ContentDeliveryManager_cw5n1h2txyewy\LocalState\Assets\"
$to = "C:\Users\Your_Name\Pictures\Windows Spotlight\"

Get-ChildItem -Path $from | `
Foreach-Object {
    If ($_.length -gt 300kb) {
        $newFilename = $to + $_.BaseName + ".jpg"
        If (-not (Test-Path $newFilename)) {
            Write-Host "Copy:" $_.BaseName
            Copy-Item $_.FullName $newFilename
        }
    }
}
分類: 資訊技術相關雜記 | 在〈用 PowerShell 自動備份 Windows 10 的焦點影像〉中留言功能已關閉

給 Android Studio (Windows) 用的預設 .gitattributes

Android Studio 建立新專案的時候並沒有預設的 .gitattributes,Project 裡面有些預設檔案的 EOL 是 LF,在 Windows 環境下當 git 設定 core.autocrlf true 且 core.safecrlf true 的時候,直接做 git add 操作的話會跳出 fatal error,因此要稍微補充一下設定。

.gitattributes

* text=auto !eol

gradlew			text eol=lf
.idea/*.xml		text eol=lf
分類: 軟體開發 | 標籤: | 在〈給 Android Studio (Windows) 用的預設 .gitattributes〉中留言功能已關閉

把 Cordova Webview 嵌入 Android Native App

Steps:

1. 把 Requirements 都下載安裝好

2. 建立 Cordova Android Project

如果 Cordova projects 放置的位置與 Android App projects 是不同的目錄的話,test_cordova 可以改成與 android app project 相同名稱,這樣會比較好知道 cordova project 是對應到哪個 android project,此外 com.example.hello 也可以改成跟 android app 一樣。

cordova create test_cordova com.example.hello HelloWorld
cordova platform add android
cordova build

在 build 之前可以視情況加入需要的 plugins,例如:

cordova plugin add cordova-plugin-device
cordova plugin add cordova-plugin-x-toast

3. 建立 Android App Project

用預設的即可,有一個 Empty Activity (MainActivity)。

4. 安裝 Cordova Project 環境到 Android Project

Compile Cordova Lib

參考 Android WebViews - Apache Cordova 步驟 1~3,把 cordova-x.x.x.jar 複製到 /libs 之後,編輯 app/build.gradle,加上:

compile files('libs/cordova-x.x.x.jar')

Example:

dependencies {
	compile fileTree(include: ['*.jar'], dir: 'libs')
	testCompile 'junit:junit:4.12'
	compile 'com.android.support:appcompat-v7:23.4.0'
	compile files('libs/cordova-5.2.2.jar')
}

複製依存檔案 (左邊是 Cordova project 的 path,右邊是 Android project 的 path)

www folder

platforms/android/assets/www -> src/main/assets/www

plugins (com/example/hello 以外的目錄,安裝 plugins 多出來的那些)

platforms/android/src/plugin_folder -> src/main/java/

config.xml

platforms/android/res/xml/config.xml -> src/main/res/xml/

然後 sync 一下 Gradle 情報。

5. 建立基本運作 Activity

把 Cordova Webview 嵌入 Android Native App 的基本作業到第四步驟就完成了,接下來可以建立基本運作的 Activity,來測試看看是不是正常運作。

建立以下兩個 Empty Activity: TestCordovaActivity, TestCordovaWithLayoutActivity,然後編輯 java 及 layout 如下:

TestCordovaActivity

public class TestCordovaActivity extends CordovaActivity {
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		super.init();
		// Load your application
		launchUrl = "file:///android_asset/www/index.html";
		loadUrl(launchUrl);
	}
}

@Override
public void onDestroy() {
	ViewGroup viewGroup = (ViewGroup)this.findViewById(android.R.id.content);
	SystemWebView webView = (SystemWebView) viewGroup.getChildAt(0);
	viewGroup.removeView(webView);
	webView.removeAllViews();
	super.onDestroy();
}

TestCordovaWithLayoutActivity

public class TestCordovaWithLayoutActivity extends CordovaActivity {

	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {

		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_test_cordova_with_layout);
		super.init();

		// Load your application
		launchUrl = "file:///android_asset/www/index2.html";
		loadUrl(launchUrl);

	}

	@Override
	protected CordovaWebView makeWebView() {
		SystemWebView webView = (SystemWebView)findViewById(R.id.cordovaWebView);
		return new CordovaWebViewImpl(new SystemWebViewEngine(webView));
	}

	@Override
	protected void createViews() {
		// Why are we setting a constant as the ID? This should be investigated
		/*
		appView.getView().setId(100);
		appView.getView().setLayoutParams(new FrameLayout.LayoutParams(
			ViewGroup.LayoutParams.MATCH_PARENT,
			ViewGroup.LayoutParams.MATCH_PARENT));
		setContentView(appView.getView());
		*/

		if (preferences.contains("BackgroundColor")) {
			int backgroundColor = preferences.getInteger("BackgroundColor", Color.BLACK);
			// Background of activity:
			appView.getView().setBackgroundColor(backgroundColor);
		}

		appView.getView().requestFocusFromTouch();
	}

	@Override
	public void onDestroy() {
		SystemWebView webView = (SystemWebView)findViewById(R.id.cordovaWebView);
		((ViewGroup)webView.getParent()).removeView(webView);
		webView.removeAllViews();
		// If we called webView.destory(), we will get 'mWebViewCore is null' error.
		//webView.destroy();
		super.onDestroy();
	}

}

activity_test_cordova_with_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	xmlns:tools="http://schemas.android.com/tools"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:paddingBottom="@dimen/activity_vertical_margin"
	android:paddingLeft="@dimen/activity_horizontal_margin"
	android:paddingRight="@dimen/activity_horizontal_margin"
	android:paddingTop="@dimen/activity_vertical_margin"
	tools:context="com.fandratec.fssfordksh.TestCordovaWithLayoutActivity">

	<TextView
		android:layout_width="match_parent"
		android:layout_height="100dp"
		android:background="#FF0000"
		android:textColor="#FFFFFF"
		android:gravity="center"
		android:text="This is native text view"
		/>

	<org.apache.cordova.engine.SystemWebView
		android:id="@+id/cordovaWebView"
		android:layout_width="match_parent"
		android:layout_height="match_parent"
		/>

</RelativeLayout>

MainActivity

public class MainActivity extends AppCompatActivity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
	}

	public void startCordovaActivity(View view) {
		Intent intent = new Intent(this, TestCordovaActivity.class);
		startActivity(intent);
	}

	public void startCordovaActivityWithLayout(View view) {
		Intent intent = new Intent(this, TestCordovaWithLayoutActivity.class);
		startActivity(intent);
	}

}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	xmlns:tools="http://schemas.android.com/tools"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:paddingBottom="@dimen/activity_vertical_margin"
	android:paddingLeft="@dimen/activity_horizontal_margin"
	android:paddingRight="@dimen/activity_horizontal_margin"
	android:paddingTop="@dimen/activity_vertical_margin"
	tools:context="com.fandratec.fssfordksh.MainActivity">

	<TextView
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:text="Hello World!"
		android:id="@+id/textView"/>

	<Button
		android:text="Start Cordova Activity Without Layout"
		android:onClick="startCordovaActivity"
		android:layout_width="200dp"
		android:layout_height="100dp"
		android:id="@+id/button"
		android:layout_below="@+id/textView"
		android:layout_alignParentStart="true"/>

	<Button
		android:text="Start Cordova Activity With Layout"
		android:onClick="startCordovaActivityWithLayout"
		android:layout_width="200dp"
		android:layout_height="100dp"
		android:layout_below="@+id/button"
		android:layout_alignParentStart="true"/>

</RelativeLayout>

以上都弄好後,就可以 run 了。

6. 延伸

如果這個 Android App 要連線到 Internet 或是取得 GPS 座標,記得在 AndroidManifest.xml 加入權限需求宣告,如果沒加上的話會出現錯誤。(例如 loadUrl(REMOTE_URL) 就會直接獲得 "Application 錯誤")

<manifest>
	<uses-permission android:name="android.permission.INTERNET" />
	<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
	<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
</manifest>

另外,現在版本的 Cordova project 預設啟用 whitelist 機制,想從 CordovaWebView 連到local (/assets/www) 外的內容並在其中呈現,得在 config.xml 加上設定,要不然會另外呼叫預設瀏覽器:

<widget>
	<allow-navigation href="https://YOUR.SITE/*" />
</widget>

References:

分類: 軟體開發 | 標籤: | 在〈把 Cordova Webview 嵌入 Android Native App〉中留言功能已關閉