2015/12/03

CKEditor Image Upload On Codeigniter

這篇文章會一步步實作 CKEditorCodeIgniter 中圖片以及檔案上傳到 server 的功能,首先我們前往 CKEditor 官網下載最新的版本,這個 demo 使用的是 4.6,存檔後我會放在 assets/ckeditor/ 下,我會使用 Sybio/ImageWorkshop 這個套件對圖片做第一次的壓縮,我們通常不希望 user 存超大的東西在我們這啦,畢竟不是相簿,前台可以順利顯示為準則。

assets/ckeditor/config.js
CKEDITOR.editorConfig = function( config ) {
    config.toolbar =
        [
            {name:'document', items:['Source','-','Preview','Print']},
            {name:'clipboard', items:['Cut','Copy','Paste','PasteText','PasteFromWord','-','Undo','Redo']},
            {name:'basicstyles', items:['Bold','Italic','Underline','Strike','Subscript','Superscript','-','RemoveFormat']},
            '/',
            {name:'paragraph', items:['NumberedList','BulletedList','-','Outdent','Indent','-','Blockquote','-','JustifyLeft','JustifyCenter','JustifyRight','JustifyBlock','-','BidiLtr','BidiRtl']},
            {name:'links', items:['Link','Unlink']},
            {name:'insert', items:['Image','Table']},
            {name:'tools', items:['Maximize']}
        ];
    config.enterMode = CKEDITOR.ENTER_BR;
    config.filebrowserImageUploadUrl = baseUrl + 'ckeditor/image_upload';
    config.filebrowserUploadUrl = baseUrl + 'ckeditor/file_upload';
};

這是我常用的 config,工具可以自行增減。

application/views/ckeditor.php
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>CKEditor Upload Demo</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
    <script src="assets/ckeditor/ckeditor.js"></script>
    <script src="assets/ckeditor/adapters/jquery.js"></script>
    <script type="text/javascript" charset="utf-8">
    var baseUrl = "<?php echo base_url(); ?>";
    window.CKEDITOR_BASEPATH = "<?php echo base_url('assets/ckeditor'); ?>";

    $(function() {
        $('.ckeditor').ckeditor();
    });
    </script>
</head>
<body>
<?php echo form_open_multipart('ckeditor/upload'); ?>
<?php echo form_textarea(['name' => 'content', 'class' => 'ckeditor']); ?>
<?php echo form_submit('submit', '上傳'); ?>
<?php echo form_close(); ?>
</body>
</html>

簡單的設定一下 ckeditor 必要的環境。

application/controllers/Ckeditor.php
<?php

defined('BASEPATH') or exit('No direct script access allowed');

use PHPImageWorkshop\ImageWorkshop;

class Ckeditor extends CI_Controller
{
    /**
     * Upload path.
     *
     * @var string
     */
    private $path = 'assets/uploads/ckeditor/';

    /**
     * Index page.
     */
    public function index()
    {
        $this->load->helper('form');
        $this->load->helper('url');
        $this->load->view('ckeditor');
    }

    /**
     * CKEditor image upload
     *
     * Upload by ImageWorkshop package
     *
     * @return string
     */
    public function image_upload()
    {
        $this->load->helper('url');
        $funcNum = $this->input->get('CKEditorFuncNum');
        $source = $_FILES['upload']['tmp_name'];
        $file = $_FILES['upload']['name'];
        $ext = pathinfo($file, PATHINFO_EXTENSION);
        $ratio = 1200; // 寬度最大比例
        $allows = [1, 2, 3]; // GIF, JPEG, PNG
        $newName = date('YmdHis').rand(1000, 9999); //新檔名
        $message = '';
        $url = '';

        try {
            $exif = exif_imagetype($source);

            if (!$exif) {
                throw new Exception('檔案格式錯誤');
            }

            if (!in_array($exif, $allows)) {
                throw new Exception('僅允許 gif, jpeg, png');
            }

            $imageInfo = getimagesize($source);
            $layer = ImageWorkshop::initFromPath($source);

            // 若寬度大於比例,縮放置比例
            if ($imageInfo[0] > $ratio) {
                $layer->resizeInPixel($ratio, null, true);
            }

            $fileName = "{$newName}.{$ext}";
            $layer->save($this->path, $fileName);
            $url = base_url($this->path.$fileName);
        } catch (Exception $e) {
            $message = $e->getMessage();
        }

        $string = "<script type=\"text/javascript\">window.parent.CKEDITOR.tools.callFunction({$funcNum}, '{$url}', '{$message}');</script>";

        $this->output
            ->set_content_type('text/html')
            ->set_output($string);
    }

