Showing posts with label JavaScript. Show all posts
Showing posts with label JavaScript. Show all posts

April 30, 2024

JS Geolocation API – Use watchPosition()

The watchPosition() method of the Geolocation interface is used to register a handler function that will be called automatically each time the position of the device changes. You can also, optionally, specify an error handling callback function.

This method retrieves periodic updates about the current geographic location of the device, allows to detect a change in position of device. The location object contains geographic coordinates with information about speed.

Like we have 3 parameters in getCurrentPosition() method, watchPosition() accepts the same:

  • success: A callback function that retrieves the location information.
  • error (Optional): An optional callback function that takes a GeolocationPositionError object as an input parameter.
  • options (Optional): This optional parameter specifies a set of options for retrieving the location information.

    You can specify:

    • Accuracy of the returned location information
    • Timeout for retrieving the location information
    • Use of cached location information
Return value: An integer ID that identifies the registered handler. The ID can be passed to the Geolocation.clearWatch() to unregister the handler and stop receiving location updates.

Example Code:

function onSuccess(position) {
	//do something with position data.
	console.log("Latitude: " + position.coords.latitude);
	console.log("Longitude: " + position.coords.longitude);
}

function onError(error) {
  switch(error.code) {
    case error.PERMISSION_DENIED:
      console.log("User denied the request for Geolocation.");
      break;
    case error.POSITION_UNAVAILABLE:
      console.log("Location information is unavailable.");
      break;
    case error.TIMEOUT:
      console.log("The request to get user location timed out.");
      break;
    case error.UNKNOWN_ERROR:
      console.log("An unknown error occurred.");
      break;
  }
}

// Options: throw an error if no update is received every 30 seconds.
var watchID = navigator.geolocation.watchPosition(onSuccess, onError, { timeout: 30000 });

Related Post(s):

March 21, 2024

JS Geolocation API – getCurrentPosition with options parameter

