Showing posts with label javascript. Show all posts
Showing posts with label javascript. Show all posts

Saturday, August 24, 2024

JavaScript Blobs

A Blob (Binary Large Object) is a data structure used to store raw data. It can be created using the Blob constructor. For instance:

const myBlob = new Blob(['Hello, world!'], { type: 'text/plain' });

You can use Blobs to create URLs, which can be directly embedded into HTML documents. For example, you can create a Blob containing text data and then generate a download link for it. When the user clicks this link, they can download the Blob content as a file. This is shown below:

<html>
<head/>
<body>
  <h1>Download Blob Example</h1>

  <script>
    const createDownloadLink = (content, filename) => {
      // Create a Blob from the content
      const blob = new Blob([content], { type: 'text/plain' });
      
      // Create a URL for the Blob
      const url = URL.createObjectURL(blob);
      
      // Create an <a> element
      const a = document.createElement('a');
      a.href = url;
      a.download = filename;
      a.textContent = `Download ${filename}`;

      // append to body
      document.body.appendChild(a);
      
      // revoke URL after some time or on user action
      // URL.revokeObjectURL(url); 
    }
    createDownloadLink('some content', 'example.txt');
  </script>
</body>
</html>

You can also use a Blob to dynamically generate code and create JavaScript files on-the-fly! Here’s an example of how to create a Web Worker from a Blob:

<html>
<head/>
<body>
  <h1>Web Worker Blob Example</h1>
  <p id="result"></p>

  <script>
    const workerScript = `
      onmessage = e => {
        postMessage(e.data * 2);
      };
    `;

    const blob = new Blob([workerScript], { type: 'application/javascript' });
    const url = URL.createObjectURL(blob);
    const worker = new Worker(url);

    worker.onmessage = e => {
      document.getElementById('result').textContent = 'Worker result: ' + e.data;
      URL.revokeObjectURL(url); // Clean up Blob URL
    };

    worker.postMessage('2');
  </script>
</body>
</html>

Saturday, August 10, 2024

Shared Web Workers

In my previous post, I discussed how Web Workers can be used to enhance the responsiveness of web applications by offloading resource-intensive computations to run in the background, thus preventing them from blocking the main thread. Today, let's look into Shared Web Workers and how they can further boost your app's efficiency.

Shared Web Workers are a special type of Web Worker that can be accessed from multiple browsing contexts, such as different tabs, windows, or iframes. This shared access can facilitate various functionalities, including real-time communication, managing shared state, and caching data across multiple tabs or windows. By reusing a single worker instance, Shared Web Workers help reduce memory consumption and improve performance compared to creating a new worker for each context.

The following example shows the basics of using a Shared Web Worker.

1. Create the Shared Web Worker Script

Shared Web Workers use ports to communicate. You need to handle the onconnect event to establish communication with the port and the onmessage event to process incoming messages.

// worker.js
onconnect = (event) => {
  const port = event.ports[0];
  port.onmessage = (e) => {
    port.postMessage(e.data[0] + e.data[1]);
  };
};

2. Use the Shared Web Worker in Your Main Script

In your main script, initialise the Shared Web Worker. This can be done from multiple scripts or HTML pages. Once created, any script running on the same origin can access the worker and communicate with it. The various scripts will use the same worker for tasks, even if they are running in different windows.

<html>
  <head/>
  <body>
    <h1>Shared Web Worker Example</h1>
    <p id="result">Computing...</p>
    <script>
      const worker = new SharedWorker('path/to/worker.js');
      worker.port.onmessage = (e) => {
        document.getElementById('result').textContent = `Result: ${e.data}`;
      };
      worker.port.postMessage([1, 2]);
    </script>
  </body>
</html>

Communication between the main script and the Shared Web Worker is done using the port.postMessage method to send messages and the port.onmessage event to receive messages.

Related posts:
Web Workers

Saturday, August 03, 2024

Web Workers

Web Workers allow you to perform resource-intensive computations in background threads, without blocking the main thread that handles user interactions and UI updates. This makes it possible to perform tasks such as data processing, complex calculations, and large data fetching asynchronously, keeping your web application responsive.

The following example shows the basics of using a web worker to perform a simple "sum" calculation.

1. Create a Web Worker Script

First, create your web worker script:

// worker.js
onmessage = (e) => {
  const workerResult = e.data[0] + e.data[1];
  postMessage(workerResult);
};