    /**
     * CKEditor image upload
     *
     * @return string
     */
    public function file_upload()
    {
        $this->load->helper('url');
        $funcNum = $this->input->get('CKEditorFuncNum');
        $source = $_FILES['upload']['tmp_name'];
        $file = $_FILES['upload']['name'];
        $ext = pathinfo($file, PATHINFO_EXTENSION);
        $newName = date('YmdHis').rand(1000, 9999); //新檔名
        $message = '';
        $url = '';

        try {
            $fileName = "{$newName}.{$ext}";
            $target = $this->path.$fileName;

            if (move_uploaded_file($source, $target)) {
                $url = base_url($target);
            } else {
                $message = '上傳失敗';
            }
        } catch (Exception $e) {
            $message = $e->getMessage();
        }

        $string = "<script type=\"text/javascript\">window.parent.CKEDITOR.tools.callFunction({$funcNum}, '{$url}', '{$message}');</script>";

        $this->output
            ->set_content_type('text/html')
            ->set_output($string);
    }
}

我們建立了 Ckeditor 的 controller,把 form 放在首頁,這個部份包含了圖片跟檔案上傳的程式碼,圖片部份也包含了檔案格式檢查、檔案尺寸檢查,如果還需要檢查更多的東西就在 try catch 裡面再加。

2015/10/19

watch.js

現在處於 node.js 幼幼班的階段,變覺得 node.js 的發明實在太方便,尤其是自己對 JavaScript 有一定的熟悉度所以學起來很愉悅,今天的 case 是,之前有介紹過 r.js,未來會朝向所有相關的 js 檔案全部壓成一個減少 server request,在開發階段會面臨一個問題就是東西有改動就要 compile,每次都要手動執行也挺累的,今天看到一個套件名為 watch,他可以偵測資料夾內檔案新增、修改、刪除的事件,於是寫了個 script 來幫我監聽並且做 auto compile。

~/watch/project.js

var watch = require('watch');
var path = require('path');
var exec = require('child_process').exec;
var monitorPath = '/var/www/project/assets/js/';

watch.createMonitor(monitorPath + 'src/', function(monitor) {
    monitor.on('changed', function(f) {
        var baseName = path.basename(f);
        var body = path.basename(f, '.js');
        var ext = path.extname(f);

        if (ext === '.js') {
            var command = 'r.js -o ' + monitorPath + 'build.js name=src/' + body + ' out=' + monitorPath + 'build/' + baseName + ' optimize=none';

            console.log('start compile ' + baseName);
            exec(command, function(error, stdout) {
                console.log(stdout);
            });
        }
    });
});

我在根目錄下面建立了一個 watch 的目錄,日後有新的專案的話就會多建一個檔案來監控,東西都設定完成後,只要在該目錄下 node project.js,watch 就會自動啟動了,我這邊編譯先把 uglifying 關掉,因為他會影響編譯速度,如果檔案已經確定是 final 不會變動的話,在去手動執行有 uglifying 的指令,如果在 windows 遇到錯誤訊息,只要把 command 改成 r.js.cmd 就可以了。

2015/10/13

r.js

用了一陣子 requirejs,之前都使用一些奇淫巧技讓每個頁面可以依照相依性導入各自頁面的 javascript,但其實這只達到了 requirejs 的非同步載入而已,檔案一多病沒有減少 request,requirejs 最強大的功能應該是他的 r.js 功能,他可以把相依性依照你設定的方式載入,並且合併成一個檔案,今天來示範一下我現在的布局。

目錄結構

- js/
 - build/
 - jquery/
 - src/
 - tool/

將所有的 javascript 檔案放在 js 目錄下,使用了四個目錄:

  • build/ (compiler 過後的檔案)
  • jquery/ (jQuery 相關檔案)
  • src/ (compiler 前的檔案)
  • tool/ (一些共用的工具檔案)

今天要示範我們要在 index.html 引入 index.js,index.js 要包含 jqueryjquery.formtools/func.js,並且依照相依性載入,首先我們建立 js/build.js

js/build.js
({
    baseUrl: './',
    paths: {
        'jquery': 'jquery/jquery.min',
        'form': 'jquery/jquery.form'
    },
    shim: {
        'form': ['jquery'],
        'src/index': ['form']
    },
    waitSeconds: 20
})

