2021/12/08

Imitate Jetbrains Moving Radial Gradient Effect

今天在逛 Jetbrains 新產品 fleet 的網頁時,看到一個圓形漸層會跟著滑鼠移動的特效,覺得很有意思。

手癢來刻一個類似的。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Glow Cursor</title>
<style>
.box {
width: 300px;
height: 300px;
border: 2px solid blue;
}
</style>
</head>
<body>
<div class="box"></div>
<script>
var box = document.querySelector('.box');
box.addEventListener('mousemove', function (e) {
var rect = e.target.getBoundingClientRect();
var x = e.clientX - rect.left;
var y = e.clientY - rect.top;
box.style.background = `radial-gradient(circle at ${x}px ${y}px, rgba(138, 189, 61, 1) 0%, rgba(255, 255, 255, 1) 150px`;
});
box.addEventListener('mouseleave', function () {
box.style.background = null;
});
</script>
</body>
</html>

效果大概是這樣 Demo

2021/12/03

Imitate Laravel Collection Sum Funtion in PHP, Python, Node.js

Laravel 包含了許多好用的套件,Collection 應該是最好用的,將陣列導進去以後可以做各種花式操作,自己嘗試在 PHP、Python、Node.js 上模擬一下作法。

PHP

<?php
class Collection
{
private array $collections;
public function __construct(array $collections)
{
$this->collections = $collections;
}
public function sum(Closure $callable = null): int
{
$callbackIn = ($callable === null) ? $this->isNotCallback() : $this->isCallback($callable);
return array_reduce($this->collections, fn($carry, $item) => $carry + $callbackIn($item), 0);
}
private function isNotCallback(): Closure
{
return fn(int $value): int => $value;
}
private function isCallback(Closure $callable): Closure
{
return fn(int $value): int => $callable($value);
}
}
$collection = new Collection([1, 2, 3]);
$cal = fn($item): int => $item + 5;
echo $collection->sum(), PHP_EOL;
echo $collection->sum($cal);

Python

from functools import reduce
from typing import Callable
class Collection(object):
collections: list
def __init__(self, collections: list):
self.collections = collections
def sum(self, callback=None) -> int:
callback_in = self.is_not_callable() if callback is None else self.is_callable(callback)
return reduce(lambda carry, item: carry + callback_in(item), self.collections, 0)
@staticmethod
def is_not_callable() -> Callable[[int], int]:
return lambda x: x
@staticmethod
def is_callable(callback) -> Callable[[int], Callable[[int], int]]:
return lambda x: callback(x)
def call_me() -> Callable[[int], any]:
return lambda x: x + 5
collection = Collection([1, 2, 3])
print(collection.sum())
print(collection.sum(call_me()))

Node.js

class Collection {
private collections: any[];
constructor(collections: any[]) {
this.collections = collections;
}
public sum(callback: Function = null) {
const callbackIn = (callback === null) ? this.isNotCallable() : this.isCallable(callback);
return this.collections.reduce((carry, item) => carry + callbackIn(item), 0);
}
private isNotCallable(): Function {
return (value: any) => value;
}
private isCallable(callback): Function {
return (value: any) => callback(value);
}
}
const collections = new Collection([1, 2, 3]);
const cal = (value) => value + 5;
console.log(collections.sum());
console.log(collections.sum(cal));

2021/12/02

Laravel Connet To MSSQL Server On CentOS

PHP 安裝

# Install PHP 7.4
sudo yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
sudo yum -y install https://rpms.remirepo.net/enterprise/remi-release-7.rpm
sudo yum -y install yum-utils
sudo yum-config-manager --enable remi-php74
sudo yum install -y php php-pdo php-odbc php-mbstring php-zip php-xml

MSSQL Driver 安裝,擇一安裝

1. MSSQL Driver(首選,支援版本 SQL Server 2000 以上)

sudo yum install php-sqlsrv
curl https://packages.microsoft.com/config/rhel/7/prod.repo > /etc/yum.repos.d/mssql-release.repo
sudo yum remove unixODBC-utf16 unixODBC-utf16-devel #to avoid conflicts
sudo ACCEPT_EULA=Y yum install -y msodbcsql17

2. FreeTDS(Sql Server 2000)