In the last post (JS Geolocation API – Get a User's Location) we have covered navigator.geolocation.getCurrentPosition() method to get user's location. We have used the success and error callbacks to receive the position object or report error.

The method getCurrentPosition() also accepts an options object as third paramter (optional).

This options object allows you to specify:

  • enableHighAccurancy (default false): if set to true, response is slower and more accurate.
  • maximumAge (default 0): milliseconds when cached value is valid, the device may decide to use valid cached data instead of sensor measure. Represents age for the returned position value (up until this age it will be cached and reused if the same position is requested again, after this the browser will request fresh position data)
  • timeout (default infinity): milliseconds before the API gives up and calls the error handler (the second parameter).

The example below calls getCurrentPosition() with both success and error callbacks, and pass the options object in third argument:

var options = {
  enableHighAccuracy: true,
  timeout: 5000, //timeout 5 seconds
  maximumAge: 10000 //(location) age 10 seconds
};

navigator.geolocation.getCurrentPosition(success, error, options);

Related Post(s):

JS Geolocation API – Get a User's Location

The JavaScript Geolocation API provides access to geographical location data associated with a user's device. This can be determined using GPS, WIFI, IP Geolocation and so on.

Geolocation is most accurate for devices with GPS, like smartphones.

To protect the user's privacy, it requests permission to locate the device. If the user grants permission, you will gain access to location data such as latitude, longitude, altitude and speed etc.

The Geolocation API is available through the navigator.geolocation object.

If the object exists, geolocation services are available. You can test for the presence of geolocation:

if ("geolocation" in navigator) {
  console.log("geolocation is available");
} else {
  console.log("geolocation is not available");
}

navigator.geolocation object provides the method getCurrentPosition() to return the user's position.

There are three possible arguments with this method:

  • A success callback (required)
  • An error callback (optional)
  • An options object (optional)

The example below calls getCurrentPosition() with both success and error callbacks, and returns the latitude and longitude of the user's position:

function getLocation() {
  if (navigator.geolocation) {
    navigator.geolocation.getCurrentPosition(usePositionData, useError);
  } else {
    console.log("Geolocation is not supported by this browser.");
  }
}

function usePositionData(position) {
	//do something with position data.
	console.log("Latitude: " + position.coords.latitude);
	console.log("Longitude: " + position.coords.longitude);
}

function useError(error) {
  switch(error.code) {
    case error.PERMISSION_DENIED:
      console.log("User denied the request for Geolocation.");
      break;
    case error.POSITION_UNAVAILABLE:
      console.log("Location information is unavailable.");
      break;
    case error.TIMEOUT:
      console.log("The request to get user location timed out.");
      break;
    case error.UNKNOWN_ERROR:
      console.log("An unknown error occurred.");
      break;
  }
}

Related Post(s):

October 29, 2023

Kendo UI Grid - Nested child grids

To add hierarchy or nested child grids in Kendo UI Grid component, we can use the detailInit event for the parent grid and initialize the next level of hierarchy.

Lets say we have the following function which will return dummy data based on ParentAccountID.

private getGridData(parentAccountID: string) {
	var data = [
		{
			"RowLabel": "Initial Balance",
			"AccountID": "1",
			"AccountNo": 1,
			"AccountName": "Account1",
			"AccountTitle": "1 - Account1",
			"ParentAccountID": null,
		},
		{
			"RowLabel": "Initial Balance",
			"AccountID": "2",
			"AccountNo": 2,
			"AccountName": "Account2",
			"AccountTitle": "2 - Account2",
			"ParentAccountID": null,
		},
		{
			"RowLabel": "Initial Balance",
			"AccountID": "3",
			"AccountNo": 3,
			"AccountName": "Account3",
			"AccountTitle": "3 - Account3",
			"ParentAccountID": "1",
		},
		{
			"RowLabel": "Initial Balance",
			"AccountID": "4",
			"AccountNo": 4,
			"AccountName": "Account4",
			"AccountTitle": "4 - Account4",
			"ParentAccountID": "2",
		}
	];

	var tempData = data;
	tempData = tempData.filter((obj) => {
		return obj.ParentAccountID === parentAccountID;
	});
		
	return tempData;
}

Here is how we can define the grid configuration to call above function to get data for ParentAccountID.

private createDataInquiryGrid3() {

	var tempData = this.getGridData(null);//Pass null as ParentAccountID for 1st level data.
	
	var element = $("#grid").kendoGrid({
		dataSource: {
			data: tempData,
		},
		height: 600,
		sortable: false,
		pageable: false,
		detailInit: this.detailInit,//this event will define child grid configuration
		dataBound: function () {
			this.expandRow(this.tbody.find("tr.k-master-row").first());
		},
		columns: [
			{ field: "AccountTitle", title: " ", aggregates: ["count"] },
			{ field: "AccountNo", title: "AccountNo" },
			{ field: "AccountName", title: "AccountName" },
		]
	});
}

private detailInit(e) {
	//e.data contains the current row's data-object.
	var getDataFunction = window["getGridData"];
	var tempData = getDataFunction(e.data.AccountID);
	
	$("<div/>").appendTo(e.detailCell).kendoGrid({
		dataSource: {
			data: tempData,
		},
		scrollable: false,
		sortable: false,
		pageable: false,
		columns: [
			{ field: "AccountTitle", title: " ", aggregates: ["count"] },
			{ field: "AccountNo", title: "AccountNo" },
			{ field: "AccountName", title: "AccountName" },
		]
	});
}

It will display the grids like this:

With detailInit event you can add multiple level of nested grids using the same event in nested grids.

References:

Related Post(s):

October 28, 2023

Kendo UI Grid - Column Template

Kendo UI grid will display the column data in table format. If we need to customize the display value for specific column we can use the template function. It allows us to define the template which renders the actual content for that column.

columns array can be defined like this:

columns: [
    { field: "AccountTitle", title: "AccountTitle"},
    { field: "TransactionType", title: "Type" },
    { field: "DatePosting", title: "Date", format: "{0:dd/MM/yyyy}" },
	{
		field: "Status",
		template: function (dataRow) {
			var linkHtml = "";
			if (dataRow.StatusNo == '1') {
				linkHtml = "<span style='color:green;'>Active</span>";
			}
			else {
				linkHtml = "<span style='color:red;'>Deactive</span<";
			}
			return linkHtml;
		},
		title: "Status"
	},
]

Note that Status column, we have defined a template function, which will accept the dataRow as parameter and return the final html as string, which the Kendo Grid will display as cell's content.

References:

Related Post(s):

July 24, 2023

Compile TypeScript code with ASP.NET WebSite Project

In previous posts we have seen how to configure TypeScript code with ASP.NET Core Project and Web Application Project . In this post we will configure the TypeScript in an ASP.NET WebSite Project (.Net Framework).

Suppose we already have an ASP.Net WebSite Project.

Inside Scripts folder, Create a new TypeScript file, say app.ts.

Add the folowing code:

sayHello(name: string) {
	console.log("Hello " + name);
}

As we changed some configurations for TypeScript compiler in the last post, we will do the same here. Select Add New Item, and choose TypeScript Configuration File and use the default name of tsconfig.json. Replace the content in tsconfig.json file with following.

{
  "compilerOptions": {
    "noImplicitAny": false,
    "noEmitOnError": true,
    "removeComments": false,
    "sourceMap": false,
    "target": "ES2015",
    "allowJs": false,
    "inlineSourceMap": true,
    "sourceRoot": "./",
    "outDir": "Scripts",
    "inlineSources": true,
    "lib": [ "es2015", "es2016", "dom", "es2018.promise" ]
  },
  "exclude": [
    "./Scripts/JS"
  ],
  "include": [
    "Scripts/*.ts"
  ],
  "compileOnSave": true
}

The include section above instructs the compiler to compile all typescritps files inside Scripts folder. It will exlude the files for compilation which will be palced inside Scripts/JS folder as mentioned in exclude section above. It also tells the compiler to copy the output js files in Scripts folder (mentioned by outDir key). We need to use the same path when referencing the js file in HTML.

<script src="Scripts/app.js"></script>

Save the changes, and reload the project.

Now we have all setup with TypeScript, we can write TypeScript code and it should work.

Related Post(s):

June 22, 2023

Compile TypeScript code with ASP.NET Web Applicaton Project

In the last post we have seen how to configure TypeScript code with ASP.NET Core Project. In this post we will configure the TypeScript in an ASP.NET Web Applicaton Project (.Net Framework).

Suppose we already have an ASP.Net Web Application Project.

Inside Scripts folder, Create a new TypeScript file, say app.ts.

Add the folowing code:

sayHello(name: string) {
	console.log("Hello " + name);
}

As we changed some configurations for TypeScript compiler in the last post, we will do the same here. Select Add New Item, and choose TypeScript Configuration File and use the default name of tsconfig.json. Replace the content in tsconfig.json file with following.

{
  "compilerOptions": {
    "noImplicitAny": false,
    "noEmitOnError": true,
    "removeComments": false,
    "sourceMap": false,
    "target": "ES2015",
    "allowJs": false,
    "inlineSourceMap": true,
    "sourceRoot": "./",
    "outDir": "Scripts",
    "inlineSources": true,
    "lib": [ "es2015", "es2016", "dom", "es2018.promise" ]
  },
  "exclude": [
    "./Scripts/JS"
  ],
  "compileOnSave": true
}

It will exlude the files for compilation which will be palced inside Scripts/JS folder as mentioned in exclude section above. It also tells the compiler to copy the output js files in Scripts folder (mentioned by outDir key). We need to use the same path when referencing the js file in HTML.

<script src="Scripts/app.js"></script>

Sometimes you might also need to manually add a TypeScript compiler task to your website project. Edit [YourProjectName].csproj file, and add the following <Target> element before the </Project> closing tag:

<Target Name="TypeScriptCompile" BeforeTargets="Build">
  <Exec Command="tsc" />
</Target>

Save the changes, and reload the project.

Now we have all setup with TypeScript, we can write TypeScript code and it should work.

Related Post(s):

June 18, 2023

Compile TypeScript code with ASP.NET Core

In this post I will explain the steps we need to setup TypeScript into an ASP.NET Core project.

Lets suppose we already have an ASP.Net Core Project.

We need to install Nuget Package Microsoft.TypeScript.MSBuild to build typescript code/files.

Create a new file named app.ts.

Add the folowing code:

sayHello(name: string) {
	console.log("Hello " + name);
}

We need to tell TypeScript by configuration settings to direct the behavior for compilation. Select Add New Item, and choose TypeScript Configuration File and use the default name of tsconfig.json. Replace the content in tsconfig.json file with following.

{
  "compileOnSave": true,
  "compilerOptions": {
    "noImplicitAny": false,
    "noEmitOnError": true,
    "removeComments": false,
    "sourceMap": true,
    "target": "es5",
    "outDir": "wwwroot/ts_build"
  },
  "exclude": [
    "./node_modules",
    "./wwwroot",
  ],
  "include": [
    "./TypeScripts"
  ]
}

It will include any typescript file for compilation which will be palced inside TypeScripts folder as mentioned in include section above. It also tells the compiler to copy the output js files in wwwroot/ts_build folder (mentioned by outDir key). We need to use the same path when referencing the js file in HTML.

<script src="~/ts_build/app.js"></script>

Now we have all setup with TypeScript, we can write TypeScript code and it should work.

References:

Related Post(s):

May 4, 2023

TypeScript - Promise.race()

The Promise.race() static method takes an iterable of promises as input and returns a single Promise. This returned promise settles with the eventual state of the first promise that settles (either fulfilled or rejected).

It's useful when you want the first async task to complete, but do not care about its eventual state (i.e. it can either succeed or fail).

Lets see an example:

const promise1 = new Promise((resolve, reject) => {
  setTimeout(resolve, 300, 'promise1 value');
});

const promise2 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'promise2 value');
});