build.js 就是所有要 compiler 檔案的預設檔案,可以設定路徑、相依性等相關規範,我在這邊會將所有跟 jquery 相關的 plugin 都 depends on jquery,這樣當我各個 js 隨便調用任一個 jquery 的 plugin 時就會預載 jquery,接下來我們建立 src/index.js

src/index.js
requirejs(['tool/func'], function(func) {
    $(function() {
        func.init();
    });
});

我把 plugin 的調用寫在 build.js 裡面,工具的調用寫在各自的 js 裡面,這樣彈性變很大。

tool/func.js
define(function() {
    return {
        init: function() {
            console.log('func');
        }
    }
});

func.js 放個簡單的 funtion 讓 index.js 測試調用,接下來我們執行 r.js

$ r.js -o build.js name=src/index out=build/index.js

Tracing dependencies for: src/index
Uglifying file: D:/req/js/build/index.js

D:/req/js/build/index.js
----------------
D:/req/js/jquery/jquery.min.js
D:/req/js/jquery/jquery.form.js
D:/req/js/tool/func.js
D:/req/js/src/index.js

r.js 透過 build.js 執行了 src/index compiler 到 build/index.js,並且是依照我要的 order,接下來將結果放到 index.html 裡面。

index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>r.js</title>
    <script data-main="js/build/index" src="js/require.js"></script>
</head>
<body>

</body>
</html>

執行網頁後在 console 得到 func 的 output,如果東西在開發階段,可以加一個參數讓 compiler 的檔案不作最佳化, $ r.js -o build.js name=src/index out=build/index.js optimize=none,這樣就可以方便除錯了。

假設我們今天編譯了一個公用的工具,一旦你有修改到那隻工具,就要面臨每個檔案都必須重新編譯的問題,我們可以另外設一個 config 檔案來做群組的編譯。

builds.js
({
    paths: {
        'jquery': '../jquery/jquery.min',
        'form': '../jquery/jquery.form',
        'init': '../tool/func.js',
    },
    shim: {
        'form': ['jquery'],
        'src/index': ['form']
    },
    baseUrl: './',
    appDir: 'src/',
    dir: 'build/',
    waitSeconds: 20,
    modules: [
        {name: 'index'},
        {name: 'other'}
    ]
})

設定完畢後執行 r.js -o builds.js,就會將 modules 裡面的內容全部重新跑一遍。

2015/10/07

CodeIgniter Migration

一般的程式設計師設計資料庫常常會使用 PHPMyAdmin 或者是一些軟體直接在 DB 上做表單的建立或管理,一個人的專案可能還 ok,但如果遇到 co-work,或者是測試機與正式機不同機器,很難避免粗心漏掉內容的問題,最好的解決方案就是用程式產生相關的內容,PHP 兩個熱門的框架 Laravel 以及 CodeIgniter 都有提供 Database Migration 的功能,今天來初探 CodeIgniter 的 Migration 部分。

CodeIgniter 所有 Migration 的內容只要看過上面兩個連結就可以做完,讓我們來 step by step 案例。

前置作業

application/config/migration.php

這個檔案是設定 migration config 的位置,有幾個重點要設定

$config['migration_enabled'] = true;

miagration_enabled 的部分開啟。

$config['migration_type'] = 'sequential';

修改 migration_typesequential,看個人習慣,這個設定是指 migration 的檔案取名方式,如果是 timestamp 檔名取法則用日期當做順序,例: 20121031104401_add_blog.php,而 sequential 的話就是按照檔名排序,例:001_add_blog.php,其他設定如 miagration 資料夾位置,這邊我用 default,所以我們建立一個目錄 application/migrations/

實做

application/migrations/001_create_users_table.php
class Migration_Create_users_table extends CI_Migration
{
    public function up()
    {
        $this->dbforge->add_field([
            'id' => [
                'type' => 'int',
                'unsigned' => true,
                'auto_increment' => true,
            ],
            'username' => [
                'type' => 'varchar',
                'constraint' => 50,
            ],
            'password' => [
                'type' => 'varchar',
                'constraint' => 50,
            ],
            'gender' => [
                'type' => 'tinyint',
                'constraint' => 1
            ],
            'created_at' => [
                'type' => 'datetime'
            ],
            'updated_at TIMESTAMP DEFAULT NOW() ON UPDATE NOW()'
        ]);

        $this->dbforge->add_key('id', true);
        $this->dbforge->create_table('users');
    }

