Sunday, August 6, 2017

React datetime component, disable keyboard input

We currently use react on our current project and react-datetime library to render datetime picker.



Recently we got a requirment to disable keyboard input, so that users can choose a date only from the calendar UI. Unfortunately this functionality is not availbale out of the box. But there is a way to do it.

ReactDOM package provides a method to locate html element that contains react component.

ReactDOM.findDOMNode(this);

The simplest solution would be to wrap <Datetime /> component with a custom one and then disable user input, simply suppress all keyboard interaction.

Each react component has a set of hooks , one of them is componentDidMount. This method is invoked once when component is mounted. This is the right place to do some magic.

componentDidMount() {
  const componentPlaceholder = ReactDOM.findDOMNode(this);
  $(componentPlaceholder)
   .find('input')
   .on('keydown', () => false);
}

We simply search for the html element that contains the component and then search for an input inside of that element. Then we add event listenet for keydown event and suppres all keys. It would probably make sense to add some exceptions for some special keys like F11, F12, etc.

Full component code:


import React from 'react';
import ReactDOM from 'react-dom';
import Datetime from 'react-datetime';
import 'react-datetime/css/react-datetime.css';

export default class DateTimePicker extends React.Component {

    componentDidMount() {
      const componentPlaceholder = ReactDOM.findDOMNode(this);
      $(componentPlaceholder)
          .find('input')
          .on('keydown', () => false);
    }

    render() {
        return <Datetime {...this.props} />;
    }
}

Thursday, December 29, 2016

Unity and Quartz .NET

In my current project we need to use Quartz .NET for scheduled tasks. Since we already use Unity IoC it makes sense to inject all dependencies into quartz jobs. After googling I found this repository but example in that soulution as well as the nuget package keep throwing exceptions. Turned out to be that in order to integrate any IoC container with quartz it's enough to do three simple steps.

1. Implement IJobFactory interface

public class UnityJobFactory : IJobFactory
{
    private readonly IUnityContainer _container;

    public UnityJobFactory(IUnityContainer container)
    {
        _container = container;
    }

    public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
    {
        return _container.Resolve(bundle.JobDetail.JobType) as IJob;
    }

    public void ReturnJob(IJob job)
    {
    }
}

2. Inherit StdSchedulerFactory

public class UnitySchedulerFactory : StdSchedulerFactory
{
    private readonly IJobFactory _jobFactory;

    public UnitySchedulerFactory(IJobFactory jobFactory)
    {
        _jobFactory = jobFactory;
    }

    protected override IScheduler Instantiate(QuartzSchedulerResources rsrcs, QuartzScheduler qs)
    {
        qs.JobFactory = _jobFactory;

        return base.Instantiate(rsrcs, qs);
    }
}

3. Register those two implementations in the container

container.RegisterType<IJobFactory, UnityJobFactory>();

container.RegisterType<ISchedulerFactory, UnitySchedulerFactory>();

In adition to keep everything more separated, independat and easier to use, we can extract registration into a Unity extension.

public class QuartzUnityExtension : UnityContainerExtension
{
    protected override void Initialize()
    {
        this.Container.RegisterType<IJobFactory, UnityJobFactory>();

        this.Container.RegisterType<ISchedulerFactory, UnitySchedulerFactory>();
    }
}

And of cause an example.


public class MyJob : IJob
{
    private readonly ISomeService _someService;

    public MyJob(ISomeService someService)
    {
        _someService = someService;
    }

    public void Execute(IJobExecutionContext context)
    {
        Console.WriteLine(_someService.GiveMeSomething());
    }
}

public interface ISomeService
{
    string GiveMeSomething();
}

public class SomeService : ISomeService
{
    public string GiveMeSomething()
    {
        return "Something";
    }
}

public class Program
{
    public static void Main(string[] args)
    {
        var container = new UnityContainer();

        container.AddNewExtension<QuartzUnityExtension>();

        container.RegisterType<ISomeService, SomeService>();

        var scheduler = container.Resolve<ISchedulerFactory>().GetScheduler();

        scheduler.ScheduleJob(
            new JobDetailImpl("myJob", typeof(MyJob)),
            new CalendarIntervalTriggerImpl("TestTrigger", IntervalUnit.Second, 2)
        );

        scheduler.Start();

        Thread.Sleep(TimeSpan.FromSeconds(10));

        scheduler.Shutdown();
    }
}

Friday, October 23, 2015

Nodejs 4.x.x on Raspberry Pi

Simple steps to install nodejs 4.x.x on Raspberry Pi 2:

sudo apt-get update

sudo apt-get upgrade

curl -sL https://deb.nodesource.com/setup | sudo bash -

sudo apt-get install nodejs

I got version 0.10.4 yours can be different.

Let's update it to the version 4.x.x:

sudo npm install -g n

sudo n stable

Restart the terminal and type

node -v

I got version v4.2.1

P.S. OS version on my Raspberry Pi:

Distributor ID: Debian
Description:    Debian GNU/Linux 7.8 (wheezy)
Release:        7.8
Codename:       wheezy

You can check your version simply by typing:

lsb_release -a

Tuesday, October 13, 2015

Nodejs 4.x.x on Debian 7 wheezy

If you use debian 7 wheezy and cannot install nodejs of version 4.x.x most likely it's because you have a wrong version of gcc. Nodejs 4.x.x requires gcc 4.8 or higher. Unfortunately Debian has gcc 4.8 starting from version 8 jessie. Fortunately there is a way to install it.

