jquery.fancytree
Version:
jQuery tree view / tree grid plugin with support for keyboard, inline editing, filtering, checkboxes, drag'n'drop, and lazy loading
405 lines (345 loc) • 10.9 kB
JavaScript
;(function($, window, document, undefined) {
/*globals QUnit */
var TOOLS = {},
log = [],
FIXTURE_SELECTOR = "#tree";
window.TEST_TOOLS = TOOLS;
// TOOLS.EVENT_SEQUENCE = [];
TOOLS.EVENT_SEQUENCE = "<deprecated: use assert.EVENT_SEQUENCE instead>";
TOOLS.TOTAL_ELAP = 0;
/*******************************************************************************
* QUnit setup
*/
TOOLS.initQUnit = function() {
// See https://github.com/axemclion/grunt-saucelabs
QUnit.done(function (testResults) {
var details, i, len,
tests = [];
for(i = 0, len = log.length; i < len; i++) {
details = log[i];
tests.push({
name: details.name,
result: details.result,
expected: details.expected,
actual: details.actual,
source: details.source
});
}
testResults.tests = tests;
// Expand first section when all tests are run
$("ol#qunit-tests > li:first > ol").show("slow");
/*jshint camelcase:false*/ // jscs: disable
window.global_test_results = testResults; // used by saucelabs
/*jshint camelcase:true*/ // jscs: enable
});
// See https://github.com/axemclion/grunt-saucelabs
QUnit.testStart(function(testDetails){
QUnit.log(function(details){
if (!details.result) {
details.name = testDetails.name;
log.push(details);
}
});
});
// Silence, please
$.ui.fancytree.debugLevel = 1;
};
TOOLS.createInfoSection = function() {
// Create the first informational section
QUnit.module("Configuration and Summary");
QUnit.test("Version info", function(assert) {
TOOLS.setup(assert);
assert.expect(5);
assert.ok(true, "Fancytree v" + $.ui.fancytree.version + ", buildType='" + $.ui.fancytree.buildType + "'");
assert.ok(true, "jQuery UI " + jQuery.ui.version + " (uiBackCompat=" + $.uiBackCompat + ")");
assert.ok(true, "jQuery " + jQuery.fn.jquery);
assert.ok(true, "Browser: " + TOOLS.getBrowserInfo());
assert.ok(true, "Cumulated test time: " + TOOLS.TOTAL_ELAP + " milliseconds");
});
};
/*******************************************************************************
* Tool functions
*/
//function simulateClick(selector) {
// var event = document.createEvent("MouseEvents");
// event.initEvent("click", true, true);
// $(selector).each(function(){
// this.dispatchEvent(event);
// });
//}
/** Helper to reset environment for asynchronous Fancytree tests. */
TOOLS.appendEvent = function(assert, msg) {
if( !assert || !assert.deepEqual ) { $.error("assert must be passed"); }
if( typeof msg !== "string" ) { $.error("msg must be a string"); }
if( !assert.EVENT_SEQUENCE ) { $.error("TOOLS.setup() was not called"); }
assert.EVENT_SEQUENCE.push(msg);
};
/** Helper to reset environment for asynchronous Fancytree tests. */
TOOLS.setup = function(assert) {
if( !assert ) { $.error("Need assert arg"); }
if( assert.EVENT_SEQUENCE ) { $.error("Duplicate setup()"); }
assert.EVENT_SEQUENCE = [];
if( $(FIXTURE_SELECTOR).is(":ui-fancytree") ){
$(FIXTURE_SELECTOR).fancytree("destroy");
}
};
/** Return an info string of current browser. */
TOOLS.getBrowserInfo = function() {
var n = navigator.appName,
ua = navigator.userAgent,
tem,
m = ua.match(/(opera|chrome|safari|firefox|msie)\/?\s*(\.?\d+(\.\d+)*)/i);
if(m && (tem = ua.match(/version\/([\.\d]+)/i)) !== null){
m[2]= tem[1];
}
m = m ? [m[1], m[2]] : [n, navigator.appVersion, "-?"];
return m.join(", ");
};
/** Get FancytreeNode from current tree. */
TOOLS.getNode = function(key){
return TOOLS.getTree().getNodeByKey(key);
};
/** Get first node with matching title. */
TOOLS.getNodeByTitle = function(title){
var tree = $("#tree").fancytree("getTree");
return tree.findFirst(function(n){ return n.title === title; });
};
/** Get current Fancytree. */
TOOLS.getTree = function(){
return $(FIXTURE_SELECTOR).fancytree("getTree");
};
/** Get node title as rendered in the DOM. */
TOOLS.getNodeTitle = function(key){
var node = TOOLS.getNode(key);
if(!node){
return undefined;
}
return $(node.span).find(".fancytree-title").html();
};
/** Convert array of nodes to array to array of node keys. */
TOOLS.getNodeKeyArray = function(nodeArray){
if(!$.isArray(nodeArray)){
return nodeArray;
}
return $.map(nodeArray, function(n){ return n.key; });
};
/** Generate a large hierarchy of nodes
*/
TOOLS.addGenericNodes = function(node, options, callback) {
var d, f, i, j, k, key,
opts = $.extend({
level1: 1,
level2: 0,
level3: 0,
disableUpdate: true
}, options);
function _cb(parentNode, data, i, j, k) {
if( !callback || callback(data, i, j, k) !== false ) {
return parentNode.addChildren(data);
}
}
if( opts.disableUpdate ) {
node.tree.enableUpdate(false);
}
for(i=0; i<opts.level1; i++) {
key = "" + (i+1);
f = _cb(node, {title: "Folder_" + key, key: key, folder: true}, i, 0, 0);
for (j=0; j<opts.level2; j++) {
key = "" + (i+1) + "." + (j+1);
d = _cb(f, {title: "Node_" + key, key: key}, i, j, 0);
for (k=0; k<opts.level3; k++) {
key = "" + (i+1) + "." + (j+1) + "." + (k+1);
_cb(d, {title: "Node_" + key, key: key}, i, j, k);
}
}
}
if( opts.disableUpdate ) {
node.tree.enableUpdate(true);
}
};
/** Fake an Ajax request, return a $.Promise. */
TOOLS.fakeAjaxLoad = function(node, count, delay){
delay = delay || 0;
if($.isArray(delay)){ // random delay range [min..max]
delay = Math.round(delay[0] + Math.random() * (delay[1] - delay[0]));
}
var dfd = new $.Deferred();
setTimeout(function(){
var i,
children = [];
for(i=0; i<count; i++){
children.push({
key: node.key + "_" + (i+1),
title: node.title + "_" + (i+1),
lazy: true
});
}
// emulate ajax deferred: done(data, textStatus, jqXHR)
dfd.resolveWith(this, [children, null, null]);
}, delay);
return dfd.promise();
};
/** Format a number as string with thousands-separator. */
TOOLS.formatNumber = function(num) {
var parts = num.toFixed(0).toString().split(".");
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
return parts.join(".");
};
TOOLS.makeBenchWrapper = function(assert, testName, count, callback) {
return function() {
var elap,
start = Date.now();
// callback.apply(this, arguments);
callback.call();
elap = Date.now() - start;
if( count && elap ){
assert.ok(true, testName + " took " + elap + " milliseconds, " + TOOLS.formatNumber(1000 * count / elap) + " items/sec");
}else{
assert.ok(true, testName + " took " + elap + " milliseconds");
}
TOOLS.TOTAL_ELAP += elap;
};
};
/* Execute callback immediately and log timing as test result.
* This function should be called inside a QUnit.test() function.
*/
TOOLS.benchmark = function(assert, testName, count, callback) {
TOOLS.makeBenchWrapper(assert, testName, count, callback).call();
};
/* Execute callback, then immediately and log timing as test result.
*
* Example:
*
tools.benchmarkWithReflowAsync(assert, tree, "Add 500x500 nodes", null, function(){
// Add benchark code:
tools.addGenericNodes(node, {level1: 500, level2: 500});
}).done(function(){
// Reflow and Redraw finished and have beem logged
// ...
});
*
* This function should be called inside a QUnit.test() function.
*/
TOOLS.benchmarkWithReflowAsync = function(assert, tree, testName, count, callback) {
var elap1, elap2, elap3, msg,
dfd = new $.Deferred(),
start = Date.now();
callback.call();
elap1 = Date.now() - start; // raw execution time
// Query div size to trigger a layout reflow
// As a call to a dummy function to prevent optimizations (cargo-cult?)
// $.noop(window.innerHeight);
$.noop(tree.$div[0].offsetHeight);
elap2 = Date.now() - start; // execution time incl. reflow
// Yield to interpreter -- Hopefully this will cause the browser to redraw,
// so we can capture the timings:
setTimeout(function(){
elap3 = Date.now() - start; // execution time incl. reflow & redraw
msg = testName + " took " + elap3 + " ms (reflow w/o redraw: " + elap2 + " ms, raw: " + elap1 + " ms)";
if( count && elap1 ){
msg += ", " + TOOLS.formatNumber(1000 * count / elap3) + " items/sec";
}
assert.ok(true, msg);
TOOLS.TOTAL_ELAP += elap3;
dfd.resolve();
}, 0);
return dfd.promise();
};
/**
* AsyncTimer
*/
function AsyncTimer(assert, name, count, start){
this.assert = assert;
this.done = null;
this.name = "AsyncTimer(" + name + ")";
this.stamp = null;
this.count = count;
if(start !== false){
this.start();
}
}
TOOLS.AsyncTimer = AsyncTimer;
AsyncTimer.prototype = {
toString: function(){
return this.name;
},
start: function(){
/*jshint expr:true */
window.console && window.console.time && window.console.time(this.name);
// halt QUnit
// this.done = this.assert.async();
this.stamp = Date.now();
this.lastStamp = this.stamp;
},
stop: function(){
/*jshint expr:true */
window.console && window.console.timeEnd && window.console.timeEnd(this.name);
var elap = Date.now() - this.stamp;
if( this.count && elap ){
this.assert.ok(true, this.name + " took " + elap + " milliseconds, " + TOOLS.formatNumber(1000.0 * this.count / elap) + " items/sec");
}else{
this.assert.ok(true, this.name + " took " + elap + " milliseconds");
}
TOOLS.TOTAL_ELAP += elap;
// Continue QUnit
// this.done();
},
subtime: function(info){
var now = Date.now(),
elap = now - this.lastStamp;
this.lastStamp = now;
this.assert.ok(true, "... " + this.name + " until '" + info + "' took " + elap + " milliseconds");
}
};
/** Create a profile wrapper.
*
*/
/*
function profileWrapper(fn, flag, opts){
if( flag === false ){
return fn;
}
opts = $.extend({printTime: true}, opts);
var start, elap,
stats = {
count: 0,
countDeep: 0,
maxLevel: 0,
min: Math.pow(2, 32) - 1,
max: 0,
sum: 0
},
name = fn.name,
level = 0,
//
wrapper = function(){
level += 1;
stats.countDeep += 1;
stats.maxLevel = Math.max(stats.maxLevel, level);
if( level === 1 ){
stats.count += 1;
if( opts.printTime ){
console.time(name);
}
start = new Date().getTime();
fn.apply(this, arguments);
elap = new Date().getTime() - start;
if(opts.printTime){
console.timeEnd(name);
}
stats.min = Math.min(stats.min, elap);
stats.max = Math.max(stats.max, elap);
stats.sum += elap;
}else{
// We don't collect stats for recursive calls
fn.apply(this, arguments);
}
level -= 1;
};
wrapper.stats = function(){
return stats;
};
return wrapper;
}
*/
}(jQuery, window, document));