Friday, September 20, 2019

Building Input autocomplete component using Ember

Ember is a framework for ambitious web developers, even though building application using Ember is faster, there were some bottlenecks like creating a select list using HTML and getting the data from the HTML element and submitting to the API etc.

Ember comes with a power-train called components and this will solve our problem, we should architect components carefully so that can be reused across. The solution is through mix of things which i experienced and there might be better way of solving the same problem.


Assumptions 

Latest version of Ember is installed and you have basic experience of building a Application with knowledge of using HTML, Javascript, NodeJS, Ember Rest Adapter, Ember Model, Ember View and Ember Controller.

Step 1

Run the following command under Ember project folder


ember g component input-autocomplete  
 
The above command will create a controller and view for the component. The controller will be created under component folder with name input-autocomplete.js and template with name input-autocomplete under template/component/input-autocomplete.hbs.
The following code should be copied into the component template.
{{input
  name=this.name
  value=this.value
  placeholder=this.placeholder
  key-up=(action "search")
  focusIn=(action "showdropdown")
  focusOut=(action "hidedropdown")
  class="form-control"
}}
<div id="myInputautocomplete-list" class="autocomplete-items">
    {{#each this.filteredResult as |data|}}
        <div {{action 'choose' data.title}} {{action "choose" data.title on="mouseDown"}} >
            {{data.title}}
            <input type="hidden" value="{{data.title}}">
        </div>
    {{/each}}
</div>

Step 2

Edit your component controller file and add the following code, in our case it is component/input-autocomplete.js
import Component from '@ember/component';


export default Component.extend({
    value: '',
    currentFocus: -1,
    childCount: 0,
    filteredResult: null,
    lov: null,
    init() {
        this._super(...arguments);
        this.filter('').then((results=> this.set('filteredResult'results));
    },
    didRender() {
        this._super(...arguments);
        //this.currentFocus = -1;
        this.lov = this.element.children[1];
        if (document.activeElement !== this.element.firstChild) {
            this.element.children[1].style.display = "none";
        }
        else 
            if (this.childCount !== this.element.children[1].children.length) {
                this.currentFocus = -1;
                this.childCount = this.element.children[1].children.length;
            }
    },
    keyDown: function (evt) {
        var charCode = (evt.which != undefined? evt.which : event.keyCode;
        if (charCode === 40) { //Up Arrow Key
            if (this.currentFocus < this.element.children[1].children.length-1)
                this.currentFocus++;
            else 
                this.currentFocus = 0;
            this.removeHighlight();
            this.highlight(this.currentFocus);
        } else if (charCode === 38) { //Down Arrow key
            if (this.currentFocus > 0)
                this.currentFocus--;
            else 
                this.currentFocus = this.element.children[1].children.length-1;
            this.removeHighlight();
            this.highlight(this.currentFocus);
        } else if (charCode == 13) { //Entter Key
            this.set('value',this.element.children[1].children[this.currentFocus].children[0].value);
            this.removeHighlight();
            this.element.children[1].style.display = "none";
            this.element.children[0].blur();
        } 
    },
    highlight: function(pIndex) {
        this.element.children[1].children[pIndex].classList.add("autocomplete-active");
    },
    removeHighlight: function() {
        for (var i =0 ; i < this.element.children[1].children.length ; i++) {
            this.element.children[1].children[i].classList.remove("autocomplete-active");
        }
    },
    actions: {
        choose(pSelectedText) {
            this.set('value',pSelectedText);
            this.removeHighlight();
            this.element.children[1].style.display= "none";
            this.element.children[0].blur();
        },
        hidedropdown() {
            this.element.children[1].style.display = "none";
        },
        showdropdown() {
            this.element.children[1].style.display = "block";
        },
        search() {
            this.element.children[1].style.display = "block";
            let filterInputValue = this.value;
            let filterAction = this.filter;
            filterAction(filterInputValue).then((filterResults=> this.set('filteredResult'filterResults));
        }
    }
});

Step 3
Edit your parent controller file and copy the following code, in my case it is controller/todo.js


import Controller from '@ember/controller';
export default Controller.extend({
    filter:'',
    filteredList: null,
    actions: {
        searchTodo(param) {
            if (param !== '') {
              return this.store.query('todo', { filter: {title: param } });
            } else {
              return this.store.findAll('todo');
            }
        }
    }


});

Step 4

  Edit your parent view file and copy the following code, in my case it is templates/todo.hbs


    <div class="form-group">
        <label for="title">myTodo</label>
        {{input-autocomplete value=mytodo name="mytodo" placeholder="Enter Todo" id="mytodo" filter=(action 'searchTodo')}}
    </div>

Step 5

Start your Ember server with the following command




ember server
 
Step 6 

Run the application from web browser by entering http://localhost:4200/todo focus on the autocmplete field which we created and you will notice a list of todo's appearing from the database and when start typing will notice the list getting filtered, you can use up arrow, down arrow and mouse to navigate in the autocomplete select list.

Note: I am using a Rest based Adapter pointing to nodeJS API with mongoDB. Following code is part of adapter/application.js


import DS from 'ember-data';


export default DS.RESTAdapter.extend({
    host: 'http://localhost:3000'
});

Todo Model as Bonus code


import DS from 'ember-data';
const { Model } = DS;
import { computed } from '@ember/object';


export default Model.extend({
    title: DS.attr('string'),
    body: DS.attr('string'),
    date: DS.attr('date'),
    created_at: DS.attr('string', {
        defaultValue: function() {
            return new Date();
        }
    }),
    isexpired: computed('date',function() {
        return new Date() > this.get('date');
    })
});

Serializer as Bonus code


import DS from 'ember-data';
import { assign } from '@ember/polyfills';


export default DS.RESTSerializer.extend({
    primaryKey: '_id',
    serializeIntoHash: function(hashtyperecordoptions) {
        assign(hashthis.serialize(recordoptions));
      }
});
  

Hope the code is pretty straight forward and hope it helps you. 

Happy Embering :).


Wednesday, February 27, 2019

Zookeeper Configuration best practices

  1. If you are using watches, you must look for the connected watch event. When a ZooKeeper client disconnects from a server, you will not receive notification of changes until reconnected. If you are watching for a znode to come into existance, you will miss the event if the znode is created and deleted while you are disconnected.
  2. You must test ZooKeeper server failures. The ZooKeeper service can survive failures as long as a majority of servers are active. The question to ask is: can your application handle it? In the real world a client's connection to ZooKeeper can break. (ZooKeeper server failures and network partitions are common reasons for connection loss.) The ZooKeeper client library takes care of recovering your connection and letting you know what happened, but you must make sure that you recover your state and any outstanding requests that failed. Find out if you got it right in the test lab, not in production - test with a ZooKeeper service made up of a several of servers and subject them to reboots.
  3. The list of ZooKeeper servers used by the client must match the list of ZooKeeper servers that each ZooKeeper server has. Things can work, although not optimally, if the client list is a subset of the real list of ZooKeeper servers, but not if the client lists ZooKeeper servers not in the ZooKeeper cluster.
  4. Be careful where you put that transaction log. The most performance-critical part of ZooKeeper is the transaction log. ZooKeeper must sync transactions to media before it returns a response. A dedicated transaction log device is key to consistent good performance. Putting the log on a busy device will adversely effect performance. If you only have one storage device, put trace files on NFS and increase the snapshotCount; it doesn't eliminate the problem, but it can mitigate it.
  5. Set your Java max heap size correctly. It is very important to avoid swapping. Going to disk unnecessarily will almost certainly degrade your performance unacceptably. Remember, in ZooKeeper, everything is ordered, so if one request hits the disk, all other queued requests hit the disk.To avoid swapping, try to set the heapsize to the amount of physical memory you have, minus the amount needed by the OS and cache. The best way to determine an optimal heap size for your configurations is to run load tests. If for some reason you can't, be conservative in your estimates and choose a number well below the limit that would cause your machine to swap. For example, on a 4G machine, a 3G heap is a conservative estimate to start with.