Hướng dẫn can javascript interact with local files? - javascript có thể tương tác với các tệp cục bộ không?

Như đã đề cập trước đây, API hệ thống tệp và tệp, cùng với API FileWriter, có thể được sử dụng để đọc và ghi các tệp từ ngữ cảnh của tab/cửa sổ trình duyệt sang máy khách.

Có một số điều liên quan đến API FileSystem và FileWriter mà bạn nên biết, một số trong đó đã được đề cập, nhưng đáng để lặp lại:

  • Việc triển khai API hiện chỉ tồn tại trong các trình duyệt dựa trên crom (Chrome & Opera)
  • Cả hai API đã được đưa ra khỏi đường đua tiêu chuẩn W3C vào ngày 24 tháng 4 năm 2014 và đến bây giờ là độc quyền
  • Loại bỏ các API (nay là độc quyền) khỏi việc triển khai các trình duyệt trong tương lai là một khả năng
  • Hộp cát (một vị trí trên đĩa bên ngoài các tệp không thể tạo ra hiệu ứng) được sử dụng để lưu trữ các tệp được tạo bằng APIsandbox (a location on disk outside of which files can produce no effect) is used to store the files created with the APIs
  • Một hệ thống tệp ảo (cấu trúc thư mục không nhất thiết phải tồn tại trên đĩa ở cùng một hình thức khi nó được truy cập từ bên trong trình duyệt) được sử dụng biểu thị các tệp được tạo bằng APIvirtual file system (a directory structure which does not necessarily exist on disk in the same form that it does when accessed from within the browser) is used represent the files created with the APIs

Dưới đây là những ví dụ đơn giản về cách sử dụng API, trực tiếp và gián tiếp, song song để làm những điều này:

BakedGoods*

Viết tệp:

bakedGoods.set({
    data: [{key: "testFile", value: "Hello world!", dataFormat: "text/plain"}],
    storageTypes: ["fileSystem"],
    options: {fileSystem:{storageType: Window.PERSISTENT}},
    complete: function(byStorageTypeStoredItemRangeDataObj, byStorageTypeErrorObj){}
});

Đọc tài liệu:

bakedGoods.get({
        data: ["testFile"],
        storageTypes: ["fileSystem"],
        options: {fileSystem:{storageType: Window.PERSISTENT}},
        complete: function(resultDataObj, byStorageTypeErrorObj){}
});

Sử dụng tệp RAW, FileWriter và API hệ thống tập tin

Viết tệp:

function onQuotaRequestSuccess(grantedQuota)
{

    function saveFile(directoryEntry)
    {

        function createFileWriter(fileEntry)
        {

            function write(fileWriter)
            {
                var dataBlob = new Blob(["Hello world!"], {type: "text/plain"});
                fileWriter.write(dataBlob);              
            }

            fileEntry.createWriter(write);
        }

        directoryEntry.getFile(
            "testFile", 
            {create: true, exclusive: true},
            createFileWriter
        );
    }

    requestFileSystem(Window.PERSISTENT, grantedQuota, saveFile);
}

var desiredQuota = 1024 * 1024 * 1024;
var quotaManagementObj = navigator.webkitPersistentStorage;
quotaManagementObj.requestQuota(desiredQuota, onQuotaRequestSuccess);

Đọc tài liệu:

function onQuotaRequestSuccess(grantedQuota)
{

    function getfile(directoryEntry)
    {

        function readFile(fileEntry)
        {

            function read(file)
            {
                var fileReader = new FileReader();

                fileReader.onload = function(){var fileData = fileReader.result};
                fileReader.readAsText(file);             
            }

            fileEntry.file(read);
        }

        directoryEntry.getFile(
            "testFile", 
            {create: false},
            readFile
        );
    }

    requestFileSystem(Window.PERSISTENT, grantedQuota, getFile);
}

var desiredQuota = 1024 * 1024 * 1024;
var quotaManagementObj = navigator.webkitPersistentStorage;
quotaManagementObj.requestQuota(desiredQuota, onQuotaRequestSuccess);