Promise.race([promise1, promise2]).then((value) => {
     // promise2 is faster, so the 'value' will be the result of promise2
   console.log(value);
});

Output will be :

promise2 value

In this example, both promises will get resolved, but since the promise2 is faster (with less waiting time), the Promise.race() method will return promise2, it is the first promise in the input list which get settled (rejected or resolved).

If the iterable contains one or more non-promise values and/or an already settled promise, then Promise.race() will settle to the first of these values found in the iterable.

See this example:

const promise1 = new Promise((resolve, reject) => {
  setTimeout(reject, 100, 'promise1 rejected');
});

const promise2 = new Promise((resolve, reject) => {
  reject('promise2 rejected');
});

Promise.race([promise1, promise2])
.then((value) => {
  console.log(value);
})
// promise2 is already settled (rejected), 
// so the 'error' will be the rejection-error of promise2
.catch((error) => { console.error(error);})
;

Output will be :

promise2 rejected

Since promise2 is already settled (rejected), Promise.race() method will return promise2.

References:

Related Post(s):

TypeScript - Promise.any()

The Promise.any() method is useful for returning the first promise that fulfills. It short-circuits after a promise fulfills, so it does not wait for the other promises to complete once it finds one.

This method returns the first fulfilled value and ignores all rejected promises up until the first promise that fulfills. This can be beneficial if we need only one promise to fulfill but we do not care which one does.