    public function down()
    {
        $this->dbforge->drop_table('users');
    }
}

class name 一定要是 Migration_ 加上字首大寫並且去掉序列的檔名,不然不會動,up 是建立的部分,down 則是給 rollback 用的,所以我們這邊的 up 是 create 某個 table 的話,down 就是 drop 某個 table,以此類推,add_key 的部分有加 true 會是 primary key,沒有的話就是一般的 index,基本上欄位的設定就是把 sql 的欄位建立方式填進去並且給值,constraint 不給的話預設會是最大,如果在 MySQL 裡面試是非題的話就是用 true false 給值,像是 null => false 則預設不能 null,如果沒下的話預設值看 MySQL 怎麼設定。

application/controllers/Migrate.php
class Migrate extends CI_Controller
{
    public function start()
    {
        $this->load->library('migration');

        if ($this->migration->latest() === false) {
            echo $this->migration->error_string();
        } else {
            echo 'migrated'.PHP_EOL;
        }
    }
}

我們建立一個 controller 來執行 migration,網路上有很多方法可以讓這個 controller 只能透過 cli 執行,但我實際跑過,如果你是下 $this->migration->latest() 的話,從 browser 重複執行並不會影響建構好的 table,所以我是沒有打算放,而且客戶常常都只有給 FTP 哪有辦法走 cli 模式。

建立完畢後我在本機的 cli 跑 php index.php migrate start 得到:

"chan" Sid: S-1-5-21-759891990-2490853520-3465201229-1000
migrated

打開資料庫看得到了 migrations 以及 users table,這樣就是順利完成了,接下來我們來模擬幾個場景。

application/migrations/002_add_ip_to_users_table.php
class Migration_Add_ip_to_users_table extends CI_Migration
{
    public function up()
    {
        $field = [
            'ip' => [
                'type' => 'varchar',
                'constraint' => 100,
                'null' => false,
            ]
        ];

        $this->dbforge->add_column('users', $field, 'password');
    }

    public function down()
    {
        $this->dbforge->drop_column('users', 'ip');
    }
}

這個場景就是在 users 裡面增加一個欄位,add_column 的第三個欄位等於 MySQL 的 after

application/migrations/003_drop_gender_from_users_table
class Migration_Drop_gender_from_users_table extends CI_Migration
{
    public function up()
    {
        $this->dbforge->drop_column('users', 'gender');
    }
}

users table 移掉 gender 這個 column。

004_modify_password_from_users_table.php
class Migration_Modify_password_from_users_table extends CI_Migration
{
    public function up()
    {
        $field = [
            'password' => [
                'name' => 'password',
                'type' => 'varchar',
                'constraint' => 100,
            ]
        ];

        $this->dbforge->modify_column('users', $field);
    }
}

password 的長度增加到 100,在 MySQL 如果要改一個欄位的內容 alter 的語法你必須把其他欄位設定都在打一次,所以不要只有打 'constraint' => 100 而已,這樣會出事。

2015/10/06

CodeIgniter Drivers

Codeigniter 裡面有一個功能叫 drivers,但官方的文件著墨很少,網路上討論的文章也不算多,但我覺得以一個不是使用 namespace 那麼方便做 DI 以及調用資源的 framework 來說,這個功能還頗好應用的,一般人使用 CI 都會用他的 library 功能,把自己的 class 放在這邊調用,但大部分就是一層式的沒有階級觀念,就不用提 interface 了,dirvers 可以靈活的做到調用,先來講怎麼設定 driver 環境,以各家第三方登入為例。

首先我們先建立目錄

  • Auth
  • Auth/Auth.php
  • Auth/drivers
  • Auth/drivers/Auth_facebook.php
  • Auth/drivers/Auth_google.php

命名規則就是檔名必須跟目錄名稱相同,第一個字首要大寫(包含初始的目錄),只要任何一個名稱取錯都不會動,我試過了。

Auth.php
class Auth extends CI_Driver_Library
{
    protected $valid_drivers;

    public function __construct()
    {
        $this->valid_drivers = array('facebook', 'google');
    }

    public function login()
    {
        echo 'login from auth driver'.PHP_EOL;
    }
}