As shown above, the web worker performs the computation in the onmessage event handler and then calls postMessage, to post the result back to the main thread.

2. Invoke the Web Worker in Your Main Script

In the main script, initialise the web worker and invoke it with some data:

<html>
  <head/>
  <body>
    <h1>Web Worker Example</h1>
    <p id="result">Computing...</p>
    <script>
      const worker = new Worker('worker.js');
      worker.onmessage = (e) => {
        document.getElementById('result').textContent = `Result: ${e.data}`;
      };
      worker.postMessage([1, 2]);
    </script>
  </body>
</html>

Communication between the main script and the web worker is done using the postMessage method to send messages and the onmessage event to receive messages.

React Example

Here is how you would do it in React:

import React, { useState } from 'react';

const App = () => {
  const [result, setResult] = useState(null);

  const handleClick = () => {
    // Create a new Web Worker
    const worker = new Worker(new URL('./worker.js', import.meta.url));

    // Set up message handler
    worker.onmessage = (e) => {
      setResult(e.data);
      worker.terminate(); // Clean up the worker
    };

    // send data to the worker
    worker.postMessage([1, 2]);
  };

  return (
    <div>
      <h1>Simple Web Worker Example</h1>
      <button onClick={handleClick}>Start Computation</button>
      {result !== null && <p>Result from Worker: {result}</p>}
    </div>
  );
};
export default App;

Saturday, May 26, 2018

HTML5 Date Input with jQuery Fallback

HTML5 introduced a new date input type which allows a user to enter a date using a date picker.

<input id="date" type="date" value="2018-05-26">

This is what it looks like in Chrome:

However, not all browsers support this input type. In unsupported browsers, such as Internet Explorer, you will simply see a text input field.

In this post, I will show how you can detect if a browser supports the date input type and how you can fall back to using jQuery's date picker if it doesn't.

Checking if the browser supports date input:

The following code creates an input element and sets its type to date. If the browser does not support date input, this operation will not work and the input type will degrade to text.

var input = document.createElement("input");
input.setAttribute("type", "date");
if (input.type !== "date") {
    console.log("browser does not support date input");
}
Alternatively, use the Modernizr library, which makes it easy to detect the features that a browser supports:
if (!Modernizr.inputtypes.date) {
    console.log("browser does not support date input");
}
Falling back to jQuery's date picker:

The JSFiddle below shows how you would use jQuery's date picker if the browser does not support date input.

Saturday, January 23, 2016

Ext JS - Changing the "Today" button in a date field

The datefield in Ext JS has a date picker dropdown containing a handy "Today" button, which allows you to quickly select today's date. But what if you're not interested in today's date? This post shows how you can change the text and the handler of the "Today" button in order to select a different date e.g. yesterday.

My jsfiddle for changing the "Today" button is embedded below. After the date field has been rendered, it gets a reference to the Today button via the date field's picker object and then changes its handler to select yesterday's date instead.

Saturday, March 28, 2015

Ext JS - Reading a JSON File

This post shows how you can read a JSON file in your Ext JS applications.

Let's say you have the following person.json file, which contains information about a person:

{
    "name" : "Joe Bloggs",
    "age"  : "15"
}

In order to load this file, you need to define an Ext.data.Store as shown below:

Ext.define('MyApp.store.Person', {
    extend: 'Ext.data.Store',
    fields: ['name', 'age'],
    autoLoad: true,
    proxy: {
        type: 'ajax',
        url: 'person.json',
        reader: 'json',
        pageParam: false,
        startParam: false,
        limitParam: false,
        noCache: false,
        listeners: {
            exception: function(proxy, response, operation) {
                // add appropriate error handling!
                console.log("Could not read file");
            }
        }
    },

    // return the specified field of the person e.g. name.
    get: function(field) {
        return this.first() && this.first().get(field);
    }
});

Then create the store and once it has loaded, you can access the data.

var store = Ext.create('MyApp.store.Person');
store.on('load', function() {
    console.log(store.get('name'));
});

Saturday, December 27, 2014

A Bookmarklet to Tick All Checkboxes on a Webpage

Recently, I found myself on a website where I had to tick 100+ checkboxes and there was no "Select all" button on the page. Obviously, I couldn't do this manually, so I wrote the following bookmarklet.

javascript:(function(){for(i of document.getElementsByTagName('input')){if(i.type=='checkbox') i.checked=!i.checked;}})()

