tablednd.js ➔ TableDnD   F
last analyzed

Complexity

Conditions 20

Size

Total Lines 118
Code Lines 62

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 62
c 0
b 0
f 0
dl 0
loc 118
rs 0
cc 20

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like tablednd.js ➔ TableDnD often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
// ===================================================================
2
// Copyright Denis Howlett <[email protected]>
3
// http://www.isocra.com/
4
//
5
// This code is free software; you can redistribute it and/or modify
6
// it under the terms of the GNU Lesser General Public License as
7
// published by the Free Software Foundation; either version 2.1 of the
8
// License, or (at your option) any later version.
9
//
10
// This code is distributed in the hope that it will be useful, but
11
// WITHOUT ANY WARRANTY; without even the implied warranty of
12
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13
// Lesser General Public License for more details.
14
//
15
// Copies of the GNU Lesser General Public License are available at
16
// http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
17
// http://www.gnu.org/licenses/lgpl.html
18
// ===================================================================
19
20
/** Keep hold of the current table being dragged */
21
var currenttable = null;
22
23
/** Capture the onmousemove so that we can see if a row from the current
24
 *  table if any is being dragged.
25
 * @param ev the event (for Firefox and Safari, otherwise we use window.event for IE)
26
 */
27
document.onmousemove = function(ev){
28
    if (currenttable && currenttable.dragObject) {
0 ignored issues
show
Complexity Best Practice introduced by jianchen
There is no return statement if currenttable && currenttable.dragObject is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
29
        ev   = ev || window.event;
30
        var mousePos = currenttable.mouseCoords(ev);
31
        var y = mousePos.y - currenttable.mouseOffset.y;
32
        if (y != currenttable.oldY) {
33
            // work out if we're going up or down...
34
            var movingDown = y > currenttable.oldY;
35
            // update the old value
36
            currenttable.oldY = y;
37
            // update the style to show we're dragging
38
            currenttable.dragObject.style.backgroundColor = "#eee";
39
            // If we're over a row then move the dragged row to there so that the user sees the
40
            // effect dynamically
41
            var currentRow = currenttable.findDropTargetRow(y);
42
            if (currentRow) {
43
                if (movingDown && currenttable.dragObject != currentRow) {
44
                    currenttable.dragObject.parentNode.insertBefore(currenttable.dragObject, currentRow.nextSibling);
45
                } else if (! movingDown && currenttable.dragObject != currentRow) {
46
                    currenttable.dragObject.parentNode.insertBefore(currenttable.dragObject, currentRow);
47
                }
48
            }
49
        }
50
51
        return false;
52
    }
53
}
54
55
// Similarly for the mouseup
56
document.onmouseup   = function(ev){
0 ignored issues
show
Unused Code introduced by jianchen
The parameter ev is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
57
    if (currenttable && currenttable.dragObject) {
58
        var droppedRow = currenttable.dragObject;
59
        // If we have a dragObject, then we need to release it,
60
        // The row will already have been moved to the right place so we just reset stuff
61
        droppedRow.style.backgroundColor = 'transparent';
62
        currenttable.dragObject   = null;
63
        // And then call the onDrop method in case anyone wants to do any post processing
64
        currenttable.onDrop(currenttable.table, droppedRow);
65
        currenttable = null; // let go of the table too
66
    }
67
}
68
69
70
/** get the source element from an event in a way that works for IE and Firefox and Safari
71
 * @param evt the source event for Firefox (but not IE--IE uses window.event) */
72
function getEventSource(evt) {
73
    if (window.event) {
74
        evt = window.event; // For IE
75
        return evt.srcElement;
76
    } else {
0 ignored issues
show
Comprehensibility introduced by jianchen
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
77
        return evt.target; // For Firefox
78
    }
79
}
80
81
/**
82
 * Encapsulate table Drag and Drop in a class. We'll have this as a Singleton
83
 * so we don't get scoping problems.
84
 */