Lets see an example:

const p1 = Promise.reject("some error");
const p2 = Promise.resolve("resolve value1");
const p3 = Promise.resolve("resolve value2");

const promises = [p1, p2, p3];

(Promise as any).any(promises)
.then((value) => console.log(value))
.catch((error) => { console.error(error.message);});

Output will be :

resolve value1

In this example, the first promise p1 is rejected, but Promise.any method ignores this and continue to the next promise p2 which get resolved/fulfilled. Once it finds the first fulfilled promise it stops processing further promises.

If all of the input promises are rejected, then it rejects with an AggregateError containing an array of rejection reasons.

See this example:

const p1 = Promise.reject("some error1");
const p2 = Promise.reject("some error2");
const p3 = Promise.reject("some error3");

const promises = [p1, p2, p3];

(Promise as any).any(promises)
.then((value) => console.log(value))
.catch((error) => { console.error(error);});

Output will be :

[AggregateError: All promises were rejected] {
  [errors]: [ 'some error1', 'some error2', 'some error3' ]
}

When all of the input promises are rejected, it generates the AggregateError (array of the rejection error from each input promise).

References:

Related Post(s):

April 6, 2023

TypeScript - Promise.allSettled()

The Promise.allSettled() method is one of the promise concurrency methods. It takes an iterable of promises as input and returns a single Promise. This returned promise fulfills when all of the input's promises settle, with an array of objects that describe the outcome of each promise.

The result(object) returned by each input promise has the following properties:
  • status: A string, indicating the eventual state of the promise (either "fulfilled" or "rejected").
  • value: if status is "fulfilled". The result/value of the promise.
  • reason: if status is "rejected". The reason for promise rejection.

Promise.allSettled() is typically used when you have multiple asynchronous tasks that are not dependent on one another to complete successfully, and we like to know the result of each promise.

Lets see an example:

const p1 = 10;
const p2 = Promise.resolve(20);
const p3 = Promise.reject("some error");
const p4 = new Promise((resolve, reject) => {
  setTimeout(() => {
	resolve("value from promise4");
  }, 100);
});

Promise.allSettled([p1, p2, p3, p4])
.then((results) => {
  console.log(results);
});

Output will be :

[
  { status: 'fulfilled', value: 10 },
  { status: 'fulfilled', value: 20 },
  { status: 'rejected', reason: 'some error' },
  { status: 'fulfilled', value: 'value from promise4' }
]

Promise.allSettled() executes .then() for all the input promises regardless of the status of promise (either rejected or fulfilled). You can inspect the result object to find status, value and reason properties.

References:

Related Post(s):

TypeScript - Promise.all()

The Promise.all() method is one of the promise concurrency methods. It takes an interable of promises as input and returns a single promise object. It is useful for aggregating the result of multiple promises passed as input.

  • This returned promise fulfills when all of the input's promises fulfill/resolve.
  • It rejects immediately when any of the input's promises rejects.

It is typically used when there are multiple asynchronous tasks that we want to fulfill before the code execution continues.

In this example, since all the input promises are resolved, so the final promise will also get resolved.

const p1 = 10;
const p2 = Promise.resolve(20);
const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
	resolve("value from promise3");
  }, 100);
});

Promise.all([p1, p2, p3]).then((values) => {
  console.log(values);
});

Output will be :

[ 10, 20, 'value from promise3' ]

Note that, the values parameter in .then() function will be an array containing all the output values from each input promise.

Lets see an example, if one of the promise from input is rejected.

// pass 4 promises: (1,2,3 and rejected-promise)
const p = Promise.all([1, 2, 3, Promise.reject(new Error("some error"))]);

p.then((values) => {
  //this will not get called.
  console.log(values);
})
.catch((error) => {
  //catch here for rejected promise
  console.error(error.message);
});

Output will be :

some error

Promise.all() executes the success callback when all the input promises are resolved. If any of the promise is rejected, then the rejection callback will be executed.

References:

Related Post(s):

March 20, 2023

JavaScript - What is Promise

A promise object is an instance of the Promise class. It represents the eventual completion or failure of an asynchronous operation.

A Promise is kind of proxy for a value might be unknown when the promise is created. You can associate handlers with a promise to receive notification/result of success and failure of asynchronous operation performed by promise executor.

Promise executor is simply a function passed to the constructor of Promise class, it controls the behavior of promise's resolution (success )or rejection (failure).

Promise lets asynchronous methods return values like synchronous methods. Instead of immediately returning the final value, it returns a promise object as a mechanism to supply the value at some point in the future.

A Promise object can have one of these states:

  • pending: initial state (neither fulfilled nor rejected)
  • fulfilled: when the operation was completed successfully.
  • rejected: when the operation failed.

To create a promise, we use new Promise(executor) syntax and provide an executor function as an argument.

To consume a promise, we use .then() function to receive the result of promise.

The .then() method of the promise object takes up to two arguments:

  1. First argument is a callback function for the fulfilled case of the promise (usullay known as resolve)
  2. Second argument is a callback function for the rejected case (usullay known as reject).

An example:

let myPromise = new Promise(function(resolve, reject) {
    let status_code = 200;  //to call resolve/success
    //let status_code = 201; //to call reject/failure
  
    if (status_code == 200) {
      resolve("Status code is 200");
    } else {
      reject("Status code is not 200");
    }
  });
  
myPromise.then(
	function(value) {console.log("Success:", value);},
	function(error) {console.log("Error:", error);}
);

For this example, inside the Promise executor function, if the variable status_code value is 200 then it will call resolve handler of the promise, which ultimately fall in the fulfilled callback of .then() function.

If you change the status_code value to anything other than 200, then it will call reject handler of the promise, which ultimately fall in the rejected callback of .then() function.

References:

Related Post(s):

December 20, 2022

Install TypeScript compiler in VS Code

If you need to run the typescript from VS Code terminal you need to install the typescript compiler either globally or in your workspace.

This typescript compiler will transpile the TypeScript code to JavaScript.