Save this as a bookmark in your browser and click on it to toggle all checkboxes on the page you are on.

Sunday, November 30, 2014

Logging the Duration of Ext Ajax Requests

The following snippet shows how to log the duration of ajax requests in your Ext JS application:

Ext.Ajax.on('beforerequest', function(conn, response, options){
    console.log("Requesting:" + response.url);
    console.time(response.url);
});
Ext.Ajax.on('requestcomplete', function(conn, response, options){
    console.timeEnd(options.url);
});

Note that this code uses console.time(), which is a non-standard feature and may not work in all browsers.

Saturday, November 22, 2014

Ext JS - Caching AJAX Responses to HTML5 Web Storage for Better Performance

This post shows how you can improve performance of your Ext JS applications by using a "Caching AJAX Proxy". This proxy saves URL responses to HTML5 Web Storage (e.g. session storage or local storage), which means that when the same URL is requested multiple times, a cached response is returned, instead of sending a request to the server each time. This makes the application more responsive and also reduces load on the server handling the requests.

/**
 * A Caching Ajax Proxy which uses AJAX requests to get data from a server and
 * then stores the data to HTML5 Web Storage. If the storage fills up, it removes
 * entries from the cache until space is available.
 * (Compatible with Ext JS 4.2)
 */
Ext.define('App.data.proxy.CachingAjax', {
  extend: 'Ext.data.proxy.Ajax',
  alias: 'proxy.cachingajax',

  // use session storage, but can be configured to localStorage too
  storage: window.sessionStorage,

  // @Override
  doRequest: function(operation, callback, scope) {
    var cachedResponse = this.getItemFromCache(this.url);
    if (!cachedResponse) {
        this.callParent(arguments);
    }
    else {
        console.log('Got cached data for: ' + this.url);
        this.processResponse(true, operation, null, cachedResponse,
                             callback, scope, true);
    }
  },

  // @Override
  processResponse: function(success, operation, request, response,
                            callback, scope, isCached) {
    if (success === true && !isCached) {
        this.putItemInCache(this.url, response.responseText);
    }
    this.callParent(arguments);
  },

  /**
   * @private
   * Returns the data from the cache for the specified key
   * @param {String} the url
   * @return {String} the cached url response, or null if not in cache
   */
  getItemFromCache: function(key) {
    return this.storage ? this.storage.getItem(key) : null;
  },

  /**
   * @private
   * Puts an entry in the cache.
   * Removes a third of the entries if the cache is full.
   * @param {String} the url
   * @param {String} the data
   */
  putItemInCache: function(key, value) {
    if (!this.storage) return;
    try {
      this.storage.setItem(key, value);
    } catch (e) {
      // this might happen if the storage is full.
      // Remove a third of the items and retry.
      // If it fails again, disable the cache quietly.
      console.log('Error putting data in cache. CacheSize: ' + this.storage.length +
                  ', ErrorCode: ' + e.code + ', Message: ' + e.name);

      while (this.storage.length != 0) {
        var toRemove = this.storage.length / 3;
        for (var i = 0; i < toRemove ; i++) {
          var item = this.storage.key(0);
          if (item) this.storage.removeItem(item);
          else break;
        }
        console.log('Removed one-third of the cache. Cache size is now: ' + this.storage.length);
        try {
          this.storage.setItem(key, value);
          break;
        } catch (e) {
          console.log('Error putting data in cache again. CacheSize: ' + this.storage.length +
                      ', ErrorCode: ' + e.code + ', Message: ' + e.name);
        }
      }
      if (this.storage.length == 0) {
        console.log("Cache disabled");
        this.storage = null;
      }
    }
  }
});
Usage:
var store = Ext.create('Ext.data.Store', {
  model: 'User',
  proxy: {
    type: 'cachingajax',
    url : 'http://mywebsite/path'
  }
});

Obviously, you should only use this caching proxy when the server-side data is static, because if it is changing frequently your application will end up displaying stale, cached data.

This proxy can also be extended in the future to remove cached entries after specific time intervals or clear out the entire cache when the application starts up.

Tuesday, September 30, 2014

Ext JS CSV Data Reader

In a previous post, I showed how you can convert CSV to JSON in JavaScript. In this post, I will show how you can display CSV data in an Ext JS grid panel using a custom Reader.

Currently, there are three kinds of Readers available in Ext JS 4.2.2:

