diff options
author | junjianx@chromium.org <junjianx@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-08-26 02:20:56 +0000 |
---|---|---|
committer | junjianx@chromium.org <junjianx@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-08-26 02:20:56 +0000 |
commit | 623d620fb864a3e983902c5ccdad4c547b680e2b (patch) | |
tree | 915c1959754fa450e58dd452b7a283b8955857f1 /tools/deep_memory_profiler | |
parent | 55af928a7fedbb15853a0226592547526832e2e5 (diff) | |
download | chromium_src-623d620fb864a3e983902c5ccdad4c547b680e2b.zip chromium_src-623d620fb864a3e983902c5ccdad4c547b680e2b.tar.gz chromium_src-623d620fb864a3e983902c5ccdad4c547b680e2b.tar.bz2 |
Add tree menu for dmprof visualizer. Use jqTree as third-party plugin
BUG=259206
NOTRY=True
Review URL: https://chromiumcodereview.appspot.com/23075010
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@219490 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'tools/deep_memory_profiler')
16 files changed, 3476 insertions, 238 deletions
diff --git a/tools/deep_memory_profiler/visualizer/graph-view.js b/tools/deep_memory_profiler/visualizer/graph-view.js new file mode 100644 index 0000000..b9b62bc --- /dev/null +++ b/tools/deep_memory_profiler/visualizer/graph-view.js @@ -0,0 +1,126 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * This is a view class showing flot graph. + * @param {Object} model Must have addListener method. + * @construct + */ +var GraphView = function(model) { + this.model_ = model; + // Update graph view and menu view when model changed. + model.addListener('changed', this.redraw.bind(this)); +}; + +/** + * Generate lines for flot plotting. + * @param {Array.<Object>} categories + * @return {Array.<Object>} + */ +GraphView.prototype.generateLines_ = function(categories) { + function getLeaves(node, breakdowns) { + if ('breakdowns' in node) { + node.breakdowns.forEach(function(breakdown) { + getLeaves(breakdown, breakdowns); + }); + } else { + breakdowns.push(node); + } + } + + var lines = {}; + var snapshotNum = categories.length; + // Initialize lines with all zero. + categories.forEach(function(category) { + var breakdowns = []; + getLeaves(category, breakdowns); + breakdowns.forEach(function(breakdown) { + var name = breakdown.name; + if (lines[name]) + return; + lines[name] = []; + for (var i = 0; i < snapshotNum; ++i) + lines[name].push([i, 0]); + }); + }); + + // Assignment lines with values of categories. + categories.forEach(function(category, index) { + var breakdowns = []; + getLeaves(category, breakdowns); + breakdowns.forEach(function(breakdown) { + var name = breakdown.name; + var memory = breakdown.memory; + lines[name][index] = [index, memory]; + }); + }); + + return Object.keys(lines).map(function(name) { + return { + label: name, + data: lines[name] + }; + }); +}; + +/** + * Update garph view when model updated. + * TODO(junjianx): use redraw function to improve perfomance. + * @param {Array.<Object>} categories + */ +GraphView.prototype.redraw = function(categories) { + var placeholder = '#graph-div'; + var lines = this.generateLines_(categories); + var graph = $.plot(placeholder, lines, { + series: { + stack: true, + lines: { show: true, fill: true } + }, + grid: { + hoverable: true, + clickable: true + } + }); + + // Bind click event so that user can select breakdown by clicking stack + // area. It firstly checks x range which clicked point is in, and all lines + // share same x values, so it is checked only once at first. Secondly, it + // checked y range by accumulated y values because this is a stack graph. + $(placeholder).bind('plotclick', function(event, pos, item) { + // If only <=1 line exists or axis area clicked, return. + var right = binarySearch.call(lines[0].data.map(function(point) { + return point[0]; + }), pos.x); + if (lines.length <= 1 || right === lines.length || right === 0) + return; + + // Calculate interpolate y value of every line. + for (var i = 0; i < lines.length; ++i) { + var line = lines[i].data; + // [left, right] is the range including clicked point. + var left = right - 1; + var leftPoint = { + x: line[left][0], + y: (leftPoint ? leftPoint.y : 0) + line[left][1] + }; + var rightPoint = { + x: line[right][0], + y: (rightPoint ? rightPoint.y : 0) + line[right][1] + }; + + // Calculate slope of the linear equation. + var slope = (rightPoint.y - leftPoint.y) / (rightPoint.x - leftPoint.x); + var interpolateY = slope * (pos.x - rightPoint.x) + rightPoint.y; + if (interpolateY >= pos.y) + break; + } + + // If pos.y is higher than all lines, return. + if (i === lines.length) + return; + + // TODO(junjianx): temporary log for checking selected object. + console.log('line ' + i + ' is selected.'); + }); +}; diff --git a/tools/deep_memory_profiler/visualizer/index.css b/tools/deep_memory_profiler/visualizer/index.css new file mode 100644 index 0000000..4bbc166 --- /dev/null +++ b/tools/deep_memory_profiler/visualizer/index.css @@ -0,0 +1,30 @@ +/* Copyright 2013 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. */ + +#graph-div { + width: 1024px; + height: 600px; + margin-top: 30px; + margin-left: 50px; + float: left; +} + +#info-div { + width: 240px; + height: 600px; + margin-top: 35px; + margin-left: 50px; + float: left; + box-shadow: 0 4px 16px rgba(0,0,0,0.2); + outline: 1px solid rgba(0,0,0,0.2); + overflow: auto; +} + +#breakdown-menu { + padding-left: 15px; +} + +#subs-dropdown { + padding-left: 15px; +}
\ No newline at end of file diff --git a/tools/deep_memory_profiler/visualizer/main.html b/tools/deep_memory_profiler/visualizer/index.html index 75df003..b930bb4 100644 --- a/tools/deep_memory_profiler/visualizer/main.html +++ b/tools/deep_memory_profiler/visualizer/index.html @@ -5,15 +5,24 @@ Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. --> <meta charset="utf-8"> -<link rel="stylesheet" href="main.css"> +<link rel="stylesheet" href="index.css"> +<link rel="stylesheet" href="third_party/jqTree/jqtree.css"> + <script src="../../../third_party/flot/jquery.min.js"></script> <script src="../../../third_party/flot/jquery.flot.min.js"></script> <script src="../../../third_party/flot/jquery.flot.stack.min.js"></script> +<script src="third_party/jqTree/tree.jquery.js"></script> <script src="utility.js"></script> -<script src="profiler.js"></script> -<script src="main.js"></script> +<script src="profiler-model.js"></script> +<script src="graph-view.js"></script> +<script src="menu-view.js"></script> +<script src="index.js"></script> <body> <h2>Deep Memory Profiler Visulaizer</h2> - <div id="plot" class="plot-container"></div> + <div id="graph-div"></div> + <div id="info-div"> + <div id="subs-dropdown"></div> + <div id="breakdown-menu"></div> + </div> </body> diff --git a/tools/deep_memory_profiler/visualizer/index.js b/tools/deep_memory_profiler/visualizer/index.js new file mode 100644 index 0000000..431bfde --- /dev/null +++ b/tools/deep_memory_profiler/visualizer/index.js @@ -0,0 +1,17 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +$(function() { + // Read original data and plot. + $.getJSON('data/result.json', function(jsonData) { + // Create model. + var profiler = new Profiler(jsonData); + // Create views subscribing model events. + var graphView = new GraphView(profiler); + var menuView = new MenuView(profiler); + + // initialize categories according to roots information. + profiler.initializeCategories(); + }); +}); diff --git a/tools/deep_memory_profiler/visualizer/main.css b/tools/deep_memory_profiler/visualizer/main.css deleted file mode 100644 index 5d9ed53..0000000 --- a/tools/deep_memory_profiler/visualizer/main.css +++ /dev/null @@ -1,9 +0,0 @@ -/* Copyright 2013 The Chromium Authors. All rights reserved. - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. */ - -.plot-container { - width: 1240px; - height: 720px; - margin: 30px auto 30px auto; -}
\ No newline at end of file diff --git a/tools/deep_memory_profiler/visualizer/main.js b/tools/deep_memory_profiler/visualizer/main.js deleted file mode 100644 index 1d96213..0000000 --- a/tools/deep_memory_profiler/visualizer/main.js +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -/** - * Generate lines for flot plotting. - * @param {Array.<Object>} categories - * @return {Array.<Object>} - */ -var generateLines = function(categories) { - var lines = {}; - var snapshotNum = categories.length; - - // Initialize lines with all zero. - categories.forEach(function(categories) { - Object.keys(categories).forEach(function(breakdownName) { - if (lines[breakdownName]) - return; - lines[breakdownName] = []; - for (var i = 0; i < snapshotNum; ++i) - lines[breakdownName].push([i, 0]); - }); - }); - - // Assignment lines with values of categories. - categories.forEach(function(categories, index) { - Object.keys(categories).forEach(function(breakdownName) { - lines[breakdownName][index] = [index, categories[breakdownName]]; - }); - }); - - return Object.keys(lines).map(function(breakdownName) { - return { - label: breakdownName, - data: lines[breakdownName] - }; - }); -}; - -$(function() { - // Read original data and plot. - $.getJSON('data/result.json', function(jsonData) { - var profiler = new Profiler(jsonData); - var categories = profiler.getCategories(); - var lines = generateLines(categories); - var placeholder = '#plot'; - - // Bind click event so that user can select breakdown by clicking stack - // area. It firstly checks x range which clicked point is in, and all lines - // share same x values, so it is checked only once at first. Secondly, it - // checked y range by accumulated y values because this is a stack graph. - $(placeholder).bind('plotclick', function(event, pos, item) { - // If only <=1 line exists or axis area clicked, return. - var right = binarySearch.call(lines[0].data.map(function(point) { - return point[0]; - }), pos.x); - if (lines.length <= 1 || right === lines.length || right === 0) - return; - - // Calculate interpolate y value of every line. - for (var i = 0; i < lines.length; ++i) { - var line = lines[i].data; - // [left, right] is the range including clicked point. - var left = right - 1; - var leftPoint = { - x: line[left][0], - y: (leftPoint ? leftPoint.y : 0) + line[left][1] - }; - var rightPoint = { - x: line[right][0], - y: (rightPoint ? rightPoint.y : 0) + line[right][1] - }; - - // Calculate slope of the linear equation. - var slope = (rightPoint.y - leftPoint.y) / (rightPoint.x - leftPoint.x); - var interpolateY = slope * (pos.x - rightPoint.x) + rightPoint.y; - if (interpolateY >= pos.y) - break; - } - - // If pos.y is higher than all lines, return. - if (i === lines.length) - return; - - // TODO(junjianx): temporary log for checking selected object. - console.log('line ' + i + ' is selected.'); - }); - - // Plot stack graph. - $.plot(placeholder, lines, { - series: { - stack: true, - lines: { show: true, fill: true } - }, - grid: { - hoverable: true, - clickable: true - } - }); - }); -}); diff --git a/tools/deep_memory_profiler/visualizer/menu-view.js b/tools/deep_memory_profiler/visualizer/menu-view.js new file mode 100644 index 0000000..1939f50 --- /dev/null +++ b/tools/deep_memory_profiler/visualizer/menu-view.js @@ -0,0 +1,75 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * This is a view class showing tree-menu. + * @param {Object} model Must have addListener method. + * @construct + */ +var MenuView = function(model) { + this.model_ = model; + // Update graph view and menu view when model changed. + model.addListener('changed', this.redraw.bind(this)); +}; + +/** + * Update menu view when model updated. + * @param {Array.<Object>} categories + */ +MenuView.prototype.redraw = function(categories) { + function generateTree(origin, target) { + target.label = origin.name; + + if ('breakdowns' in origin) { + target.children = []; + origin.breakdowns.forEach(function(breakdown) { + var child = {}; + target.children.push(child); + generateTree(breakdown, child); + }); + } + } + + function mergeTree(left, right) { + if (!('children' in right) && 'children' in left) + return; + if ('children' in right && !('children' in left)) + left.children = right.children; + if ('children' in right && 'children' in left) { + right.children.forEach(function(child) { + // Find child with the same label in right tree. + var index = left.children.reduce(function(previous, current, index) { + if (child.label === current.label) + return index; + return previous; + }, -1); + if (index === -1) + left.children.push(child); + else + mergeTree(child, left.children[index]); + }); + } + } + + // Merge trees in all snapshots. + var union = null; + categories.forEach(function(category) { + var tree = {}; + generateTree(category, tree); + if (!union) + union = tree; + else + mergeTree(union, tree); + }); + + var placeholder = '#breakdown-menu'; + // Draw breakdown menu. + $(placeholder).tree({ + data: [union], + autoOpen: true, + onCreateLi: function(node, $li) { + // TODO(junjianx): Add checkbox to decide the breakdown visibility. + } + }); +}; diff --git a/tools/deep_memory_profiler/visualizer/profiler-model.js b/tools/deep_memory_profiler/visualizer/profiler-model.js new file mode 100644 index 0000000..878d0bd --- /dev/null +++ b/tools/deep_memory_profiler/visualizer/profiler-model.js @@ -0,0 +1,170 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * This class provides data access interface for dump file profiler. + * @constructor + */ +var Profiler = function(jsonData) { + this.jsonData_ = jsonData; + // Initialize template and calculate categories with roots information. + // TODO(junjianx): Make file path an argument. + this.template_ = jsonData.templates['l2']; + + // Trigger event. + this.callbacks_ = {}; +}; + +/** + * Mimic Eventemitter in node. Add new listener for event. + * @param {string} event + * @param {Function} callback + */ +Profiler.prototype.addListener = function(event, callback) { + if (!this.callbacks_[event]) + this.callbacks_[event] = $.Callbacks(); + this.callbacks_[event].add(callback); +}; + +/** + * This function will emit the event. + * @param {string} event + */ +Profiler.prototype.emit = function(event) { + // Listeners should be able to receive arbitrary number of parameters. + var eventArguments = Array.prototype.slice.call(arguments, 1); + + if (this.callbacks_[event]) + this.callbacks_[event].fire.apply(this, eventArguments); +}; + +/** + * Remove listener from event. + * @param {string} event + * @param {Function} callback + */ +Profiler.prototype.removeListener = function(event, callback) { + if (this.callbacks_[event]) + this.callbacks_[event].remove(callback); +}; + +/** + * Calcualte initial categories according default template. + */ +Profiler.prototype.initializeCategories = function() { + this.categories_ = this.calculateCategories_(); + this.emit('changed', this.categories_); +}; + +Profiler.prototype.accumulate_ = function( + template, snapshot, worldUnits, localUnits, nodePath, nodeName) { + var self = this; + var totalMemory = 0; + var worldName = template[0]; + var rootBreakdownName = template[1]; + var breakdowns = snapshot.worlds[worldName].breakdown[rootBreakdownName]; + // Make deep copy of localUnits. + var remainderUnits = localUnits.slice(0); + var categories = { + name: nodeName || worldName + '-' + rootBreakdownName, + nodePath: nodePath.slice(0), + breakdowns: [] + }; + + Object.keys(breakdowns).forEach(function(breakdownName) { + var breakdown = breakdowns[breakdownName]; + if (breakdown['hidden'] === true) + return; + + // Accumulate breakdowns. + var matchedUnits = intersection(breakdown.units, localUnits); + var memory = matchedUnits.reduce(function(previous, current) { + return previous + worldUnits[worldName][current]; + }, 0); + totalMemory += memory; + remainderUnits = difference(remainderUnits, matchedUnits); + + // Handle subs options if exists. + if (!(breakdownName in template[2])) { + categories.breakdowns.push({ + name: breakdownName, + memory: memory + }); + + if ('subs' in breakdown && breakdown.subs.length) { + var length = categories.breakdowns.length; + categories.breakdowns[length-1].subs = breakdown.subs; + } + } else { + var subTemplate = template[2][breakdownName]; + var subWorldName = subTemplate[0]; + var subRootBreakdownName = subTemplate[1]; + var subNodePath = nodePath.slice(0).concat([breakdownName, 2]); + var result = null; + + // If subs is in the same world, units should be filtered. + if (subWorldName === worldName) { + result = self.accumulate_(subTemplate, snapshot, worldUnits, + matchedUnits, subNodePath, breakdownName); + categories.breakdowns.push(result.categories); + if (!result.remainderUnits.length) + return; + + var remainMemory = + result.remainderUnits.reduce(function(previous, current) { + return previous + worldUnits[subWorldName][current]; + }, 0); + + categories.breakdowns.push({ + name: breakdownName + '-remaining', + memory: remainMemory + }); + } else { + var subLocalUnits = Object.keys(worldUnits[subWorldName]); + subLocalUnits = subLocalUnits.map(function(unitName) { + return parseInt(unitName, 10); + }); + + result = self.accumulate_(subTemplate, snapshot, worldUnits, + subLocalUnits, subNodePath, breakdownName); + categories.breakdowns.push(result.categories); + + if (memory > result.totalMemory) { + categories.breakdowns.push({ + name: breakdownName + '-remaining', + memory: memory - result.totalMemory + }); + } + } + } + }); + + return { + categories: categories, + totalMemory: totalMemory, + remainderUnits: remainderUnits + }; +}; + +Profiler.prototype.calculateCategories_ = function() { + var self = this; + + return self.jsonData_.snapshots.map(function(snapshot) { + var worldUnits = {}; + for (var worldName in snapshot.worlds) { + worldUnits[worldName] = {}; + var units = snapshot.worlds[worldName].units; + for (var unitName in units) + worldUnits[worldName][unitName] = units[unitName][0]; + } + var localUnits = Object.keys(worldUnits[self.template_[0]]); + localUnits = localUnits.map(function(unitName) { + return parseInt(unitName, 10); + }); + + var result = + self.accumulate_(self.template_, snapshot, worldUnits, localUnits, [2]); + return result.categories; + }); +};
\ No newline at end of file diff --git a/tools/deep_memory_profiler/visualizer/profiler.js b/tools/deep_memory_profiler/visualizer/profiler.js deleted file mode 100644 index 672c8f0..0000000 --- a/tools/deep_memory_profiler/visualizer/profiler.js +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -/** - * This class provides data access interface for dump file profiler - * @constructor - */ -var Profiler = function(jsonData) { - this._jsonData = jsonData; -}; - -/** - * Get units of a snapshot in a world. - * Exception will be thrown when no world of given name exists. - * @param {string} worldName - * @param {number} snapshotIndex - * @return {Object.<string, number>} - * @private - */ -Profiler.prototype.getUnits_ = function(worldName, snapshotIndex) { - var snapshot = this._jsonData.snapshots[snapshotIndex]; - if (!snapshot.worlds[worldName]) - throw 'no world ' + worldName + ' in snapshot ' + index; - - // Return units. - var world = snapshot.worlds[worldName]; - var units = {}; - for (var unitName in world.units) - units[unitName] = world.units[unitName][0]; - return units; -}; - -/** - * Get first-level breakdowns of a snapshot in a world. - * Exception will be thrown when no world of given name exists. - * @param {string} worldName - * @param {number} snapshotIndex - * @return {Object.<string, Object>} - * @private - */ -Profiler.prototype.getBreakdowns_ = function(worldName, snapshotIndex) { - var snapshot = this._jsonData.snapshots[snapshotIndex]; - if (!snapshot.worlds[worldName]) - throw 'no world ' + worldName + ' in snapshot ' + index; - - // Return breakdowns. - // TODO(junjianx): handle breakdown with arbitrary-level structure. - return snapshot.worlds[worldName].breakdown; -}; - -/** - * Get categories from fixed hard-coded worlds and breakdowns temporarily. - * TODO(junjianx): remove the hard-code and support general cases. - * @return {Array.<Object>} - */ -Profiler.prototype.getCategories = function() { - var categories = []; - var snapshotNum = this._jsonData.snapshots.length; - - for (var snapshotIndex = 0; snapshotIndex < snapshotNum; ++snapshotIndex) { - // Initial categories object for one snapshot. - categories.push({}); - - // Handle breakdowns in malloc world. - var mallocBreakdown = this.getBreakdowns_('malloc', snapshotIndex); - var mallocUnits = this.getUnits_('malloc', snapshotIndex); - if (!mallocBreakdown['component']) - throw 'no breakdown ' + 'component' + ' in snapshot ' + snapshotIndex; - - var componentBreakdown = mallocBreakdown['component']; - var componentMemory = 0; - Object.keys(componentBreakdown).forEach(function(breakdownName) { - var breakdown = componentBreakdown[breakdownName]; - var memory = breakdown.units.reduce(function(previous, current) { - return previous + mallocUnits[current]; - }, 0); - componentMemory += memory; - - if (componentBreakdown['hidden'] === true) - return; - else - categories[snapshotIndex][breakdownName] = memory; - }); - - // Handle breakdowns in vm world. - var vmBreakdown = this.getBreakdowns_('vm', snapshotIndex); - var vmUnits = this.getUnits_('vm', snapshotIndex); - if (!vmBreakdown['map']) - throw 'no breakdown ' + 'map' + ' in snapshot ' + snapshotIndex; - - var mapBreakdown = vmBreakdown['map']; - - Object.keys(mapBreakdown).forEach(function(breakdownName) { - var breakdown = mapBreakdown[breakdownName]; - var memory = breakdown.units.reduce(function(previous, current) { - return previous + vmUnits[current]; - }, 0); - - if (vmBreakdown['hidden'] === true) - return; - else if (breakdownName === 'mmap-tcmalloc') - categories[snapshotIndex]['tc-unused'] = memory - componentMemory; - else - categories[snapshotIndex][breakdownName] = memory; - }); - } - - return categories; -}; diff --git a/tools/deep_memory_profiler/visualizer/third_party/jqTree/LICENSE b/tools/deep_memory_profiler/visualizer/third_party/jqTree/LICENSE new file mode 100644 index 0000000..d09301f --- /dev/null +++ b/tools/deep_memory_profiler/visualizer/third_party/jqTree/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2011 Marco Braak + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/tools/deep_memory_profiler/visualizer/third_party/jqTree/OWNERS b/tools/deep_memory_profiler/visualizer/third_party/jqTree/OWNERS new file mode 100644 index 0000000..a207fbd --- /dev/null +++ b/tools/deep_memory_profiler/visualizer/third_party/jqTree/OWNERS @@ -0,0 +1,2 @@ +junjianx@chromium.org +dmikurube@chromium.org diff --git a/tools/deep_memory_profiler/visualizer/third_party/jqTree/README.chromium b/tools/deep_memory_profiler/visualizer/third_party/jqTree/README.chromium new file mode 100644 index 0000000..ae316fd --- /dev/null +++ b/tools/deep_memory_profiler/visualizer/third_party/jqTree/README.chromium @@ -0,0 +1,29 @@ +Name: jqTree +URL: http://mbraak.github.io/jqTree/ +Version: 0.17 +License: Apache 2.0 +License File: LICENSE +Security Critical: no + +Description: +This is a jquery plugin to draw a tree from a JSON data. This library is +required only by src/tools/deep_memory_profiler, and it doesn't go into the +binary. + +Local Modifications: +Removed the following files: +* bower.json +* build +* docs_src/ +* examples/ +* extra/ +* intodex.html +* jqtree-circle.png +* jqtree.jquery.jsonon +* phantomjs/ +* screenshot.png +* sitemap.txt +* src/ +* test/ +* tree_logo.png +* .travis.yml diff --git a/tools/deep_memory_profiler/visualizer/third_party/jqTree/README.md b/tools/deep_memory_profiler/visualizer/third_party/jqTree/README.md new file mode 100644 index 0000000..d9ee14d --- /dev/null +++ b/tools/deep_memory_profiler/visualizer/third_party/jqTree/README.md @@ -0,0 +1,21 @@ +[![Travis Status](https://secure.travis-ci.org/mbraak/jqTree.png)](http://travis-ci.org/mbraak/jqTree) + +#jqTree + +JqTree is a tree widget. Read more in the [documentation](http://mbraak.github.io/jqTree/). + +##Features + +* Create a tree from JSON data +* Drag and drop +* Works on ie7+, firefox 3.6+, chrome and safari +* Written in Coffeescript + +The project is hosted on [github](https://github.com/mbraak/jqTree), has a [test suite](http://mbraak.github.io/jqTree/test/test.html). + +See index.html for the full documentation. The documentation is also on [github](http://mbraak.github.io/jqTree/) + +##Thanks + +The code for the mouse widget is heavily inspired by the mouse widget from jquery ui. +Tree designed by Hernan D. Schlosman from The Noun Project. diff --git a/tools/deep_memory_profiler/visualizer/third_party/jqTree/jqtree.css b/tools/deep_memory_profiler/visualizer/third_party/jqTree/jqtree.css new file mode 100644 index 0000000..5f040ce --- /dev/null +++ b/tools/deep_memory_profiler/visualizer/third_party/jqTree/jqtree.css @@ -0,0 +1,139 @@ +ul.jqtree-tree { + margin-left: 12px; +} + +ul.jqtree-tree, +ul.jqtree-tree ul.jqtree_common { + list-style: none outside; + margin-bottom: 0; + padding: 0; +} + +ul.jqtree-tree ul.jqtree_common { + display: block; + margin-left: 12px; + margin-right: 0; +} +ul.jqtree-tree li.jqtree-closed > ul.jqtree_common { + display: none; +} + +ul.jqtree-tree li.jqtree_common { + clear: both; + list-style-type: none; +} +ul.jqtree-tree .jqtree-toggler { + display: block; + position: absolute; + left: -1.5em; + top: 30%; + *top: 0; /* fix for ie7 */ + font-size: 12px; + line-height: 12px; + font-family: arial; /* fix for ie9 */ + border-bottom: none; + color: #333; +} + +ul.jqtree-tree .jqtree-toggler:hover { + color: #000; +} + +ul.jqtree-tree .jqtree-element { + cursor: pointer; +} + +ul.jqtree-tree .jqtree-title { + color: #1C4257; + vertical-align: middle; +} + +ul.jqtree-tree li.jqtree-folder { + margin-bottom: 4px; +} + +ul.jqtree-tree li.jqtree-folder.jqtree-closed { + margin-bottom: 1px; +} + +ul.jqtree-tree li.jqtree-folder .jqtree-title { + margin-left: 0; +} + +ul.jqtree-tree .jqtree-toggler.jqtree-closed { + background-position: 0 0; +} + +span.jqtree-dragging { + color: #fff; + background: #000; + opacity: 0.6; + cursor: pointer; + padding: 2px 8px; +} + +ul.jqtree-tree li.jqtree-ghost { + position: relative; + z-index: 10; + margin-right: 10px; +} + +ul.jqtree-tree li.jqtree-ghost span { + display: block; +} + +ul.jqtree-tree li.jqtree-ghost span.jqtree-circle { + background-image: url(jqtree-circle.png); + background-repeat: no-repeat; + height: 8px; + width: 8px; + position: absolute; + top: -4px; + left: 2px; +} + +ul.jqtree-tree li.jqtree-ghost span.jqtree-line { + background-color: #0000ff; + height: 2px; + padding: 0; + position: absolute; + top: -1px; + left: 10px; + width: 100%; +} + +ul.jqtree-tree li.jqtree-ghost.jqtree-inside { + margin-left: 48px; +} + +ul.jqtree-tree span.jqtree-border { + position: absolute; + display: block; + left: -2px; + top: 0; + border: solid 2px #0000ff; + -webkit-border-radius: 6px; + -moz-border-radius: 6px; + border-radius: 6px; + margin: 0; +} + +ul.jqtree-tree .jqtree-element { + width: 100%; /* todo: why is this in here? */ + *width: auto; /* ie7 fix; issue 41 */ + position: relative; +} + +ul.jqtree-tree li.jqtree-selected > .jqtree-element, +ul.jqtree-tree li.jqtree-selected > .jqtree-element:hover { + background-color: #97BDD6; + background: -webkit-gradient(linear, left top, left bottom, from(#BEE0F5), to(#89AFCA)); + background: -moz-linear-gradient(top, #BEE0F5, #89AFCA); + background: -ms-linear-gradient(top, #BEE0F5, #89AFCA); + background: -o-linear-gradient(top, #BEE0F5, #89AFCA); + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7); +} + +ul.jqtree-tree .jqtree-moving > .jqtree-element .jqtree-title { + outline: dashed 1px #0000ff; +}
\ No newline at end of file diff --git a/tools/deep_memory_profiler/visualizer/third_party/jqTree/tree.jquery.js b/tools/deep_memory_profiler/visualizer/third_party/jqTree/tree.jquery.js new file mode 100644 index 0000000..2ebff8b --- /dev/null +++ b/tools/deep_memory_profiler/visualizer/third_party/jqTree/tree.jquery.js @@ -0,0 +1,2609 @@ +// Generated by CoffeeScript 1.6.3 +/* +Copyright 2013 Marco Braak + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + + +(function() { + var $, BorderDropHint, DragAndDropHandler, DragElement, FolderElement, GhostDropHint, JqTreeWidget, KeyHandler, MouseWidget, Node, NodeElement, Position, SaveStateHandler, ScrollHandler, SelectNodeHandler, SimpleWidget, html_escape, indexOf, json_escapable, json_meta, json_quote, json_str, _indexOf, _ref, _ref1, _ref2, + __slice = [].slice, + __hasProp = {}.hasOwnProperty, + __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; + + $ = this.jQuery; + + SimpleWidget = (function() { + SimpleWidget.prototype.defaults = {}; + + function SimpleWidget(el, options) { + this.$el = $(el); + this.options = $.extend({}, this.defaults, options); + } + + SimpleWidget.prototype.destroy = function() { + return this._deinit(); + }; + + SimpleWidget.prototype._init = function() { + return null; + }; + + SimpleWidget.prototype._deinit = function() { + return null; + }; + + SimpleWidget.register = function(widget_class, widget_name) { + var callFunction, createWidget, destroyWidget, getDataKey; + getDataKey = function() { + return "simple_widget_" + widget_name; + }; + createWidget = function($el, options) { + var data_key, el, widget, _i, _len; + data_key = getDataKey(); + for (_i = 0, _len = $el.length; _i < _len; _i++) { + el = $el[_i]; + widget = new widget_class(el, options); + if (!$.data(el, data_key)) { + $.data(el, data_key, widget); + } + widget._init(); + } + return $el; + }; + destroyWidget = function($el) { + var data_key, el, widget, _i, _len, _results; + data_key = getDataKey(); + _results = []; + for (_i = 0, _len = $el.length; _i < _len; _i++) { + el = $el[_i]; + widget = $.data(el, data_key); + if (widget && (widget instanceof SimpleWidget)) { + widget.destroy(); + } + _results.push($.removeData(el, data_key)); + } + return _results; + }; + callFunction = function($el, function_name, args) { + var el, result, widget, widget_function, _i, _len; + result = null; + for (_i = 0, _len = $el.length; _i < _len; _i++) { + el = $el[_i]; + widget = $.data(el, getDataKey()); + if (widget && (widget instanceof SimpleWidget)) { + widget_function = widget[function_name]; + if (widget_function && (typeof widget_function === 'function')) { + result = widget_function.apply(widget, args); + } + } + } + return result; + }; + return $.fn[widget_name] = function() { + var $el, args, argument1, function_name, options; + argument1 = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : []; + $el = this; + if (argument1 === void 0 || typeof argument1 === 'object') { + options = argument1; + return createWidget($el, options); + } else if (typeof argument1 === 'string' && argument1[0] !== '_') { + function_name = argument1; + if (function_name === 'destroy') { + return destroyWidget($el); + } else { + return callFunction($el, function_name, args); + } + } + }; + }; + + return SimpleWidget; + + })(); + + this.SimpleWidget = SimpleWidget; + + /* + This widget does the same a the mouse widget in jqueryui. + */ + + + MouseWidget = (function(_super) { + __extends(MouseWidget, _super); + + function MouseWidget() { + _ref = MouseWidget.__super__.constructor.apply(this, arguments); + return _ref; + } + + MouseWidget.is_mouse_handled = false; + + MouseWidget.prototype._init = function() { + this.$el.bind('mousedown.mousewidget', $.proxy(this._mouseDown, this)); + this.$el.bind('touchstart.mousewidget', $.proxy(this._touchStart, this)); + this.is_mouse_started = false; + this.mouse_delay = 0; + this._mouse_delay_timer = null; + this._is_mouse_delay_met = true; + return this.mouse_down_info = null; + }; + + MouseWidget.prototype._deinit = function() { + var $document; + this.$el.unbind('mousedown.mousewidget'); + this.$el.unbind('touchstart.mousewidget'); + $document = $(document); + $document.unbind('mousemove.mousewidget'); + return $document.unbind('mouseup.mousewidget'); + }; + + MouseWidget.prototype._mouseDown = function(e) { + var result; + if (e.which !== 1) { + return; + } + result = this._handleMouseDown(e, this._getPositionInfo(e)); + if (result) { + e.preventDefault(); + } + return result; + }; + + MouseWidget.prototype._handleMouseDown = function(e, position_info) { + if (MouseWidget.is_mouse_handled) { + return; + } + if (this.is_mouse_started) { + this._handleMouseUp(position_info); + } + this.mouse_down_info = position_info; + if (!this._mouseCapture(position_info)) { + return; + } + this._handleStartMouse(); + this.is_mouse_handled = true; + return true; + }; + + MouseWidget.prototype._handleStartMouse = function() { + var $document; + $document = $(document); + $document.bind('mousemove.mousewidget', $.proxy(this._mouseMove, this)); + $document.bind('touchmove.mousewidget', $.proxy(this._touchMove, this)); + $document.bind('mouseup.mousewidget', $.proxy(this._mouseUp, this)); + $document.bind('touchend.mousewidget', $.proxy(this._touchEnd, this)); + if (this.mouse_delay) { + return this._startMouseDelayTimer(); + } + }; + + MouseWidget.prototype._startMouseDelayTimer = function() { + var _this = this; + if (this._mouse_delay_timer) { + clearTimeout(this._mouse_delay_timer); + } + this._mouse_delay_timer = setTimeout(function() { + return _this._is_mouse_delay_met = true; + }, this.mouse_delay); + return this._is_mouse_delay_met = false; + }; + + MouseWidget.prototype._mouseMove = function(e) { + return this._handleMouseMove(e, this._getPositionInfo(e)); + }; + + MouseWidget.prototype._handleMouseMove = function(e, position_info) { + if (this.is_mouse_started) { + this._mouseDrag(position_info); + return e.preventDefault(); + } + if (this.mouse_delay && !this._is_mouse_delay_met) { + return true; + } + this.is_mouse_started = this._mouseStart(this.mouse_down_info) !== false; + if (this.is_mouse_started) { + this._mouseDrag(position_info); + } else { + this._handleMouseUp(position_info); + } + return !this.is_mouse_started; + }; + + MouseWidget.prototype._getPositionInfo = function(e) { + return { + page_x: e.pageX, + page_y: e.pageY, + target: e.target, + original_event: e + }; + }; + + MouseWidget.prototype._mouseUp = function(e) { + return this._handleMouseUp(this._getPositionInfo(e)); + }; + + MouseWidget.prototype._handleMouseUp = function(position_info) { + var $document; + $document = $(document); + $document.unbind('mousemove.mousewidget'); + $document.unbind('touchmove.mousewidget'); + $document.unbind('mouseup.mousewidget'); + $document.unbind('touchend.mousewidget'); + if (this.is_mouse_started) { + this.is_mouse_started = false; + this._mouseStop(position_info); + } + }; + + MouseWidget.prototype._mouseCapture = function(position_info) { + return true; + }; + + MouseWidget.prototype._mouseStart = function(position_info) { + return null; + }; + + MouseWidget.prototype._mouseDrag = function(position_info) { + return null; + }; + + MouseWidget.prototype._mouseStop = function(position_info) { + return null; + }; + + MouseWidget.prototype.setMouseDelay = function(mouse_delay) { + return this.mouse_delay = mouse_delay; + }; + + MouseWidget.prototype._touchStart = function(e) { + var touch; + if (e.originalEvent.touches.length > 1) { + return; + } + touch = e.originalEvent.changedTouches[0]; + return this._handleMouseDown(e, this._getPositionInfo(touch)); + }; + + MouseWidget.prototype._touchMove = function(e) { + var touch; + if (e.originalEvent.touches.length > 1) { + return; + } + touch = e.originalEvent.changedTouches[0]; + return this._handleMouseMove(e, this._getPositionInfo(touch)); + }; + + MouseWidget.prototype._touchEnd = function(e) { + var touch; + if (e.originalEvent.touches.length > 1) { + return; + } + touch = e.originalEvent.changedTouches[0]; + return this._handleMouseUp(this._getPositionInfo(touch)); + }; + + return MouseWidget; + + })(SimpleWidget); + + this.Tree = {}; + + $ = this.jQuery; + + Position = { + getName: function(position) { + return Position.strings[position - 1]; + }, + nameToIndex: function(name) { + var i, _i, _ref1; + for (i = _i = 1, _ref1 = Position.strings.length; 1 <= _ref1 ? _i <= _ref1 : _i >= _ref1; i = 1 <= _ref1 ? ++_i : --_i) { + if (Position.strings[i - 1] === name) { + return i; + } + } + return 0; + } + }; + + Position.BEFORE = 1; + + Position.AFTER = 2; + + Position.INSIDE = 3; + + Position.NONE = 4; + + Position.strings = ['before', 'after', 'inside', 'none']; + + this.Tree.Position = Position; + + Node = (function() { + function Node(o, is_root, node_class) { + if (is_root == null) { + is_root = false; + } + if (node_class == null) { + node_class = Node; + } + this.setData(o); + this.children = []; + this.parent = null; + if (is_root) { + this.id_mapping = {}; + this.tree = this; + this.node_class = node_class; + } + } + + Node.prototype.setData = function(o) { + var key, value, _results; + if (typeof o !== 'object') { + return this.name = o; + } else { + _results = []; + for (key in o) { + value = o[key]; + if (key === 'label') { + _results.push(this.name = value); + } else { + _results.push(this[key] = value); + } + } + return _results; + } + }; + + Node.prototype.initFromData = function(data) { + var addChildren, addNode, + _this = this; + addNode = function(node_data) { + _this.setData(node_data); + if (node_data.children) { + return addChildren(node_data.children); + } + }; + addChildren = function(children_data) { + var child, node, _i, _len; + for (_i = 0, _len = children_data.length; _i < _len; _i++) { + child = children_data[_i]; + node = new _this.tree.node_class(''); + node.initFromData(child); + _this.addChild(node); + } + return null; + }; + addNode(data); + return null; + }; + + /* + Create tree from data. + + Structure of data is: + [ + { + label: 'node1', + children: [ + { label: 'child1' }, + { label: 'child2' } + ] + }, + { + label: 'node2' + } + ] + */ + + + Node.prototype.loadFromData = function(data) { + var node, o, _i, _len; + this.removeChildren(); + for (_i = 0, _len = data.length; _i < _len; _i++) { + o = data[_i]; + node = new this.tree.node_class(o); + this.addChild(node); + if (typeof o === 'object' && o.children) { + node.loadFromData(o.children); + } + } + return null; + }; + + /* + Add child. + + tree.addChild( + new Node('child1') + ); + */ + + + Node.prototype.addChild = function(node) { + this.children.push(node); + return node._setParent(this); + }; + + /* + Add child at position. Index starts at 0. + + tree.addChildAtPosition( + new Node('abc'), + 1 + ); + */ + + + Node.prototype.addChildAtPosition = function(node, index) { + this.children.splice(index, 0, node); + return node._setParent(this); + }; + + Node.prototype._setParent = function(parent) { + this.parent = parent; + this.tree = parent.tree; + return this.tree.addNodeToIndex(this); + }; + + /* + Remove child. This also removes the children of the node. + + tree.removeChild(tree.children[0]); + */ + + + Node.prototype.removeChild = function(node) { + node.removeChildren(); + return this._removeChild(node); + }; + + Node.prototype._removeChild = function(node) { + this.children.splice(this.getChildIndex(node), 1); + return this.tree.removeNodeFromIndex(node); + }; + + /* + Get child index. + + var index = getChildIndex(node); + */ + + + Node.prototype.getChildIndex = function(node) { + return $.inArray(node, this.children); + }; + + /* + Does the tree have children? + + if (tree.hasChildren()) { + // + } + */ + + + Node.prototype.hasChildren = function() { + return this.children.length !== 0; + }; + + Node.prototype.isFolder = function() { + return this.hasChildren() || this.load_on_demand; + }; + + /* + Iterate over all the nodes in the tree. + + Calls callback with (node, level). + + The callback must return true to continue the iteration on current node. + + tree.iterate( + function(node, level) { + console.log(node.name); + + // stop iteration after level 2 + return (level <= 2); + } + ); + */ + + + Node.prototype.iterate = function(callback) { + var _iterate, + _this = this; + _iterate = function(node, level) { + var child, result, _i, _len, _ref1; + if (node.children) { + _ref1 = node.children; + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + child = _ref1[_i]; + result = callback(child, level); + if (_this.hasChildren() && result) { + _iterate(child, level + 1); + } + } + return null; + } + }; + _iterate(this, 0); + return null; + }; + + /* + Move node relative to another node. + + Argument position: Position.BEFORE, Position.AFTER or Position.Inside + + // move node1 after node2 + tree.moveNode(node1, node2, Position.AFTER); + */ + + + Node.prototype.moveNode = function(moved_node, target_node, position) { + if (moved_node.isParentOf(target_node)) { + return; + } + moved_node.parent._removeChild(moved_node); + if (position === Position.AFTER) { + return target_node.parent.addChildAtPosition(moved_node, target_node.parent.getChildIndex(target_node) + 1); + } else if (position === Position.BEFORE) { + return target_node.parent.addChildAtPosition(moved_node, target_node.parent.getChildIndex(target_node)); + } else if (position === Position.INSIDE) { + return target_node.addChildAtPosition(moved_node, 0); + } + }; + + /* + Get the tree as data. + */ + + + Node.prototype.getData = function() { + var getDataFromNodes, + _this = this; + getDataFromNodes = function(nodes) { + var data, k, node, tmp_node, v, _i, _len; + data = []; + for (_i = 0, _len = nodes.length; _i < _len; _i++) { + node = nodes[_i]; + tmp_node = {}; + for (k in node) { + v = node[k]; + if ((k !== 'parent' && k !== 'children' && k !== 'element' && k !== 'tree') && Object.prototype.hasOwnProperty.call(node, k)) { + tmp_node[k] = v; + } + } + if (node.hasChildren()) { + tmp_node.children = getDataFromNodes(node.children); + } + data.push(tmp_node); + } + return data; + }; + return getDataFromNodes(this.children); + }; + + Node.prototype.getNodeByName = function(name) { + var result; + result = null; + this.iterate(function(node) { + if (node.name === name) { + result = node; + return false; + } else { + return true; + } + }); + return result; + }; + + Node.prototype.addAfter = function(node_info) { + var child_index, node; + if (!this.parent) { + return null; + } else { + node = new this.tree.node_class(node_info); + child_index = this.parent.getChildIndex(this); + this.parent.addChildAtPosition(node, child_index + 1); + return node; + } + }; + + Node.prototype.addBefore = function(node_info) { + var child_index, node; + if (!this.parent) { + return null; + } else { + node = new this.tree.node_class(node_info); + child_index = this.parent.getChildIndex(this); + this.parent.addChildAtPosition(node, child_index); + return node; + } + }; + + Node.prototype.addParent = function(node_info) { + var child, new_parent, original_parent, _i, _len, _ref1; + if (!this.parent) { + return null; + } else { + new_parent = new this.tree.node_class(node_info); + new_parent._setParent(this.tree); + original_parent = this.parent; + _ref1 = original_parent.children; + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + child = _ref1[_i]; + new_parent.addChild(child); + } + original_parent.children = []; + original_parent.addChild(new_parent); + return new_parent; + } + }; + + Node.prototype.remove = function() { + if (this.parent) { + this.parent.removeChild(this); + return this.parent = null; + } + }; + + Node.prototype.append = function(node_info) { + var node; + node = new this.tree.node_class(node_info); + this.addChild(node); + return node; + }; + + Node.prototype.prepend = function(node_info) { + var node; + node = new this.tree.node_class(node_info); + this.addChildAtPosition(node, 0); + return node; + }; + + Node.prototype.isParentOf = function(node) { + var parent; + parent = node.parent; + while (parent) { + if (parent === this) { + return true; + } + parent = parent.parent; + } + return false; + }; + + Node.prototype.getLevel = function() { + var level, node; + level = 0; + node = this; + while (node.parent) { + level += 1; + node = node.parent; + } + return level; + }; + + Node.prototype.getNodeById = function(node_id) { + return this.id_mapping[node_id]; + }; + + Node.prototype.addNodeToIndex = function(node) { + if (node.id) { + return this.id_mapping[node.id] = node; + } + }; + + Node.prototype.removeNodeFromIndex = function(node) { + if (node.id) { + return delete this.id_mapping[node.id]; + } + }; + + Node.prototype.removeChildren = function() { + var _this = this; + this.iterate(function(child) { + _this.tree.removeNodeFromIndex(child); + return true; + }); + return this.children = []; + }; + + Node.prototype.getPreviousSibling = function() { + var previous_index; + if (!this.parent) { + return null; + } else { + previous_index = this.parent.getChildIndex(this) - 1; + if (previous_index >= 0) { + return this.parent.children[previous_index]; + } else { + return null; + } + } + }; + + Node.prototype.getNextSibling = function() { + var next_index; + if (!this.parent) { + return null; + } else { + next_index = this.parent.getChildIndex(this) + 1; + if (next_index < this.parent.children.length) { + return this.parent.children[next_index]; + } else { + return null; + } + } + }; + + return Node; + + })(); + + this.Tree.Node = Node; + + /* + Copyright 2013 Marco Braak + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + + + JqTreeWidget = (function(_super) { + __extends(JqTreeWidget, _super); + + function JqTreeWidget() { + _ref1 = JqTreeWidget.__super__.constructor.apply(this, arguments); + return _ref1; + } + + JqTreeWidget.prototype.defaults = { + autoOpen: false, + saveState: false, + dragAndDrop: false, + selectable: true, + useContextMenu: true, + onCanSelectNode: null, + onSetStateFromStorage: null, + onGetStateFromStorage: null, + onCreateLi: null, + onIsMoveHandle: null, + onCanMove: null, + onCanMoveTo: null, + onLoadFailed: null, + autoEscape: true, + dataUrl: null, + closedIcon: '►', + openedIcon: '▼', + slide: true, + nodeClass: Node + }; + + JqTreeWidget.prototype.toggle = function(node, slide) { + if (slide == null) { + slide = true; + } + if (node.is_open) { + return this.closeNode(node, slide); + } else { + return this.openNode(node, slide); + } + }; + + JqTreeWidget.prototype.getTree = function() { + return this.tree; + }; + + JqTreeWidget.prototype.selectNode = function(node) { + return this._selectNode(node, true); + }; + + JqTreeWidget.prototype._selectNode = function(node, must_toggle) { + var canSelect, openParents, saveState, + _this = this; + if (must_toggle == null) { + must_toggle = false; + } + if (!this.select_node_handler) { + return; + } + canSelect = function() { + if (_this.options.onCanSelectNode) { + return _this.options.selectable && _this.options.onCanSelectNode(node); + } else { + return _this.options.selectable; + } + }; + openParents = function() { + var parent; + parent = node.parent; + if (parent && parent.parent && !parent.is_open) { + return _this.openNode(parent, false); + } + }; + saveState = function() { + if (_this.options.saveState) { + return _this.save_state_handler.saveState(); + } + }; + if (!node) { + this._deselectCurrentNode(); + saveState(); + return; + } + if (!canSelect()) { + return; + } + if (this.select_node_handler.isNodeSelected(node)) { + if (must_toggle) { + this._deselectCurrentNode(); + this._triggerEvent('tree.select', { + node: null, + previous_node: node + }); + } + } else { + this._deselectCurrentNode(); + this.addToSelection(node); + this._triggerEvent('tree.select', { + node: node + }); + openParents(); + } + return saveState(); + }; + + JqTreeWidget.prototype.getSelectedNode = function() { + return this.select_node_handler.getSelectedNode(); + }; + + JqTreeWidget.prototype.toJson = function() { + return JSON.stringify(this.tree.getData()); + }; + + JqTreeWidget.prototype.loadData = function(data, parent_node) { + return this._loadData(data, parent_node); + }; + + JqTreeWidget.prototype.loadDataFromUrl = function(url, parent_node, on_finished) { + if ($.type(url) !== 'string') { + on_finished = parent_node; + parent_node = url; + url = null; + } + return this._loadDataFromUrl(url, parent_node, on_finished); + }; + + JqTreeWidget.prototype._loadDataFromUrl = function(url_info, parent_node, on_finished) { + var $el, addLoadingClass, parseUrlInfo, removeLoadingClass, + _this = this; + $el = null; + addLoadingClass = function() { + var folder_element; + if (!parent_node) { + $el = _this.element; + } else { + folder_element = new FolderElement(parent_node, _this); + $el = folder_element.getLi(); + } + return $el.addClass('jqtree-loading'); + }; + removeLoadingClass = function() { + if ($el) { + return $el.removeClass('jqtree-loading'); + } + }; + parseUrlInfo = function() { + if ($.type(url_info) === 'string') { + url_info = { + url: url_info + }; + } + if (!url_info.method) { + return url_info.method = 'get'; + } + }; + addLoadingClass(); + if (!url_info) { + url_info = this._getDataUrlInfo(parent_node); + } + parseUrlInfo(); + return $.ajax({ + url: url_info.url, + data: url_info.data, + type: url_info.method.toUpperCase(), + cache: false, + dataType: 'json', + success: function(response) { + var data; + if ($.isArray(response) || typeof response === 'object') { + data = response; + } else { + data = $.parseJSON(response); + } + removeLoadingClass(); + _this._loadData(data, parent_node); + if (on_finished && $.isFunction(on_finished)) { + return on_finished(); + } + }, + error: function(response) { + removeLoadingClass(); + if (_this.options.onLoadFailed) { + return _this.options.onLoadFailed(response); + } + } + }); + }; + + JqTreeWidget.prototype._loadData = function(data, parent_node) { + var n, selected_nodes_under_parent, _i, _len; + this._triggerEvent('tree.load_data', { + tree_data: data + }); + if (!parent_node) { + this._initTree(data); + } else { + selected_nodes_under_parent = this.select_node_handler.getSelectedNodes(parent_node); + for (_i = 0, _len = selected_nodes_under_parent.length; _i < _len; _i++) { + n = selected_nodes_under_parent[_i]; + this.select_node_handler.removeFromSelection(n); + } + parent_node.loadFromData(data); + parent_node.load_on_demand = false; + this._refreshElements(parent_node.parent); + } + if (this.is_dragging) { + return this.dnd_handler.refreshHitAreas(); + } + }; + + JqTreeWidget.prototype.getNodeById = function(node_id) { + return this.tree.getNodeById(node_id); + }; + + JqTreeWidget.prototype.getNodeByName = function(name) { + return this.tree.getNodeByName(name); + }; + + JqTreeWidget.prototype.openNode = function(node, slide) { + if (slide == null) { + slide = true; + } + return this._openNode(node, slide); + }; + + JqTreeWidget.prototype._openNode = function(node, slide, on_finished) { + var doOpenNode, parent, + _this = this; + if (slide == null) { + slide = true; + } + doOpenNode = function(_node, _slide, _on_finished) { + var folder_element; + folder_element = new FolderElement(_node, _this); + return folder_element.open(_on_finished, _slide); + }; + if (node.isFolder()) { + if (node.load_on_demand) { + return this._loadFolderOnDemand(node, slide, on_finished); + } else { + parent = node.parent; + while (parent && !parent.is_open) { + if (parent.parent) { + doOpenNode(parent, false, null); + } + parent = parent.parent; + } + doOpenNode(node, slide, on_finished); + return this._saveState(); + } + } + }; + + JqTreeWidget.prototype._loadFolderOnDemand = function(node, slide, on_finished) { + var _this = this; + if (slide == null) { + slide = true; + } + return this._loadDataFromUrl(null, node, function() { + return _this._openNode(node, slide, on_finished); + }); + }; + + JqTreeWidget.prototype.closeNode = function(node, slide) { + if (slide == null) { + slide = true; + } + if (node.isFolder()) { + new FolderElement(node, this).close(slide); + return this._saveState(); + } + }; + + JqTreeWidget.prototype.isDragging = function() { + return this.is_dragging; + }; + + JqTreeWidget.prototype.refreshHitAreas = function() { + return this.dnd_handler.refreshHitAreas(); + }; + + JqTreeWidget.prototype.addNodeAfter = function(new_node_info, existing_node) { + var new_node; + new_node = existing_node.addAfter(new_node_info); + this._refreshElements(existing_node.parent); + return new_node; + }; + + JqTreeWidget.prototype.addNodeBefore = function(new_node_info, existing_node) { + var new_node; + new_node = existing_node.addBefore(new_node_info); + this._refreshElements(existing_node.parent); + return new_node; + }; + + JqTreeWidget.prototype.addParentNode = function(new_node_info, existing_node) { + var new_node; + new_node = existing_node.addParent(new_node_info); + this._refreshElements(new_node.parent); + return new_node; + }; + + JqTreeWidget.prototype.removeNode = function(node) { + var parent; + parent = node.parent; + if (parent) { + this.select_node_handler.removeFromSelection(node, true); + node.remove(); + return this._refreshElements(parent.parent); + } + }; + + JqTreeWidget.prototype.appendNode = function(new_node_info, parent_node) { + var is_already_root_node, node; + if (!parent_node) { + parent_node = this.tree; + } + is_already_root_node = parent_node.isFolder(); + node = parent_node.append(new_node_info); + if (is_already_root_node) { + this._refreshElements(parent_node); + } else { + this._refreshElements(parent_node.parent); + } + return node; + }; + + JqTreeWidget.prototype.prependNode = function(new_node_info, parent_node) { + var node; + if (!parent_node) { + parent_node = this.tree; + } + node = parent_node.prepend(new_node_info); + this._refreshElements(parent_node); + return node; + }; + + JqTreeWidget.prototype.updateNode = function(node, data) { + var id_is_changed; + id_is_changed = data.id && data.id !== node.id; + if (id_is_changed) { + this.tree.removeNodeFromIndex(node); + } + node.setData(data); + if (id_is_changed) { + this.tree.addNodeToIndex(node); + } + this._refreshElements(node.parent); + return this._selectCurrentNode(); + }; + + JqTreeWidget.prototype.moveNode = function(node, target_node, position) { + var position_index; + position_index = Position.nameToIndex(position); + this.tree.moveNode(node, target_node, position_index); + return this._refreshElements(); + }; + + JqTreeWidget.prototype.getStateFromStorage = function() { + return this.save_state_handler.getStateFromStorage(); + }; + + JqTreeWidget.prototype.addToSelection = function(node) { + this.select_node_handler.addToSelection(node); + return this._getNodeElementForNode(node).select(); + }; + + JqTreeWidget.prototype.getSelectedNodes = function() { + return this.select_node_handler.getSelectedNodes(); + }; + + JqTreeWidget.prototype.isNodeSelected = function(node) { + return this.select_node_handler.isNodeSelected(node); + }; + + JqTreeWidget.prototype.removeFromSelection = function(node) { + this.select_node_handler.removeFromSelection(node); + return this._getNodeElementForNode(node).deselect(); + }; + + JqTreeWidget.prototype.scrollToNode = function(node) { + var $element, top; + $element = $(node.element); + top = $element.offset().top - this.$el.offset().top; + return this.scroll_handler.scrollTo(top); + }; + + JqTreeWidget.prototype.getState = function() { + return this.save_state_handler.getState(); + }; + + JqTreeWidget.prototype.setState = function(state) { + this.save_state_handler.setState(state); + return this._refreshElements(); + }; + + JqTreeWidget.prototype._init = function() { + JqTreeWidget.__super__._init.call(this); + this.element = this.$el; + this.mouse_delay = 300; + this.is_initialized = false; + if (typeof SaveStateHandler !== "undefined" && SaveStateHandler !== null) { + this.save_state_handler = new SaveStateHandler(this); + } else { + this.options.saveState = false; + } + if (typeof SelectNodeHandler !== "undefined" && SelectNodeHandler !== null) { + this.select_node_handler = new SelectNodeHandler(this); + } + if (typeof DragAndDropHandler !== "undefined" && DragAndDropHandler !== null) { + this.dnd_handler = new DragAndDropHandler(this); + } else { + this.options.dragAndDrop = false; + } + if (typeof ScrollHandler !== "undefined" && ScrollHandler !== null) { + this.scroll_handler = new ScrollHandler(this); + } + if ((typeof KeyHandler !== "undefined" && KeyHandler !== null) && (typeof SelectNodeHandler !== "undefined" && SelectNodeHandler !== null)) { + this.key_handler = new KeyHandler(this); + } + this._initData(); + this.element.click($.proxy(this._click, this)); + if (this.options.useContextMenu) { + return this.element.bind('contextmenu', $.proxy(this._contextmenu, this)); + } + }; + + JqTreeWidget.prototype._deinit = function() { + this.element.empty(); + this.element.unbind(); + this.key_handler.deinit(); + this.tree = null; + return JqTreeWidget.__super__._deinit.call(this); + }; + + JqTreeWidget.prototype._initData = function() { + if (this.options.data) { + return this._loadData(this.options.data); + } else { + return this._loadDataFromUrl(this._getDataUrlInfo()); + } + }; + + JqTreeWidget.prototype._getDataUrlInfo = function(node) { + var data, data_url, url_info; + data_url = this.options.dataUrl || this.element.data('url'); + if ($.isFunction(data_url)) { + return data_url(node); + } else if ($.type(data_url) === 'string') { + url_info = { + url: data_url + }; + if (node && node.id) { + data = { + node: node.id + }; + url_info['data'] = data; + } + return url_info; + } else { + return data_url; + } + }; + + JqTreeWidget.prototype._initTree = function(data) { + this.tree = new this.options.nodeClass(null, true, this.options.nodeClass); + if (this.select_node_handler) { + this.select_node_handler.clear(); + } + this.tree.loadFromData(data); + this._openNodes(); + this._refreshElements(); + if (!this.is_initialized) { + this.is_initialized = true; + return this._triggerEvent('tree.init'); + } + }; + + JqTreeWidget.prototype._openNodes = function() { + var max_level; + if (this.options.saveState) { + if (this.save_state_handler.restoreState()) { + return; + } + } + if (this.options.autoOpen === false) { + return; + } else if (this.options.autoOpen === true) { + max_level = -1; + } else { + max_level = parseInt(this.options.autoOpen); + } + return this.tree.iterate(function(node, level) { + if (node.hasChildren()) { + node.is_open = true; + } + return level !== max_level; + }); + }; + + JqTreeWidget.prototype._refreshElements = function(from_node) { + var $element, createFolderLi, createLi, createNodeLi, createUl, doCreateDomElements, escapeIfNecessary, is_root_node, node_element, + _this = this; + if (from_node == null) { + from_node = null; + } + escapeIfNecessary = function(value) { + if (_this.options.autoEscape) { + return html_escape(value); + } else { + return value; + } + }; + createUl = function(is_root_node) { + var class_string; + if (is_root_node) { + class_string = 'jqtree-tree'; + } else { + class_string = ''; + } + return $("<ul class=\"jqtree_common " + class_string + "\"></ul>"); + }; + createLi = function(node) { + var $li; + if (node.isFolder()) { + $li = createFolderLi(node); + } else { + $li = createNodeLi(node); + } + if (_this.options.onCreateLi) { + _this.options.onCreateLi(node, $li); + } + return $li; + }; + createNodeLi = function(node) { + var class_string, escaped_name, li_classes; + li_classes = ['jqtree_common']; + if (_this.select_node_handler && _this.select_node_handler.isNodeSelected(node)) { + li_classes.push('jqtree-selected'); + } + class_string = li_classes.join(' '); + escaped_name = escapeIfNecessary(node.name); + return $("<li class=\"" + class_string + "\"><div class=\"jqtree-element jqtree_common\"><span class=\"jqtree-title jqtree_common\">" + escaped_name + "</span></div></li>"); + }; + createFolderLi = function(node) { + var button_char, button_classes, escaped_name, folder_classes, getButtonClasses, getFolderClasses; + getButtonClasses = function() { + var classes; + classes = ['jqtree-toggler']; + if (!node.is_open) { + classes.push('jqtree-closed'); + } + return classes.join(' '); + }; + getFolderClasses = function() { + var classes; + classes = ['jqtree-folder']; + if (!node.is_open) { + classes.push('jqtree-closed'); + } + if (_this.select_node_handler && _this.select_node_handler.isNodeSelected(node)) { + classes.push('jqtree-selected'); + } + return classes.join(' '); + }; + button_classes = getButtonClasses(); + folder_classes = getFolderClasses(); + escaped_name = escapeIfNecessary(node.name); + if (node.is_open) { + button_char = _this.options.openedIcon; + } else { + button_char = _this.options.closedIcon; + } + return $("<li class=\"jqtree_common " + folder_classes + "\"><div class=\"jqtree-element jqtree_common\"><a class=\"jqtree_common " + button_classes + "\">" + button_char + "</a><span class=\"jqtree_common jqtree-title\">" + escaped_name + "</span></div></li>"); + }; + doCreateDomElements = function($element, children, is_root_node, is_open) { + var $li, $ul, child, _i, _len; + $ul = createUl(is_root_node); + $element.append($ul); + for (_i = 0, _len = children.length; _i < _len; _i++) { + child = children[_i]; + $li = createLi(child); + $ul.append($li); + child.element = $li[0]; + $li.data('node', child); + if (child.hasChildren()) { + doCreateDomElements($li, child.children, false, child.is_open); + } + } + return null; + }; + if (from_node && from_node.parent) { + is_root_node = false; + node_element = this._getNodeElementForNode(from_node); + node_element.getUl().remove(); + $element = node_element.$element; + } else { + from_node = this.tree; + $element = this.element; + $element.empty(); + is_root_node = true; + } + doCreateDomElements($element, from_node.children, is_root_node, is_root_node); + return this._triggerEvent('tree.refresh'); + }; + + JqTreeWidget.prototype._click = function(e) { + var $button, $el, $target, event, node; + $target = $(e.target); + $button = $target.closest('.jqtree-toggler'); + if ($button.length) { + node = this._getNode($button); + if (node) { + this.toggle(node, this.options.slide); + e.preventDefault(); + return e.stopPropagation(); + } + } else { + $el = $target.closest('.jqtree-element'); + if ($el.length) { + node = this._getNode($el); + if (node) { + event = this._triggerEvent('tree.click', { + node: node + }); + if (!event.isDefaultPrevented()) { + return this._selectNode(node, true); + } + } + } + } + }; + + JqTreeWidget.prototype._getNode = function($element) { + var $li; + $li = $element.closest('li'); + if ($li.length === 0) { + return null; + } else { + return $li.data('node'); + } + }; + + JqTreeWidget.prototype._getNodeElementForNode = function(node) { + if (node.isFolder()) { + return new FolderElement(node, this); + } else { + return new NodeElement(node, this); + } + }; + + JqTreeWidget.prototype._getNodeElement = function($element) { + var node; + node = this._getNode($element); + if (node) { + return this._getNodeElementForNode(node); + } else { + return null; + } + }; + + JqTreeWidget.prototype._contextmenu = function(e) { + var $div, node; + $div = $(e.target).closest('ul.jqtree-tree .jqtree-element'); + if ($div.length) { + node = this._getNode($div); + if (node) { + e.preventDefault(); + e.stopPropagation(); + this._triggerEvent('tree.contextmenu', { + node: node, + click_event: e + }); + return false; + } + } + }; + + JqTreeWidget.prototype._saveState = function() { + if (this.options.saveState) { + return this.save_state_handler.saveState(); + } + }; + + JqTreeWidget.prototype._mouseCapture = function(position_info) { + if (this.options.dragAndDrop) { + return this.dnd_handler.mouseCapture(position_info); + } else { + return false; + } + }; + + JqTreeWidget.prototype._mouseStart = function(position_info) { + if (this.options.dragAndDrop) { + return this.dnd_handler.mouseStart(position_info); + } else { + return false; + } + }; + + JqTreeWidget.prototype._mouseDrag = function(position_info) { + var result; + if (this.options.dragAndDrop) { + result = this.dnd_handler.mouseDrag(position_info); + if (this.scroll_handler) { + this.scroll_handler.checkScrolling(); + } + return result; + } else { + return false; + } + }; + + JqTreeWidget.prototype._mouseStop = function(position_info) { + if (this.options.dragAndDrop) { + return this.dnd_handler.mouseStop(position_info); + } else { + return false; + } + }; + + JqTreeWidget.prototype._triggerEvent = function(event_name, values) { + var event; + event = $.Event(event_name); + $.extend(event, values); + this.element.trigger(event); + return event; + }; + + JqTreeWidget.prototype.testGenerateHitAreas = function(moving_node) { + this.dnd_handler.current_item = this._getNodeElementForNode(moving_node); + this.dnd_handler.generateHitAreas(); + return this.dnd_handler.hit_areas; + }; + + JqTreeWidget.prototype._selectCurrentNode = function() { + var node, node_element; + node = this.getSelectedNode(); + if (node) { + node_element = this._getNodeElementForNode(node); + if (node_element) { + return node_element.select(); + } + } + }; + + JqTreeWidget.prototype._deselectCurrentNode = function() { + var node; + node = this.getSelectedNode(); + if (node) { + return this.removeFromSelection(node); + } + }; + + return JqTreeWidget; + + })(MouseWidget); + + SimpleWidget.register(JqTreeWidget, 'tree'); + + NodeElement = (function() { + function NodeElement(node, tree_widget) { + this.init(node, tree_widget); + } + + NodeElement.prototype.init = function(node, tree_widget) { + this.node = node; + this.tree_widget = tree_widget; + return this.$element = $(node.element); + }; + + NodeElement.prototype.getUl = function() { + return this.$element.children('ul:first'); + }; + + NodeElement.prototype.getSpan = function() { + return this.$element.children('.jqtree-element').find('span.jqtree-title'); + }; + + NodeElement.prototype.getLi = function() { + return this.$element; + }; + + NodeElement.prototype.addDropHint = function(position) { + if (position === Position.INSIDE) { + return new BorderDropHint(this.$element); + } else { + return new GhostDropHint(this.node, this.$element, position); + } + }; + + NodeElement.prototype.select = function() { + return this.getLi().addClass('jqtree-selected'); + }; + + NodeElement.prototype.deselect = function() { + return this.getLi().removeClass('jqtree-selected'); + }; + + return NodeElement; + + })(); + + FolderElement = (function(_super) { + __extends(FolderElement, _super); + + function FolderElement() { + _ref2 = FolderElement.__super__.constructor.apply(this, arguments); + return _ref2; + } + + FolderElement.prototype.open = function(on_finished, slide) { + var $button, doOpen, + _this = this; + if (slide == null) { + slide = true; + } + if (!this.node.is_open) { + this.node.is_open = true; + $button = this.getButton(); + $button.removeClass('jqtree-closed'); + $button.html(this.tree_widget.options.openedIcon); + doOpen = function() { + _this.getLi().removeClass('jqtree-closed'); + if (on_finished) { + on_finished(); + } + return _this.tree_widget._triggerEvent('tree.open', { + node: _this.node + }); + }; + if (slide) { + return this.getUl().slideDown('fast', doOpen); + } else { + this.getUl().show(); + return doOpen(); + } + } + }; + + FolderElement.prototype.close = function(slide) { + var $button, doClose, + _this = this; + if (slide == null) { + slide = true; + } + if (this.node.is_open) { + this.node.is_open = false; + $button = this.getButton(); + $button.addClass('jqtree-closed'); + $button.html(this.tree_widget.options.closedIcon); + doClose = function() { + _this.getLi().addClass('jqtree-closed'); + return _this.tree_widget._triggerEvent('tree.close', { + node: _this.node + }); + }; + if (slide) { + return this.getUl().slideUp('fast', doClose); + } else { + this.getUl().hide(); + return doClose(); + } + } + }; + + FolderElement.prototype.getButton = function() { + return this.$element.children('.jqtree-element').find('a.jqtree-toggler'); + }; + + FolderElement.prototype.addDropHint = function(position) { + if (!this.node.is_open && position === Position.INSIDE) { + return new BorderDropHint(this.$element); + } else { + return new GhostDropHint(this.node, this.$element, position); + } + }; + + return FolderElement; + + })(NodeElement); + + html_escape = function(string) { + return ('' + string).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g, '/'); + }; + + _indexOf = function(array, item) { + var i, value, _i, _len; + for (i = _i = 0, _len = array.length; _i < _len; i = ++_i) { + value = array[i]; + if (value === item) { + return i; + } + } + return -1; + }; + + indexOf = function(array, item) { + if (array.indexOf) { + return array.indexOf(item); + } else { + return _indexOf(array, item); + } + }; + + this.Tree.indexOf = indexOf; + + this.Tree._indexOf = _indexOf; + + if (!((this.JSON != null) && (this.JSON.stringify != null) && typeof this.JSON.stringify === 'function')) { + json_escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + json_meta = { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"': '\\"', + '\\': '\\\\' + }; + json_quote = function(string) { + json_escapable.lastIndex = 0; + if (json_escapable.test(string)) { + return '"' + string.replace(json_escapable, function(a) { + var c; + c = json_meta[a]; + return (typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4)); + }) + '"'; + } else { + return '"' + string + '"'; + } + }; + json_str = function(key, holder) { + var i, k, partial, v, value, _i, _len; + value = holder[key]; + switch (typeof value) { + case 'string': + return json_quote(value); + case 'number': + if (isFinite(value)) { + return String(value); + } else { + return 'null'; + } + case 'boolean': + case 'null': + return String(value); + case 'object': + if (!value) { + return 'null'; + } + partial = []; + if (Object.prototype.toString.apply(value) === '[object Array]') { + for (i = _i = 0, _len = value.length; _i < _len; i = ++_i) { + v = value[i]; + partial[i] = json_str(i, value) || 'null'; + } + return (partial.length === 0 ? '[]' : '[' + partial.join(',') + ']'); + } + for (k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + v = json_str(k, value); + if (v) { + partial.push(json_quote(k) + ':' + v); + } + } + } + return (partial.length === 0 ? '{}' : '{' + partial.join(',') + '}'); + } + }; + if (this.JSON == null) { + this.JSON = {}; + } + this.JSON.stringify = function(value) { + return json_str('', { + '': value + }); + }; + } + + SaveStateHandler = (function() { + function SaveStateHandler(tree_widget) { + this.tree_widget = tree_widget; + } + + SaveStateHandler.prototype.saveState = function() { + var state; + state = JSON.stringify(this.getState()); + if (this.tree_widget.options.onSetStateFromStorage) { + return this.tree_widget.options.onSetStateFromStorage(state); + } else if (typeof localStorage !== "undefined" && localStorage !== null) { + return localStorage.setItem(this.getCookieName(), state); + } else if ($.cookie) { + $.cookie.raw = true; + return $.cookie(this.getCookieName(), state, { + path: '/' + }); + } + }; + + SaveStateHandler.prototype.restoreState = function() { + var state; + state = this.getStateFromStorage(); + if (state) { + this.setState($.parseJSON(state)); + return true; + } else { + return false; + } + }; + + SaveStateHandler.prototype.getStateFromStorage = function() { + if (this.tree_widget.options.onGetStateFromStorage) { + return this.tree_widget.options.onGetStateFromStorage(); + } else if (typeof localStorage !== "undefined" && localStorage !== null) { + return localStorage.getItem(this.getCookieName()); + } else if ($.cookie) { + $.cookie.raw = true; + return $.cookie(this.getCookieName()); + } else { + return null; + } + }; + + SaveStateHandler.prototype.getState = function() { + var open_nodes, selected_node, selected_node_id, + _this = this; + open_nodes = []; + this.tree_widget.tree.iterate(function(node) { + if (node.is_open && node.id && node.hasChildren()) { + open_nodes.push(node.id); + } + return true; + }); + selected_node = this.tree_widget.getSelectedNode(); + if (selected_node) { + selected_node_id = selected_node.id; + } else { + selected_node_id = ''; + } + return { + open_nodes: open_nodes, + selected_node: selected_node_id + }; + }; + + SaveStateHandler.prototype.setState = function(state) { + var open_nodes, selected_node, selected_node_id, + _this = this; + if (state) { + open_nodes = state.open_nodes; + selected_node_id = state.selected_node; + this.tree_widget.tree.iterate(function(node) { + node.is_open = node.id && node.hasChildren() && (indexOf(open_nodes, node.id) >= 0); + return true; + }); + if (selected_node_id && this.tree_widget.select_node_handler) { + this.tree_widget.select_node_handler.clear(); + selected_node = this.tree_widget.getNodeById(selected_node_id); + if (selected_node) { + return this.tree_widget.select_node_handler.addToSelection(selected_node); + } + } + } + }; + + SaveStateHandler.prototype.getCookieName = function() { + if (typeof this.tree_widget.options.saveState === 'string') { + return this.tree_widget.options.saveState; + } else { + return 'tree'; + } + }; + + return SaveStateHandler; + + })(); + + SelectNodeHandler = (function() { + function SelectNodeHandler(tree_widget) { + this.tree_widget = tree_widget; + this.clear(); + } + + SelectNodeHandler.prototype.getSelectedNode = function() { + var selected_nodes; + selected_nodes = this.getSelectedNodes(); + if (selected_nodes.length) { + return selected_nodes[0]; + } else { + return false; + } + }; + + SelectNodeHandler.prototype.getSelectedNodes = function() { + var id, node, selected_nodes; + if (this.selected_single_node) { + return [this.selected_single_node]; + } else { + selected_nodes = []; + for (id in this.selected_nodes) { + node = this.tree_widget.getNodeById(id); + if (node) { + selected_nodes.push(node); + } + } + return selected_nodes; + } + }; + + SelectNodeHandler.prototype.isNodeSelected = function(node) { + if (node.id) { + return this.selected_nodes[node.id]; + } else if (this.selected_single_node) { + return this.selected_single_node.element === node.element; + } else { + return false; + } + }; + + SelectNodeHandler.prototype.clear = function() { + this.selected_nodes = {}; + return this.selected_single_node = null; + }; + + SelectNodeHandler.prototype.removeFromSelection = function(node, include_children) { + var _this = this; + if (include_children == null) { + include_children = false; + } + if (!node.id) { + if (node.element === this.selected_single_node.element) { + return this.selected_single_node = null; + } + } else { + delete this.selected_nodes[node.id]; + if (include_children) { + return node.iterate(function(n) { + delete _this.selected_nodes[node.id]; + return true; + }); + } + } + }; + + SelectNodeHandler.prototype.addToSelection = function(node) { + if (node.id) { + return this.selected_nodes[node.id] = true; + } else { + return this.selected_single_node = node; + } + }; + + return SelectNodeHandler; + + })(); + + DragAndDropHandler = (function() { + function DragAndDropHandler(tree_widget) { + this.tree_widget = tree_widget; + this.hovered_area = null; + this.$ghost = null; + this.hit_areas = []; + this.is_dragging = false; + } + + DragAndDropHandler.prototype.mouseCapture = function(position_info) { + var $element, node_element; + $element = $(position_info.target); + if (this.tree_widget.options.onIsMoveHandle && !this.tree_widget.options.onIsMoveHandle($element)) { + return null; + } + node_element = this.tree_widget._getNodeElement($element); + if (node_element && this.tree_widget.options.onCanMove) { + if (!this.tree_widget.options.onCanMove(node_element.node)) { + node_element = null; + } + } + this.current_item = node_element; + return this.current_item !== null; + }; + + DragAndDropHandler.prototype.mouseStart = function(position_info) { + var offset; + this.refreshHitAreas(); + offset = $(position_info.target).offset(); + this.drag_element = new DragElement(this.current_item.node, position_info.page_x - offset.left, position_info.page_y - offset.top, this.tree_widget.element); + this.is_dragging = true; + this.current_item.$element.addClass('jqtree-moving'); + return true; + }; + + DragAndDropHandler.prototype.mouseDrag = function(position_info) { + var area, can_move_to; + this.drag_element.move(position_info.page_x, position_info.page_y); + area = this.findHoveredArea(position_info.page_x, position_info.page_y); + can_move_to = this.canMoveToArea(area); + if (area) { + if (this.hovered_area !== area) { + this.hovered_area = area; + if (this.mustOpenFolderTimer(area)) { + this.startOpenFolderTimer(area.node); + } + if (can_move_to) { + this.updateDropHint(); + } + } + } else { + this.removeHover(); + this.removeDropHint(); + this.stopOpenFolderTimer(); + } + return true; + }; + + DragAndDropHandler.prototype.canMoveToArea = function(area) { + var position_name; + if (!area) { + return false; + } else if (this.tree_widget.options.onCanMoveTo) { + position_name = Position.getName(area.position); + return this.tree_widget.options.onCanMoveTo(this.current_item.node, area.node, position_name); + } else { + return true; + } + }; + + DragAndDropHandler.prototype.mouseStop = function(position_info) { + this.moveItem(position_info); + this.clear(); + this.removeHover(); + this.removeDropHint(); + this.removeHitAreas(); + if (this.current_item) { + this.current_item.$element.removeClass('jqtree-moving'); + } + this.is_dragging = false; + return false; + }; + + DragAndDropHandler.prototype.refreshHitAreas = function() { + this.removeHitAreas(); + return this.generateHitAreas(); + }; + + DragAndDropHandler.prototype.removeHitAreas = function() { + return this.hit_areas = []; + }; + + DragAndDropHandler.prototype.clear = function() { + this.drag_element.remove(); + return this.drag_element = null; + }; + + DragAndDropHandler.prototype.removeDropHint = function() { + if (this.previous_ghost) { + return this.previous_ghost.remove(); + } + }; + + DragAndDropHandler.prototype.removeHover = function() { + return this.hovered_area = null; + }; + + DragAndDropHandler.prototype.generateHitAreas = function() { + var addPosition, getTop, groupPositions, handleAfterOpenFolder, handleClosedFolder, handleFirstNode, handleNode, handleOpenFolder, hit_areas, last_top, positions, + _this = this; + positions = []; + last_top = 0; + getTop = function($element) { + return $element.offset().top; + }; + addPosition = function(node, position, top) { + positions.push({ + top: top, + node: node, + position: position + }); + return last_top = top; + }; + groupPositions = function(handle_group) { + var group, position, previous_top, _i, _len; + previous_top = -1; + group = []; + for (_i = 0, _len = positions.length; _i < _len; _i++) { + position = positions[_i]; + if (position.top !== previous_top) { + if (group.length) { + handle_group(group, previous_top, position.top); + } + previous_top = position.top; + group = []; + } + group.push(position); + } + return handle_group(group, previous_top, _this.tree_widget.element.offset().top + _this.tree_widget.element.height()); + }; + handleNode = function(node, next_node, $element) { + var top; + top = getTop($element); + if (node === _this.current_item.node) { + addPosition(node, Position.NONE, top); + } else { + addPosition(node, Position.INSIDE, top); + } + if (next_node === _this.current_item.node || node === _this.current_item.node) { + return addPosition(node, Position.NONE, top); + } else { + return addPosition(node, Position.AFTER, top); + } + }; + handleOpenFolder = function(node, $element) { + if (node === _this.current_item.node) { + return false; + } + if (node.children[0] !== _this.current_item.node) { + addPosition(node, Position.INSIDE, getTop($element)); + } + return true; + }; + handleAfterOpenFolder = function(node, next_node, $element) { + if (node === _this.current_item.node || next_node === _this.current_item.node) { + return addPosition(node, Position.NONE, last_top); + } else { + return addPosition(node, Position.AFTER, last_top); + } + }; + handleClosedFolder = function(node, next_node, $element) { + var top; + top = getTop($element); + if (node === _this.current_item.node) { + return addPosition(node, Position.NONE, top); + } else { + addPosition(node, Position.INSIDE, top); + if (next_node !== _this.current_item.node) { + return addPosition(node, Position.AFTER, top); + } + } + }; + handleFirstNode = function(node, $element) { + if (node !== _this.current_item.node) { + return addPosition(node, Position.BEFORE, getTop($(node.element))); + } + }; + this.iterateVisibleNodes(handleNode, handleOpenFolder, handleClosedFolder, handleAfterOpenFolder, handleFirstNode); + hit_areas = []; + groupPositions(function(positions_in_group, top, bottom) { + var area_height, area_top, position, _i, _len; + area_height = (bottom - top) / positions_in_group.length; + area_top = top; + for (_i = 0, _len = positions_in_group.length; _i < _len; _i++) { + position = positions_in_group[_i]; + hit_areas.push({ + top: area_top, + bottom: area_top + area_height, + node: position.node, + position: position.position + }); + area_top += area_height; + } + return null; + }); + return this.hit_areas = hit_areas; + }; + + DragAndDropHandler.prototype.iterateVisibleNodes = function(handle_node, handle_open_folder, handle_closed_folder, handle_after_open_folder, handle_first_node) { + var is_first_node, iterate, + _this = this; + is_first_node = true; + iterate = function(node, next_node) { + var $element, child, children_length, i, must_iterate_inside, _i, _len, _ref3; + must_iterate_inside = (node.is_open || !node.element) && node.hasChildren(); + if (node.element) { + $element = $(node.element); + if (!$element.is(':visible')) { + return; + } + if (is_first_node) { + handle_first_node(node, $element); + is_first_node = false; + } + if (!node.hasChildren()) { + handle_node(node, next_node, $element); + } else if (node.is_open) { + if (!handle_open_folder(node, $element)) { + must_iterate_inside = false; + } + } else { + handle_closed_folder(node, next_node, $element); + } + } + if (must_iterate_inside) { + children_length = node.children.length; + _ref3 = node.children; + for (i = _i = 0, _len = _ref3.length; _i < _len; i = ++_i) { + child = _ref3[i]; + if (i === (children_length - 1)) { + iterate(node.children[i], null); + } else { + iterate(node.children[i], node.children[i + 1]); + } + } + if (node.is_open) { + return handle_after_open_folder(node, next_node, $element); + } + } + }; + return iterate(this.tree_widget.tree); + }; + + DragAndDropHandler.prototype.findHoveredArea = function(x, y) { + var area, high, low, mid, tree_offset; + tree_offset = this.tree_widget.element.offset(); + if (x < tree_offset.left || y < tree_offset.top || x > (tree_offset.left + this.tree_widget.element.width()) || y > (tree_offset.top + this.tree_widget.element.height())) { + return null; + } + low = 0; + high = this.hit_areas.length; + while (low < high) { + mid = (low + high) >> 1; + area = this.hit_areas[mid]; + if (y < area.top) { + high = mid; + } else if (y > area.bottom) { + low = mid + 1; + } else { + return area; + } + } + return null; + }; + + DragAndDropHandler.prototype.mustOpenFolderTimer = function(area) { + var node; + node = area.node; + return node.isFolder() && !node.is_open && area.position === Position.INSIDE; + }; + + DragAndDropHandler.prototype.updateDropHint = function() { + var node_element; + if (!this.hovered_area) { + return; + } + this.removeDropHint(); + node_element = this.tree_widget._getNodeElementForNode(this.hovered_area.node); + return this.previous_ghost = node_element.addDropHint(this.hovered_area.position); + }; + + DragAndDropHandler.prototype.startOpenFolderTimer = function(folder) { + var openFolder, + _this = this; + openFolder = function() { + return _this.tree_widget._openNode(folder, _this.tree_widget.options.slide, function() { + _this.refreshHitAreas(); + return _this.updateDropHint(); + }); + }; + return this.open_folder_timer = setTimeout(openFolder, 500); + }; + + DragAndDropHandler.prototype.stopOpenFolderTimer = function() { + if (this.open_folder_timer) { + clearTimeout(this.open_folder_timer); + return this.open_folder_timer = null; + } + }; + + DragAndDropHandler.prototype.moveItem = function(position_info) { + var doMove, event, moved_node, position, previous_parent, target_node, + _this = this; + if (this.hovered_area && this.hovered_area.position !== Position.NONE && this.canMoveToArea(this.hovered_area)) { + moved_node = this.current_item.node; + target_node = this.hovered_area.node; + position = this.hovered_area.position; + previous_parent = moved_node.parent; + if (position === Position.INSIDE) { + this.hovered_area.node.is_open = true; + } + doMove = function() { + _this.tree_widget.tree.moveNode(moved_node, target_node, position); + _this.tree_widget.element.empty(); + return _this.tree_widget._refreshElements(); + }; + event = this.tree_widget._triggerEvent('tree.move', { + move_info: { + moved_node: moved_node, + target_node: target_node, + position: Position.getName(position), + previous_parent: previous_parent, + do_move: doMove, + original_event: position_info.original_event + } + }); + if (!event.isDefaultPrevented()) { + return doMove(); + } + } + }; + + return DragAndDropHandler; + + })(); + + DragElement = (function() { + function DragElement(node, offset_x, offset_y, $tree) { + this.offset_x = offset_x; + this.offset_y = offset_y; + this.$element = $("<span class=\"jqtree-title jqtree-dragging\">" + node.name + "</span>"); + this.$element.css("position", "absolute"); + $tree.append(this.$element); + } + + DragElement.prototype.move = function(page_x, page_y) { + return this.$element.offset({ + left: page_x - this.offset_x, + top: page_y - this.offset_y + }); + }; + + DragElement.prototype.remove = function() { + return this.$element.remove(); + }; + + return DragElement; + + })(); + + GhostDropHint = (function() { + function GhostDropHint(node, $element, position) { + this.$element = $element; + this.node = node; + this.$ghost = $('<li class="jqtree_common jqtree-ghost"><span class="jqtree_common jqtree-circle"></span><span class="jqtree_common jqtree-line"></span></li>'); + if (position === Position.AFTER) { + this.moveAfter(); + } else if (position === Position.BEFORE) { + this.moveBefore(); + } else if (position === Position.INSIDE) { + if (node.isFolder() && node.is_open) { + this.moveInsideOpenFolder(); + } else { + this.moveInside(); + } + } + } + + GhostDropHint.prototype.remove = function() { + return this.$ghost.remove(); + }; + + GhostDropHint.prototype.moveAfter = function() { + return this.$element.after(this.$ghost); + }; + + GhostDropHint.prototype.moveBefore = function() { + return this.$element.before(this.$ghost); + }; + + GhostDropHint.prototype.moveInsideOpenFolder = function() { + return $(this.node.children[0].element).before(this.$ghost); + }; + + GhostDropHint.prototype.moveInside = function() { + this.$element.after(this.$ghost); + return this.$ghost.addClass('jqtree-inside'); + }; + + return GhostDropHint; + + })(); + + BorderDropHint = (function() { + function BorderDropHint($element) { + var $div, width; + $div = $element.children('.jqtree-element'); + width = $element.width() - 4; + this.$hint = $('<span class="jqtree-border"></span>'); + $div.append(this.$hint); + this.$hint.css({ + width: width, + height: $div.height() - 4 + }); + } + + BorderDropHint.prototype.remove = function() { + return this.$hint.remove(); + }; + + return BorderDropHint; + + })(); + + ScrollHandler = (function() { + function ScrollHandler(tree_widget) { + this.tree_widget = tree_widget; + this.previous_top = -1; + this._initScrollParent(); + } + + ScrollHandler.prototype._initScrollParent = function() { + var $scroll_parent, getParentWithOverflow, setDocumentAsScrollParent, + _this = this; + getParentWithOverflow = function() { + var css_value, css_values, parent, scroll_parent, _i, _j, _len, _len1, _ref3, _ref4; + css_values = ['overflow', 'overflow-y']; + scroll_parent = null; + _ref3 = _this.tree_widget.$el.parents(); + for (_i = 0, _len = _ref3.length; _i < _len; _i++) { + parent = _ref3[_i]; + for (_j = 0, _len1 = css_values.length; _j < _len1; _j++) { + css_value = css_values[_j]; + if ((_ref4 = $.css(parent, css_value)) === 'auto' || _ref4 === 'scroll') { + return $(parent); + } + } + } + return null; + }; + setDocumentAsScrollParent = function() { + _this.scroll_parent_top = 0; + return _this.$scroll_parent = null; + }; + if (this.tree_widget.$el.css('position') === 'fixed') { + setDocumentAsScrollParent(); + } + $scroll_parent = getParentWithOverflow(); + if ($scroll_parent && $scroll_parent.length && $scroll_parent[0].tagName !== 'HTML') { + this.$scroll_parent = $scroll_parent; + return this.scroll_parent_top = this.$scroll_parent.offset().top; + } else { + return setDocumentAsScrollParent(); + } + }; + + ScrollHandler.prototype.checkScrolling = function() { + var hovered_area; + hovered_area = this.tree_widget.dnd_handler.hovered_area; + if (hovered_area && hovered_area.top !== this.previous_top) { + this.previous_top = hovered_area.top; + if (this.$scroll_parent) { + return this._handleScrollingWithScrollParent(hovered_area); + } else { + return this._handleScrollingWithDocument(hovered_area); + } + } + }; + + ScrollHandler.prototype._handleScrollingWithScrollParent = function(area) { + var distance_bottom; + distance_bottom = this.scroll_parent_top + this.$scroll_parent[0].offsetHeight - area.bottom; + if (distance_bottom < 20) { + this.$scroll_parent[0].scrollTop += 20; + this.tree_widget.refreshHitAreas(); + return this.previous_top = -1; + } else if ((area.top - this.scroll_parent_top) < 20) { + this.$scroll_parent[0].scrollTop -= 20; + this.tree_widget.refreshHitAreas(); + return this.previous_top = -1; + } + }; + + ScrollHandler.prototype._handleScrollingWithDocument = function(area) { + var distance_top; + distance_top = area.top - $(document).scrollTop(); + if (distance_top < 20) { + return $(document).scrollTop($(document).scrollTop() - 20); + } else if ($(window).height() - (area.bottom - $(document).scrollTop()) < 20) { + return $(document).scrollTop($(document).scrollTop() + 20); + } + }; + + ScrollHandler.prototype.scrollTo = function(top) { + var tree_top; + if (this.$scroll_parent) { + return this.$scroll_parent[0].scrollTop = top; + } else { + tree_top = this.tree_widget.$el.offset().top; + return $(document).scrollTop(top + tree_top); + } + }; + + ScrollHandler.prototype.isScrolledIntoView = function(element) { + var $element, element_bottom, element_top, view_bottom, view_top; + $element = $(element); + if (this.$scroll_parent) { + view_top = 0; + view_bottom = this.$scroll_parent.height(); + element_top = $element.offset().top - this.scroll_parent_top; + element_bottom = element_top + $element.height(); + } else { + view_top = $(window).scrollTop(); + view_bottom = view_top + $(window).height(); + element_top = $element.offset().top; + element_bottom = element_top + $element.height(); + } + return (element_bottom <= view_bottom) && (element_top >= view_top); + }; + + return ScrollHandler; + + })(); + + KeyHandler = (function() { + var DOWN, LEFT, RIGHT, UP; + + LEFT = 37; + + UP = 38; + + RIGHT = 39; + + DOWN = 40; + + function KeyHandler(tree_widget) { + this.tree_widget = tree_widget; + $(document).bind('keydown.jqtree', $.proxy(this.handleKeyDown, this)); + } + + KeyHandler.prototype.deinit = function() { + return $(document).unbind('keydown.jqtree'); + }; + + KeyHandler.prototype.handleKeyDown = function(e) { + var current_node, key, moveDown, moveLeft, moveRight, moveUp, selectNode, + _this = this; + current_node = this.tree_widget.getSelectedNode(); + selectNode = function(node) { + if (node) { + _this.tree_widget.selectNode(node); + if (_this.tree_widget.scroll_handler && (!_this.tree_widget.scroll_handler.isScrolledIntoView($(node.element).find('.jqtree-element')))) { + _this.tree_widget.scrollToNode(node); + } + return false; + } else { + return true; + } + }; + moveDown = function() { + return selectNode(_this.getNextNode(current_node)); + }; + moveUp = function() { + return selectNode(_this.getPreviousNode(current_node)); + }; + moveRight = function() { + if (current_node.hasChildren() && !current_node.is_open) { + _this.tree_widget.openNode(current_node); + return false; + } else { + return true; + } + }; + moveLeft = function() { + if (current_node.hasChildren() && current_node.is_open) { + _this.tree_widget.closeNode(current_node); + return false; + } else { + return true; + } + }; + if (!current_node) { + return true; + } else { + key = e.which; + switch (key) { + case DOWN: + return moveDown(); + case UP: + return moveUp(); + case RIGHT: + return moveRight(); + case LEFT: + return moveLeft(); + } + } + }; + + KeyHandler.prototype.getNextNode = function(node, include_children) { + var next_sibling; + if (include_children == null) { + include_children = true; + } + if (include_children && node.hasChildren() && node.is_open) { + return node.children[0]; + } else { + if (!node.parent) { + return null; + } else { + next_sibling = node.getNextSibling(); + if (next_sibling) { + return next_sibling; + } else { + return this.getNextNode(node.parent, false); + } + } + } + }; + + KeyHandler.prototype.getPreviousNode = function(node) { + var previous_sibling; + if (!node.parent) { + return null; + } else { + previous_sibling = node.getPreviousSibling(); + if (previous_sibling) { + if (!previous_sibling.hasChildren() || !previous_sibling.is_open) { + return previous_sibling; + } else { + return this.getLastChild(previous_sibling); + } + } else { + if (node.parent.parent) { + return node.parent; + } else { + return null; + } + } + } + }; + + KeyHandler.prototype.getLastChild = function(node) { + var last_child; + if (!node.hasChildren()) { + return null; + } else { + last_child = node.children[node.children.length - 1]; + if (!last_child.hasChildren() || !last_child.is_open) { + return last_child; + } else { + return this.getLastChild(last_child); + } + } + }; + + return KeyHandler; + + })(); + +}).call(this); diff --git a/tools/deep_memory_profiler/visualizer/utility.js b/tools/deep_memory_profiler/visualizer/utility.js index f595fd0..cdb7243 100644 --- a/tools/deep_memory_profiler/visualizer/utility.js +++ b/tools/deep_memory_profiler/visualizer/utility.js @@ -6,22 +6,51 @@ * This function returns the first element index that >= target, when no element * in the array >= target, return array.length. * This function must be called in the shape of binarySearch(array, target). - * @param {number} target + * @param {number} target * @return {number} */ var binarySearch = function(target) { - 'use strict'; + 'use strict'; - var left = 0; - var right = this.length - 1; - while (left <= right) { - var mid = Math.floor((left + right) / 2); - if (this[mid] < target) - left = mid + 1; - else if (this[mid] > target) - right = mid - 1; - else - return mid; - } - return left; + var left = 0; + var right = this.length - 1; + while (left <= right) { + var mid = Math.floor((left + right) / 2); + if (this[mid] < target) + left = mid + 1; + else if (this[mid] > target) + right = mid - 1; + else + return mid; + } + return left; }; + +/** + * Return the intersection set of two arrays. + * @param {Array.<*>} left + * @param {Array.<*>} right + * @return {Array.<*>} + */ +var intersection = function(left, right) { + return left.reduce(function(previous, current) { + if (right.indexOf(current) != -1) + previous.push(current); + return previous; + }, []); +}; + +/** + * Return the difference set of left with right. + * Notice: difference(left, right) differentiates with difference(right, left). + * @param {Array.<*>} left + * @param {Array.<*>} right + * @return {Array.<*>} + */ +var difference = function(left, right) { + return left.reduce(function(previous, current) { + if (right.indexOf(current) == -1) + previous.push(current); + return previous; + }, []); +};
\ No newline at end of file |