sudo yum -y install php-mssql
sudo yum -y install unixODBC-*
sudo yum -y install freetds
cd /usr/lib64
sudo mv libtdsodbc.so.0.0.0 libtdsodbc.so
sudo vi /etc/odbcinst.ini

在最後增加:

[FreeTDS]
Description = ODBC for FreeTDS
Driver64 = /usr/lib64/libtdsodbc.so
Setup64 = /usr/lib64/libtdsodbc.so
FileUsage = 1
sudo vi /etc/freetds.conf

在最後增加:

[SqlServer2000]
host = {IP}
port = 1433
tds version = 8.0
client charset = UTF-8

在 Laravel 的配置一切照舊,除了 MSSQL 的 host 要指到 SqlServer2000,若遇到時間格式的問題可以參考此篇,使用 FreeTDS 可以通吃,但怕會有無法預期的問題,不建議同一台 Server 同時使用兩種方式。

參考網站

2021/11/18

update multiple package to latest at once

npm 有一個指令可以偵測套件是否有出新的可以更新,我故意裝了 jquery@2 以及 bootstrap@4。

npm ls
test@1.0.0 D:\node_test
├── bootstrap@4.6.1
└── jquery@2.2.4

我們來執行更新檢查:

npm outdated
Package Current Wanted Latest Location Depended by
bootstrap 4.6.1 4.6.1 5.1.3 node_modules/bootstrap node_test
jquery 2.2.4 2.2.4 3.6.0 node_modules/jquery node_test

bootstrap 跟 jquery 都有新版本可以更新,更新指令如下:

npm install jquery@latest bootstrap@latest

如果之前就有加入 package.json 的話則不用指定 --save 或是 --save-dev,一兩個套件可能還好,假設今天有十幾個要一次更新就累了,而 npm 本身沒有提供全部更新到最新版的指令,npm update 跑的是在 package.json 的板號下的最新版,所以跨了版號就不執行了。

npmjs 當然有套件可以輔助這件事,像是 npm-check,但我們也可以透過 shell script 自己處理,以下示範 awk 以及 cut 的用法。

# 指令一,利用 awk
npm outdated | sed 1d | awk -F ' ' '{print sprintf("%s@latest", $1)}' | xargs npm install
npm outdated | sed 1d | awk -F ' ' '{print sprintf("%s@%s", $1, $4)}' | xargs npm install
# 指令二,利用 cut
npm outdated | sed 1d | cut -d' ' -f1 | xargs -I {} echo '{}@latest' | xargs npm install

2021/08/20

Form Validator In Angular

Angular 的 ReactiveFormsModule 相當強大,包含了 two way binding data 以及好用的 validator,以下的範例示範怎麼針對單一欄位以及整體表單自製 validator,如果 username 填寫 chan 的話表示帳號重複,年齡大於 100 的話則錯誤,group 的部分兩者都要填寫。

ts
import { Component, OnInit } from '@angular/core';
import { AbstractControl, FormBuilder, ValidationErrors, ValidatorFn } from '@angular/forms';
@Component({
selector: 'app-form-validator',
templateUrl: './form-validator.component.html',
styleUrls: ['./form-validator.component.scss']
})
export class FormValidatorComponent implements OnInit {
public form = this.fb.group({
username: ['', [this.userNameChecker()]],
age: ['', [this.numberChecker()]]
}, { validators: this.bothRequired() });
public ageLimit: number = 100;
constructor(
private fb: FormBuilder
) { }
ngOnInit(): void {
}
get username(): AbstractControl {
return this.form.controls.username;
}
get age(): AbstractControl {
return this.form.controls.age;
}
private userNameChecker(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
if (control.value === '') {
return null;
}
if (control.value === 'chan') {
return { userNameExists: true };
}
return null;
}
}
private numberChecker(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
if (control.value === '') {
return null;
}
if (control.value > this.ageLimit) {
return { tooBig: true };
}
return null;
}
}
private bothRequired(): ValidatorFn {
return (group: AbstractControl): ValidationErrors | null => {
return (group.get('username')?.value === '' || group.get('age')?.value === '') ? { bothRequired: true } : null;
}
}
}
html
<h3>{{ form.value | json }}</h3>
<div [formGroup]="form">
<div>
username: <input type="text" formControlName="username">
<div *ngIf="username.errors?.userNameExists">username exists</div>
</div>
<div>
age: <input type="number" formControlName="age">
<div *ngIf="age.errors?.tooBig">age should lower than {{ ageLimit }}</div>
</div>
<div>
<button [disabled]="form.invalid">submit</button>
</div>
</div>

