Bài viết sau hướng dẫn tạo HTML Form truyền dữ liệu có cấu trúc (object và array) lên server.

Cách tạo form gửi object/array và xử lý bằng PHP

Nếu đã từng sử dụng PHP hẳn bạn biết có thể dùng $_POST để nhận dữ liệu có cấu trúc từ client bằng cách dùng dấu ngoặc vuông trong thuộc tính name của input để thể hiện được cấu trúc đó.

Ví dụ theo cách thông thường khi muốn truyền tham số lên server ta làm như sau:

<form action="register.php" method="POST">
    <input name="username" type="text" value="User Name">
    <input name="password" type="password" value="Password">
    <input type="submit" value="Submit">
</form>

// Result of $_POST (PHP):
// array(
//     'username' => 'User Name',
//     'password' => 'Password'
// )

Dùng ngoặc vuông trong thuộc tính name, ta có thể gửi một Object lên server như sau:

<form action="register.php" method="POST">
    <input name="user[username]" type="text" value="User Name">
    <input name="user[password]" type="password" value="Password">
    <input type="submit" value="Submit">
</form>

// Result of $_POST (PHP):
// array(
//     'user' => array(
//         'username' => 'User Name'
//         'password' => 'Password'
//     )
// )

Nếu không chỉ định index trong dấu ngoặc vuông, PHP ngầm hiểu đó là một Array và sẽ tự tăng index (tính từ 0) nếu gặp phần tử trùng lặp.

<form action="register.php" method="POST">
    <input name="user[]" type="text" value="User Name">
    <input name="user[]" type="password" value="Password">
    <input type="submit" value="Submit">
</form>

// Result of $_POST (PHP):
// array(
//     'user' => array(
//         0 => 'User Name',
//         1 => 'Password'
//     )
// )

Và tất nhiên ta có thể chỉ định index của mảng nếu muốn.

<form action="register.php" method="POST">
    <input name="user[0]" type="text" value="User Name">
    <input name="user[1]" type="password" value="Password">
    <input type="submit" value="Submit">
</form>

// Result of $_POST (PHP):
// array(
//     'user' => array(
//         0 => 'User Name',
//         1 => 'Password'
//     )
// )

Tuyệt vời hơn, cách này cũng có thể áp dụng với cấu trúc dữ liệu lồng nhau (nested).

<form action="register.php" method="POST">
    <input name="user[auth][username]" type="text" value="User Name">
    <input name="user[auth][password]" type="password" value="Password">
    <input name="user[info][][address]" type="text" value="Address 1">
    <input name="user[info][][city]" type="password" value="City 1">
    <input name="user[info][][address]" type="text" value="Address 2">
    <input name="user[info][][city]" type="password" value="City 2">
    <input type="submit" value="Submit">
</form>

// Result of $_POST (PHP):
// array(
//     'user' => array(
//         'auth' => array(
//             'username' => 'User Name',
//             'password' => 'Password'
//         ),
//         'info' => array(
//             0 => array(
//                 'address' => 'Address 1',
//                 'city' => 'City 1',
//             ),
//             1 => array(
//                 'address' => 'Address 2',
//                 'city' => 'City 2',
//             )
//         )
//     )
// )

Xử lý form có object/array với JavaScript

Đôi khi phía Server lại là của bên thứ 3 và dữ liệu quy ước nhận được lại có Content Type là JSON chẳng hạn thì cách làm như trên hoàn toàn vô hiệu.

Cách làm của tôi là thay vì submit trực tiếp, tôi tách dữ liệu của Form thành một JSON object, sau đó sử dụng jQuery AJAX để gửi lên server.

<form action="register.php" method="POST">
    <input name="user[auth][username]" type="text" value="User Name">
    <input name="user[auth][password]" type="password" value="Password">
    <input name="user[info][][address]" type="text" value="Address 1">
    <input name="user[info][][city]" type="password" value="City 1">
    <input name="user[info][][address]" type="text" value="Address 2">
    <input name="user[info][][city]" type="password" value="City 2">
    <input type="submit" value="Submit">
</form>

<script src="jquery.min.js"></script>
<script src="jquery.icreativ.js"></script>
<script>
$(document).ready(function() {
    $('form').submit(function(event) {
        let data = $(this).serializeToObject(); // convert to object
        $.ajax({
            method: 'POST',
            url: $(this).attr('action'),
            data: JSON.stringify( data ),
            contentType: 'application/json; charset=UTF-8' // submit a JSON object
        }).done(function(response) {
            // Process the response here
        });
        event.preventDefault(); // <- avoid reloading
   });
});
</script>

Ở đây tôi sử dụng một jQuery plugin là $.serializeToObject (không phải mặc định của jQuery đâu nhé).

Nhiệm vụ của plugin này là chuyển đổi dữ liệu trong các input của một form thành một JSON object giống như những gì một server PHP nhận được như đã nói ở phần trước. Với ví dụ trên, data trả về có cấu trúc như sau:

{
    'user': {
        'auth': {
            'username': 'User Name',
            'password': 'Password'
        },
        'info' : {
            [
                {
                    'address': 'Address 1',
                    'city': 'City 1'
                },
                {
                    'address': 'Address 1',
                    'city': 'City 1'
                }
            ]
        }
    }
}

Mã nguồn của plugin $.serializeToObject như sau:

jquery.icreativ.js