In order to install you need to adjust sources.list file in /etc/apt. Just add on line at the bottom of this file:

deb http://ftp.uk.debian.org/debian/ jessie main non-free contrib

Now check if you have preferences file in /etc/apt. If no then just create one.
Add following content to this file:

Package: *
Pin: release n=wheezy
Pin-Priority: 900

Package: gcc*
Pin: release n=jessie
Pin-Priority: 910

Now just use aptitude to install required version of gcc

sudo aptitude update
sudo aptitude install gcc/jessie

Thursday, October 1, 2015

Interesting behavior in Moment.js

Momemnt.js is a great library to work with dates. One of the features that I really like is ability to parse dates in different formats. Recently our QA has found that dates in short format, where the year is represented by 2 digits behave differently. For example date format is DD.MM.YY in this case we will have following results:

'12.05.88' becomes Thu May 12 1988 00:00:00 GMT+0200 (W. Europe Daylight Time)
'12.05.52' becomes Sun May 12 2052 00:00:00 GMT+0200 (W. Europe Daylight Time)

We got two dates in different centuries.
Turned out to be there is a special function called parseTwoDigitYear and default implementation is:


hooks.parseTwoDigitYear = function (input) {
    return toInt(input) + (toInt(input) > 68 ? 1900 : 2000);
};

It means that all years after 68 will be in 20th century but 68 and everything bellow 68 will go to the 21st. What is also interesting that 68 is just a random number. Good thing is that it could be adjusted.
You can override this behavior. As example let's make it return always the year 1995.


moment.parseTwoDigitYear = function (input) {
    return 1995;
};

You can find more about customization here

Tuesday, July 21, 2015

JSCS for different platforms

We use jscs in our project. It's nice tool, and has nice feature, it allows to use configuration files from the other teams. We decided to take one from Airbnb. Everything was great, but there is one problem, some developers in our team use Windows environments, other Mac OS, and our build machine is hosted on Debian. It brings us the situation that some machine have CRLF line endings and some LF endings. And we need some way to use jscs on the all environments. Since we use gulp task manager, it was logical to include jscs check in our build process, and in order to make it work we just put one if statement that overrides line endings rule based on the OS where the script is executed.

.jscsrc file

{
  "preset": "airbnb",
  "fileExtensions": [ ".js" ],
  "validateLineBreaks": 'LF'
}

Function inside gulp files, that reads and adjust if need .jscsrc


function readRules() {
  return new Promise((resolve, reject) => fs.readFile('.jscsrc', (error, data) => {
    if (error) {
      reject(error);
    }
    else {
      let config = JSON.parse(data);
      if (os.platform() === 'win32') {
        config.validateLineBreaks = 'CRLF'
      }

      resolve(config);
    }
  }));
}

It was fine and worked fine for us. Another problem that we ran into was Webstorm support. Webstorm is a very cool IDE for javascript and turned out to be, that it also supports jscs validation out of the box. You just need go to the settings and enable it. But the same issue as before, we have different environments and Webstorm parses all sources and applies jscs config to each of them, but there is now way to apply any conditions. We didn't find any nice way to tune that behavior for Webstorm, We decided to simply ignore line endings during development process in IDE but still have it as a part of our build script. So we just disable that rule completely by setting  validateLineBreaks to null.

.jscsrc file that suppress validateLineBreaks from parent configuration(airbnb)

{
  "preset": "airbnb",
  "fileExtensions": [ ".js" ],
  "validateLineBreaks": null
}

It means that validation by Webstorm, which simply runs node.js under the hood and passes jscssrc.json as a parameter, will consider all line endings to be correct. OK, great, one small thing to change is gulp task, to put both line endings into play, instead of overriding as before, since now jscs has it to be set to NULL.

Adjusted function in gulp file that set correct line endings for both OS's


function readRules() {
  return new Promise((resolve, reject) => fs.readFile('.jscsrc', (error, data) => {
    if (error) {
      reject(error);
    }
    else {
      let config = JSON.parse(data);

      config.validateLineBreaks = os.platform() === 'win32' ? 'CRLF' : 'LF';

      resolve(config);
    }
  }));
}

Some things to mention:
1. Since we take Airbnb version as base one, we get settings where validateLineBreaks set to LF
2. Why do we care about line endings at all...well it's a good practice and we want to be consistent.
3. How does it work between environments?! Git does the magic. We simply set autocrlf setting to true. It means that every time somebody checkout sources under Windows OS git normalized all files and replace LF with CRLF, on *NIX environments git replace CRLF line endings with LF, of cause if there are such.

Summary:
1. There is no way to put some conditions into jscsrc
2. By setting jscs rule to null we can disable it completely
2. Some OS specific validation like validateLineBreaks could be a part of a build task(gulp, grunt, etc.)

Friday, December 12, 2014

NancyFX global error handling

Add global error handling to NancyFX Web API is very easy. We need to add a Bottstapper class and inherit it from DefaultNancyBootstrapper. Let's override RequestStartup method.


public class Bootstrapper : DefaultNancyBootstrapper
{
    private ILogger logger;

    protected override void RequestStartup(TinyIoCContainer container, IPipelines pipelines, NancyContext context)
    {
        pipelines.OnError += (ctx, e) =>
        {
            this.logger.Error(e.Message, e);

            return null;
        };

        base.RequestStartup(container, pipelines, context);
    }
}

This method is executed on each call, if any unhandled exception happens we will end up here.