A CSV reader isn't available but is quite easy to create, simply by extending the JSON reader. The code for my CSV Reader is shown below. It first converts the CSV response from the server into JSON format and then invokes the parent JSON reader.

/**
 * The CSV Reader is used by a Proxy to read a server response
 * that is sent back in CSV format.
 */
Ext.define('CsvReader', {
    extend: 'Ext.data.reader.Json',
    alias : 'reader.csv',

    // converts csv into json
    toJson: function(csvData){
      var lines = csvData.split("\n");
      var colNames = lines[0].split(",");
      var records = [];
      for(var i = 1; i < lines.length; i++) {
        if (lines[i] == "") continue;
        var record = {};
        var bits = lines[i].split(",");
        for (var j = 0; j < bits.length; j++) {
          record[colNames[j]] = bits[j];
        }
        records.push(record);
      }
      return records;
    },

    // override
    getResponseData: function(response) {
        try {
            return this.readRecords(response.responseText);
        } catch (ex) {
            error = new Ext.data.ResultSet({
                total  : 0,
                count  : 0,
                records: [],
                success: false,
                message: ex.message
            });
            this.fireEvent('exception', this, response, error);
            console.log(error);
            return error;
        }
    },

    // override
    readRecords: function(strData) {
        var result = this.toJson(strData);
        return this.callParent([result]);
    }
});

Now let's use it to display an example CSV file containing book data in a grid.

author,title,publishDate
Dan Simmons,Hyperion,1989
Douglas Adams,The Hitchhiker's Guide to the Galaxy,1979

Here is some sample code to read the CSV file into a grid using the CSV reader defined above:

Ext.define('Book', {
    extend: 'Ext.data.Model',
    fields: [
        {name: 'author', type: 'string'},
        {name: 'title', type: 'string'},
        {name: 'publishDate', type: 'string'}
    ]
});

var bookStore = Ext.create('Ext.data.Store', {
    model: 'Book',
    autoLoad: true,
    proxy: {
        type: 'ajax',
        reader: 'csv',
        url: 'data.csv'
    }
});

Ext.application({
    name: 'MyApp',
    launch: function() {
        Ext.create('Ext.Viewport', {
            layout: 'fit',
            items: [{
                xtype:'grid',
                title: 'Books',
                store: bookStore,
                columns: [
                   { text: 'Author', dataIndex: 'author' },
                   { text: 'Title', dataIndex: 'title', flex:1 },
                   { text: 'Publish Date', dataIndex: 'publishDate' }
                ],
                height: 200,
                width: 400
            }],
            renderTo: Ext.getBody()
        });
    }
});

Related posts:
Converting CSV to JSON in JavaScript

Saturday, August 30, 2014

Converting CSV to JSON in JavaScript

This post shows how you can convert a simple CSV file to JSON in JavaScript.

Consider the following sample CSV:

author,title,publishDate
Dan Simmons,Hyperion,1989
Douglas Adams,The Hitchhiker's Guide to the Galaxy,1979

The desired JSON output is:

[{"author":"Dan Simmons","title":"Hyperion","publishDate":"1989"},
{"author":"Douglas Adams","title":"The Hitchhiker's Guide to the Galaxy","publishDate":"1979"}]

The following JavaScript function transforms CSV into JSON. (Note that this implementation is quite naive and will not handle quoted fields containing commas!)

function toJson(csvData) {
  var lines = csvData.split("\n");
  var colNames = lines[0].split(",");
  var records=[];
  for(var i = 1; i < lines.length; i++) {
    var record = {};
    var bits = lines[i].split(",");
    for (var j = 0 ; j < bits.length ; j++) {
      record[colNames[j]] = bits[j];
    }
    records.push(record);
  }
  return records;
}

A simple test:

csv="author,title,publishDate\nDan Simmons,Hyperion,1989\nDouglas Adams,The Hitchhiker's Guide to the Galaxy,1979";
json = toJson(csv);
console.log(JSON.stringify(json));

To read a CSV file in JavaScript and convert it to JSON:

var rawFile = new XMLHttpRequest();
rawFile.open("GET", "books.csv", true);
rawFile.onreadystatechange = function () {
  if (rawFile.readyState === 4) {
    if (rawFile.status === 200 || rawFile.status == 0) {
      var allText = rawFile.responseText;
      var result = toJson(allText);
      console.log(JSON.stringify(result));
    }
  }
}
rawFile.send(null);

You might also like:
Converting XML to CSV using XSLT 1.0