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;
      });
    }
  }
}

2021/02/01

Work With Typescript And Webpack And Route

呈上個文章,如果我們想有類似 SPA 那種 routes 的功能,讓重複資源如 jQuery 統一一包該怎麼實做。

目錄結構

|   a.html
|   b.html
|   tsconfig.json
|   webpack.config.js
+---app
|
\---src
    |   main.ts
    |   routes.ts
    \---class
            a.ts
            b.ts
            main.interface.ts

我把 index.ts 改成了 main.ts,增加了,程式碼內容如下:

a.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Typescript + Webpack</title>
    <script src="app/main.js"></script>
</head>
<body id="a">
    <div id="content"></div>
</body>
</html>
b.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Typescript + Webpack</title>
    <script src="app/main.js"></script>
</head>
<body id="b">
    <div id="content"></div>
</body>
</html>
main.ts
import $ from 'jquery';
import { routes } from './routes';

$(function () {
    const classLoader = async (routeName: string): Promise<any> => {
        const imported = await import('./class/' + routeName);
        const className = routeName.toUpperCase();

        return new imported[className]();
    }

    routes.forEach(route => {
        if ($(`body#${route}`).is('*')) {
            classLoader(route).then(target => target.init());

            return false;
        }
    });
});
routes.ts
export const routes: string[] = [
    'a',
    'b'
]
webpack.config.js
const path = require('path');

module.exports = {
    mode: 'development',
    entry: {
        main: {import: './src/main.ts'}
    },
    module: {
        rules: [
            {
                test: /\.tsx?$/,
                use: 'ts-loader',
                exclude: /node_modules/,
            },
        ],
    },
    resolve: {
        extensions: ['.tsx', '.ts', '.js'],
    },
    output: {
        path: path.resolve(__dirname, 'app'),
    },
};
main.interface.ts
export interface mainInterface {
    init(): void
}
a.ts
import { mainInterface } from './main.interface';
import $ from 'jquery';

export class A implements mainInterface {
    init(): void {
        $('div#content').html('a');
    }
}
b.ts
import { mainInterface } from './main.interface';
import $ from 'jquery';

export class B implements mainInterface {
    init(): void {
        $('div#content').html('b');
    }
}

N.html 對應 N.ts,將所有要對應的 class 設定加到 routes.tsmain.ts 會動態偵測到 html 檔案的 body 有沒有相對應的 id,有的話就去撈相對應的 class,而相對應的 class 繼承 interface 一定要有 init() 這個 method,最為這個頁面時要做的預設事件,這樣 compile 的結果 jQuery 或其他的套件只會有一份,並且做到隔離不同頁面所需要的功能。

2021/01/29

Work With Typescript And Webpack

因為工作的關係接觸了 angular,所以學習了 typescript,覺得這工具很好用,於是希望在非 angular 的環境下寫 js 時也用 typescript,這時要借用 webpack 幫忙打包成 browser 可以使用的內容。

目錄結構

├─app
└─src
    │  index.ts
    │
    └─class
            chan.ts
│  index.html
│  tsconfig.json
│  webpack.config.js

config

tsconfig.json
{
  "compilerOptions": {
    "allowSyntheticDefaultImports": true,
    "noImplicitAny": true,
    "module": "es6",
    "target": "es5",
    "allowJs": true
  }
}

typescript 的基礎配置

webpack.config.js
const path = require('path');

module.exports = {
    mode: 'development',
    entry: {
        index: {import: './src/index.ts'}
    },
    module: {
        rules: [
            {
                test: /\.tsx?$/,
                use: 'ts-loader',
                exclude: /node_modules/,
            },
        ],
    },
    resolve: {
        extensions: ['.tsx', '.ts', '.js'],
    },
    output: {
        path: path.resolve(__dirname, 'app'),
    },
};

webpack 的配置,會掃描 entry 我指定的檔案輸出到 app 同樣的檔名,用非 spa 的方式開發往往還是不同頁面使用不同的 js,所以這個架構對 user 並不能節省資源,因為每個 js 都會包一份 jquery,只是對開發者方便的工具而已,除非你有把握把程式寫到每個頁面隔離不互相干擾,那就可以一支檔案打天下,也是 spa 的運作方式。

npm i --save webpack webpack-cli typescript ts-loader

安裝 compiler 需要的工具

使用方式

npm i -s jquery dayjs
index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Typescript + Webpack</title>
    <script src="app/index.js"></script>
</head>
<body>
    <div id="content"></div>
</body>
</html>
index.ts
import $ from 'jquery';
import { Chan } from './class/chan';

$(function () {
    const chan = new Chan();

    $('div#content').html(chan.getNow());
});
chan.ts
import dayjs from 'dayjs';

export class Chan {
    public getNow(): string {
        return dayjs().toString();
    }
}

使用 webpack compiler 之後我們頁面上的 div#content 會出現 Fri, 29 Jan 2021 09:09:52 GMT,大功告成。

2020/12/22

Delete None Keeping Files

當我們在備份文件的時候,通常只保留一定的數量,例如說 10 天份,我們可能會這樣寫:

$ find PATH_TO_FILES -mtime +30 | xargs rm -f &> /dev/null
$ find PATH_TO_FILES -mtime +30 -exec rm {} \ &> /dev/null;

這兩種寫法結果一樣,都是會刪除建立超過三十天以上的檔案,不過這會有個問題,假設今天因為某些因素備份指令中斷了,但 crontab 依舊在跑刪除指令,若 op 沒有及時發現,備份檔終究會被刪光,因此另一種寫法我認為是比較好的

我們先製造六個檔案

i=1; while [ $i -lt 7 ]; do touch $i; ((i++)); done

$ ll -d ./*
-rw-r--r-- 1 root root 0 12月 22 11:45 ./1
-rw-r--r-- 1 root root 0 12月 22 11:45 ./2
-rw-r--r-- 1 root root 0 12月 22 11:45 ./3
-rw-r--r-- 1 root root 0 12月 22 11:45 ./4
-rw-r--r-- 1 root root 0 12月 22 11:45 ./5
-rw-r--r-- 1 root root 0 12月 22 11:45 ./6

假設我們只要保留 5 個檔案,指令就是:

$ ls -ltd ./* | tail -n +6 | awk '{print $NF}' | xargs rm -f &> /dev/null

-d 過濾了 ./ 以及 ../-t 會照時間排序,tail -n +6 就是拉出超過 n 數量的結果,要保留 5 份的話要填入 6,所以檔名 1-6 時 6 會被篩選出來,awk '{print $NF}' 會印出篩選出來資訊的尾端,也就是檔名,最後就用 xargs rm -f 刪除該檔案,這樣寫的話不管時間的影響該備份目錄永遠會留下你指定的檔案數量。