Sử dụng tệp RAW, FileWriter và API hệ thống tập tin

  • Mặc dù API FileSystem và FileWriter không còn theo dõi tiêu chuẩn, việc sử dụng chúng có thể được chứng minh trong một số trường hợp, theo ý kiến ​​của tôi, bởi vì:
  • Sự quan tâm được đổi mới từ các nhà cung cấp trình duyệt không thực hiện có thể đặt chúng trở lại ngay trên nó
  • Sự thâm nhập thị trường của các trình duyệt thực hiện (dựa trên crom) cao

Google (đóng góp chính cho crom) đã không được đưa ra và ngày cuối đời cho API

Cho dù "một số trường hợp" bao gồm của riêng bạn, tuy nhiên, là để bạn quyết định.

Vì lý do bảo mật và quyền riêng tư, các ứng dụng web không có quyền truy cập trực tiếp vào Fileson thiết bị của người dùng. Nếu bạn cần đọc một hoặc nhiều tệp cục bộ, bạn có thể thực hiện thông qua việc sử dụng đầu vào tệp và trình Filereader. Trong bài viết này, chúng tôi sẽ xem xét cách thức hoạt động của điều này thông qua một vài ví dụ.
on the user's device. If you need to read one or multiple local files, you can do
this through the usage of a file input and a FileReader. In this post we will take a look
at how this works through a few examples.

TL;DR

  • JavaScript không có quyền truy cập trực tiếp vào các tệp cục bộ do bảo mật và quyền riêng tư.
  • Chúng tôi có thể cung cấp cho người dùng khả năng chọn các tệp thông qua phần tử đầu vào ____10 mà sau đó chúng tôi có thể xử lý.
  • Đầu vào
    bakedGoods.get({
            data: ["testFile"],
            storageTypes: ["fileSystem"],
            options: {fileSystem:{storageType: Window.PERSISTENT}},
            complete: function(resultDataObj, byStorageTypeErrorObj){}
    });
    
    0 có thuộc tính
    bakedGoods.get({
            data: ["testFile"],
            storageTypes: ["fileSystem"],
            options: {fileSystem:{storageType: Window.PERSISTENT}},
            complete: function(resultDataObj, byStorageTypeErrorObj){}
    });
    
    2 với (các) tệp đã chọn.
  • Chúng ta có thể sử dụng
    bakedGoods.get({
            data: ["testFile"],
            storageTypes: ["fileSystem"],
            options: {fileSystem:{storageType: Window.PERSISTENT}},
            complete: function(resultDataObj, byStorageTypeErrorObj){}
    });
    
    3 để truy cập nội dung của (các) tệp đã chọn.

Làm thế nào nó hoạt động

Vì JavaScript trong trình duyệt không thể truy cập các tệp cục bộ từ thiết bị của người dùng, chúng tôi cần cung cấp cho người dùng một cách để chọn một hoặc nhiều tệp để chúng tôi sử dụng. Điều này có thể được thực hiện với phần tử đầu vào tệp HTML:
we need to provide the user with a way to select one or multiple files for us to use.
This can be done with the HTML file input element:


Nếu chúng tôi muốn cho phép lựa chọn nhiều tệp, chúng tôi có thể thêm thuộc tính

bakedGoods.get({
        data: ["testFile"],
        storageTypes: ["fileSystem"],
        options: {fileSystem:{storageType: Window.PERSISTENT}},
        complete: function(resultDataObj, byStorageTypeErrorObj){}
});
4:


Chúng ta có thể sử dụng sự kiện

bakedGoods.get({
        data: ["testFile"],
        storageTypes: ["fileSystem"],
        options: {fileSystem:{storageType: Window.PERSISTENT}},
        complete: function(resultDataObj, byStorageTypeErrorObj){}
});
5 của trường đầu vào để trả lời trình chọn tệp thêm một phần tử UI khác để cho phép người dùng bắt đầu xử lý tệp đã chọn một cách rõ ràng.
or add another UI element to let the user explicitly start the processing of the selected file.

Cũng lưu ý: Việc lựa chọn một tệp có phần tử đầu vào không tải lên tệp ở bất cứ đâu, điều duy nhất xảy ra là tệp có sẵn cho JavaScript trên trang.
the only thing that happens is that the file becomes available to the JavaScript on the page.

Đầu vào tệp có thuộc tính

