Pages

Friday, 30 January 2015

Creating web socket service in JDeveloper

Usecase:

This blog post talks about creating web socket based services in JDeveloper. At the end of this post, you will be familiar with creating a basic web socket based service that takes in a path parameter and prints out a message for you.

Pre-requisites:

JDeveloper 12.1.3.0.0

Steps:
  • Create a new custom application 'WebSocketSample' in JDeveloper. In Step 2 of the wizard, select the project feature as 'Web Socket' and toggle it to the right. Once you do this, you will see Web Socket as well as java listed out in the RHS. Finish the wizard.


  • Next, create a new java class. Call it 'WebSocketWithParam'. Call the package 'websocket'. In this java class, we will write the code for the socket service. We will have three methods with 'name' as parameters. These parameters are configured as PathParams as shown below. We will add the import for them in a while.
  1. package websocket;
    
    public class WebSocketWithParam {
        public String helloThere(@PathParam("yourname") String name){
        return "Hello There "+name;
        }
    
        public String yourmessage(@PathParam("yourname") String name, String msg){
        return "From "+name+" Message: "+msg;
        }
    
        public String bye(@PathParam("yourname") String name){
        return "Bye There "+name;
        }
    }
  • Focus on the class name and using PI, select the check box Web Socket Service. This will generate the annotation @ServerEndpoint("service"). Remember to append a '/' before the name of the ServerEndPoint. ie., make it @ServerEndpoint("/service"). Focus on helloThere method and using PI, check the checkbox for OnOpen Method.
    Focus on yourmessage method and using PI, check the checkbox for OnMessage Method. Now focus on the parameter 'name' present on this method and in PI, add 'yourname' as Web Socket Client. Also, modify @ServerEndpoint("/service") to @ServerEndpoint("/service/{yourname}")

    Focus on bye method and using PI, check the checkbox for OnClose Method. Also fix the import for PathParam by adding the import javax.websocket.server.PathParam.

             Final code:
  • package websocket;
    
    import javax.websocket.OnClose;
    import javax.websocket.OnMessage;
    import javax.websocket.OnOpen;
    import javax.websocket.server.PathParam;
    import javax.websocket.server.ServerEndpoint;
    
    @ServerEndpoint("/service/{yourname}")
    public class WebSocketWithParam {
        @OnOpen
        public String helloThere(@PathParam("yourname") String name){
        return "Hello There "+name;
        }
    
        @OnMessage
        public String yourmessage(@PathParam("yourname") String name, String msg){
        return "From "+name+" Message: "+msg;
        }
    
        @OnClose
        public String bye(@PathParam("yourname") String name){
        return "Bye There "+name;
        }
    }
  • The websocket is now ready. Right click on the class 'WebSocketWithParam' and select Run. A target URL will be generated for you in the weblogic server window, as shown below:
  • Clicking on this target URL should result in a dialog 'Connected Successfully'.
  • To test if the service is behaving as expected, I recommend that you use 'Dark WebSocket Terminal' available for free on the chrome store. Feed in the generated target URL in the terminal and enter a message. On sending the request, you should be able to see the response received from the web socket.


Tuesday, 27 January 2015

Angularjs Directive for an Animated Highlight Slider

Recently I had to work on angularjs and one of the component that had to be reproduced was a slider to slide across to the selected element. This is an attempt to write out the same. Any suggestion for improvements is welcome.
The objective is to transition the slider from one heading to another.

A continuation for the below post with a simpler approach is available in Part 2 of this post

The directive can be seen in action here

A jsfiddle for the same is available here.

Components:
  • Define the Angular App
  • Define the HTML
  • Define the Angular Directive
  • Define the transition css
Assumption
  • Bootstrap is used for the Grid Layout.
  • All selectors are rendered on the same line.
HTML :

The code is al

<div  class='row' data-ng-init='moveSlider="slider1"'>
 <div class='col-xs-4 text-center'>
  <div>
   <span><a id='slider1' data-ng-click='moveSlider="slider1"'>Slider1</a></span>
  </div>
 </div>
 <div class='col-xs-4 text-center'>
  <div>
   <span><a id='slider2' data-ng-click='moveSlider="slider2"'>Slider2</a></span>
  </div>
 </div>
 <div class='col-xs-4 text-center'>
  <div>
   <span><a id='slider3' data-ng-click='moveSlider="slider3"'>Slider3</a></span>
  </div>
 </div>
</div>
<underline-slider slider-class='"sliderdownarrow"' selector='moveSlider' selector-map='{slider1 : 0,slider2 : 1,slider3 : 2}' duration='3' > 
 <div class='row visible-xs-block'>
  <div class='col-xs-4 text-center'>
   <div class='sliderdownarrow'  ></div>
  </div>
  <div class='col-xs-4 text-center'>
   <div class='sliderdownarrow'  ></div>
  </div>
  <div class='col-xs-4 text-center'>
   <div class='sliderdownarrow'  ></div>
  </div>
 </div>
</underline-slider>