$valid_drivers 是必要參數,他表示允許調用的 dirver 名稱,呼叫的時候不需要帶 parent name,這樣寫起來也比較清爽。

Auth_facebook.php
class Auth_facebook extends CI_Driver
{
    public function login()
    {
        echo 'login from auth facebook'.PHP_EOL;
    }
}

基本上被調用的 driver 子項目很簡單,繼承 CI_Driver 就可以了,我們接著建立一個叫 Login 的 controller。

application/controllers/Login.php
class Login extends CI_Controller
{
    public function index()
    {
        $this->load->driver('auth');
        $this->auth->login();
        $this->auth->facebook->login();
    }
}

在 command line 執行 php index.php login index 會得到

login from auth driver
login from auth facebook

寫到這就知道前面部屬好的話,之後要調用各家的 login 只要在 Auth/drivers 下增加,並且加入 $valid_drivers 設定即可,根據 SOLID 原則,會變動的 config 內容不應該存在在 class 裡面,所以我們在 config 增加一個 auth.php

application/config/auth.php
$config['valid_drivers'] = array('facebook', 'google');

然後將 Auth.php 改寫如下。

class Auth extends CI_Driver_Library
{
    protected $valid_drivers;
    protected $ci;

    public function __construct()
    {
        $this->ci =& get_instance();
        $this->ci->config->load('auth', true);
        $this->valid_drivers = $this->ci->config->item('valid_drivers', 'auth');
    }

    public function login()
    {
        echo 'login from auth driver'.PHP_EOL;
    }
}

這樣的話之後要加內容只要改 config 就好,但這樣的彈性還不是最好的,我們應該要讓 Login controller 可以接受變數選擇調用哪個登入才對,來對他動一點手腳。

class Login extends CI_Controller
{
    public function index($method = null)
    {
        $this->load->driver('auth');
        $this->auth->login();
        $this->auth->{$method}->login();
    }
}

然後執行 php index.php login index facebook,這樣會得到跟上面一樣的結果,但我們應該更嚴謹一點,檢查該 method 是否存在,在來強化一下。

class Login extends CI_Controller
{
    public function index($method = null)
    {
        $this->config->load('auth', true);

        if (!in_array($method, $this->config->item('valid_drivers', 'auth'))) {
            die('method not exists');
        }

        $this->load->driver('auth');
        $this->auth->login();
        $this->auth->{$method}->login();
    }
}

如果亂執行一通 php index.php login index twitter,會得到 method not exists,這大致上是 drivers 的全貌,怎麼變化應用讓功能更有彈性就要視不同的情況去配置了。

2015/09/23

PHP Mockery

去上了鐵哥的 PHP 測試的課程,回來一股腦的想要把每個看的到的可怕東西都 refactor,但鐵哥有交代,refactor 之前最好還是先下測試,正所謂先套保命索,再去走鋼索,好詩好詩,於是這幾天我除了重複看鐵哥的 readme 想熟記心法以外,完全醉心於 Mockery 了,之前也寫過一陣子測試,但有些情況很難模擬,部分原因是自己 code 拆的不夠細不好測,不然就是某些情況必須有外部的結果才能測試,例如說 mail 啦、資料庫等等,鐵哥介紹 Mockery 這個好用的工具以後解決了我的問題,而且我發現寫 code 為了想要好 mock 來拆 code 變成一種 refactoring 的依據,筆記一下這幾天的心得,用最簡單的方式解說,中間跳過一些繁複的過程。

DI

Game.php
class Game
{
    public function result(DB $db)
    {
        return $db->data();
    }
}
DB.php
class DB
{
    public function data()
    {
        return false;
    }
}

上面執行 var_dump((new Game)->result(new DB)); 的結果會是 false,我們來撰寫測試。

GameTest.php
use Mockery as m;

class GameTest extends PHPUnit_Framework_TestCase
{
    public function testResult()
    {
        $db = new DB;
        $game = new Game();

        $this->assertTrue($game->result($db));
    }

    public function tearDown()
    {
        m::close();
    }
}
執行結果
PHPUnit 4.7.5 by Sebastian Bergmann and contributors.

F

Time: 519 ms, Memory: 9.75Mb

There was 1 failure:

1) GameTest::testResult
Failed asserting that false is true.

D:\www\phpunit\tests\GameTest.php:12

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

因為 DBdata() 回傳的是 false,讓我們來 mock 他。

use Mockery as m;