(function ( $ ) {
    $.fn.serializeToObject = function() {
        let data = $(this).serializeArray(),
            ret = {};

        data.forEach( obj => {
            var matches = obj.name.match(/^[a-zA-Z0-9_]+(\[[a-zA-Z0-9_]*])+$/);    
            if (matches) { // array type input
                let lastKey = obj.name.match(/^[a-zA-Z0-9_]+/)[0],
                    dimensions = obj.name.match(/\[[a-zA-Z0-9_]*\]/g),
                    ref = ret;

                dimensions.forEach( function(key, depth) {
                    key = key.replace(/[\[\]]/g, ''); // remove square brackets

                    if (key === '') { // dynamic array
                        if (lastKey == '')  {
                            let idx = ref.length - 1;
                            if (idx == -1 || !ref[idx]) {
                                ref[idx + 1] = [];
                            }
                        } else {
                            if (!ref[lastKey]) {
                                ref[lastKey] = [];
                            }
                        }
                    } else if (isInteger(key)) { // indexed array
                        if (lastKey == '')  {
                            let idx = ref.length - 1;
                            if (idx == -1 || ref[idx][key] !== undefined) {
                                ref[idx + 1] = [];
                            }
                        } else {
                            if (!ref[lastKey]) {
                                ref[lastKey] = [];
                            }
                        }
                    } else { // object
                        if (lastKey == '')  {
                            let idx = ref.length - 1;
                            if (idx < 0 || ref[idx][key] !== undefined) {
                                ref[idx + 1] = {};
                            }
                        } else {
                            if (!ref[lastKey]) {
                                ref[lastKey] = {};
                            }
                        }
                    }
                    if (lastKey === '') {
                        let idx = ref.length - 1;
                        ref = ref[idx];
                    } else {
                        ref = ref[lastKey];
                    }
                    if (depth === dimensions.length - 1) {
                        if (key === '') {
                            let idx = ref.length;
                            ref[idx] = obj.value;
                        } else {
                            ref[key] = obj.value;
                        }
                    }
                    lastKey = key;
                });
            } else {
                if (!ret[obj.name]) {                    
                    ret[obj.name] = obj.value;
                } else {
                    if (ret[obj.name].constructor === Array) {
                        ret[obj.name].push(obj.value);
                    } else {
                        let temp = ret[obj.name];
                        ret[obj.name] = [temp, obj.value];
                    }
                }
            }
        } );
        return ret;
    }
} )( jQuery );

Nếu hứng thú với Vanilla JS thì tôi cũng chia sẻ mã nguồn như sau:

function serializeArray(form) {
    var result = [];

    Array.prototype.slice.call(form.elements).forEach(function (field) {
        if (!field.name || field.disabled
        || ['file', 'reset', 'submit', 'button'].indexOf(field.type) > -1)
            return;

        if (field.type === 'select-multiple') {
            Array.prototype.slice.call(field.options).forEach(function (option) {
                if (!option.selected)
                    return;
                result.push({
                    name: field.name,
                    value: option.value
                });
            });
           return;
        }
        if (['checkbox', 'radio'].indexOf(field.type) > -1 && !field.checked)
            return;
        result.push({
            name: field.name,
            value: field.value
        });
    });
    return result;
}

function serializeObject(form) {
    let data = serializeArray(form);
    let ret = {};

    data.forEach( obj => {
        var matches = obj.name.match(/^[a-zA-Z0-9_]+(\[[a-zA-Z0-9_]*\])+$/);
        if (matches) { // array type input
            let lastKey = obj.name.match(/^[a-zA-Z0-9_]+/)[0],
                dimensions = obj.name.match(/\[[a-zA-Z0-9_]*\]/g),
                ref = ret;

            dimensions.forEach( function(key, depth) {
                key = key.replace(/[\[\]]/g, ''); // remove square brackets

                if (key === '') { // dynamic array
                    if (lastKey == '')  {
                        let idx = ref.length - 1;
                        if (idx == -1 || !ref[idx]) {
                            ref[idx + 1] = [];
                        }
                    } else {
                        if (!ref[lastKey]) {
                            ref[lastKey] = [];
                        }
                    }
                } else if (isInteger(key)) { // indexed array
                    if (lastKey == '')  {
                        let idx = ref.length - 1;
                        if (idx == -1 || ref[idx][key] !== undefined) {
                            ref[idx + 1] = [];
                        }
                    } else {
                        if (!ref[lastKey]) {
                            ref[lastKey] = [];
                        }
                    }
                } else { // object
                    if (lastKey == '')  {
                        let idx = ref.length - 1;
                        if (idx < 0 || ref[idx][key] !== undefined) {
                            ref[idx + 1] = {};
                        }
                    } else {
                        if (!ref[lastKey]) {
                            ref[lastKey] = {};
                        }
                    }
                }
                if (lastKey === '') {
                    let idx = ref.length - 1;
                    ref = ref[idx];
                } else {
                    ref = ref[lastKey];
                }
                if (depth === dimensions.length - 1) {
                    if (key === '') {
                        let idx = ref.length;
                        ref[idx] = obj.value;
                    } else {
                        if (!ref[key]) {
                            ref[key] = obj.value;
                        } else {
                            if (ref[key].constructor === Array)
                                ref[key].push(obj.value);
                            else {
                                let temp = ref[key];
                                ref[key] = [temp, obj.value];
                            }
                        }
                    }
                }
                lastKey = key;
            });
        } else {
            if (!ret[obj.name]) {
                ret[obj.name] = obj.value;
            } else {
                if (ret[obj.name].constructor === Array) {
                    ret[obj.name].push(obj.value);
                } else {
                    let temp = ret[obj.name];
                    ret[obj.name] = [temp, obj.value];
                }
            }
        }
    } );
    return ret;
};