Share this blog!

During the implementation of delete and clear container operations, I had to implement delete confirmation dialog boxes using Bootstrap Dialog. I thought of using a common confirmation function, which would take the action function as a parameter and call it if confirmed. (Note: jQuery's confirm provides similar functionality with the default dialog box)

I started by defining the following approach which works fine with global functions. But when Backbone view instances are concerned, it did not work. I assumed the problem was that the passed function has incorrect context - note that "this" prints different objects in the console.

var viewDemo = Backbone.View.extend({
    mainFunc: function(){
        this.intermediateFunc(this.ABC);
    }
    intermediateFunc : function(callback){
        console.log(this); //prints the correct view
        callback();
    }
    ABC : function(){
        console.log(this); //prints 'window' when passed through a function
    }
});


As usual, StackOverflow came up with the solution when I asked this question. The solution of mu is too short explains 3 approaches:

1. Using Function.prototype.bind


mainFunc: function(){
    this.intermediateFunc(this.ABC.bind(this));
}

2. Function.prototype.call or Function.prototype.apply


mainFunc: function(){
    this.intermediateFunc(this.ABC, this);
},
intermediateFunc : function(callback, context) {
    console.log(this); //prints the correct view
    if(context)
        callback.call(context);
    else
        callback();
}

Or the same could be used as:

mainFunc: function(){
    this.intermediateFunc(this.ABC, this);
},
intermediateFunc : function(callback, context) {
    console.log(this); //prints the correct view
    context = context || this;
    callback.call(context);
}

3. The old var self = this; trick


mainFunc: function() {
    var _this = this;
    this.intermediateFunc(function() { return _this.ABC() });
}

Internship diaries #4


At the start of the 4th four-week period, a review meeting was held where several new tasks were specified. I started working on them starting from the minor tasks.

The connector make over

One such task was converting the connectors from straight lines to lines with margin-like ends.
Earlier, the connector was simply an svg:line where I update the x1, y1, x2 and y2 properties directly using d3.selector.attr. In the new requirement, an svg:polyline was introduced as the solution where the points had to be updated instead of the previous x and y coordinates. Each polyline was of 3 segments and needed 4 pairs of coordinates. The earlier connectors already had 2 pairs of coordinates and I used the following to calculate the remaining 2 pairs.


In order to improve clicking on the connector, I added another polyline with the same points but higher width and invisible in the editor except for mouse hover. The events regarding the connectors were set to be captured by this underlying polyline and the increased area improved the usability of the connectors.

Clicked right

Another task was to add the load-file and clear containers functions to a right click menu on the container. For this I used predefined menus in <ul> tags in the main file, which were toggled on contextmenu event. There was a problem causing the menus to be displayed off the desired positions. After researching on this I found out the cause and the solution.

Lists (both unordered and ordered) have padding and margin set by default which was causing the undesired shifts. The default padding was meant for the bullet icons or numbering of list items. The simple solution was to set padding and margin to zero in the <ul> of the menu.

Hidden foreigner

Another problem faced during right click menu implementation was that the menus were not working for upper nodes of the container. After testing on multiple scenarios I found out that this occurs in Firefox but not Chrome and that the inspect-element pointed to a blank square rather than the nodes. The research on this showed that the <foreignObject> I used to hide the file-input was literally covering the upper nodes. Although I have set display:none to the input, I had not hid the <foreignObject> itself which was 100px wide and tall. To solve it, I set display:none to the parent element and as an extra precaution, set the height and width to 1px.

Build on the go

Among the major tasks I completed were dynamic addition of the nodes to the tree, which was also a use case of allowing the user to build a schema manually. I started on it by adding the root element. I used BootstrapDialog to create dynamic modal dialog boxes that gets the information such as the title and the type of the node. When the root element is added, a schema is initiated and stored in the Tree object.

The rules assumed as optimum are as follows:

  1. The options for the type of the root element are limited to Object and Array
  2. Only nodes of type Object or Array are allowed to add child nodes
  3. New nodes to the root element are added only as children nodes
  4. Sibling added to a node will be placed right after the triggered node
  5. Child added to a node will be placed as the last child of the triggered node

When I was working on updating the schema during node addition, I noticed that JS does not allow adding new nodes to a specific location (to be added as the immediate sibling) since “JavaScript objects are unordered lists of properties”. Therefore a custom method was to be used to set out the right order.

I created a new temporary object, looped through the keys of the parent and kept adding them to the new object. When the triggered node is detected, I added the new sibling node and kept adding the rest of the nodes and finally replace the parent object with the new object.

Once the schema update was completed, I worked on displaying the new nodes. I wrote two (for sibling and child use cases) recursive functions to detect the node right-above the desired location. The functions were based on the HTML DOM hierarchy of the browser. Then I pushed down the nodes below the desired location and added the new node.

One of the problems I faced was that, the new nodes were added at the end of the HTML DOM hierarchy regardless of the position of the node in the tree, which gave incorrect results. Therefore a mechanism was used to re-order the elements in par with the schema, which was implemented using jQuery’s append and detach.

It’s the end of the 16th week of the internship and this post explains the progress of the JavaScript based data mapper so far.

After the ‘backbone’ of the app was set out, focus was given to adding more UI functionalities – where the user can customize the input, output and the operations.


The front-end

So far, the data mapper initializes with an input container, output container and a tool palette(integrated from the NEL tooling platform).

Each tool in the tool palette can be dragged and dropped on the canvas, where the corresponding operator is drawn. Each operator has its defined set of input/output labels and valid types and each operator has the option to be deleted.


Additionally, the direct connectors (Connecting Input container to output container) will be corresponding to a “Direct operator” without visible components.

The “Input” tool in the “Tree containers” is used to drag and drop extra input tree containers.

Each tree container has a total of 3 options which may appear or not based on the content in it. Empty containers have the option “Add new root” in place of the “Clear container” option in non-empty containers and the abstract functionalities of the options are as follows:


  1. Load schema: Select the structure based on a file of one of the types of XML, JSON, CSV, XSD, JSON Schema.
  2. Add new root: Build the structure manually by defining the root and adding nodes.
  3. Clear container: Empties the container



The extra containers added by the user have the additional option to be deleted.

When reading from a file or building the structure in a tree container, the containers create “Node”s in a tree-structure. The parent nodes are of type Object or Array and others are leaf nodes and each leaf node has an “Anchor”. Input type anchors can be dragged and dropped on a node of opposite type which will create “Connector”s. A hidden svg:polyline of higher width is used to improve clicking on the connector.

Each node has 3 options as follows:

  1. Add node: Add a new node to the structure.
  2. Edit node: Edit the title and type of node
  3. Delete node: Delete the node from the app.

When adding a new node, it is added as a sibling by default, but if the triggered node is a parent node, there is an option to add the new node as a child. When adding a child node, the child is added as the last child of the triggered node and when adding a sibling node, the sibling is added as the immediate next sibling of the triggered node.

The back-end at a glance


The tree structure of each tree container is maintained as a JSON Schema. When reading a file of a different type such as XML, JSON, CSV and XSD, the structure is converted to JSON Schema type and is manipulated throughout the app.

As a whole, the app creates and maintains a global JS object “Diagram” which includes the objects of the diagram as Backbone models or collections.


1
2
3
4
5
6
7
8
9
var Diagram = {
    Canvas: {}, //the editor = svg
    TreeContainers: {}, /* Backbone collection corresponding to Input/Output container
                                 the model contains the loaded schema as a JS object and a collection of nodes */
    Connectors: {}, //all the 'lines' drawn in the application
    ToolList: {}, //The collection of tools
    PalleteView: {},
    Operators: {} //a Backbone collection of all the operators in the canvas
 };

The result

The generated graph of mappings is calculated by means of the input/output adjacency lists of each operator. The result includes:
  1. A variable list: All the nodes
  2. Operators: A list of all operators including direct operators
  3. Input adjacency list: Input nodes of the operators in the list
  4. Output adjacency list: Output nodes of the operators in the list





One of the tricky requirements faced during connector implementation was the right-click menus. I used a shared html ul components which were triggered by assigning classes, since it would be less-consumptive than creating menus for each component.

The connectors of the data mapper are used to connect input nodes to output nodes and they are implemented using svg polylines. The stroke-width of the lines were maintained at 2 and this made it hard to click on them. Therefore it was suggested to use a hidden background overlay that would be highlighted during mouse over and click and the right click menu was to be triggered on this overlay.

 As for the overlay, I used a copy of the connector, but with a wider stroke-width and defined mouseover and mouseleave event functions to change the opacity of the overlay.

It worked fine until it was observed that, when the mouse is hovered on the right click menu, mouseleave is triggered and the highlight is removed. I wished to keep the connector highlighted during its focus, which includes right click menu propagation.

After discussing with a team member on multiple solutions, it was decided to use a simple solution of manipulating classes to check if menu is displayed before triggering mouseleave.

The following needs improvement, but demonstrates the basic idea of the solution:

  1. Corresponding question on StackOverflow


As a requirement in my internship project (as described in this post), I had to insert new attributes to the JSON schema to a specified location, ie, to add an immediate sibling to the clicked node. Since JavaScript is an unordered list of properties, typical JSON methods could not be used. Therefore a solution where iterating over the keys and cloning them to a new object was used.

The sample code is as follows: 
The highlight of this week's implementation was updating the schema with the user input. The user is basically allowed to add nodes to a schema and that logic could be used to build a schema from scratch which needs an additional step of setting the root element.

The modal dialogs were implemented using BootstrapDialog which offered simple a simple template to create dynamic modal dialog boxes. I used a simple String operation to hide the add-as-child option.

var isLeaf = this.isLeafElement ? ' style="display:none;" ' : ' ';
BootstrapDialog.show({
    title: "Add new node",
    message: 'Title: <input id="title" type="text"><br>Type:<select id="type">' + this.getTypeOptionList("Object") + '</select><div ' + isLeaf + ' ><br>Add as child: <input type="checkbox" id="isChild" ></div> ',
    draggable: true,
    buttons: [{
        label: 'Add',
        cssClass: "btn-primary",
        action: function (dialogRef) {
            self.model.addNode(dialogRef.getModalBody().find('#title').val());
            dialogRef.close();
        }
    },
        {
            label: 'Cancel',
            action: function (dialogRef) {
                dialogRef.close();
            }
        }
    ]
});


The rules I assumed as optimum included:

  1. The options for the type of the root element are limited to Object and Array
  2. Only nodes of type Object or Array are allowed to add child nodes
  3. New nodes to the root element are added only as children nodes
  4. Sibling added to a node will be placed right after the triggered node
  5. Child added to a node will be placed as the last child of the triggered node
The initial goal was to update the schema, and to reload the container which will draw the tree-structure according to the updated schema. However, this will remove any current connectors attached to the container. Therefore dynamic node drawing was needed - which was kept for later consideration.

The schema-update was achieved using a recursive method, which was quite similar to the method I used to get the JSON path from a schema.

Initially, it would determine the relevant parent node of the expected new node and iterate the schema to find it. Adding as a child was not a big deal since it was to be added as the last child of the parent and simply updating the object as data[newAttribute] = newValue; would give the desired result.

Adding the sibling was the tough job since, it had to be added as the immediate sibling - to enhance user experience. The main reason was that JavaScript objects are unordered lists of properties. Therefore, in order to achieve the desired result of adding as the immediate sibling, I had to follow a String operation on parent data of the node.

The steps taken were:

  1. Select the parent data set - the target
  2. Detect the triggered node in the target set as a String
  3. Concatenate the triggered node string with the new node string
  4. Replace with the previously detected string
  5. Parse the new string as a JSON 
UPDATE: The string operation was replaced by an iterating function as in this post.

Next PostNewer Posts Previous PostOlder Posts Home