class GameTest extends PHPUnit_Framework_TestCase
{
    public function testResult()
    {
        $db = m::mock('DB');
        $db->shouldReceive('data')
            ->once()
            ->andReturn(true);
        $game = new Game();

        $this->assertTrue($game->result($db));
    }

    public function tearDown()
    {
        m::close();
    }
}
執行結果
PHPUnit 4.7.5 by Sebastian Bergmann and contributors.

.

Time: 485 ms, Memory: 10.25Mb

OK (1 test, 1 assertion)

Mock 內部呼叫 method

Game.php
class Game
{
    public function result()
    {
        return $this->data();
    }

    public function data()
    {
        return false;
    }
}

執行 var_dump((new Game)->result()); 的結果為 false

GameTest.php
use Mockery as m;

class GameTest extends PHPUnit_Framework_TestCase
{
    public function testResult()
    {
        $game = new Game();

        $this->assertTrue($game->result());
    }

    public function tearDown()
    {
        m::close();
    }
}
執行結果
PHPUnit 4.7.5 by Sebastian Bergmann and contributors.

F

Time: 415 ms, Memory: 9.75Mb

There was 1 failure:

1) GameTest::testResult
Failed asserting that false is true.

D:\www\phpunit\tests\GameTest.php:11

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

讓我們使用 partial 來 mock 他。

use Mockery as m;

class GameTest extends PHPUnit_Framework_TestCase
{
    public function testResult()
    {
        $game = m::mock('Game[data]');
        $game->shouldReceive('data')
            ->once()
            ->andReturn(true);

        $this->assertTrue($game->result());
    }

    public function tearDown()
    {
        m::close();
    }
}
執行結果
PHPUnit 4.7.5 by Sebastian Bergmann and contributors.

.

Time: 442 ms, Memory: 10.25Mb

OK (1 test, 1 assertion)

Mock new class

Game.php
class Game
{
    public function result()
    {
        $db = new DB;

        return $db->data();
    }
}

執行 var_dump((new Game)->result()); 結果為 false

GameTest.php
use Mockery as m;

class GameTest extends PHPUnit_Framework_TestCase
{
    public function testResult()
    {
        $game = new Game;

        $this->assertTrue($game->result());
    }

    public function tearDown()
    {
        m::close();
    }
}
執行結果
PHPUnit 4.7.5 by Sebastian Bergmann and contributors.

F

Time: 403 ms, Memory: 9.75Mb

There was 1 failure:

1) GameTest::testResult
Failed asserting that false is true.

D:\www\phpunit\tests\GameTest.php:11

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

讓我們用 overload 來 mock 他。

use Mockery as m;

class GameTest extends PHPUnit_Framework_TestCase
{
    public function testResult()
    {
        $game = new Game;
        $mock = m::mock('overload:DB');
        $mock->shouldReceive('data')
            ->once()
            ->andReturn(true);

        $this->assertTrue($game->result());
    }

    public function tearDown()
    {
        m::close();
    }
}
執行結果
PHPUnit 4.7.5 by Sebastian Bergmann and contributors.

.

Time: 466 ms, Memory: 10.25Mb

OK (1 test, 1 assertion)

overload 目前發現對 static method 也有效用,但他會無法偵測到其他事件,例如 once()twice(),所以 static function 建議抽出來執行。

class Game
{
    public function result()
    {
        $data = $this->data();

        return $data;
    }

    public function data()
    {
        return DB::data();
    }
}

這樣就可以使用剛介紹 partial 的方法驗證了。

2015/09/16

jQuery Map And Get

我們常常會用到 jQuery 去 fetch 一段 DOM 以後拿到值來做事情,舉例來說今天有三個 input。

HTML
<input type="text" value="a">
<input type="text" value="b">
<input type="text" value="c">

我們想要 fetch DOM 把所有的 value 拿出來並且組成 a, b, c 這樣的字串該怎麼做,方法當然很多,一般來說會利用 $.each 來組。

$.each
var result = [];

$(':input').each(function() {
    result.push(this.value);   
});

result = result.join(',');

console.log(result); // a,b,c

如果理解更多的 jQuery API 的話,可以用一些特殊技巧用更少的行數達成某些結果。

$.map
var result = $(':input').map(function() {
    return this.value;   
}).get().join(',');

console.log(result); // a,b,c

mapget 是 jQuery 常拿來處理陣列的技巧,如果善加利用可以省去很多迴圈或是宣告的事情。