2021/08/13

Stop The Process By Condition In Ansible

Ansible 的任務沒有下中斷的話,即便有使用 when 偵測,playbook 會繼續往下走,可以利用 fail 這個參數讓條件不符合時直接把 process 中斷,以 stat 偵測目錄是否存在來做範例。

- hosts: localhost
vars:
path: /tmp/sync/
tasks:
- name: "stat {{ path }}"
stat:
path: "{{ path }}"
register: the_dir
- name: "shutdown when {{ path }} is not exists"
fail:
msg: "{{ path }} not exists"
when: the_dir.stat.exists == False
- name: "echo yes when {{ path }} exists"
debug:
msg: 'yes'
when: the_dir.stat.exists

當我們設定的 path 不存在時,會直接中斷,不會走道最後的 "echo yes when {{ path }} exists"

2021/08/11

Input And Output In Angular

Angular 的 @Input 以及 @Output 修飾器是用來讓父與子元件可以跨元件溝通資訊,應用範圍很廣,譬如說有些固定的表單功能我們可以把他切細,提供給各單元的表單套入使用,可以減少重複造輪子的問題,下面示範如何讓子元件改變父元件表單的內容。

父 ts
import { Component, OnInit } from '@angular/core';
import { FormBuilder } from '@angular/forms';
@Component({
selector: 'app-example',
templateUrl: './example.component.html',
styleUrls: ['./example.component.scss']
})
export class ExampleComponent implements OnInit {
public form = this.fb.group({
username: 'chan',
amount: 100
});
constructor(
private fb: FormBuilder
) { }
ngOnInit(): void {
}
public patchAmount(amount: number): void {
this.form.patchValue({
amount: amount
})
}
}
父 html
<h1>Input Output Example</h1>
<h3>{{ form.value | json }}</h3>
<form [formGroup]="form">
<div>
<input type="text" formControlName="username">
</div>
<div>
<app-example-number [amount]="form.get('amount')?.value" (patchParentAmount)="patchAmount($event)"></app-example-number>
</div>
</form>
子 ts
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
@Component({
selector: 'app-example-number',
templateUrl: './example-number.component.html',
styleUrls: ['./example-number.component.scss']
})
export class ExampleNumberComponent implements OnInit {
@Input() amount: number = 0;
@Output() patchParentAmount = new EventEmitter<number>();
constructor() { }
ngOnInit(): void {
}
public updateAmount(): void {
this.patchParentAmount.emit(this.amount);
}
}
子 html
<input type="number" [(ngModel)]="amount" (ngModelChange)="updateAmount()">

父元件傳了預設的 amount 過去給子元件,子元件透過監聽 ngModelChange 將改變的數字 emit 到父元件執行 patch form value 的動作,就可以做到互動更新了。

2021/08/09

Dynamic Form Field In Angular

Angular 的 form builder 蠻強大的,two way binding data 以及 validator 相當好用,其中有一個特性 FormArray 可以做許多運用,來做一個動態產生表單欄位的範例。

ts
import { Component, OnInit } from '@angular/core';
import { FormArray, FormBuilder, FormGroup } from '@angular/forms';
@Component({
selector: 'app-dy-demo',
templateUrl: './dy-demo.component.html',
styleUrls: ['./dy-demo.component.scss']
})
export class DyDemoComponent implements OnInit {
public form = this.fb.group({
username: 'chan',
family: this.fb.array([])
})
constructor(
private fb: FormBuilder
) { }
ngOnInit(): void {
}
public newLine(): FormGroup {
return this.fb.group({
name: '',
age: ''
})
}
public family(): FormArray {
return this.form.get('family') as FormArray;
}
public add(): void {
return this.family().push(this.newLine());
}
public remove(i: number): void {
this.family().removeAt(i);
}
}
html
<h3>{{ form.value | json }}</h3>
<div>
<button (click)="add()">add</button>
</div>
<form action="" [formGroup]="form">
<div>
<input type="text" formControlName="username">
</div>
<div formArrayName="family">
<ng-container *ngFor="let f of family().controls; let i=index" [formGroupName]="i">
<div>
username: <input type="text" formControlName="name"> age: <input type="number" formControlName="age">
<button (click)="remove(i)">remove</button>
</div>
</ng-container>
</div>
</form>