Following are the steps we need to install and compile TypeScript in VS Code.

  • Install node.js Package Manager (npm).
  • Install TypeScript globally (-g) by this command:
    npm install -g typescript
    
  • If want to install TypeScript locally in a single project, you can use this command:
    npm install --save-dev typescript
    
  • Test your install by checking the version.
    tsc --version
    

TypeScript HelloWorld:

Let's start with a simple Hello World example. Create a new folder HelloWorld and open in VS Code.

mkdir HelloWorld
cd HelloWorld
code .

Create a new file called helloworld.ts.

Add the following TypeScript code in helloworld.ts file.

let message: string = 'Hello World';
console.log(message);

To compile the TypeScript code, you can open the Integrated Terminal (Ctrl+`)

Run this command to compile helloworld.ts

tsc helloworld.ts

It will create a new helloworld.js JavaScript file in the same folder.

To run the javascript file, you can use this command:

node helloworld.js

It will run the javascript code and display the script output in console.

July 25, 2022

TypeScript: How to Pass a Function as a Parameter

In JavaScript you can pass the callback functions as parameters when triggering other functions. In this post, we will see an example how to do the same in TypeScript.

Let see the following code:

function f1(callback) {
  console.log('f1() function called!');
  callback();
}

function f2() {
  console.log('f2() function called!');
}

f1(f2);

In this exmaple, we have two functions f1() and f2(). We are passing f2() function as a callback paramter to function f1(), which will internally call the function f2().

It seems all ok in Javascript. However we are not leveraging the power of TypeScript.

If we pass anything (other than function, e.g. number) as a parameter to f1(), it will be compiled successfully but gives the runtime error. To avoid this issue, we can define the type of the callback parameter as Function.

So the f1() function will become:

function f1(callback: Function) {
  console.log('f1() function called!');
  callback();
}

Here f1() is enforcing the paramter should be of type Function. If you will pass anything other than the Function type, you will get compile error.

If you want to also use the return type of the callback function (lets say the return type will be number), then this solution will not work. Because the Function type could not enforce TypeScript to pass a function with desired return type.

For this, you can use an arrow function expression that returns a type to provide a valid type definition.

Let’s modify the definition of the f1() function.

function f1(callback: () => number) {
  console.log('f1() function called!');
  const number = callback();
  console.log('callback() function returns value: ' + number);
}

This helps the code to be more predictable with TypeScript, and prevent the unexpected runtime behavior often caused by lack of type definition.

June 20, 2022

Convert seconds to hh-mm-ss with JavaScript/TypeScript

I received this requirement while working on Angular application and developing the two-factor authentication screen with SMS OTP. This screen has the timer countdown to show remaining time in seconds (in mm:ss format) to enable the button to resend OTP.

I found two different functions to format the seconds in hh:mm:ss format.

In first method, you can manually perform the arithmetic operations to extract hours, minutes and seconds from given value.

Convert_Seconds_To_HHMMSS(seconds) {

      let hour = Math.floor(seconds / 3600);
      let minute = Math.floor((seconds % 3600) / 60);
      let second = seconds % 60;

      if(hour.toString().length === 1) {
            hour = `0${hour}`;
      }
      if(minute.toString().length === 1) {
            minute = `0${minute}`;
      }
      if(second.toString().length === 1) {
            second = `0${second}`;
      };

      let timeFormatted = `${hour}-${minute}-${second}`;

      return timeFormatted;
}

In second method, you can use one-liner solution using Date.toISOString() function.

new Date(seconds * 1000).toISOString().substring(11, 16)

If the seconds value is less than 3600 (less than an hour) or you don't want t show the hours in formatted string, and only need to show minutes and seconds (mm:ss), then simply change the arguments for substring() function to extract the required string part.

new Date(seconds * 1000).toISOString().substring(14, 19)

March 21, 2022

Using JavaScript Object.assign() method

In this post, we will see how to use the JavaScript Object.assign() method to copy and merge objects.

The syntax of the Object.assign() method is:

Object.assign(target, ...sources)

The Object.assign() method copies all enumerable own properties from one or more source objects to a target object. It returns the modified target object.

The Object.assign() invokes the getters on the source objects and setters on the target.

Clone an object

The following example uses the Object.assign() method to clone an object.

let book = {
    title: 'My Title'
};

let clonedBook = Object.assign({}, book);

console.log(clonedBook);
Output will contain the properties copied from source object.
{ title: 'My Title' }

Note that the Object.assign() only creates a shallow clone, not a deep clone.

Merge objects

The Object.assign() can merge two or more source objects into a target object which will contain properties consisting of all the properties of the source objects. For example:

let book = {
    title: 'My Title'
};

let bookTemplate = {
    noOfPage: 200,
    author: 'Author'
};

let bookPublishTemplate = {
    publisher: 'My Publisher',
    publishDate: '21-Mar-2022'
};

let newBook = Object.assign({}, book, bookTemplate, bookPublishTemplate);

console.log(newBook);
Output will contain the properties merged from all the source objects.
{
    title: 'My Title',
    noOfPage: 200,
    author: 'Author'
    publisher: 'My Publisher',
    publishDate: '21-Mar-2022'
}

If two or more source objects have the property with same name, the property of the later object overwrites the earlier one:

let book = {
    title: 'My Title'
};

let bookTemplate = {
    title: 'My Title from bookTemplate'
    noOfPage: 200,
    author: 'Author'
};

let bookPublishTemplate = {
    author: 'Author from bookPublishTemplate'
    publisher: 'My Publisher',
    publishDate: '21-Mar-2022'
};

let newBook = Object.assign({}, book, bookTemplate, bookPublishTemplate);

console.log(newBook);
Output will contain the properties merged from all the source objects, also the duplicate properties will be overwritten by the later source objects.
{
    title: 'My Title from bookTemplate'
    noOfPage: 200,
    author: 'Author from bookPublishTemplate'
    publisher: 'My Publisher',
    publishDate: '21-Mar-2022'
}

January 20, 2022

What is Redux Library

When the application size grows in terms of codebase and number of components, the state management become one of the major issues. There are many different options to achieve centeral state management, one of which is the Redux library.

Redux is a library for managing and updating application state. It helps you manage the "global" state that is required by different parts of your application. Redux serves as a centralized store for state that needs to be accessed across the entire application. It asks you to follow certain restrictions and rules to ensure that the state can only be updated in a certain manner to achieve a predictable behavior.

Redux is useful in scenarios like:

  • Large amount of application state is needed to be shared in different parts of the app
  • The app state is updated frequently
  • Application has complex logic for state update
  • Large application codebase

Redux organizes the application state in a single data structure called as store. The different components of the application read the state from this store. Having the restrictive rules, it ensures that the store is never be mutated directly. A reducer function is used to make the new state by combining the old state and the mutations defined by the action.

Following are the basic elements of Redux:

  • Store

    The store is a single JS object. Usually you need to add a TypeScript file to the project with a new interface type declaration. This interface will contain all the properties that are required to keep in the store.

  • Actions

    Actions are plain JS objects that represent something that has happened or triggered. Can be compared to events.

  • Reducers

    A reducer is a function that specifies the logic how the state changes in response to an action (or event). An important point here is the fact that a reducer function does not modify the state, it always returns a new state object by merging the old state object with the new modifications.

    A reducer function must always be a pure function, means that the function must ensure that if the same input is provided, then always the same output is produced.

    For example, the following reducer function takes the old state and return the increment by adding 1 to the the state property count. This way, it will always return the same new state if the old state do not have any changes. Hence the the same input will always produce the the same output.

    function reducer(state, action) {
      switch (action.type) {
    	case: 'INCREMENT':
    	   return { count: state.count + 1 };
      }
    }
    	

References:

April 14, 2021

Download Excel File using AJAX in jQuery

The Excel file can be downloaded as BLOB using jQuery AJAX and XmlHttpRequest (XHR) request. Once the file is successfully sent by the server, it could be downloaded using the Response object inside the Success event handler of jQuery AJAX function.

In this post I will explain the sample code to download the Excel file using AJAX in jQuery.

Lets say the following javascript function DownloadFile is called when user clicked the Download button on the web page. It accepts an arbitrary parameter fileId, you can use any other parameter like filename etc.

Inside the DownloadFile function, you can pass the url(or an accessible file path on server) in the URL parameter of the jQuery AJAX call.

Inside the success callback of AJAX function, we can read and download the target file as byte array from the xhr object. We read the response object as Blob.

function DownloadFile(fileId) {
        $.ajax({
            type: "POST",
            url: "MyController/MyAction",
            data: JSON.stringify({ fileId: fileId }),
            contentType: "application/json; charset=utf-8",
            xhrFields: {
                responseType: 'blob'
            },
            success: function (response, status, xhr) {

                var filename = "";
                var disposition = xhr.getResponseHeader('Content-Disposition');

                if (disposition) {
                    var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
                    var matches = filenameRegex.exec(disposition);
                    if (matches !== null && matches[1]) filename = matches[1].replace(/['"]/g, '');
                }
                var linkelem = document.createElement('a');
                try {
                    var blob = new Blob([response], { type: 'application/octet-stream' });

                    if (typeof window.navigator.msSaveBlob !== 'undefined') {
                        //   IE workaround for "HTML7007: One or more blob URLs were revoked by closing the blob for which they were created. These URLs will no longer resolve as the data backing the URL has been freed."
                        window.navigator.msSaveBlob(blob, filename);
                    } else {
                        var URL = window.URL || window.webkitURL;
                        var downloadUrl = URL.createObjectURL(blob);

                        if (filename) {
                            // use HTML5 a[download] attribute to specify filename
                            var a = document.createElement("a");

                            // safari doesn't support this yet
                            if (typeof a.download === 'undefined') {
                                window.location = downloadUrl;
                            } else {
                                a.href = downloadUrl;
                                a.download = filename;
                                document.body.appendChild(a);
                                a.target = "_blank";
                                a.click();
                            }
                        } else {
                            window.location = downloadUrl;
                        }
                    }

                } catch (ex) {
                    console.log(ex);
                }
            },
            failure: function (response) {
                console.log(response.d);
            },
            error: function (response) {
                console.log(response.d);
            }
        });         
    }

You can use the same code to download any binary file, like PDF etc.

Since we are creating html anchor element and simulate click to download file at client. Its good idea to also remove the link once you finished working.

In the following jquery code snippet we are creating anchor element, appending to the body tag and at the end we also removed it.

var a = $("<a />");
a.attr("download", fileName);
a.attr("href", link);
$("body").append(a);
a[0].click();
$("body").remove(a); 

March 14, 2019

How to disable ASP.Net button after click (prevent double clicking)

There could be two possible options you can apply to disable asp.net button after click to keep user from double clicking. Simple disabling the button won't help because you may also have to deal with client side validation on form controls.

For example, we have following markup with asp.net textbox and button.

 <div class="col-md-6">
  <div class="form-group">
   <label>
    User Name: 
    <asp:RequiredFieldValidator runat="server" ControlToValidate="txtUserName" ErrorMessage="Required"
     CssClass="Validator" SetFocusOnError="True" ValidationGroup="SaveUser">*</asp:RequiredFieldValidator>
   </label>
   <asp:TextBox ID="txtUserName" runat="server" class="form-control" ></asp:TextBox>
  </div>
 </div>

 <asp:Button ID="btnSave" runat="server" Text="Save" OnClick="btnSave_Click" CssClass="btn btn-primary" ValidationGroup="SaveUser"/>

We have to disable this asp.net button after clicking. Following options will help you prevent double click problem while also considering client side validation.

  • First option is to use OnClientClick event of asp.net button. We have to return false if client validation failed, else we will disable the button and optionally change the button text to indicate some progress. We also have to set UseSubmitBehavior property value to false. Final markup for the button will be:

       <asp:Button ID="btnSave" runat="server" Text="Save" OnClick="btnSave_Click" CssClass="btn btn-primary" ValidationGroup="SaveUser"
        OnClientClick="if (!Page_ClientValidate()){ return false; } this.disabled = true; this.value = 'Saving...';" 
        UseSubmitBehavior="false"
       />
      
  • Second option is to disable the button on post back event from JavaScript, i.e. window.onbeforeunload. Here is the button markup and script which sets button's disabled property, optionally you can also change button's text to indicate progress for post back event.

       <asp:Button ID="btnSave" runat="server" Text="Save" OnClick="btnSave_Click" CssClass="btn btn-primary" ValidationGroup="SaveUser"/>
      
      <script type = "text/javascript">
       function disableButton() {
        document.getElementById("<%=btnsave.ClientID %>").disabled = true;
        document.getElementById("<%=btnsave.ClientID %>").value = 'Saving...';
       }
       window.onbeforeunload = disableButton;
      </script>
      

I hope this helps some of you who get stuck with a similar problem.