bakedGoods.get({
        data: ["testFile"],
        storageTypes: ["fileSystem"],
        options: {fileSystem:{storageType: Window.PERSISTENT}},
        complete: function(resultDataObj, byStorageTypeErrorObj){}
});
2 là danh sách (vì có thể có nhiều tệp được chọn) của các đối tượng
bakedGoods.get({
        data: ["testFile"],
        storageTypes: ["fileSystem"],
        options: {fileSystem:{storageType: Window.PERSISTENT}},
        complete: function(resultDataObj, byStorageTypeErrorObj){}
});
7.



Đối tượng tệp `trông như thế này:

{
  name: 'test.txt',         // the name of the selected file
  size: 1024,               // the size in bytes
  type: 'text/plain',       // the assumed file type based on file extension. This might be incorrect.
  lastModified: 12345567890 // timestamp of the last change according to the user's system
  lastModifiedDate: 'Thu Jul 04 2019 09:22:51 GMT+0200 (Central European Summer Time)' // a date object for the last modified timestamp
}

Bây giờ chúng tôi có khả năng chọn một tệp và xem siêu dữ liệu, nhưng làm thế nào chúng tôi có thể truy cập nội dung tệp? Để có được nội dung thực tế của một tệp đã chọn, chúng tôi cần một

bakedGoods.get({
        data: ["testFile"],
        storageTypes: ["fileSystem"],
        options: {fileSystem:{storageType: Window.PERSISTENT}},
        complete: function(resultDataObj, byStorageTypeErrorObj){}
});
3.
To get the actual content of a selected file, we need a
bakedGoods.get({
        data: ["testFile"],
        storageTypes: ["fileSystem"],
        options: {fileSystem:{storageType: Window.PERSISTENT}},
        complete: function(resultDataObj, byStorageTypeErrorObj){}
});
3.

Trình đọc tệp lấy một đối tượng

bakedGoods.get({
        data: ["testFile"],
        storageTypes: ["fileSystem"],
        options: {fileSystem:{storageType: Window.PERSISTENT}},
        complete: function(resultDataObj, byStorageTypeErrorObj){}
});
7 và cung cấp cho chúng tôi các phương thức để có quyền truy cập vào dữ liệu như:

  • Một chuỗi dữ liệu văn bản
  • một url dữ liệu
  • Một chuỗi dữ liệu nhị phân
  • Một mảng bupuffer chứa dữ liệu nhị phân thô

Trong các ví dụ sau, chúng tôi sẽ sử dụng các phương thức

function onQuotaRequestSuccess(grantedQuota)
{

    function saveFile(directoryEntry)
    {

        function createFileWriter(fileEntry)
        {

            function write(fileWriter)
            {
                var dataBlob = new Blob(["Hello world!"], {type: "text/plain"});
                fileWriter.write(dataBlob);              
            }

            fileEntry.createWriter(write);
        }

        directoryEntry.getFile(
            "testFile", 
            {create: true, exclusive: true},
            createFileWriter
        );
    }

    requestFileSystem(Window.PERSISTENT, grantedQuota, saveFile);
}

var desiredQuota = 1024 * 1024 * 1024;
var quotaManagementObj = navigator.webkitPersistentStorage;
quotaManagementObj.requestQuota(desiredQuota, onQuotaRequestSuccess);
0 và
function onQuotaRequestSuccess(grantedQuota)
{

    function saveFile(directoryEntry)
    {

        function createFileWriter(fileEntry)
        {

            function write(fileWriter)
            {
                var dataBlob = new Blob(["Hello world!"], {type: "text/plain"});
                fileWriter.write(dataBlob);              
            }

            fileEntry.createWriter(write);
        }

        directoryEntry.getFile(
            "testFile", 
            {create: true, exclusive: true},
            createFileWriter
        );
    }

    requestFileSystem(Window.PERSISTENT, grantedQuota, saveFile);
}

var desiredQuota = 1024 * 1024 * 1024;
var quotaManagementObj = navigator.webkitPersistentStorage;
quotaManagementObj.requestQuota(desiredQuota, onQuotaRequestSuccess);
1 để hiển thị nội dung của các tệp văn bản và hình ảnh.

Ví dụ một: Đọc tệp văn bản

Để hiển thị nội dung tệp là văn bản, chúng tôi thay đổi trình xử lý sự kiện

bakedGoods.get({
        data: ["testFile"],
        storageTypes: ["fileSystem"],
        options: {fileSystem:{storageType: Window.PERSISTENT}},
        complete: function(resultDataObj, byStorageTypeErrorObj){}
});
5:

document.getElementById('fileInput').addEventListener('change', function selectedFileChanged() {
  if (this.files.length === 0) {
    console.log('No file selected.');
    return;
  }

  const reader = new FileReader();
  reader.onload = function fileReadCompleted() {
    // when the reader is done, the content is in reader.result.
    console.log(reader.result);
  };
  reader.readAsText(this.files[0]);
});

Đầu tiên chúng tôi đảm bảo rằng có một tệp có thể được đọc. Nếu người dùng hủy hoặc khác với hộp thoại Chọn tệp mà không cần chọn tệp, chúng tôi không có gì để đọc và thoát chức năng của chúng tôi.
closes the file selection dialog without selecting a file, we have nothing to read and exit our function.

Sau đó chúng tôi tiến hành tạo một

bakedGoods.get({
        data: ["testFile"],
        storageTypes: ["fileSystem"],
        options: {fileSystem:{storageType: Window.PERSISTENT}},
        complete: function(resultDataObj, byStorageTypeErrorObj){}
});
3. Trình đọc hoạt động trước khi không chặn các bản cập nhật chính và UI rất quan trọng khi đọc các tệp lớn (như video).
to not block the main thread and UI updates which is important when reading large files (like videos).

Trình đọc phát ra một sự kiện 'tải' (ví dụ tương tự như hình ảnh) cho biết mã của chúng tôi rằng việc đọc đã kết thúc. Trình đọc giữ nội dung tệp trong thuộc tính

function onQuotaRequestSuccess(grantedQuota)
{

    function saveFile(directoryEntry)
    {

        function createFileWriter(fileEntry)
        {

            function write(fileWriter)
            {
                var dataBlob = new Blob(["Hello world!"], {type: "text/plain"});
                fileWriter.write(dataBlob);              
            }

            fileEntry.createWriter(write);
        }

        directoryEntry.getFile(
            "testFile", 
            {create: true, exclusive: true},
            createFileWriter
        );
    }

    requestFileSystem(Window.PERSISTENT, grantedQuota, saveFile);
}

var desiredQuota = 1024 * 1024 * 1024;
var quotaManagementObj = navigator.webkitPersistentStorage;
quotaManagementObj.requestQuota(desiredQuota, onQuotaRequestSuccess);
4 của nó. Dữ liệu trong thuộc tính này phụ thuộc vào phương thức nào chúng tôi sử dụng để đọc tệp.
The reader keeps the file content in its
function onQuotaRequestSuccess(grantedQuota)
{

    function saveFile(directoryEntry)
    {

        function createFileWriter(fileEntry)
        {

            function write(fileWriter)
            {
                var dataBlob = new Blob(["Hello world!"], {type: "text/plain"});
                fileWriter.write(dataBlob);              
            }

            fileEntry.createWriter(write);
        }

        directoryEntry.getFile(
            "testFile", 
            {create: true, exclusive: true},
            createFileWriter
        );
    }

    requestFileSystem(Window.PERSISTENT, grantedQuota, saveFile);
}

var desiredQuota = 1024 * 1024 * 1024;
var quotaManagementObj = navigator.webkitPersistentStorage;
quotaManagementObj.requestQuota(desiredQuota, onQuotaRequestSuccess);
4 property. The data in this property depends on which method we used to read the file.

Trong ví dụ của chúng tôi, chúng tôi đọc tệp bằng phương thức

function onQuotaRequestSuccess(grantedQuota)
{

    function saveFile(directoryEntry)
    {

        function createFileWriter(fileEntry)
        {

            function write(fileWriter)
            {
                var dataBlob = new Blob(["Hello world!"], {type: "text/plain"});
                fileWriter.write(dataBlob);              
            }

            fileEntry.createWriter(write);
        }

        directoryEntry.getFile(
            "testFile", 
            {create: true, exclusive: true},
            createFileWriter
        );
    }

    requestFileSystem(Window.PERSISTENT, grantedQuota, saveFile);
}

var desiredQuota = 1024 * 1024 * 1024;
var quotaManagementObj = navigator.webkitPersistentStorage;
quotaManagementObj.requestQuota(desiredQuota, onQuotaRequestSuccess);
0, vì vậy
function onQuotaRequestSuccess(grantedQuota)
{

    function saveFile(directoryEntry)
    {

        function createFileWriter(fileEntry)
        {

            function write(fileWriter)
            {
                var dataBlob = new Blob(["Hello world!"], {type: "text/plain"});
                fileWriter.write(dataBlob);              
            }

            fileEntry.createWriter(write);
        }

        directoryEntry.getFile(
            "testFile", 
            {create: true, exclusive: true},
            createFileWriter
        );
    }

    requestFileSystem(Window.PERSISTENT, grantedQuota, saveFile);
}

var desiredQuota = 1024 * 1024 * 1024;
var quotaManagementObj = navigator.webkitPersistentStorage;
quotaManagementObj.requestQuota(desiredQuota, onQuotaRequestSuccess);
4 sẽ là một chuỗi văn bản.

Ví dụ Hai: Hiển thị hình ảnh từ thiết bị của người dùng

Nếu chúng ta muốn hiển thị hình ảnh, hãy đọc tệp vào một chuỗi không hữu ích. Một cách rõ ràng,

bakedGoods.get({
        data: ["testFile"],
        storageTypes: ["fileSystem"],
        options: {fileSystem:{storageType: Window.PERSISTENT}},
        complete: function(resultDataObj, byStorageTypeErrorObj){}
});
3 có phương thức
function onQuotaRequestSuccess(grantedQuota)
{

    function saveFile(directoryEntry)
    {

        function createFileWriter(fileEntry)
        {

            function write(fileWriter)
            {
                var dataBlob = new Blob(["Hello world!"], {type: "text/plain"});
                fileWriter.write(dataBlob);              
            }

            fileEntry.createWriter(write);
        }

        directoryEntry.getFile(
            "testFile", 
            {create: true, exclusive: true},
            createFileWriter
        );
    }

    requestFileSystem(Window.PERSISTENT, grantedQuota, saveFile);
}

var desiredQuota = 1024 * 1024 * 1024;
var quotaManagementObj = navigator.webkitPersistentStorage;
quotaManagementObj.requestQuota(desiredQuota, onQuotaRequestSuccess);
1 đọc chuỗi được mã hóa tệp Inan có thể được sử dụng làm nguồn cho phần tử
function onQuotaRequestSuccess(grantedQuota)
{

    function saveFile(directoryEntry)
    {

        function createFileWriter(fileEntry)
        {

            function write(fileWriter)
            {
                var dataBlob = new Blob(["Hello world!"], {type: "text/plain"});
                fileWriter.write(dataBlob);              
            }

            fileEntry.createWriter(write);
        }

        directoryEntry.getFile(
            "testFile", 
            {create: true, exclusive: true},
            createFileWriter
        );
    }

    requestFileSystem(Window.PERSISTENT, grantedQuota, saveFile);
}

var desiredQuota = 1024 * 1024 * 1024;
var quotaManagementObj = navigator.webkitPersistentStorage;
quotaManagementObj.requestQuota(desiredQuota, onQuotaRequestSuccess);
9. Mã này khá giống như trước đây, với các ngoại lệ mà chúng tôi đọc tệp với
function onQuotaRequestSuccess(grantedQuota)
{

    function saveFile(directoryEntry)
    {

        function createFileWriter(fileEntry)
        {

            function write(fileWriter)
            {
                var dataBlob = new Blob(["Hello world!"], {type: "text/plain"});
                fileWriter.write(dataBlob);              
            }

            fileEntry.createWriter(write);
        }

        directoryEntry.getFile(
            "testFile", 
            {create: true, exclusive: true},
            createFileWriter
        );
    }

    requestFileSystem(Window.PERSISTENT, grantedQuota, saveFile);
}

var desiredQuota = 1024 * 1024 * 1024;
var quotaManagementObj = navigator.webkitPersistentStorage;
quotaManagementObj.requestQuota(desiredQuota, onQuotaRequestSuccess);
1 và hiển thị kết quả dưới dạng hình ảnh:
Conveniently the
bakedGoods.get({
        data: ["testFile"],
        storageTypes: ["fileSystem"],
        options: {fileSystem:{storageType: Window.PERSISTENT}},
        complete: function(resultDataObj, byStorageTypeErrorObj){}
});
3 has a
function onQuotaRequestSuccess(grantedQuota)
{

    function saveFile(directoryEntry)
    {

        function createFileWriter(fileEntry)
        {

            function write(fileWriter)
            {
                var dataBlob = new Blob(["Hello world!"], {type: "text/plain"});
                fileWriter.write(dataBlob);              
            }

            fileEntry.createWriter(write);
        }

        directoryEntry.getFile(
            "testFile", 
            {create: true, exclusive: true},
            createFileWriter
        );
    }

    requestFileSystem(Window.PERSISTENT, grantedQuota, saveFile);
}

var desiredQuota = 1024 * 1024 * 1024;
var quotaManagementObj = navigator.webkitPersistentStorage;
quotaManagementObj.requestQuota(desiredQuota, onQuotaRequestSuccess);
1 method that reads the file into
an encoded string that can be used as the source for an
function onQuotaRequestSuccess(grantedQuota)
{

    function saveFile(directoryEntry)
    {

        function createFileWriter(fileEntry)
        {

            function write(fileWriter)
            {
                var dataBlob = new Blob(["Hello world!"], {type: "text/plain"});
                fileWriter.write(dataBlob);              
            }

            fileEntry.createWriter(write);
        }

        directoryEntry.getFile(
            "testFile", 
            {create: true, exclusive: true},
            createFileWriter
        );
    }

    requestFileSystem(Window.PERSISTENT, grantedQuota, saveFile);
}

var desiredQuota = 1024 * 1024 * 1024;
var quotaManagementObj = navigator.webkitPersistentStorage;
quotaManagementObj.requestQuota(desiredQuota, onQuotaRequestSuccess);
9 element. The code is pretty much the same as previously,
with the exceptions that we read the file with
function onQuotaRequestSuccess(grantedQuota)
{

    function saveFile(directoryEntry)
    {

        function createFileWriter(fileEntry)
        {

            function write(fileWriter)
            {
                var dataBlob = new Blob(["Hello world!"], {type: "text/plain"});
                fileWriter.write(dataBlob);              
            }

            fileEntry.createWriter(write);
        }

        directoryEntry.getFile(
            "testFile", 
            {create: true, exclusive: true},
            createFileWriter
        );
    }

    requestFileSystem(Window.PERSISTENT, grantedQuota, saveFile);
}

var desiredQuota = 1024 * 1024 * 1024;
var quotaManagementObj = navigator.webkitPersistentStorage;
quotaManagementObj.requestQuota(desiredQuota, onQuotaRequestSuccess);
1 and display the result as an image:

document.getElementById('fileInput').addEventListener('change', function selectedFileChanged() {
  if (this.files.length === 0) {
    console.log('No file selected.');
    return;
  }

  const reader = new FileReader();
  reader.onload = function fileReadCompleted() {
      const img = new Image();          // creates an  element
      img.src = reader.result;          // loads the data URL as the image source
      document.body.appendChild(img);   // adds the image to the body
  };
  reader.readAsDataURL(this.files[0]);
});

JavaScript có thể truy cập tệp cục bộ không?

Trình duyệt web (và JavaScript) chỉ có thể truy cập các tệp cục bộ với quyền người dùng.Để chuẩn hóa quyền truy cập tệp từ trình duyệt, W3C đã xuất bản API tệp HTML5 vào năm 2014. Nó xác định cách truy cập và tải lên các tệp cục bộ với các đối tượng tệp trong các ứng dụng web.can only access local files with user permission. To standardize the file access from the browser, the W3C published the HTML5 File API in 2014. It defines how to access and upload local files with file objects in web applications.

JavaScript có thể sửa đổi các tệp cục bộ không?

Bạn không thể sửa đổi các tệp cục bộ từ JavaScript..

JavaScript có thể xử lý các tệp không?

JavaScript là ngôn ngữ lập trình phổ biến cho phép bạn xử lý các tệp trong trình duyệt..

HTML có thể đọc các tệp cục bộ không?

HTML 5 cung cấp một cách tiêu chuẩn để tương tác với các tệp cục bộ với sự trợ giúp của API tệp.API tệp cho phép tương tác với các tệp đơn, cũng như blob.API FileReader có thể được sử dụng để đọc một tệp không đồng bộ trong sự hợp tác với xử lý sự kiện JavaScript.. The File API allows interaction with single, multiple as well as BLOB files. The FileReader API can be used to read a file asynchronously in collaboration with JavaScript event handling.