目前覺得 Angular 的內容真是易學難精啊。

2021/08/05

Array Object In Angular

angular 使用了 typescript,強型別是 ts 大殺器之一,除了簡單的 stringnumber 那些定義以外,array object 使用度也很高,最常見的作法如下。

interface Member {
username: string;
password: string;
}
const members: Member[] = [
{
username: 'chan',
password: '123456'
}
];
console.log(members);

這樣做沒什麼大問題,但設計上我覺得有點卡,因為 interface Member 就是一個單一物件,另一種寫法比較囉唆一點,但我覺得比較好:

interface Member {
username: string;
password: string;
}
interface Members extends Array<Member> {}
const members: Members = [
{
username: 'chan',
password: '123456'
}
];
console.log(members);

這樣調用 Members 時很明確就是要用陣列,而 Member 則可以給 member detail 使用,職責比較清楚,detail 理論上應該是最小單位,但如果 list 部分有自己的需求也可以自行擴充,type alias 也有相同的作法:

type Member = {
username: string;
password: string;
}
type Members = Member[];
const members: Members = [
{
username: 'chan',
password: '123456'
}
];
console.log(members);

2021/07/27

Specific String On Typescript

今天在寫 anguar 的時候想到一個問題,一般我們在 parameter 後面帶的會是型別,像是:

const tester = (params: { name: string }): void => {
console.log(params.name);
}
tester({name: 'a'}); // a

那是否可以直接限定想要的內容,答案是可以的:

const tester = (params: { name: 'a' | 'b' }): void => {
console.log(params.name);
}
tester({name: 'a'}); // a

此時如果你下 tester({name: 'c'}); 會得到 TS2322: Type '"c"' is not assignable to type '"a" | "b"'. 的警告,不過多寫了幾個案例以後發現一個雷,不能用變數帶入:

const tester = (params: { name: 'a' | 'b' }): void => {
console.log(params.name);
}
const params = {
name: 'a'
};
tester(params); // a

上方的寫法會得到警告 TS2345: Argument of type '{ name: string; }' is not assignable to parameter of type '{ name: "a" | "b"; }'. Types of property 'name' are incompatible. Type 'string' is not assignable to type '"a" | "b"'.,最簡單的解法是:

const tester = (params: { name: 'a' | 'b' }): void => {
console.log(params.name);
}
const params = {
name: 'a' as const
};
tester(params); // a

不過上社群討論後,最好的解法應該是用 enum

enum TesterParam {
A = 'a',
B = 'b'
}
const tester = (params: { name: TesterParam }): void => {
console.log(params.name);
}
const params = {
name: TesterParam.A
};
tester(params); // a

這樣的架構對維護來說是最好的。

2021/06/16

Compare two arrays is equal in JavaScript

在 PHP 中,可以簡單使用比較運算子比對兩個陣列。

$a = ['a', 'b', 'c'];
$b = ['a', 'b', 'c'];
var_dump($a === $b); // true

甚至排序不一樣透過 sort 也可以解決。

$a = ['a', 'b', 'c'];
$b = ['a', 'c', 'b'];
var_dump(sort($a) === sort($b)); // true

但在 js 的世界不行。

const a = [
'a',
'b',
'c'
];
const b = [
'a',
'b',
'c'
];
console.log(a == b); // false
console.log(a === b); // false

在網路上看到一個解法挺好的,記錄一下。

const a = [
'a',
'b',
'c'
];
const b = [
'a',
'b',
'c'
];
const result = a.sort().every((value, index) => {
return value === b.sort()[index];
});
console.log(result); // true

寫成 prototype 就是。

const a = [
'a',
'b',
'c'
];
const b = [
'a',
'b',
'c'
];
Array.prototype.equals = function (array) {
return this.sort().every((value, index) => {
return value === array.sort()[index];
});
}
console.log(a.equals(b)); // true

2021/05/07

使用 CSS 讓圖片不破格

現在是 responsive 的時代,所有的頁面都必須做響應式,處理這種網站的時候,圖片最令人頭痛,有時候無法規範客戶是否傳我們要求的比例,所以必須在前端做一些手腳讓畫面看起來起碼不那麼糟。