85
function TableDnD() {
86
    /** Keep hold of the current drag object if any */
87
    this.dragObject = null;
88
    /** The current mouse offset */
89
    this.mouseOffset = null;
90
    /** The current table */
91
    this.table = null;
92
    /** Remember the old value of Y so that we don't do too much processing */
93
    this.oldY = 0;
94
95
    /** Initialise the drag and drop by capturing mouse move events */
96
    this.init = function(table) {
97
        this.table = table;
98
        var rows = table.tBodies[0].rows; //getElementsByTagName("tr")
99
        for (var i=0; i<rows.length; i++) {
100
			// John Tarr: added to ignore rows that I've added the NoDnD attribute to (Category and Header rows)
101
			var nodrag = rows[i].getAttribute("NoDrag")
102
			if (nodrag == null || nodrag == "undefined") { //There is no NoDnD attribute on rows I want to drag
0 ignored issues
show
Best Practice introduced by jianchen
Comparing nodrag to null using the == operator is not safe. Consider using === instead.
Loading history...
103
				this.makeDraggable(rows[i]);
104
			}
105
        }
106
    }
107
108
    /** This function is called when you drop a row, so redefine it in your code
109
        to do whatever you want, for example use Ajax to update the server */
110
    this.onDrop = function(table, droppedRow) {
0 ignored issues
show
Unused Code introduced by jianchen
The parameter droppedRow is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by jianchen
The parameter table is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
111
        // Do nothing for now
112
    }
113
114
	/** Get the position of an element by going up the DOM tree and adding up all the offsets */
115
    this.getPosition = function(e){
116
        var left = 0;
117
        var top  = 0;
118
		/** Safari fix -- thanks to Luis Chato for this! */
119
		if (e.offsetHeight == 0) {
0 ignored issues
show
Best Practice introduced by jianchen
Comparing e.offsetHeight to 0 using the == operator is not safe. Consider using === instead.
Loading history...
120
			/** Safari 2 doesn't correctly grab the offsetTop of a table row
121
			    this is detailed here:
122
			    http://jacob.peargrove.com/blog/2006/technical/table-row-offsettop-bug-in-safari/
123
			    the solution is likewise noted there, grab the offset of a table cell in the row - the firstChild.
124
			    note that firefox will return a text node as a first child, so designing a more thorough
125
			    solution may need to take that into account, for now this seems to work in firefox, safari, ie */
126
			e = e.firstChild; // a table cell
127
		}
128
129
        while (e.offsetParent){
130
            left += e.offsetLeft;
131
            top  += e.offsetTop;
132
            e     = e.offsetParent;
133
        }
134
135
        left += e.offsetLeft;
136
        top  += e.offsetTop;
137
138
        return {x:left, y:top};
139
    }
140
141
	/** Get the mouse coordinates from the event (allowing for browser differences) */
142
    this.mouseCoords = function(ev){
143
        if(ev.pageX || ev.pageY){
144
            return {x:ev.pageX, y:ev.pageY};
145
        }
146
        return {
147
            x:ev.clientX + document.body.scrollLeft - document.body.clientLeft,
148
            y:ev.clientY + document.body.scrollTop  - document.body.clientTop
149
        };
150
    }
151
152
	/** Given a target element and a mouse event, get the mouse offset from that element.
153
		To do this we need the element's position and the mouse position */
154
    this.getMouseOffset = function(target, ev){
155
        ev = ev || window.event;
156
157
        var docPos    = this.getPosition(target);
158
        var mousePos  = this.mouseCoords(ev);
159
        return {x:mousePos.x - docPos.x, y:mousePos.y - docPos.y};
160
    }
161
162
	/** Take an item and add an onmousedown method so that we can make it draggable */
163
    this.makeDraggable = function(item) {
164
        if(!item) return;
0 ignored issues
show
Coding Style Best Practice introduced by jianchen
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
165
        var self = this; // Keep the context of the TableDnd inside the function
166
        item.onmousedown = function(ev) {
167
            // Need to check to see if we are an input or not, if we are an input, then
168
            // return true to allow normal processing
169
            var target = getEventSource(ev);
170
            if (target.tagName == 'INPUT' || target.tagName == 'SELECT') return true;
0 ignored issues
show
Coding Style Best Practice introduced by jianchen
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
171
            currenttable = self;
172
            self.dragObject  = this;
173
            self.mouseOffset = self.getMouseOffset(this, ev);
174
            return false;
175
        }
176
        item.style.cursor = "move";
177
    }
178
179
    /** We're only worried about the y position really, because we can only move rows up and down */
180
    this.findDropTargetRow = function(y) {
181
        var rows = this.table.tBodies[0].rows;
182
		for (var i=0; i<rows.length; i++) {
183
			var row = rows[i];
184
			// John Tarr added to ignore rows that I've added the NoDnD attribute to (Header rows)
185
			var nodrop = row.getAttribute("NoDrop");
186
			if (nodrop == null || nodrop == "undefined") {  //There is no NoDnD attribute on rows I want to drag
0 ignored issues
show
Best Practice introduced by jianchen
Comparing nodrop to null using the == operator is not safe. Consider using === instead.
Loading history...
187
				var rowY    = this.getPosition(row).y;
188
				var rowHeight = parseInt(row.offsetHeight)/2;
189
				if (row.offsetHeight == 0) {
0 ignored issues
show
Best Practice introduced by jianchen
Comparing row.offsetHeight to 0 using the == operator is not safe. Consider using === instead.
Loading history...
190
					rowY = this.getPosition(row.firstChild).y;
191
					rowHeight = parseInt(row.firstChild.offsetHeight)/2;
192
				}
193
				// Because we always have to insert before, we need to offset the height a bit
194
				if ((y > rowY - rowHeight) && (y < (rowY + rowHeight))) {
195
					// that's the row we're over
196
					return row;
197
				}
198
			}
199
		}
200
		return null;
201
	}
202
}
203