As you can notice there are there are 3 headings/tabs named slider1, slider2 and slider3 that are enclosed in a a row (the rows have been created using bootstrap).
The directive is named underline-slider which needs the following attributes (all attributes are mandatory):
  • slider-class - This attribute is used to identify the element that needs to animate. Corresponding to each of the link/tab there is a element with this class within the directive as can be seen in the html.
  • selector - This should be an angular variable that is bound to the link. The change in this value triggers the directive to transition from one position to another.
  • selector-map - This should provide a map of all possible selector values and its position. First position starts with 0 similar to an array as it will be used to identify the elements to transition.
  • duration - The duration should be specified in seconds. The value should match the css transition period. This is used to ensure that at the end of the transition all styles are reverted and the slider corresponding to the current element is shown. This will ensure that the slider is placed within the div that has been provided for each of the tab ensuring that the slider is responsive to a page resize.
Note:

  • The link/tab relative to which the slider should animate should have the same id as the selector variable. In the above the anchor id and the model value match.
  • Initiate the tab/link that has to be highlighted on first load. In the above example it has been done with data-ng-init='moveSlider="slider1"'.
  • The duration value 3 matches the transition property of the animating class (sliderdownarrow).
  • Since we are animating on property left we should ensure that the slider-class defined in css contains the left set to a numeric value else the transition will not take effect.


Angular App:

angular.module('slider',[]);
Controller and Directive:
The complete source code is available in the following GIT

angular.module('sliderapp').controller('sliderAppCtrl', ['$scope',
 function() {
 }
]);
angular.module('sliderapp').directive('underlineSlider',['$compile','$timeout',function($compile,$timeout){
return{
 restrict :'EA',
 scope : {
  sliderClass : '=',
  selector : '=',
  selectorMap : '=',
  sliderStyle : '=',
  duration : '='

 },
 link : function($scope,$element, $attrs){
  
  var children = $element;
  var parent = $element;
  var iter = 0;
  var sliderParent ;
  var sliderElements = [];
  var checkClass = function(el){
   angular.forEach(el,function(child){
    var childEl = angular.element(child);
    if(childEl.hasClass($scope.sliderClass)){
     childEl.addClass('ng-hide');
     sliderElements.push(childEl);
     return childEl;
    }
    
    if(!angular.isUndefined(child)){
     var children = angular.element(child).children();
     var retClass = checkClass(children);
    }
   });
  }
  checkClass(parent);
  
  var getPosition = function(elem){
       if (typeof elem == 'string' || elem instanceof String) {
         elem = document.getElementById(elem);
       } else {
         var elm = angular.element(elem);
         if ('undefined' == typeof(elm)) {
           elm = elem;
         }
       }
       if ('undefined' == typeof(elm)) {
         return {
           left: 0,
           top: 0
         };
       }
       var rawDom = elm[0];
       var _x = 0;
       var _y = 0;
       var body = document.documentElement || document.body;
       var scrollX = window.pageXOffset || body.scrollLeft;
       var scrollY = window.pageYOffset || body.scrollTop;
       var position = rawDom.getBoundingClientRect();
       return position;

  }

  //console.log(sliderEl);
  var slideCompleteFunc;
  var promise;
  $scope.$watch(function(){return $scope.selector},function(newValue,oldValue){
   var prevChange = angular.element(document.getElementById(oldValue));
   var curChange = angular.element(document.getElementById(newValue));
   var prevElPos = $scope.selectorMap[oldValue];
   var newElPos = $scope.selectorMap[newValue];
   //console.log(prev);
   //console.log(cur);

   var sliderPrevEl = sliderElements[prevElPos];
   var sliderNewEl = sliderElements[newElPos];

   if(oldValue == newValue){
    sliderPrevEl.removeClass('ng-hide');
    return;
   }
   
   var curElPos = getPosition(curChange);
   var prevElPos = getPosition(prevChange);
   var sliderPrevElPos = getPosition(sliderPrevEl);
   
   
   //$scope.$parent.$digest();
   
   console.log(sliderPrevElPos);
   var moveLeft = curElPos.left - prevElPos.left;
   console.log(moveLeft);
    sliderNewEl.attr('style','');
    //Style it with position Absolute and float left and width 100% in case the slider does not have it already
    //To ensure that the animation is not edgy the slider should be floated and the size should be controlled using the 
    //width of the parent
    sliderPrevEl.attr('style','left:'+moveLeft+'px;');
    //Finish execution 
    if(angular.isDefined(promise)){
     var promiseRet = $timeout.cancel(promise);
     if(promiseRet){
      slideCompleteFunc;
     }
    }

    slideCompleteFunc = function(prevElement,newElement){
     prevElement.removeClass('ng-hide');
     prevElement.addClass('ng-hide');
     prevElement.attr('style','');
     newElement.removeClass('ng-hide');

    }

    promise = $timeout(function(){
     slideCompleteFunc(sliderPrevEl,sliderNewEl);
     },
     $scope.duration * 1000);

  });

 }
};
}]);




CSS:

.sliderdownarrow {
  width: 100%;
  height: 4px;
  border: 2px solid;
  border-color: #9B9B9B;
  -webkit-transition: left 5s linear;
  left: 0;
  float: left;
  position: absolute;
  transition: left 3s linear; 
}
 
.sliderdownarrow::after {
    content: ' ';
    top: 100%;
    left: 50%;
    height: 0;
    width: 0;
    position: absolute;
    border-top: 5px solid #9B9B9B;
    border-right: 5px solid transparent;
    border-left: 5px solid transparent; 
}