html
<div class="container">
<div class="row row-no-gutters">
<div class="col-md-3">
<div class="box">
<img src="https://via.placeholder.com/700x400.png">
</div>
</div>
<div class="col-md-3">
<div class="box">
<img src="https://via.placeholder.com/600x400.png">
</div>
</div>
<div class="col-md-3">
<div class="box">
<img src="https://via.placeholder.com/600x200.png">
</div>
</div>
<div class="col-md-3">
<div class="box">
<img src="https://via.placeholder.com/300x500.png">
</div>
</div>
</div>
</div>

這是大部分使用 bootstrap 時會使用的排版,一個四格圖片的配置,我放了幾張完全不同尺寸的 placeholder 圖片,一般人處理會在 img 上下 max-width: 100%,讓他寬度不會爆表,但高度的部分就會被圖片撐高了,有些人會把圖片變成 background,再利用 cover 屬性搭配 padding-top + overflow: hidden 撐高度讓圖片置中並且不破格,但這樣會影響 SEO,我自己試出了一個配置還不錯。

scss
.container {
.box {
width: 100%;
padding-top: 66.6%;
overflow: hidden;
position: relative;
border: 1px solid grey;
}
.box {
img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
}
}

這樣的配置法可以在不將圖片變成 background 的情況下讓圖片滿版居中,IE 11 以下不支援,不過我不在乎。

2021/05/05

Vagrant provision with docker and docker-compose install

vagrant 提供了 provision 初始化功能,可以類似 Dockerfile 在初始化 vagrant 的時候就把想要安裝的內容處理好,下面示範一下如何安裝一個乾淨的 ubuntu 後把 docker 以及 docker-compose 一併安裝。

Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/focal64"
config.vm.provider "virtualbox" do |vb|
vb.memory = "1024"
end
config.vm.provision "shell", inline: <<-SHELL
docker -v &> /dev/null
if [[ $? != 0 ]]; then
echo "start install docker"
curl -sL https://get.docker.com/ | sudo bash
sudo usermod -aG docker vagrant
docker -v
else
echo "docker installed"
fi
docker-compose -v &> /dev/null
if [[ $? != 0 ]]; then
echo "start install docker-compose"
sudo curl -sL "https://github.com/docker/compose/releases/download/1.29.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
docker-compose -v
else
echo "docker-compose installed"
fi
SHELL
end

由於 provision 有可能重複使用,所以最一些執行檢查是必要的,這邊我檢查已安裝的技巧是使用 -v 這個參數,如果沒有安裝,系統會報錯,因此 $? 會非 0,藉此判斷有沒有安裝過。

2021/04/09

FormArray And Checkbox

最近在學 Angular,他的 Form 相關模組挺有趣的,不過為了實現某些功能還是必須透過 JS 加工,這邊記錄一下怎麼綁 checkbox 的 cheked 結果到 FormBuilder 上。

html
<h3>{{ form.value | json }}</h3>
<div [formGroup]="form">
<input type="text" formControlName="name">
<ng-container *ngFor="let hobby of hobbies">
<label> <input type="checkbox" [value]="hobby.value" (click)="updateHobbies($event)">
{{ hobby.name }}
</label>
</ng-container></div>
ts
import { Component, OnInit } from '@angular/core';
import { FormArray, FormBuilder, FormControl } from '@angular/forms';
@Component({
selector: 'app-checkbox',
templateUrl: './checkbox.component.html',
styleUrls: ['./checkbox.component.scss']
})
export class CheckboxComponent implements OnInit {
public form = this.fb.group({
name: [''],
hobbies: this.fb.array([])
});
public hobbies: object[] = [
{ name: 'basketball', value: 'basketball' },
{ name: 'baseball', value: 'baseball' },
{ name: 'tennis', value: 'tennis' },
];
constructor(
private fb: FormBuilder
) { }
ngOnInit(): void {
}
public updateHobbies(e): void {
const hobbies: FormArray = this.form.get('hobbies') as FormArray;
if (e.target.checked) {
hobbies.push(this.fb.control(e.target.value));
} else {
let i = 0;
hobbies.controls.forEach((control: FormControl) => {
if (control.value === e.target.value) {
hobbies.removeAt(i);
return;
}
++i;
});
}
}
}