I am trying to build a custom component in Sencha Touch which would display values from two stores. I am trying to build upon Ext.us.Cover which extends from Ext.data.DataView. Since I am a beginner in Sencha, I could use some help on how to accomplish binding two stores to one view.
Here is what I have currently:
/**
* #class Ext.ux.CrossCover
* #extend Ext.DataView
*
* A CrossCover represents two intersecting Cover flows
* #author Abhishek Mukherjee
*/
Ext.define('Ext.ux.CrossCover',{
extend: 'Ext.DataView',
xtype: 'cover',
config:{
/**
* #cfg {Number} selectedIndex The idx from the Store that will be active first. Only one item can be active at a
* time
* #accessor
* #evented
*/
selectedIndex: 0,
/**
* #cfg {String} itemCls
* A css class name to be added to each item element.
*/
itemCls: '',
/**
* #cfg {Boolean} preventSelectionOnItemTap
* Prevent selection when item is tapped. This is false by default.
*/
preventSelectionOnItemTap: false,
/**
* #cfg {Number} angle for cover background items
* This is the angle that not selected items are moved in space.
*/
angle: 75,
/**
* #cfg {Boolean} set to true if you want a flat representation. Defaults to false so the
* coverflow remains 3d.
*/
flat: false,
/**
* #cfg {Boolean} preventOrientationChange
* Prevent attaching refresh method to orientation change event on Ext.Viewport
*/
preventOrientationChange: false,
/**
* #cfg {Ext.data.Store} horizontalStore
* Store containing data for horizontal cover-flow
*/
horizontalStore: '',
//private
baseCls: 'ux-cover',
//private
itemBaseCls: 'ux-cover-item-inner',
//private
scrollable: null,
//private
orientationCls: undefined
},
offset: 0,
horizontalOffset: 0,
//override
initialize: function(){
console.log("Called");
//we need somehow to put the itemCls to the tpl wraper element
this.innerItemCls = this.getItemCls();
if(this.innerItemCls) {
this.setItemCls(this.innerItemCls+'-wrap');
}
this.callParent();
console.log(this.element.type);
this.element.on({
drag: 'onDrag',
dragstart: 'onDragStart',
dragend: 'onDragEnd',
scope: this
});
this.on({
painted: 'onPainted',
itemtap: 'doItemTap',
scope: this
});
if(!this.getPreventOrientationChange()){
//subscribe to orientation change on viewport
Ext.Viewport.on('orientationchange', this.refresh, this);
}
this.setItemTransformation = (this.getFlat())?this.setItemTransformFlat:this.setItemTransform3dVertical;
},
getElementConfig: function(){
console.log("getElementConfig Called");
return {
reference: 'element',
children:[{
reference: 'innerElement',
className: 'ux-cover-scroller'
}]
};
},
applyFlat: function(flat) {
return (Ext.os.is('Android')? true : flat);
},
updateOrientationCls: function(newOrientation, oldOrientation) {
var baseCls = this.getBaseCls();
if(this.element && newOrientation != oldOrientation) {
this.element.removeCls(baseCls+'-'+oldOrientation);
this.element.addCls(baseCls+'-'+newOrientation);
}
},
applyItemTpl: function(config){
console.log("applyItemTpl Called");
if(Ext.isArray(config)){
config = config.join("");
}
return new Ext.XTemplate('<div class="' + this.getItemBaseCls() + ' ' + this.getItemCls() + ' ">'+config+'</div>');
},
onPainted: function(){
this.refresh();
},
//private
getTargetEl: function(){
console.log("getTargetEl Called");
return this.innerElement;
},
onDragStart: function(){
console.log("onDragStart Called");
this.getTargetEl().dom.style.webkitTransitionDuration = "0s";
},
onDrag: function(e){
console.log("onDrag Called");
var curr = this.getOffset(),
offset,
ln = this.getViewItems().length,
selectedIndex,
delta = (e.deltaY - e.previousDeltaY);
//slow down on border conditions
selectedIndex = this.getSelectedIndex();
if ((selectedIndex === 0 && e.deltaY > 0) || (selectedIndex === ln - 1 && e.deltaY < 0)) {
delta *= 0.5;
}
offset = curr + delta;
this.setOffset(offset, true);
},
onDragEnd: function(){
console.log("onDragEnd Called");
var idx = this.getSelectedIndex(),
y = - (idx * this.gap);
this.getTargetEl().dom.style.webkitTransitionDuration = "0.4s";
//this.setOffset(x);
this.applySelectedIndex(idx);
},
doItemTap: function(cover, index, item, evt){
if(!this.getPreventSelectionOnItemTap() && this.getSelectedIndex() !== index){
this.setSelectedIndex(index);
}
},
getSelectedIndex: function(){
console.log("getSelectedIndex Called");
var idx, ln;
if(this.isRendered()){
ln = this.getViewItems().length;
idx = - Math.round(this.getOffset() / this.gap);
this.selectedIndex = Math.min(Math.max(idx, 0), ln - 1);
}
return this.selectedIndex;
},
applySelectedIndex: function(idx){
console.log("applySelectedINdex Called");
if(this.isRendered()){
this.updateOffsetToIdx(idx);
this.selectWithEvent(this.getStore().getAt(idx));
}else{
this.selectedIndex = idx;
}
// apply horizontal index
this.applySelectedHorizontalIndex(idx);
},
applySelectedHorizontalIndex: function(idx){
console.log("applySelectedHorizontalIndex Called");
if(this.isRendered()){
this.updateHorizontalOffsetToIdx(idx);
this.selectWithEvent(this.getHorizontalStore().data.items[idx]);
}else{
this.selectedIndex = idx;
}
},
updateOffsetToIdx: function(idx){
console.log("updateOffsetToIdx Called");
var ln = this.getViewItems().length,
offset;
idx = Math.min(Math.max(idx, 0), ln - 1);
offset= -(idx * this.gap);
this.setOffset(offset);
},
updateHorizontalOffsetToIdx: function(idx){
console.log("updateHorizontalOffsetToIdx Called");
var ln = this.getHorizontalStore().data.items.length,
offset;
// console.log("Number of horizontal items is ")
idx = Math.min(Math.max(idx, 0), ln - 1);
offset= -(idx * this.gap);
this.setHorizontalOffset(offset);
},
setOffset: function(offset){
console.log("setOffset Called");
// Set offset for the vertical cover
var items = this.getViewItems(),
idx = 0,
l = items.length,
item;
this.offset = offset;
// Changing the translation to Y-axis
this.getTargetEl().dom.style.webkitTransform = "translate3d( 0," + offset + "px, 0)";
for(;idx<l;idx++){
item = Ext.get(items[idx]);
this.setItemTransformation(item, idx, offset);
}
},
setHorizontalOffset: function(offset){
console.log("setHorizontalOffset Called");
// Set offset for the horizontal cover
var hitems = this.getHorizontalStore().data.items,
hidx = 0,
hl = hitems.length,
hitem;
console.log("horizontal store items :"+hl);
this.horizontalOffset = offset;
// Changing the translation to X-axis
this.getTargetEl().dom.style.webkitTransform = "translate3d( " + offset + "px, 0, 0)";
for(;hidx<hl;hidx++){
hitem = document.createElement("div");
var newContent = document.createTextNode(hitems[hidx].data.preferredName);
hitem.appendChild(newContent);
console.log("hitem is"+hitem);
this.setItemTransform3dHorizontal(hitem, hidx, offset);
}
},
getOffset: function(){
console.log("getOffset Called");
return this.offset;
},
getHorizontalOffset: function(){
console.log(" getHorizontaloffset Called");
return this.horizontalOffset;
},
getBaseItemBox: function(containerBox){
console.log("getBaseItemBox Called");
var cH = containerBox.height,
cW = containerBox.width,
sizeFactor = (cW > cH) ? 0.68 : 0.52,
h, w;
h = w = Math.min(containerBox.width, containerBox.height) * sizeFactor;
return {
top: (containerBox.height - w) / 2 ,
height: h * 1.5,
width: w,
left: (containerBox.width - w) / 2
};
},
setBoundaries: function(itemBox){
console.log("setBoundaries Called");
var w = itemBox.width;
if(this.getFlat()){
this.gap = w * 1.1;
this.threshold = this.gap / 3;
this.delta = w * 0.2;
} else {
this.gap = w / 3;
this.threshold = this.gap / 2;
this.delta = w * 0.4;
}
},
setItemTransformation: Ext.emptyFn,
setItemTransform3dVertical: function(item, idx, offset){
console.log("type of item is:"+ item.type);
console.log("Called setItemTransform3dVertical"+offset);
var x = idx * this.gap,
ix = x + offset,
transf = "";
if(ix < this.threshold && ix >= - this.threshold){
// Changing the translation to Y-axis
transf = "translate3d( 0,"+x+"px, 150px)";
this.selectedIndex = idx;
}else if(ix > 0){
// Changing the rotation to x-axis
transf = "translate3d( 0 ,"+(x+this.delta)+"px, 0 ) rotateX(-"+this.getAngle()+"deg)";
}else{
// Changing the rotation to x-axis
transf = "translate3d( 0, "+(x-this.delta)+"px, 0 ) rotateX("+this.getAngle()+"deg)";
}
item.dom.style.webkitTransform = transf;
},
setItemTransform3dHorizontal: function(item, idx, offset){
console.log("Called setItemTransform3dHorizontal"+offset);
console.log("item receieved::"+item + " idx is "+idx+" offset is "+offset);
var x = idx * this.gap,
ix = x + offset,
transf = "";
if(ix < this.threshold && ix >= - this.threshold){
// Changing the translation to X-axis
transf = "translate3d( "+x+"px, 0, 150px)";
this.selectedIndex = idx;
}else if(ix > 0){
// Changing the rotation to Y-axis
transf = "translate3d( "+(x+this.delta)+"px, 0 , 0 ) rotateY(-"+this.getAngle()+"deg)";
}else{
// Changing the rotation to Y-axis
transf = "translate3d( "+(x-this.delta)+"px, 0, 0 ) rotateY("+this.getAngle()+"deg)";
}
item.style.webkitTransform = transf;
},
setItemTransformFlat: function(item, idx, offset){
var x = idx * this.gap,
ix = x + offset,
transf = "";
if(ix < this.threshold && ix >= - this.threshold){
// Changing the translation to Y-axis
transf = "translate3d( 0, "+x+"px, 150px)";
this.selectedIndex = idx;
}else if(ix > 0){
transf = "translate3d("+(x+this.delta)+"px, 0, 0)";
}else{
transf = "translate3d("+(x-this.delta)+"px, 0, 0)";
}
item.dom.style.webkitTransform = transf;
},
doRefresh: function(me){
console.log("doRefresh Called");
var container = me.container,
items, idx = 0, l,
orientation = Ext.Viewport.getOrientation();
this.setOrientationCls(orientation);
this.callParent([me]);
items = container.getViewItems();
l = items.length;
this.itemBox = this.getBaseItemBox(this.element.getBox());
this.setBoundaries(this.itemBox);
for(;idx<l;idx++){
this.resizeItem(items[idx]);
}
this.setSelectedIndex(this.selectedIndex);
// Refresh the horizontal cover flow
this.refreshHorizontalCover();
},
refreshHorizontalCover: function(){
console.log("refreshHorizontalCover Called");
//var container = me.container,
var hitems, hidx = 0, hl;
// orientation = Ext.Viewport.getOrientation();
//this.setOrientationCls(orientation);
//this.callParent([me]);
hitems = this.getHorizontalStore().data.items;
hl = hitems.length;
this.itemBox = this.getBaseItemBox(this.element.getBox());
this.setBoundaries(this.itemBox);
for(;hidx<hl;hidx++){
var item = document.createElement("div");
var newContent = document.createTextNode(hitems[hidx].data.preferredName);
item.appendChild(newContent); //add the text node to the newly created div.
this.resizeHorizontalItem(item);
}
this.setSelectedIndex(this.selectedIndex);
},
resizeItem: function(element){
console.log("resizeItem Called");
var itemBox = this.itemBox,
item = Ext.get(element);
item.setBox(itemBox);
/**
itemBox has an extra long in height to avoid reflection opacity over other items
I need to create a wrapper element with same bg to avoid that issue.
*/
item.down('.'+this.getItemBaseCls()).setBox({height: itemBox.height/1.5, width: itemBox.width});
},
resizeHorizontalItem: function(element){
console.log("resizeHorizontalItem Called");
var itemBox = this.itemBox,
item = Ext.get(element);
item.setBox(itemBox);
/**
itemBox has an extra long in height to avoid reflection opacity over other items
I need to create a wrapper element with same bg to avoid that issue.
*/
//item.down('.'+this.getItemBaseCls()).setBox({height: itemBox.height/1.5, width: itemBox.width});
},
//override
onStoreUpdate: function(store, record, newIndex, oldIndex) {
var me = this,
container = me.container,
item;
oldIndex = (typeof oldIndex === 'undefined') ? newIndex : oldIndex;
if (oldIndex !== newIndex) {
container.moveItemsToCache(oldIndex, oldIndex);
container.moveItemsFromCache([record]);
}
else {
item = container.getViewItems()[newIndex];
// Bypassing setter because sometimes we pass the same record (different data)
container.updateListItem(record, item);
me.resizeItem(item);
}
}
});
Idea here is to have two cover flows, one horizontal and other vertical intersecting in middle. I can display one cover flow, but the other one is not getting displayed ( or is out of view). Since for one cover I can use properties from DataView , displaying the first is relatively easier than the second. I could help some suggestion on how to display the second.
I would appreciate any help with this. Thanks a lot for your time.
Data View in Ext is bound to one store. I think that much less coding would be to take two views and put them in a layout.
I solved it by super-imposing two separate views on top of each other and passing on the event from the top view to the underlying view when the drag was on a particular direction.
Related
I'm trying to make this code work with more than one id and i can't make it work.
I have tried with querySelectorAll, but with not succes.
I also read this article, but none of the options worked for me
Can anyone help me?
This is the code:
<script>
function Scroller(options) {
this.svg = options.el;
//Animation will end when the end is at which point of othe page. .9 is at about 90% down the page/
// .1 is 10% from the top of the page. Default is middle of the page.
this.animationBounds = {};
this.animationBounds.top = options.startPoint || .5;
this.animationBounds.bottom = options.endPoint || .5;
this.animationBounds.containerBounds = this.svg.getBoundingClientRect();
this.start = this.getPagePosition('top');
this.end = this.getPagePosition('bottom');
this.svgLength = this.svg.getTotalLength();
this.svg.style.strokeDasharray = this.svgLength;
this.animateLine();
window.addEventListener('scroll', this.animateLine.bind(this));
}
Scroller.prototype.getPagePosition = function (position) {
//These positions are all relative to the current window. So they top of the page will be negative and thus need to be
//subtracted to get a positive number
var distanceFromPageTop = document.body.getBoundingClientRect().top;
var divPosition = this.animationBounds.containerBounds[position];
var startPointInCurrentWindow = window.innerHeight * this.animationBounds[position];
return divPosition - distanceFromPageTop - startPointInCurrentWindow;
};
Scroller.prototype.animateLine = function () {
this.currentVisiblePosition = window.pageYOffset;
if (this.currentVisiblePosition < this.start) {
this.svg.style.strokeDashoffset = this.svgLength;
}
if (this.currentVisiblePosition > this.end) {
this.svg.style.strokeDashoffset = '0px';
}
if (this.currentVisiblePosition > this.start && this.currentVisiblePosition < this.end) {
this.svg.style.strokeDashoffset = this.distanceRemaining() * this.pixelsPerVerticalScroll() + 'px';
}
};
Scroller.prototype.distanceRemaining = function () {
return this.end - this.currentVisiblePosition;
};
Scroller.prototype.pixelsPerVerticalScroll = function () {
this.verticalDistance = this.end - this.start;
return this.svgLength / this.verticalDistance;
};
new Scroller({
'el': **document.getElementById('line')**,
'startPoint': .8,
'endPoint': .5
});
</script>
Loop over all the elements matching the selector.
var lines = document.querySelectorAll("#line, #line1, #line2");
for (var i = 0; i < lines.length; i++) {
new Scroller({
el: lines[i],
startPoint: .8,
endPoint: .5
});
}
This is a query about something that popped up while I was experimenting with the canvas element via javascript. I wanted to have an array of points that formed a gradient which moved with time, which works perfectly apart from a bizarre pattern that comes up (only after the first wave or more), which also changes according to the number of columns and rows in the canvas (changing the size of the points just makes the patterns bigger or smaller, it's always on the same pixels.
Here's a little demo of what I mean with a bit of interface for you to mess around with, an example of the changing patterns is if the number of rows is changed to 0.75x the number of columns from the original (i.e. 40 columns, 30 rows).
http://codepen.io/zephyr/pen/GpwwWB
Javascript:
String.prototype.hexToRGBA = function(a) {
function cutHex(h) {
return (h.charAt(0) == "#") ? h.substring(1, 7) : h
}
var r = parseInt((cutHex(this)).substring(0, 2), 16);
var g = parseInt((cutHex(this)).substring(2, 4), 16);
var b = parseInt((cutHex(this)).substring(4, 6), 16);
return 'rgba(' + r.toString() + ',' + g.toString() + ',' + b.toString() + ',' + a.toString() + ')';
}
CanvasRenderingContext2D.prototype.clearDrawRect = function(shape) {
this.clearRect(shape.position.x, shape.position.y, shape.size, shape.size);
this.fillStyle = shape.color.base;
this.fillRect(shape.position.x, shape.position.y, shape.size, shape.size);
}
CanvasRenderingContext2D.prototype.render = function(render) {
(function animate() {
requestAnimationFrame(animate);
render();
})();
}
CanvasRenderingContext2D.prototype.renderAndThrottleFpsAt = function(fps, render) {
var fpsInterval, startTime, now, then, elapsed;
fpsInterval = 1000 / fps;
then = Date.now();
startTime = then;
(function animate() {
requestAnimationFrame(animate);
now = Date.now();
elapsed = now - then;
if (elapsed > fpsInterval) {
then = now - (elapsed % fpsInterval);
render();
}
})();
}
CanvasRenderingContext2D.prototype.pool = {};
CanvasRenderingContext2D.prototype.parsePoint = function(x, y, s, c) {
return {
color: c,
position: {
x: x,
y: y
},
size: s
}
}
CanvasRenderingContext2D.prototype.fillPointsPool = function(size, cols, rows, color) {
var i = cols;
var j = rows;
while(i--){
while(j--){
var x = i * size;
var y = j * size;
var a = (i * j) / (cols * rows);
var c = {
hex: color,
alpha: a,
dir: 1
};
if (typeof this.pool.points == 'undefined') {
this.pool.points = [this.parsePoint(x, y, size, c)];
} else {
this.pool.points.push(this.parsePoint(x, y, size, c));
}
}
j = rows;
}
}
CanvasRenderingContext2D.prototype.updatePointsPool = function(size, cols, rows, color) {
this.pool.points = [];
this.clearRect(0,0,this.canvas.width,this.canvas.height);
this.fillPointsPool(size, cols, rows, color);
}
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
// Populate Points
var size = document.getElementById('size');
var cols = document.getElementById('cols');
var rows = document.getElementById('rows');
var color = document.getElementById('color');
ctx.fillPointsPool(size.value, cols.value, rows.value, color.value);
size.oninput = function(){
ctx.updatePointsPool(this.value, cols.value, rows.value, color.value);
}
cols.oninput = function(){
ctx.updatePointsPool(size.value, this.value, rows.value, color.value);
}
rows.oninput = function(){
ctx.updatePointsPool(size.value, cols.value, this.value, color.value);
}
color.oninput = function(){
ctx.updatePointsPool(size.value, cols.value, rows.value, this.value);
}
ctx.renderAndThrottleFpsAt(60, function(){
var i = 0;
var len = ctx.pool.points.length;
while (i<len) {
var point = ctx.pool.points[i];
// Change alpha for wave
var delta = 0.01;
point.color.alpha = point.color.alpha + (delta * point.color.dir);
if (point.color.alpha > 1) {
point.color.dir = -1;
} else if (point.color.alpha <= 0) {
point.color.dir = 1;
}
// Calculate rgba value with new alpha
point.color.base = point.color.hex.hexToRGBA(point.color.alpha);
ctx.clearDrawRect(point);
i++;
}
});
Do any of you have an idea of what's causing the pattern to appear, and any suggestions on a fix for this?
Note: I will be changing the updatePointsPool function
You are forgetting to clamp your alpha values when you change the direction. The small error in the alpha value accumulates slowly producing the unwanted artifacts you see as the animation progresses.
To fix add the top and bottom limits to alpha in the code just after you add delta direction to alpha.
if (point.color.alpha > 1) {
point.color.alpha = 1; // clamp alpha max
point.color.dir = -1;
} else if (point.color.alpha <= 0) {
point.color.alpha = 0; // clamp alpha min
point.color.dir = 1;
}
I am creating a level selection screen to my game, and I found that I get the following error.
Uncaught TypeError: Cannot read property 'Tween' of undefined, the error comes a line that can be found in the "arrowClicked" function at the bottom of the script.
UshanGame.Selection = function(game){};
var thumbRows = 2;
var thumbCols = 3;
var thumbWidth = 128;
var thumbHeight = 128;
var thumbSpacing = 3;
var levelThumbsGroup;
var currentPage = 0;
var leftArrow;
var rightArrow;
var pages;
UshanGame.Selection.prototype = {
create: function(){
console.log("%c ✔✔ Level Selection Ready! ✔✔", "color:red;");
// array with finished levels and stars collected.
var starsArray = [1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2];
// how many pages are needed to show all levels?
pages = starsArray.length /(thumbRows * thumbCols);
leftArrow = this.add.button(50, 420, "level_arrows", this.arrowClicked);
leftArrow.anchor.setTo(0.5);
leftArrow.frame = 0;
leftArrow.alpha = 0.3;
rightArrow = this.add.button(270, 420, "level_arrows", this.arrowClicked);
rightArrow.anchor.setTo(0.5);
rightArrow.frame = 1;
// creation of the thumbails group
levelThumbsGroup = this.add.group();
// determining level thumbnails width and height for each page
var levelLength = thumbWidth * thumbCols + thumbSpacing * (thumbCols - 1);
var levelHeight = thumbWidth * thumbRows + thumbSpacing * (thumbRows - 1);
// looping through each page
for(var l = 0; l < pages; l++){
// horizontal offset to have level thumbnails horizontally centered in the page
var offsetX = (this.width-levelLength)/ 2 + game.width * l;
// I am not interested in having level thumbnails vertically centered in the page, but
// if you are, simple replace my "20" with
// (game.height-levelHeight)/2
var offsetY = 20;
// looping through each level thumbnails
for(var i = 0; i < thumbRows; i ++){
for(var j = 0; j < thumbCols; j ++){
var levelNumber = i * thumbCols + j + l *(thumbRows * thumbCols);
var levelThumb = this.add.button(offsetX + j * (thumbWidth + thumbSpacing), offsetY + i * (thumbHeight + thumbSpacing), "levels", this.thumbClicked, this);
levelThumb.frame=starsArray[levelNumber];
levelThumb.levelNumber = levelNumber + 1;
levelThumbsGroup.add(levelThumb);
// if the level is playable, also write level number
if(starsArray[levelNumber] < 4){
var style = {
font: "18px Arial",
fill: "#ffffff"
};
var levelText = this.add.text(levelThumb.x + 5, levelThumb.y + 5, levelNumber + 1,style);
levelText.setShadow(2, 2, 'rgba(0,0,0,0.5)', 1);
levelThumbsGroup.add(levelText);
}
}
}
}
},
arrowClicked: function(button){
if(button.frame == 1 && currentPage < pages - 1){
leftArrow.alpha = 1;
currentPage++;
if(currentPage == pages - 1){
button.alpha = 0.3;
}
var buttonsTween = game.add.tween(levelThumbsGroup);
buttonsTween.to({
x: currentPage * game.width * -1
}, 500, Phaser.Easing.Cubic.None);
buttonsTween.start();
}
if(button.frame==0 && currentPage>0){
rightArrow.alpha = 1;
currentPage--;
if(currentPage == 0){
button.alpha = 0.3;
}
var buttonsTween = game.add.tween(levelThumbsGroup);
buttonsTween.to({
x: currentPage * game.width * -1
}, 400, Phaser.Easing.Cubic.None);
buttonsTween.start();
}
}
};
try with
this.add.tween
instead of
game.add.tween
game.add is probably never defined so it's classified as undefined.
game is obviously defined as it's passed into the function but the message is stating that it does not contain a property .add so you're unable to call a method tween of that undefined property.
Therefor you're receiving this error; undefined does not contain tween so you must define game.add within the game object which contains tween properly in order for your code to work.
I am working in SVG editor 2.7 version. Here I need to change selection area for path tags in SVG using Javascript.
For example refer this image below:
Here I analysis about SVG editor, <g> tags will generated automatically and create only one selection area which is provided only rectangular shape only.
Here I need to change selection area for path section.
Below code is based on select.js :
(function() {'use strict';
if (!svgedit.select) {
svgedit.select = {};
}
var svgFactory_;
var config_;
var selectorManager_; // A Singleton
var gripRadius = svgedit.browser.isTouch() ? 5 : 4;
// Class: svgedit.select.Selector
// Private class for DOM element selection boxes
//
// Parameters:
// id - integer to internally indentify the selector
// elem - DOM element associated with this selector
svgedit.select.Selector = function(id, elem) {
// this is the selector's unique number
this.id = id;
// this holds a reference to the element for which this selector is being used
this.selectedElement = elem;
// this is a flag used internally to track whether the selector is being used or not
this.locked = true;
// this holds a reference to the <g> element that holds all visual elements of the selector
this.selectorGroup = svgFactory_.createSVGElement({
'element': 'g',
'attr': {'id': ('selectorGroup' + this.id)}
});
// this holds a reference to the path rect
this.selectorRect = this.selectorGroup.appendChild(
svgFactory_.createSVGElement({
'element': 'path',
'attr': {
'id': ('selectedBox' + this.id),
'fill': 'none',
'stroke': '#22C',
'stroke-width': '1',
'stroke-dasharray': '5,5',
// need to specify this so that the rect is not selectable
'style': 'pointer-events:none'
}
})
);
// this holds a reference to the grip coordinates for this selector
this.gripCoords = {
'nw': null,
'n' : null,
'ne': null,
'e' : null,
'se': null,
's' : null,
'sw': null,
'w' : null
};
this.reset(this.selectedElement);
};
// Function: svgedit.select.Selector.reset
// Used to reset the id and element that the selector is attached to
//
// Parameters:
// e - DOM element associated with this selector
svgedit.select.Selector.prototype.reset = function(e) {
this.locked = true;
this.selectedElement = e;
this.resize();
this.selectorGroup.setAttribute('display', 'inline');
};
// Function: svgedit.select.Selector.updateGripCursors
// Updates cursors for corner grips on rotation so arrows point the right way
//
// Parameters:
// angle - Float indicating current rotation angle in degrees
svgedit.select.Selector.prototype.updateGripCursors = function(angle) {
var dir,
dir_arr = [],
steps = Math.round(angle / 45);
if (steps < 0) {steps += 8;}
for (dir in selectorManager_.selectorGrips) {
dir_arr.push(dir);
}
while (steps > 0) {
dir_arr.push(dir_arr.shift());
steps--;
}
var i = 0;
for (dir in selectorManager_.selectorGrips) {
selectorManager_.selectorGrips[dir].setAttribute('style', ('cursor:' + dir_arr[i] + '-resize'));
i++;
}
};
// Function: svgedit.select.Selector.showGrips
// Show the resize grips of this selector
//
// Parameters:
// show - boolean indicating whether grips should be shown or not
svgedit.select.Selector.prototype.showGrips = function(show) {
// TODO: use suspendRedraw() here
var bShow = show ? 'inline' : 'none';
selectorManager_.selectorGripsGroup.setAttribute('display', bShow);
var elem = this.selectedElement;
this.hasGrips = show;
if (elem && show) {
this.selectorGroup.appendChild(selectorManager_.selectorGripsGroup);
this.updateGripCursors(svgedit.utilities.getRotationAngle(elem));
}
};
// Function: svgedit.select.Selector.resize
// Updates the selector to match the element's size
svgedit.select.Selector.prototype.resize = function() {
console.log(this.selectorGroup);
var selectedBox = this.selectorRect,
selectorRubberBand = this.getRubberBandBox,
mgr = selectorManager_,
selectedGrips = mgr.selectorGrips,
selected = this.selectedElement,
sw = selected.getAttribute('stroke-width'),
current_zoom = svgFactory_.currentZoom();
$('#current_zoom').val(current_zoom);
console.log(mgr);
var offset = 1/current_zoom;
if (selected.getAttribute('stroke') !== 'none' && !isNaN(sw)) {
offset += (sw/2);
}
var tagName = selected.tagName;
if (tagName === 'text') {
offset += 2/current_zoom;
}
// loop and transform our bounding box until we reach our first rotation
var tlist = svgedit.transformlist.getTransformList(selected);
var m = svgedit.math.transformListToTransform(tlist).matrix;
// This should probably be handled somewhere else, but for now
// it keeps the selection box correctly positioned when zoomed
m.e *= current_zoom;
m.f *= current_zoom;
var bbox = svgedit.utilities.getBBox(selected);
if (tagName === 'g' && !$.data(selected, 'gsvg')) {
// The bbox for a group does not include stroke vals, so we
// get the bbox based on its children.
var stroked_bbox = svgFactory_.getStrokedBBox(selected.childNodes);
if (stroked_bbox) {
bbox = stroked_bbox;
}
}
// apply the transforms
var l = bbox.x, t = bbox.y, w = bbox.width, h = bbox.height;
bbox = {x:l, y:t, width:w, height:h};
var x_varadd=70;
var y_varadd=40;
var cen_x=l;
var cen_y=t;
var dummyvarrr=0;
var gethide_curtselid=$("#hide_curtselid").val();
var gethide_curtselwidth=$("#hide_curtdrawx_width").val(w);
var gethide_curtselheight=$("#hide_curtdrawy_height").val(h);
$("#hide_curtdrawx_id").val(l);
$("#hide_curtdrawy_id").val(t);
var gethide_curtselid12=$("#hide_curtdrawx_id").val();
var gethide_curtselid13=$("#hide_curtdrawy_id").val();
offset *= current_zoom;
var nbox = svgedit.math.transformBox(l*current_zoom, t*current_zoom, w*current_zoom, h*current_zoom, m),
aabox = nbox.aabox,
nbax = aabox.x - offset,
nbay = aabox.y - offset,
nbaw = aabox.width + (offset * 2),
nbah = aabox.height + (offset * 2);
var gethide_curtselwidth=$("#hide_curtdrawx_width").val(nbaw);
var gethide_curtselheight=$("#hide_curtdrawy_height").val(nbah);
$("#hide_curtdrawx_id").val(nbax);
$("#hide_curtdrawy_id").val(nbay);
// now if the shape is rotated, un-rotate it
var cx = nbax + nbaw/2,
cy = nbay + nbah/2;
var angle = svgedit.utilities.getRotationAngle(selected);
if (angle) {
var rot = svgFactory_.svgRoot().createSVGTransform();
rot.setRotate(-angle, cx, cy);
var rotm = rot.matrix;
nbox.tl = svgedit.math.transformPoint(nbox.tl.x, nbox.tl.y, rotm);
nbox.tr = svgedit.math.transformPoint(nbox.tr.x, nbox.tr.y, rotm);
nbox.bl = svgedit.math.transformPoint(nbox.bl.x, nbox.bl.y, rotm);
nbox.br = svgedit.math.transformPoint(nbox.br.x, nbox.br.y, rotm);
// calculate the axis-aligned bbox
var tl = nbox.tl;
var minx = tl.x,
miny = tl.y,
maxx = tl.x,
maxy = tl.y;
var min = Math.min, max = Math.max;
minx = min(minx, min(nbox.tr.x, min(nbox.bl.x, nbox.br.x) ) ) - offset;
miny = min(miny, min(nbox.tr.y, min(nbox.bl.y, nbox.br.y) ) ) - offset;
maxx = max(maxx, max(nbox.tr.x, max(nbox.bl.x, nbox.br.x) ) ) + offset;
maxy = max(maxy, max(nbox.tr.y, max(nbox.bl.y, nbox.br.y) ) ) + offset;
nbax = minx;
nbay = miny;
nbaw = (maxx-minx);
nbah = (maxy-miny);
}
var sr_handle = svgFactory_.svgRoot().suspendRedraw(100);
var dstr = 'M' + nbax + ',' + nbay
+ ' L' + (nbax+nbaw) + ',' + nbay
+ ' ' + (nbax+nbaw) + ',' + (nbay+nbah)
+ ' ' + nbax + ',' + (nbay+nbah) + 'z';
// alert(dstr);
selectedBox.setAttribute('d', dstr);
var xform = angle ? 'rotate(' + [angle, cx, cy].join(',') + ')' : '';
this.selectorGroup.setAttribute('transform', xform);
// TODO(codedread): Is this if needed?
// if (selected === selectedElements[0]) {
this.gripCoords = {
'nw': [nbax, nbay],
'ne': [nbax+nbaw, nbay],
'sw': [nbax, nbay+nbah],
'se': [nbax+nbaw, nbay+nbah],
'n': [nbax + (nbaw)/2, nbay],
'w': [nbax, nbay + (nbah)/2],
'e': [nbax + nbaw, nbay + (nbah)/2],
's': [nbax + (nbaw)/2, nbay + nbah]
};
var width_rect = nbaw-offset;
var height_rect = nbah-offset;
var conversation =$('#measurement_det').val();
// alert(conversation);
if(conversation =='Meter') {
var width_gets = (parseFloat(width_rect)/100).toFixed(2)+'m';
var height_gets = (parseFloat(height_rect)/100).toFixed(2)+'m';
}
if(conversation =='Feet') {
var width_gets = (parseFloat(width_rect)/100)*3.2808399;
var feet_w = Math.floor(width_gets);
var inches_w = Math.round((width_gets - feet_w) * 12);
var width_gets = feet_w + "'" + inches_w + '"';
var width_gets = width_gets+'ft';
var height_gets = (parseFloat(height_rect)/100)*3.2808399;
var feet_h = Math.floor(height_gets);
var inches_h = Math.round((height_gets - feet_h) * 12);
var height_gets = feet_h + "'" + inches_h + '"';
var height_gets = height_gets+'ft';
}
if(selected.tagName=='rect')
{
var rect_text_id=selected.parentNode.id;
var rect_text_id1=rect_text_id.split('_');
}
if(selected.tagName=='g')
{
var sslice = selected.childNodes;
if(sslice[0].tagName=='rect')
{
var rect_text_id=sslice[0].parentNode.id;
var rect_text_id1=rect_text_id.split('_');
}
}
var dir;
for (dir in this.gripCoords) {
var coords = this.gripCoords[dir];
if((dir =='n') || (dir =='e') || (dir =='s') || (dir =='w')) {
/* selectedGrips[dir].setAttribute('cx', coords[0]);
selectedGrips[dir].setAttribute('cy', coords[1]); */
/* $('#selectorGrip_resize_n').html('');
$('#selectorGrip_resize_s').html(''); */
if(selected.childNodes[0] && selected.childNodes[0].tagName=='rect')
{
$('#meter_range_rect'+rect_text_id1[3]+'_n').text(width_gets);
$('#meter_range_rect'+rect_text_id1[3]+'_n').attr({'font-weight':''});
$('#meter_range_rect'+rect_text_id1[3]+'_s').text(width_gets);
$('#meter_range_rect'+rect_text_id1[3]+'_s').attr({'font-weight':''});
}
if(dir =='w') {
selectedGrips[dir].setAttribute('transform', 'rotate(-90,'+coords[0]+','+coords[1]+')');
// $('#selectorGrip_resize_w').html('');
if(selected.childNodes[0] && selected.childNodes[0].tagName=='rect')
{
$('#meter_range_rect'+rect_text_id1[3]+'_w').text(height_gets);
$('#meter_range_rect'+rect_text_id1[3]+'_w').attr({'font-weight':''});
}
}
if(dir =='e') {
selectedGrips[dir].setAttribute('transform', 'rotate(-90,'+coords[0]+','+coords[1]+')');
// $('#selectorGrip_resize_e').html('');
if(selected.childNodes[0] && selected.childNodes[0].tagName=='rect')
{
$('#meter_range_rect'+rect_text_id1[3]+'_e').text(height_gets);
$('#meter_range_rect'+rect_text_id1[3]+'_e').attr({'font-weight':''});
}
}
selectedGrips[dir].setAttribute('cx', coords[0]);
selectedGrips[dir].setAttribute('cy', coords[1]);
}
else {
selectedGrips[dir].setAttribute('cx', coords[0]);
selectedGrips[dir].setAttribute('cy', coords[1]);
}
}
// we want to go 20 pixels in the negative transformed y direction, ignoring scale
mgr.rotateGripConnector.setAttribute('x1', nbax + (nbaw)/2);
mgr.rotateGripConnector.setAttribute('y1', nbay);
mgr.rotateGripConnector.setAttribute('x2', nbax + (nbaw)/2);
mgr.rotateGripConnector.setAttribute('y2', nbay - (gripRadius*5));
mgr.rotateGrip.setAttribute('cx', nbax + (nbaw)/2);
mgr.rotateGrip.setAttribute('cy', nbay - (gripRadius*5));
// }
svgFactory_.svgRoot().unsuspendRedraw(sr_handle);
};
// Class: svgedit.select.SelectorManager
svgedit.select.SelectorManager = function() {
// this will hold the <g> element that contains all selector rects/grips
this.selectorParentGroup = null;
// this is a special rect that is used for multi-select
this.rubberBandBox = null;
// this will hold objects of type svgedit.select.Selector (see above)
this.selectors = [];
// this holds a map of SVG elements to their Selector object
this.selectorMap = {};
// this holds a reference to the grip elements
this.selectorGrips = {
'nw': null,
'n' : null,
'ne': null,
'e' : null,
'se': null,
's' : null,
'sw': null,
'w' : null
};
this.selectorGripsGroup = null;
this.rotateGripConnector = null;
this.rotateGrip = null;
this.initGroup();
};
// Function: svgedit.select.SelectorManager.initGroup
// Resets the parent selector group element
svgedit.select.SelectorManager.prototype.initGroup = function() {
// remove old selector parent group if it existed
if (this.selectorParentGroup && this.selectorParentGroup.parentNode) {
this.selectorParentGroup.parentNode.removeChild(this.selectorParentGroup);
}
// create parent selector group and add it to svgroot
this.selectorParentGroup = svgFactory_.createSVGElement({
'element': 'g',
'attr': {'id': 'selectorParentGroup'}
});
this.selectorGripsGroup = svgFactory_.createSVGElement({
'element': 'g',
'attr': {'display': 'none'}
});
this.selectorParentGroup.appendChild(this.selectorGripsGroup);
svgFactory_.svgRoot().appendChild(this.selectorParentGroup);
this.selectorMap = {};
this.selectors = [];
this.rubberBandBox = null;
// add the corner grips
var dir;
for (dir in this.selectorGrips) {
var grip = svgFactory_.createSVGElement({
'element': 'circle',
'attr': {
'id': ('selectorGrip_resize_' + dir),
'fill': '#04739E',
'r': gripRadius,
'style': ('cursor:' + dir + '-resize'),
// This expands the mouse-able area of the grips making them
// easier to grab with the mouse.
// This works in Opera and WebKit, but does not work in Firefox
// see https://bugzilla.mozilla.org/show_bug.cgi?id=500174
'stroke-width': 2,
'pointer-events': 'all'
}
});
/* if((dir =='n') || (dir =='e') || (dir =='s') || (dir =='w')) {
var grip = svgFactory_.createSVGElement({
'element': 'text',
'attr': {
'id': ('selectorGrip_resize_' + dir),
'fill': '#3F7F00',
// 'r': gripRadius,
'style': ('cursor:' + dir + '-resize'),
// This expands the mouse-able area of the grips making them
// easier to grab with the mouse.
// This works in Opera and WebKit, but does not work in Firefox
// see https://bugzilla.mozilla.org/show_bug.cgi?id=500174
'stroke-width': 2,
'pointer-events': 'all'
}
});
grip.textContent = '';
}else {
var grip = svgFactory_.createSVGElement({
'element': 'circle',
'attr': {
'id': ('selectorGrip_resize_' + dir),
'fill': '#04739E',
'r': gripRadius,
'style': ('cursor:' + dir + '-resize'),
// This expands the mouse-able area of the grips making them
// easier to grab with the mouse.
// This works in Opera and WebKit, but does not work in Firefox
// see https://bugzilla.mozilla.org/show_bug.cgi?id=500174
'stroke-width': 2,
'pointer-events': 'all'
}
});
}
*/
$.data(grip, 'dir', dir);
$.data(grip, 'type', 'resize');
this.selectorGrips[dir] = this.selectorGripsGroup.appendChild(grip);
}
// add rotator elems
this.rotateGripConnector = this.selectorGripsGroup.appendChild(
svgFactory_.createSVGElement({
'element': 'line',
'attr': {
'id': ('selectorGrip_rotateconnector'),
'stroke': '#22C',
'stroke-width': '0'
}
})
);
this.rotateGrip = this.selectorGripsGroup.appendChild(
svgFactory_.createSVGElement({
'element': 'circle',
'attr': {
'id': 'selectorGrip_rotate',
'fill': 'url(#rotate)',
'r': '15',
'stroke': '#22C',
'stroke-width': 2,
'style': "cursor:url(" + config_.imgPath + "rotate.png) 12 12, auto;"
}
})
);
$.data(this.rotateGrip, 'type', 'rotate');
if ($('#canvasBackground').length) {return;}
var dims = config_.dimensions;
var canvasbg = svgFactory_.createSVGElement({
'element': 'svg',
'attr': {
'id': 'canvasBackground',
'width': dims[0],
'height': dims[1],
'x': 0,
'y': 0,
'overflow': (svgedit.browser.isWebkit() ? 'none' : 'visible'), // Chrome 7 has a problem with this when zooming out
'style': 'pointer-events:none'
}
});
var rect = svgFactory_.createSVGElement({
'element': 'rect',
'attr': {
'width': '100%',
'height': '100%',
'x': 0,
'y': 0,
'stroke-width': 1,
'stroke': '#000',
'fill': '#FFF',
'style': 'pointer-events:none'
}
});
// Both Firefox and WebKit are too slow with this filter region (especially at higher
// zoom levels) and Opera has at least one bug
// if (!svgedit.browser.isOpera()) rect.setAttribute('filter', 'url(#canvashadow)');
canvasbg.appendChild(rect);
svgFactory_.svgRoot().insertBefore(canvasbg, svgFactory_.svgContent());
};
// Function: svgedit.select.SelectorManager.requestSelector
// Returns the selector based on the given element
//
// Parameters:
// elem - DOM element to get the selector for
svgedit.select.SelectorManager.prototype.requestSelector = function(elem) {
if (elem == null) {return null;}
var i,
N = this.selectors.length;
// If we've already acquired one for this element, return it.
if (typeof(this.selectorMap[elem.id]) == 'object') {
this.selectorMap[elem.id].locked = true;
$("#hide_curtselid").val(elem.id);
return this.selectorMap[elem.id];
}
for (i = 0; i < N; ++i) {
if (this.selectors[i] && !this.selectors[i].locked) {
this.selectors[i].locked = true;
this.selectors[i].reset(elem);
this.selectorMap[elem.id] = this.selectors[i];
return this.selectors[i];
}
}
// if we reached here, no available selectors were found, we create one
this.selectors[N] = new svgedit.select.Selector(N, elem);
this.selectorParentGroup.appendChild(this.selectors[N].selectorGroup);
this.selectorMap[elem.id] = this.selectors[N];
return this.selectors[N];
};
// Function: svgedit.select.SelectorManager.requestSelectors
// Returns the selector based on the given Rect and line and Path
//
// Parameters:
// elem - DOM element to get the selector for
svgedit.select.SelectorManager.prototype.requestSelectors = function(elem) {
if (elem == null) {return null;}
var i,
N = this.selectors.length;
// console.log(elem);
// If we've already acquired one for this element, return it.
if (typeof(this.selectorMap[elem.id]) == 'object') {
this.selectorMap[elem.id].locked = true;
$("#hide_curtselid").val(elem.id);
return this.selectorMap[elem.id];
}
for (i = 0; i < N; ++i) {
if (this.selectors[i] && !this.selectors[i].locked) {
this.selectors[i].locked = true;
this.selectors[i].reset(elem);
this.selectorMap[elem.id] = this.selectors[i];
return this.selectors[i];
}
}
// if we reached here, no available selectors were found, we create one
this.selectors[N] = new svgedit.select.Selector(N, elem);
this.selectorParentGroup.appendChild(this.selectors[N].selectorGroup);
this.selectorMap[elem.id] = this.selectors[N];
return this.selectors[N];
};
// Function: svgedit.select.SelectorManager.releaseSelector
// Removes the selector of the given element (hides selection box)
//
// Parameters:
// elem - DOM element to remove the selector for
svgedit.select.SelectorManager.prototype.releaseSelector = function(elem) {
if (elem == null) {return;}
var i,
N = this.selectors.length,
sel = this.selectorMap[elem.id];
for (i = 0; i < N; ++i) {
if (this.selectors[i] && this.selectors[i] == sel) {
if (sel.locked == false) {
// TODO(codedread): Ensure this exists in this module.
console.log('WARNING! selector was released but was already unlocked');
}
delete this.selectorMap[elem.id];
sel.locked = false;
sel.selectedElement = null;
sel.showGrips(false);
// remove from DOM and store reference in JS but only if it exists in the DOM
try {
sel.selectorGroup.setAttribute('display', 'none');
} catch(e) { }
break;
}
}
};
// Function: svgedit.select.SelectorManager.getRubberBandBox
// Returns the rubberBandBox DOM element. This is the rectangle drawn by the user for selecting/zooming
svgedit.select.SelectorManager.prototype.getRubberBandBox = function() {
if (!this.rubberBandBox) {
this.rubberBandBox = this.selectorParentGroup.appendChild(
svgFactory_.createSVGElement({
'element': 'path',
'attr': {
'id': 'selectorRubberBand',
'fill': '#22C',
'fill-opacity': 0.15,
'stroke': '#22C',
'stroke-width': 10.5,
'display': 'block',
'style': 'pointer-events:none'
}
})
);
}
return this.rubberBandBox;
};
/**
* Interface: svgedit.select.SVGFactory
* An object that creates SVG elements for the canvas.
*
* interface svgedit.select.SVGFactory {
* SVGElement createSVGElement(jsonMap);
* SVGSVGElement svgRoot();
* SVGSVGElement svgContent();
*
* Number currentZoom();
* Object getStrokedBBox(Element[]); // TODO(codedread): Remove when getStrokedBBox() has been put into svgutils.js
* }
*/
/**
* Function: svgedit.select.init()
* Initializes this module.
*
* Parameters:
* config - an object containing configurable parameters (imgPath)
* svgFactory - an object implementing the SVGFactory interface (see above).
*/
svgedit.select.init = function(config, svgFactory) {
config_ = config;
svgFactory_ = svgFactory;
selectorManager_ = new svgedit.select.SelectorManager();
};
/**
* Function: svgedit.select.getSelectorManager
*
* Returns:
* The SelectorManager instance.
*/
svgedit.select.getSelectorManager = function() {
return selectorManager_;
};
}());
Dynamic create <g> tags based
<g id="selectorParentGroup" >
<rect id="selectorRubberBand" fill="#22C" fill-opacity="0.15" stroke="#22C" stroke-width="10.5" display="none" style="pointer-events:none" x="2034" y="909.800048828125" width="0" height="0"/>
<path id="selectedBox0" fill="none" stroke="#22C" stroke-dasharray="5,5" style="pointer-events:none" d="M2057,948.716552734375 L2280.5,948.716552734375 2280.5,1092 2057,1092z"/>
</g>
Just check both attached images, we are on the first stage (image) we to make it us the second stage - Selection area should be in over boundary as in secondary image (should be as image right side). Kindly assist me.
All suggestion will be highly appreciated.
I am using JSTreegraph plugin to draw a tree structure.
But now I need a drag, drop and attach feature wherein I can drag any node of the tree and attach to any other node and subsequently all the children of the first node will be now the grand-children of the new node (to which it is attached).
As far as I know this plugin doesnt seem to have this feature. It simply draws the structure based on the data object passed to it.
The plugin basically assigns a class Node to all the nodes(divs) of the tree and another class NodeHover to a node on hover. No id is assigned to these divs.
So I tried using JQuery Draggable just to see if any of the node can be moved by doing this
$('.Node').draggable();
$('.NodeHover').draggable();
But it doesnt seem to work. So can anyone help me with this.
How can I get drag and attach functionality?
*EDIT:: Pardon me am not so great with using Fiddle, so am sharing a sample code here for your use:*
HTML file: which will draw the sample tree
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml">
<head>
<link href="css/JSTreeGraph.css" rel="stylesheet" type="text/css" />
<script src="js/JSTreeGraph.js" type="text/javascript"></script>
<script src="js/jquery-1.7.1.min.js" type="text/javascript"></script>
<script src="js/jquery.ui.position.js" type="text/javascript"></script>
<style type="text/css">
.Container {
position: absolute;
top: 100px;
left: 50px;
id: Container;
}
</style>
</head>
<body>
<div id="tree">
Ctrl+Click to Add Node
<br />
Double Click to Expand or Collapse
<br />
Shift+Click to Delete Node
<br />
<select id="dlLayout" onchange="ChangeLayout()">
<option value="Horizontal">
Horizontal
</option>
<option value="Vertical" selected>
Vertical
</option>
</select>
<div class="Container" id="dvTreeContainer"></div>
<script type="text/javascript">
var selectedNode;
// Root node
var rootNode = { Content: "Onida", Nodes:[] };
// First Level
rootNode.Nodes[0] = { Content: "Employee Code", navigationType: "0"};
rootNode.Nodes[1] = { Content: "Problem Area", navigationType: "1" };
// Second Level
rootNode.Nodes[1].Nodes = [{ Content : "ACC-HO", Collapsed: true /* This node renders collapsed */ },
{ Content : "ACC-SALES" },
{ Content : "BUSI. HEAD", /*This node looks different*/ ToolTip: "Click ME!" },
{ Content : "CEO"},
{ Content : "HO-ADMIN"},
{ Content : "HO-FACTORY"},
{ Content : "SALES"}];
// Third Level
rootNode.Nodes[1].Nodes[0].Nodes = [{ Content: "Billing" },
{ Content: "Credit Limit" },
{ Content: "Reconciliation" }];
rootNode.Nodes[1].Nodes[1].Nodes = [{ Content: "Billing" },
{ Content: "Others" }];
rootNode.Nodes[1].Nodes[2].Nodes = [{ Content: "AC" },
{ Content: "CTV" },
{ Content: "DVD" },
{ Content: "Washing Machine" }];
rootNode.Nodes[1].Nodes[6].Nodes = [{ Content: "Appointments" },
{ Content: "Resignations" },
{ Content: "Others" }];
// Draw the tree for the first time
RefreshTree();
function RefreshTree() {
DrawTree({ Container: document.getElementById("dvTreeContainer"),
RootNode: rootNode,
Layout: document.getElementById("dlLayout").value,
OnNodeClickFirefox: NodeClickFF,
OnNodeClickIE: NodeClickIE,
OnNodeDoubleClick: NodeDoubleClick
});
}
//function
function NodeClickFF(e) {
if (e.shiftKey){
// Delete Node
if (!this.Node.Collapsed) {
for(var index=0; index<this.Node.ParentNode.Nodes.length; index++) {
if(this.Node.ParentNode.Nodes[index].Content == this.Node.Content) {
this.Node.ParentNode.Nodes.splice(index, 1);
break;
}
}
RefreshTree();
}
// return false;
}
else if (e.ctrlKey) {
// Add new Child if Expanded
if (!this.Node.Collapsed) {
if (!this.Node.Nodes) this.Node.Nodes = new Array();
var newNodeIndex = this.Node.Nodes.length;
this.Node.Nodes[newNodeIndex] = new Object();
this.Node.Nodes[newNodeIndex].Content = this.Node.Content + "." + (newNodeIndex + 1);
RefreshTree();
}
// return false;
}
else{
fnNodeProperties(this.Node);
}
}
function NodeClickIE() {
if (typeof(event) == "undefined" && event.ctrlKey) {
// Add new Child if Expanded
if (!this.Node.Collapsed) {
if (!this.Node.Nodes) this.Node.Nodes = new Array();
var newNodeIndex = this.Node.Nodes.length;
this.Node.Nodes[newNodeIndex] = new Object();
this.Node.Nodes[newNodeIndex].Content = this.Node.Content + "." + (newNodeIndex + 1);
RefreshTree();
}
}
else if (typeof(event) == "undefined" && event.shiftKey) {
// Delete Node
if (!this.Node.Collapsed) {
for(var index=0; index<this.Node.ParentNode.Nodes.length; index++) {
if(this.Node.ParentNode.Nodes[index].Content == this.Node.Content) {
this.Node.ParentNode.Nodes.splice(index, 1);
break;
}
}
RefreshTree();
}
}
else{
fnNodeProperties(this.Node);
}
}
function NodeDoubleClick() {
if (this.Node.Nodes && this.Node.Nodes.length > 0) { // If has children
this.Node.Collapsed = !this.Node.Collapsed;
RefreshTree();
}
}
function ChangeLayout() {
RefreshTree();
}
</script>
</div>
</body>
</html>
JSTreeGraph JS file: plugin js file
function DrawTree(options) {
// Prepare Nodes
PrepareNode(options.RootNode);
// Calculate Boxes Positions
if (options.Layout == "Vertical") {
PerformLayoutV(options.RootNode);
} else {
PerformLayoutH(options.RootNode);
}
// Draw Boxes
options.Container.innerHTML = "";
DrawNode(options.RootNode, options.Container, options);
// Draw Lines
DrawLines(options.RootNode, options.Container);
}
function DrawLines(node, container) {
if ((!node.Collapsed) && node.Nodes && node.Nodes.length > 0) { // Has children and Is Expanded
for (var j = 0; j < node.Nodes.length; j++) {
if(node.ChildrenConnectorPoint.Layout=="Vertical")
DrawLineV(container, node.ChildrenConnectorPoint, node.Nodes[j].ParentConnectorPoint);
else
DrawLineH(container, node.ChildrenConnectorPoint, node.Nodes[j].ParentConnectorPoint);
// Children
DrawLines(node.Nodes[j], container);
}
}
}
function DrawLineH(container, startPoint, endPoint) {
var midY = (startPoint.Y + ((endPoint.Y - startPoint.Y) / 2)); // Half path between start en end Y point
// Start segment
DrawLineSegment(container, startPoint.X, startPoint.Y, startPoint.X, midY, 1);
// Intermidiate segment
var imsStartX = startPoint.X < endPoint.X ? startPoint.X : endPoint.X; // The lower value will be the starting point
var imsEndX = startPoint.X > endPoint.X ? startPoint.X : endPoint.X; // The higher value will be the ending point
DrawLineSegment(container, imsStartX, midY, imsEndX, midY, 1);
// End segment
DrawLineSegment(container, endPoint.X, midY, endPoint.X, endPoint.Y, 1);
}
function DrawLineV(container, startPoint, endPoint) {
var midX = (startPoint.X + ((endPoint.X - startPoint.X) / 2)); // Half path between start en end X point
// Start segment
DrawLineSegment(container, startPoint.X, startPoint.Y, midX, startPoint.Y, 1);
// Intermidiate segment
var imsStartY = startPoint.Y < endPoint.Y ? startPoint.Y : endPoint.Y; // The lower value will be the starting point
var imsEndY = startPoint.Y > endPoint.Y ? startPoint.Y : endPoint.Y; // The higher value will be the ending point
DrawLineSegment(container, midX, imsStartY, midX, imsEndY, 1);
// End segment
DrawLineSegment(container, midX, endPoint.Y, endPoint.X, endPoint.Y, 1);
}
function DrawLineSegment(container, startX, startY, endX, endY, lineWidth) {
var lineDiv = document.createElement("div");
lineDiv.style.top = startY + "px";
lineDiv.style.left = startX + "px";
if (startX == endX) { // Vertical Line
lineDiv.style.width = lineWidth + "px";
lineDiv.style.height = (endY - startY) + "px";
}
else{ // Horizontal Line
lineDiv.style.width = (endX - startX) + "px";
lineDiv.style.height = lineWidth + "px";
}
lineDiv.className = "NodeLine";
container.appendChild(lineDiv);
}
function DrawNode(node, container, options) {
var nodeDiv = document.createElement("div");
nodeDiv.style.top = node.Top + "px";
nodeDiv.style.left = node.Left + "px";
nodeDiv.style.width = node.Width + "px";
nodeDiv.style.height = node.Height + "px";
if (node.Collapsed) {
nodeDiv.className = "NodeCollapsed";
} else {
nodeDiv.className = "Node";
}
if (node.Class)
nodeDiv.className = node.Class;
if (node.Content)
nodeDiv.innerHTML = "<div class='NodeContent'>" + node.Content + "</div>";
if (node.ToolTip)
nodeDiv.setAttribute("title", node.ToolTip);
nodeDiv.Node = node;
// Events
if (options.OnNodeClickIE){
//alert('OnNodeClick');
nodeDiv.onclick = options.OnNodeClickIE;
}
// Events
if (options.OnNodeClickFirefox){
//alert('OnNodeClick');
nodeDiv.onmousedown = options.OnNodeClickFirefox;
}
//on right click
if (options.OnContextMenu){
//alert('OnContextMenu');
nodeDiv.oncontextmenu = options.OnContextMenu;
}
if (options.OnNodeDoubleClick)
nodeDiv.ondblclick = options.OnNodeDoubleClick;
nodeDiv.onmouseover = function () { // In
this.PrevClassName = this.className;
this.className = "NodeHover";
};
nodeDiv.onmouseout = function () { // Out
if (this.PrevClassName) {
this.className = this.PrevClassName;
this.PrevClassName = null;
}
};
container.appendChild(nodeDiv);
// Draw children
if ((!node.Collapsed) && node.Nodes && node.Nodes.length > 0) { // Has Children and is Expanded
for (var i = 0; i < node.Nodes.length; i++) {
DrawNode(node.Nodes[i], container, options);
}
}
}
function PerformLayoutV(node) {
var nodeHeight = 30;
var nodeWidth = 100;
var nodeMarginLeft = 40;
var nodeMarginTop = 20;
var nodeTop = 0; // defaultValue
// Before Layout this Node, Layout its children
if ((!node.Collapsed) && node.Nodes && node.Nodes.length > 0) {
for (var i = 0; i < node.Nodes.length; i++) {
PerformLayoutV(node.Nodes[i]);
}
}
if ((!node.Collapsed) && node.Nodes && node.Nodes.length > 0) { // If Has Children and Is Expanded
// My Top is in the center of my children
var childrenHeight = (node.Nodes[node.Nodes.length - 1].Top + node.Nodes[node.Nodes.length - 1].Height) - node.Nodes[0].Top;
nodeTop = (node.Nodes[0].Top + (childrenHeight / 2)) - (nodeHeight / 2);
// Is my top over my previous sibling?
// Move it to the bottom
if (node.LeftNode && ((node.LeftNode.Top + node.LeftNode.Height + nodeMarginTop) > nodeTop)) {
var newTop = node.LeftNode.Top + node.LeftNode.Height + nodeMarginTop;
var diff = newTop - nodeTop;
/// Move also my children
MoveBottom(node.Nodes, diff);
nodeTop = newTop;
}
} else {
// My top is next to my top sibling
if (node.LeftNode)
nodeTop = node.LeftNode.Top + node.LeftNode.Height + nodeMarginTop;
}
node.Top = nodeTop;
// The Left depends only on the level
node.Left = (nodeMarginLeft * (node.Level + 1)) + (nodeWidth * (node.Level + 1));
// Size is constant
node.Height = nodeHeight;
node.Width = nodeWidth;
// Calculate Connector Points
// Child: Where the lines get out from to connect this node with its children
var pointX = node.Left + nodeWidth;
var pointY = nodeTop + (nodeHeight/2);
node.ChildrenConnectorPoint = { X: pointX, Y: pointY, Layout: "Vertical" };
// Parent: Where the line that connect this node with its parent end
pointX = node.Left;
pointY = nodeTop + (nodeHeight/2);
node.ParentConnectorPoint = { X: pointX, Y: pointY, Layout: "Vertical" };
}
function PerformLayoutH(node) {
var nodeHeight = 30;
var nodeWidth = 100;
var nodeMarginLeft = 20;
var nodeMarginTop = 50;
var nodeLeft = 0; // defaultValue
// Before Layout this Node, Layout its children
if ((!node.Collapsed) && node.Nodes && node.Nodes.length>0) {
for (var i = 0; i < node.Nodes.length; i++) {
PerformLayoutH(node.Nodes[i]);
}
}
if ((!node.Collapsed) && node.Nodes && node.Nodes.length > 0) { // If Has Children and Is Expanded
// My left is in the center of my children
var childrenWidth = (node.Nodes[node.Nodes.length-1].Left + node.Nodes[node.Nodes.length-1].Width) - node.Nodes[0].Left;
nodeLeft = (node.Nodes[0].Left + (childrenWidth / 2)) - (nodeWidth / 2);
// Is my left over my left node?
// Move it to the right
if(node.LeftNode&&((node.LeftNode.Left+node.LeftNode.Width+nodeMarginLeft)>nodeLeft)) {
var newLeft = node.LeftNode.Left + node.LeftNode.Width + nodeMarginLeft;
var diff = newLeft - nodeLeft;
/// Move also my children
MoveRigth(node.Nodes, diff);
nodeLeft = newLeft;
}
} else {
// My left is next to my left sibling
if (node.LeftNode)
nodeLeft = node.LeftNode.Left + node.LeftNode.Width + nodeMarginLeft;
}
node.Left = nodeLeft;
// The top depends only on the level
node.Top = (nodeMarginTop * (node.Level + 1)) + (nodeHeight * (node.Level + 1));
// Size is constant
node.Height = nodeHeight;
node.Width = nodeWidth;
// Calculate Connector Points
// Child: Where the lines get out from to connect this node with its children
var pointX = nodeLeft + (nodeWidth / 2);
var pointY = node.Top + nodeHeight;
node.ChildrenConnectorPoint = { X: pointX, Y: pointY, Layout:"Horizontal" };
// Parent: Where the line that connect this node with its parent end
pointX = nodeLeft + (nodeWidth / 2);
pointY = node.Top;
node.ParentConnectorPoint = { X: pointX, Y: pointY, Layout: "Horizontal" };
}
function MoveRigth(nodes, distance) {
for (var i = 0; i < nodes.length; i++) {
nodes[i].Left += distance;
if (nodes[i].ParentConnectorPoint) nodes[i].ParentConnectorPoint.X += distance;
if (nodes[i].ChildrenConnectorPoint) nodes[i].ChildrenConnectorPoint.X += distance;
if (nodes[i].Nodes) {
MoveRigth(nodes[i].Nodes, distance);
}
}
}
function MoveBottom(nodes, distance) {
for (var i = 0; i < nodes.length; i++) {
nodes[i].Top += distance;
if (nodes[i].ParentConnectorPoint) nodes[i].ParentConnectorPoint.Y += distance;
if (nodes[i].ChildrenConnectorPoint) nodes[i].ChildrenConnectorPoint.Y += distance;
if (nodes[i].Nodes) {
MoveBottom(nodes[i].Nodes, distance);
}
}
}
function PrepareNode(node, level, parentNode, leftNode, rightLimits) {
if (level == undefined) level = 0;
if (parentNode == undefined) parentNode = null;
if (leftNode == undefined) leftNode = null;
if (rightLimits == undefined) rightLimits = new Array();
node.Level = level;
node.ParentNode = parentNode;
node.LeftNode = leftNode;
if ((!node.Collapsed) && node.Nodes && node.Nodes.length > 0) { // Has children and is expanded
for (var i = 0; i < node.Nodes.length; i++) {
var left = null;
if (i == 0 && rightLimits[level]!=undefined) left = rightLimits[level];
if (i > 0) left = node.Nodes[i - 1];
if (i == (node.Nodes.length-1)) rightLimits[level] = node.Nodes[i];
PrepareNode(node.Nodes[i], level + 1, node, left, rightLimits);
}
}
}
JSTreeGraph CSS file:
.NodeContent
{
font-family:Verdana;
font-size:small;
}
.Node
{
position:absolute;
background-color: #CCDAFF;
border: 1px solid #5280FF;
text-align:center;
vertical-align:middle;
cursor:pointer;
overflow:hidden;
}
.NodeHover
{
position:absolute;
background-color: #8FADFF;
border: 1px solid #5280FF;
text-align:center;
vertical-align:middle;
cursor:pointer;
overflow:hidden;
}
.NodeCollapsed
{
position:absolute;
background-color: #8FADFF;
border: 2px solid black;
text-align:center;
vertical-align:middle;
cursor:pointer;
overflow:hidden;
}
.NodeLine
{
background-color: #000066;
position:absolute;
overflow:hidden;
}
I supose that what you need is to stablish a mechanism to modify your tree logic structure on dragging and then reload the whole tree element. This way the plugin will render your new modified structure.
This is not an easy problem and you have not outlined the exact specifications.
When you move a node, should all child nodes move with it?
What happens to a node and its child elements when a node is dropped/attached?
The plugin that you are using works well for drawing static trees, but it is not written in such a way to allow for dynamically editing the tree.
I have to agree with #Bardo in that the easiest way to make this work for your needs is to understand how the tree has been manipulated. (Thankfully the plugin seems to provide an onNodeClick option which will allow you to understand which node you are intending to manipulate). Once the tree has been manipulated then it must be completely redrawn. (There doesn't appear to be a good way to partially draw a tree).