").attr("role","tooltip").addClass("ui-tooltip ui-widget ui-corner-all ui-widget-content "+(this.options.tooltipClass||"")),s=i.uniqueId().attr("id");return t("
").addClass("ui-tooltip-content").appendTo(i),i.appendTo(this.document[0].body),this.tooltips[s]={element:e,tooltip:i}},_find:function(t){var e=t.data("ui-tooltip-id");return e?this.tooltips[e]:null},_removeTooltip:function(t){t.remove(),delete this.tooltips[t.attr("id")]},_destroy:function(){var e=this;t.each(this.tooltips,function(i,s){var n=t.Event("blur"),o=s.element;n.target=n.currentTarget=o[0],e.close(n,!0),t("#"+i).remove(),o.data("ui-tooltip-title")&&(o.attr("title")||o.attr("title",o.data("ui-tooltip-title")),o.removeData("ui-tooltip-title"))}),this.liveRegion.remove()}});var v="ui-effects-",b=t;t.effects={effect:{}},function(t,e){function i(t,e,i){var s=u[e.type]||{};return null==t?i||!e.def?null:e.def:(t=s.floor?~~t:parseFloat(t),isNaN(t)?e.def:s.mod?(t+s.mod)%s.mod:0>t?0:t>s.max?s.max:t)}function s(i){var s=l(),n=s._rgba=[];return i=i.toLowerCase(),f(h,function(t,o){var a,r=o.re.exec(i),h=r&&o.parse(r),l=o.space||"rgba";return h?(a=s[l](h),s[c[l].cache]=a[c[l].cache],n=s._rgba=a._rgba,!1):e}),n.length?("0,0,0,0"===n.join()&&t.extend(n,o.transparent),s):o[i]}function n(t,e,i){return i=(i+1)%1,1>6*i?t+6*(e-t)*i:1>2*i?e:2>3*i?t+6*(e-t)*(2/3-i):t}var o,a="backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor",r=/^([\-+])=\s*(\d+\.?\d*)/,h=[{re:/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(t){return[t[1],t[2],t[3],t[4]]}},{re:/rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(t){return[2.55*t[1],2.55*t[2],2.55*t[3],t[4]]}},{re:/#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/,parse:function(t){return[parseInt(t[1],16),parseInt(t[2],16),parseInt(t[3],16)]}},{re:/#([a-f0-9])([a-f0-9])([a-f0-9])/,parse:function(t){return[parseInt(t[1]+t[1],16),parseInt(t[2]+t[2],16),parseInt(t[3]+t[3],16)]}},{re:/hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,space:"hsla",parse:function(t){return[t[1],t[2]/100,t[3]/100,t[4]]}}],l=t.Color=function(e,i,s,n){return new t.Color.fn.parse(e,i,s,n)},c={rgba:{props:{red:{idx:0,type:"byte"},green:{idx:1,type:"byte"},blue:{idx:2,type:"byte"}}},hsla:{props:{hue:{idx:0,type:"degrees"},saturation:{idx:1,type:"percent"},lightness:{idx:2,type:"percent"}}}},u={"byte":{floor:!0,max:255},percent:{max:1},degrees:{mod:360,floor:!0}},d=l.support={},p=t("
")[0],f=t.each;p.style.cssText="background-color:rgba(1,1,1,.5)",d.rgba=p.style.backgroundColor.indexOf("rgba")>-1,f(c,function(t,e){e.cache="_"+t,e.props.alpha={idx:3,type:"percent",def:1}}),l.fn=t.extend(l.prototype,{parse:function(n,a,r,h){if(n===e)return this._rgba=[null,null,null,null],this;(n.jquery||n.nodeType)&&(n=t(n).css(a),a=e);var u=this,d=t.type(n),p=this._rgba=[];return a!==e&&(n=[n,a,r,h],d="array"),"string"===d?this.parse(s(n)||o._default):"array"===d?(f(c.rgba.props,function(t,e){p[e.idx]=i(n[e.idx],e)}),this):"object"===d?(n instanceof l?f(c,function(t,e){n[e.cache]&&(u[e.cache]=n[e.cache].slice())}):f(c,function(e,s){var o=s.cache;f(s.props,function(t,e){if(!u[o]&&s.to){if("alpha"===t||null==n[t])return;u[o]=s.to(u._rgba)}u[o][e.idx]=i(n[t],e,!0)}),u[o]&&0>t.inArray(null,u[o].slice(0,3))&&(u[o][3]=1,s.from&&(u._rgba=s.from(u[o])))}),this):e},is:function(t){var i=l(t),s=!0,n=this;return f(c,function(t,o){var a,r=i[o.cache];return r&&(a=n[o.cache]||o.to&&o.to(n._rgba)||[],f(o.props,function(t,i){return null!=r[i.idx]?s=r[i.idx]===a[i.idx]:e})),s}),s},_space:function(){var t=[],e=this;return f(c,function(i,s){e[s.cache]&&t.push(i)}),t.pop()},transition:function(t,e){var s=l(t),n=s._space(),o=c[n],a=0===this.alpha()?l("transparent"):this,r=a[o.cache]||o.to(a._rgba),h=r.slice();return s=s[o.cache],f(o.props,function(t,n){var o=n.idx,a=r[o],l=s[o],c=u[n.type]||{};null!==l&&(null===a?h[o]=l:(c.mod&&(l-a>c.mod/2?a+=c.mod:a-l>c.mod/2&&(a-=c.mod)),h[o]=i((l-a)*e+a,n)))}),this[n](h)},blend:function(e){if(1===this._rgba[3])return this;var i=this._rgba.slice(),s=i.pop(),n=l(e)._rgba;return l(t.map(i,function(t,e){return(1-s)*n[e]+s*t}))},toRgbaString:function(){var e="rgba(",i=t.map(this._rgba,function(t,e){return null==t?e>2?1:0:t});return 1===i[3]&&(i.pop(),e="rgb("),e+i.join()+")"},toHslaString:function(){var e="hsla(",i=t.map(this.hsla(),function(t,e){return null==t&&(t=e>2?1:0),e&&3>e&&(t=Math.round(100*t)+"%"),t});return 1===i[3]&&(i.pop(),e="hsl("),e+i.join()+")"},toHexString:function(e){var i=this._rgba.slice(),s=i.pop();return e&&i.push(~~(255*s)),"#"+t.map(i,function(t){return t=(t||0).toString(16),1===t.length?"0"+t:t}).join("")},toString:function(){return 0===this._rgba[3]?"transparent":this.toRgbaString()}}),l.fn.parse.prototype=l.fn,c.hsla.to=function(t){if(null==t[0]||null==t[1]||null==t[2])return[null,null,null,t[3]];var e,i,s=t[0]/255,n=t[1]/255,o=t[2]/255,a=t[3],r=Math.max(s,n,o),h=Math.min(s,n,o),l=r-h,c=r+h,u=.5*c;return e=h===r?0:s===r?60*(n-o)/l+360:n===r?60*(o-s)/l+120:60*(s-n)/l+240,i=0===l?0:.5>=u?l/c:l/(2-c),[Math.round(e)%360,i,u,null==a?1:a]},c.hsla.from=function(t){if(null==t[0]||null==t[1]||null==t[2])return[null,null,null,t[3]];var e=t[0]/360,i=t[1],s=t[2],o=t[3],a=.5>=s?s*(1+i):s+i-s*i,r=2*s-a;return[Math.round(255*n(r,a,e+1/3)),Math.round(255*n(r,a,e)),Math.round(255*n(r,a,e-1/3)),o]},f(c,function(s,n){var o=n.props,a=n.cache,h=n.to,c=n.from;l.fn[s]=function(s){if(h&&!this[a]&&(this[a]=h(this._rgba)),s===e)return this[a].slice();var n,r=t.type(s),u="array"===r||"object"===r?s:arguments,d=this[a].slice();return f(o,function(t,e){var s=u["object"===r?t:e.idx];null==s&&(s=d[e.idx]),d[e.idx]=i(s,e)}),c?(n=l(c(d)),n[a]=d,n):l(d)},f(o,function(e,i){l.fn[e]||(l.fn[e]=function(n){var o,a=t.type(n),h="alpha"===e?this._hsla?"hsla":"rgba":s,l=this[h](),c=l[i.idx];return"undefined"===a?c:("function"===a&&(n=n.call(this,c),a=t.type(n)),null==n&&i.empty?this:("string"===a&&(o=r.exec(n),o&&(n=c+parseFloat(o[2])*("+"===o[1]?1:-1))),l[i.idx]=n,this[h](l)))})})}),l.hook=function(e){var i=e.split(" ");f(i,function(e,i){t.cssHooks[i]={set:function(e,n){var o,a,r="";if("transparent"!==n&&("string"!==t.type(n)||(o=s(n)))){if(n=l(o||n),!d.rgba&&1!==n._rgba[3]){for(a="backgroundColor"===i?e.parentNode:e;(""===r||"transparent"===r)&&a&&a.style;)try{r=t.css(a,"backgroundColor"),a=a.parentNode}catch(h){}n=n.blend(r&&"transparent"!==r?r:"_default")}n=n.toRgbaString()}try{e.style[i]=n}catch(h){}}},t.fx.step[i]=function(e){e.colorInit||(e.start=l(e.elem,i),e.end=l(e.end),e.colorInit=!0),t.cssHooks[i].set(e.elem,e.start.transition(e.end,e.pos))}})},l.hook(a),t.cssHooks.borderColor={expand:function(t){var e={};return f(["Top","Right","Bottom","Left"],function(i,s){e["border"+s+"Color"]=t}),e}},o=t.Color.names={aqua:"#00ffff",black:"#000000",blue:"#0000ff",fuchsia:"#ff00ff",gray:"#808080",green:"#008000",lime:"#00ff00",maroon:"#800000",navy:"#000080",olive:"#808000",purple:"#800080",red:"#ff0000",silver:"#c0c0c0",teal:"#008080",white:"#ffffff",yellow:"#ffff00",transparent:[null,null,null,0],_default:"#ffffff"}}(b),function(){function e(e){var i,s,n=e.ownerDocument.defaultView?e.ownerDocument.defaultView.getComputedStyle(e,null):e.currentStyle,o={};if(n&&n.length&&n[0]&&n[n[0]])for(s=n.length;s--;)i=n[s],"string"==typeof n[i]&&(o[t.camelCase(i)]=n[i]);else for(i in n)"string"==typeof n[i]&&(o[i]=n[i]);return o}function i(e,i){var s,o,a={};for(s in i)o=i[s],e[s]!==o&&(n[s]||(t.fx.step[s]||!isNaN(parseFloat(o)))&&(a[s]=o));return a}var s=["add","remove","toggle"],n={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};t.each(["borderLeftStyle","borderRightStyle","borderBottomStyle","borderTopStyle"],function(e,i){t.fx.step[i]=function(t){("none"!==t.end&&!t.setAttr||1===t.pos&&!t.setAttr)&&(b.style(t.elem,i,t.end),t.setAttr=!0)}}),t.fn.addBack||(t.fn.addBack=function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}),t.effects.animateClass=function(n,o,a,r){var h=t.speed(o,a,r);return this.queue(function(){var o,a=t(this),r=a.attr("class")||"",l=h.children?a.find("*").addBack():a;l=l.map(function(){var i=t(this);return{el:i,start:e(this)}}),o=function(){t.each(s,function(t,e){n[e]&&a[e+"Class"](n[e])})},o(),l=l.map(function(){return this.end=e(this.el[0]),this.diff=i(this.start,this.end),this}),a.attr("class",r),l=l.map(function(){var e=this,i=t.Deferred(),s=t.extend({},h,{queue:!1,complete:function(){i.resolve(e)}});return this.el.animate(this.diff,s),i.promise()}),t.when.apply(t,l.get()).done(function(){o(),t.each(arguments,function(){var e=this.el;t.each(this.diff,function(t){e.css(t,"")})}),h.complete.call(a[0])})})},t.fn.extend({addClass:function(e){return function(i,s,n,o){return s?t.effects.animateClass.call(this,{add:i},s,n,o):e.apply(this,arguments)}}(t.fn.addClass),removeClass:function(e){return function(i,s,n,o){return arguments.length>1?t.effects.animateClass.call(this,{remove:i},s,n,o):e.apply(this,arguments)}}(t.fn.removeClass),toggleClass:function(e){return function(i,s,n,o,a){return"boolean"==typeof s||void 0===s?n?t.effects.animateClass.call(this,s?{add:i}:{remove:i},n,o,a):e.apply(this,arguments):t.effects.animateClass.call(this,{toggle:i},s,n,o)}}(t.fn.toggleClass),switchClass:function(e,i,s,n,o){return t.effects.animateClass.call(this,{add:i,remove:e},s,n,o)}})}(),function(){function e(e,i,s,n){return t.isPlainObject(e)&&(i=e,e=e.effect),e={effect:e},null==i&&(i={}),t.isFunction(i)&&(n=i,s=null,i={}),("number"==typeof i||t.fx.speeds[i])&&(n=s,s=i,i={}),t.isFunction(s)&&(n=s,s=null),i&&t.extend(e,i),s=s||i.duration,e.duration=t.fx.off?0:"number"==typeof s?s:s in t.fx.speeds?t.fx.speeds[s]:t.fx.speeds._default,e.complete=n||i.complete,e}function i(e){return!e||"number"==typeof e||t.fx.speeds[e]?!0:"string"!=typeof e||t.effects.effect[e]?t.isFunction(e)?!0:"object"!=typeof e||e.effect?!1:!0:!0}t.extend(t.effects,{version:"1.11.4",save:function(t,e){for(var i=0;e.length>i;i++)null!==e[i]&&t.data(v+e[i],t[0].style[e[i]])},restore:function(t,e){var i,s;for(s=0;e.length>s;s++)null!==e[s]&&(i=t.data(v+e[s]),void 0===i&&(i=""),t.css(e[s],i))},setMode:function(t,e){return"toggle"===e&&(e=t.is(":hidden")?"show":"hide"),e},getBaseline:function(t,e){var i,s;switch(t[0]){case"top":i=0;break;case"middle":i=.5;break;case"bottom":i=1;break;default:i=t[0]/e.height}switch(t[1]){case"left":s=0;break;case"center":s=.5;break;case"right":s=1;break;default:s=t[1]/e.width}return{x:s,y:i}},createWrapper:function(e){if(e.parent().is(".ui-effects-wrapper"))return e.parent();var i={width:e.outerWidth(!0),height:e.outerHeight(!0),"float":e.css("float")},s=t("
").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),n={width:e.width(),height:e.height()},o=document.activeElement;try{o.id}catch(a){o=document.body}return e.wrap(s),(e[0]===o||t.contains(e[0],o))&&t(o).focus(),s=e.parent(),"static"===e.css("position")?(s.css({position:"relative"}),e.css({position:"relative"})):(t.extend(i,{position:e.css("position"),zIndex:e.css("z-index")}),t.each(["top","left","bottom","right"],function(t,s){i[s]=e.css(s),isNaN(parseInt(i[s],10))&&(i[s]="auto")}),e.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})),e.css(n),s.css(i).show()},removeWrapper:function(e){var i=document.activeElement;
+return e.parent().is(".ui-effects-wrapper")&&(e.parent().replaceWith(e),(e[0]===i||t.contains(e[0],i))&&t(i).focus()),e},setTransition:function(e,i,s,n){return n=n||{},t.each(i,function(t,i){var o=e.cssUnit(i);o[0]>0&&(n[i]=o[0]*s+o[1])}),n}}),t.fn.extend({effect:function(){function i(e){function i(){t.isFunction(o)&&o.call(n[0]),t.isFunction(e)&&e()}var n=t(this),o=s.complete,r=s.mode;(n.is(":hidden")?"hide"===r:"show"===r)?(n[r](),i()):a.call(n[0],s,i)}var s=e.apply(this,arguments),n=s.mode,o=s.queue,a=t.effects.effect[s.effect];return t.fx.off||!a?n?this[n](s.duration,s.complete):this.each(function(){s.complete&&s.complete.call(this)}):o===!1?this.each(i):this.queue(o||"fx",i)},show:function(t){return function(s){if(i(s))return t.apply(this,arguments);var n=e.apply(this,arguments);return n.mode="show",this.effect.call(this,n)}}(t.fn.show),hide:function(t){return function(s){if(i(s))return t.apply(this,arguments);var n=e.apply(this,arguments);return n.mode="hide",this.effect.call(this,n)}}(t.fn.hide),toggle:function(t){return function(s){if(i(s)||"boolean"==typeof s)return t.apply(this,arguments);var n=e.apply(this,arguments);return n.mode="toggle",this.effect.call(this,n)}}(t.fn.toggle),cssUnit:function(e){var i=this.css(e),s=[];return t.each(["em","px","%","pt"],function(t,e){i.indexOf(e)>0&&(s=[parseFloat(i),e])}),s}})}(),function(){var e={};t.each(["Quad","Cubic","Quart","Quint","Expo"],function(t,i){e[i]=function(e){return Math.pow(e,t+2)}}),t.extend(e,{Sine:function(t){return 1-Math.cos(t*Math.PI/2)},Circ:function(t){return 1-Math.sqrt(1-t*t)},Elastic:function(t){return 0===t||1===t?t:-Math.pow(2,8*(t-1))*Math.sin((80*(t-1)-7.5)*Math.PI/15)},Back:function(t){return t*t*(3*t-2)},Bounce:function(t){for(var e,i=4;((e=Math.pow(2,--i))-1)/11>t;);return 1/Math.pow(4,3-i)-7.5625*Math.pow((3*e-2)/22-t,2)}}),t.each(e,function(e,i){t.easing["easeIn"+e]=i,t.easing["easeOut"+e]=function(t){return 1-i(1-t)},t.easing["easeInOut"+e]=function(t){return.5>t?i(2*t)/2:1-i(-2*t+2)/2}})}(),t.effects,t.effects.effect.blind=function(e,i){var s,n,o,a=t(this),r=/up|down|vertical/,h=/up|left|vertical|horizontal/,l=["position","top","bottom","left","right","height","width"],c=t.effects.setMode(a,e.mode||"hide"),u=e.direction||"up",d=r.test(u),p=d?"height":"width",f=d?"top":"left",g=h.test(u),m={},_="show"===c;a.parent().is(".ui-effects-wrapper")?t.effects.save(a.parent(),l):t.effects.save(a,l),a.show(),s=t.effects.createWrapper(a).css({overflow:"hidden"}),n=s[p](),o=parseFloat(s.css(f))||0,m[p]=_?n:0,g||(a.css(d?"bottom":"right",0).css(d?"top":"left","auto").css({position:"absolute"}),m[f]=_?o:n+o),_&&(s.css(p,0),g||s.css(f,o+n)),s.animate(m,{duration:e.duration,easing:e.easing,queue:!1,complete:function(){"hide"===c&&a.hide(),t.effects.restore(a,l),t.effects.removeWrapper(a),i()}})},t.effects.effect.bounce=function(e,i){var s,n,o,a=t(this),r=["position","top","bottom","left","right","height","width"],h=t.effects.setMode(a,e.mode||"effect"),l="hide"===h,c="show"===h,u=e.direction||"up",d=e.distance,p=e.times||5,f=2*p+(c||l?1:0),g=e.duration/f,m=e.easing,_="up"===u||"down"===u?"top":"left",v="up"===u||"left"===u,b=a.queue(),y=b.length;for((c||l)&&r.push("opacity"),t.effects.save(a,r),a.show(),t.effects.createWrapper(a),d||(d=a["top"===_?"outerHeight":"outerWidth"]()/3),c&&(o={opacity:1},o[_]=0,a.css("opacity",0).css(_,v?2*-d:2*d).animate(o,g,m)),l&&(d/=Math.pow(2,p-1)),o={},o[_]=0,s=0;p>s;s++)n={},n[_]=(v?"-=":"+=")+d,a.animate(n,g,m).animate(o,g,m),d=l?2*d:d/2;l&&(n={opacity:0},n[_]=(v?"-=":"+=")+d,a.animate(n,g,m)),a.queue(function(){l&&a.hide(),t.effects.restore(a,r),t.effects.removeWrapper(a),i()}),y>1&&b.splice.apply(b,[1,0].concat(b.splice(y,f+1))),a.dequeue()},t.effects.effect.clip=function(e,i){var s,n,o,a=t(this),r=["position","top","bottom","left","right","height","width"],h=t.effects.setMode(a,e.mode||"hide"),l="show"===h,c=e.direction||"vertical",u="vertical"===c,d=u?"height":"width",p=u?"top":"left",f={};t.effects.save(a,r),a.show(),s=t.effects.createWrapper(a).css({overflow:"hidden"}),n="IMG"===a[0].tagName?s:a,o=n[d](),l&&(n.css(d,0),n.css(p,o/2)),f[d]=l?o:0,f[p]=l?0:o/2,n.animate(f,{queue:!1,duration:e.duration,easing:e.easing,complete:function(){l||a.hide(),t.effects.restore(a,r),t.effects.removeWrapper(a),i()}})},t.effects.effect.drop=function(e,i){var s,n=t(this),o=["position","top","bottom","left","right","opacity","height","width"],a=t.effects.setMode(n,e.mode||"hide"),r="show"===a,h=e.direction||"left",l="up"===h||"down"===h?"top":"left",c="up"===h||"left"===h?"pos":"neg",u={opacity:r?1:0};t.effects.save(n,o),n.show(),t.effects.createWrapper(n),s=e.distance||n["top"===l?"outerHeight":"outerWidth"](!0)/2,r&&n.css("opacity",0).css(l,"pos"===c?-s:s),u[l]=(r?"pos"===c?"+=":"-=":"pos"===c?"-=":"+=")+s,n.animate(u,{queue:!1,duration:e.duration,easing:e.easing,complete:function(){"hide"===a&&n.hide(),t.effects.restore(n,o),t.effects.removeWrapper(n),i()}})},t.effects.effect.explode=function(e,i){function s(){b.push(this),b.length===u*d&&n()}function n(){p.css({visibility:"visible"}),t(b).remove(),g||p.hide(),i()}var o,a,r,h,l,c,u=e.pieces?Math.round(Math.sqrt(e.pieces)):3,d=u,p=t(this),f=t.effects.setMode(p,e.mode||"hide"),g="show"===f,m=p.show().css("visibility","hidden").offset(),_=Math.ceil(p.outerWidth()/d),v=Math.ceil(p.outerHeight()/u),b=[];for(o=0;u>o;o++)for(h=m.top+o*v,c=o-(u-1)/2,a=0;d>a;a++)r=m.left+a*_,l=a-(d-1)/2,p.clone().appendTo("body").wrap("
").css({position:"absolute",visibility:"visible",left:-a*_,top:-o*v}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:_,height:v,left:r+(g?l*_:0),top:h+(g?c*v:0),opacity:g?0:1}).animate({left:r+(g?0:l*_),top:h+(g?0:c*v),opacity:g?1:0},e.duration||500,e.easing,s)},t.effects.effect.fade=function(e,i){var s=t(this),n=t.effects.setMode(s,e.mode||"toggle");s.animate({opacity:n},{queue:!1,duration:e.duration,easing:e.easing,complete:i})},t.effects.effect.fold=function(e,i){var s,n,o=t(this),a=["position","top","bottom","left","right","height","width"],r=t.effects.setMode(o,e.mode||"hide"),h="show"===r,l="hide"===r,c=e.size||15,u=/([0-9]+)%/.exec(c),d=!!e.horizFirst,p=h!==d,f=p?["width","height"]:["height","width"],g=e.duration/2,m={},_={};t.effects.save(o,a),o.show(),s=t.effects.createWrapper(o).css({overflow:"hidden"}),n=p?[s.width(),s.height()]:[s.height(),s.width()],u&&(c=parseInt(u[1],10)/100*n[l?0:1]),h&&s.css(d?{height:0,width:c}:{height:c,width:0}),m[f[0]]=h?n[0]:c,_[f[1]]=h?n[1]:0,s.animate(m,g,e.easing).animate(_,g,e.easing,function(){l&&o.hide(),t.effects.restore(o,a),t.effects.removeWrapper(o),i()})},t.effects.effect.highlight=function(e,i){var s=t(this),n=["backgroundImage","backgroundColor","opacity"],o=t.effects.setMode(s,e.mode||"show"),a={backgroundColor:s.css("backgroundColor")};"hide"===o&&(a.opacity=0),t.effects.save(s,n),s.show().css({backgroundImage:"none",backgroundColor:e.color||"#ffff99"}).animate(a,{queue:!1,duration:e.duration,easing:e.easing,complete:function(){"hide"===o&&s.hide(),t.effects.restore(s,n),i()}})},t.effects.effect.size=function(e,i){var s,n,o,a=t(this),r=["position","top","bottom","left","right","width","height","overflow","opacity"],h=["position","top","bottom","left","right","overflow","opacity"],l=["width","height","overflow"],c=["fontSize"],u=["borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"],d=["borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"],p=t.effects.setMode(a,e.mode||"effect"),f=e.restore||"effect"!==p,g=e.scale||"both",m=e.origin||["middle","center"],_=a.css("position"),v=f?r:h,b={height:0,width:0,outerHeight:0,outerWidth:0};"show"===p&&a.show(),s={height:a.height(),width:a.width(),outerHeight:a.outerHeight(),outerWidth:a.outerWidth()},"toggle"===e.mode&&"show"===p?(a.from=e.to||b,a.to=e.from||s):(a.from=e.from||("show"===p?b:s),a.to=e.to||("hide"===p?b:s)),o={from:{y:a.from.height/s.height,x:a.from.width/s.width},to:{y:a.to.height/s.height,x:a.to.width/s.width}},("box"===g||"both"===g)&&(o.from.y!==o.to.y&&(v=v.concat(u),a.from=t.effects.setTransition(a,u,o.from.y,a.from),a.to=t.effects.setTransition(a,u,o.to.y,a.to)),o.from.x!==o.to.x&&(v=v.concat(d),a.from=t.effects.setTransition(a,d,o.from.x,a.from),a.to=t.effects.setTransition(a,d,o.to.x,a.to))),("content"===g||"both"===g)&&o.from.y!==o.to.y&&(v=v.concat(c).concat(l),a.from=t.effects.setTransition(a,c,o.from.y,a.from),a.to=t.effects.setTransition(a,c,o.to.y,a.to)),t.effects.save(a,v),a.show(),t.effects.createWrapper(a),a.css("overflow","hidden").css(a.from),m&&(n=t.effects.getBaseline(m,s),a.from.top=(s.outerHeight-a.outerHeight())*n.y,a.from.left=(s.outerWidth-a.outerWidth())*n.x,a.to.top=(s.outerHeight-a.to.outerHeight)*n.y,a.to.left=(s.outerWidth-a.to.outerWidth)*n.x),a.css(a.from),("content"===g||"both"===g)&&(u=u.concat(["marginTop","marginBottom"]).concat(c),d=d.concat(["marginLeft","marginRight"]),l=r.concat(u).concat(d),a.find("*[width]").each(function(){var i=t(this),s={height:i.height(),width:i.width(),outerHeight:i.outerHeight(),outerWidth:i.outerWidth()};f&&t.effects.save(i,l),i.from={height:s.height*o.from.y,width:s.width*o.from.x,outerHeight:s.outerHeight*o.from.y,outerWidth:s.outerWidth*o.from.x},i.to={height:s.height*o.to.y,width:s.width*o.to.x,outerHeight:s.height*o.to.y,outerWidth:s.width*o.to.x},o.from.y!==o.to.y&&(i.from=t.effects.setTransition(i,u,o.from.y,i.from),i.to=t.effects.setTransition(i,u,o.to.y,i.to)),o.from.x!==o.to.x&&(i.from=t.effects.setTransition(i,d,o.from.x,i.from),i.to=t.effects.setTransition(i,d,o.to.x,i.to)),i.css(i.from),i.animate(i.to,e.duration,e.easing,function(){f&&t.effects.restore(i,l)})})),a.animate(a.to,{queue:!1,duration:e.duration,easing:e.easing,complete:function(){0===a.to.opacity&&a.css("opacity",a.from.opacity),"hide"===p&&a.hide(),t.effects.restore(a,v),f||("static"===_?a.css({position:"relative",top:a.to.top,left:a.to.left}):t.each(["top","left"],function(t,e){a.css(e,function(e,i){var s=parseInt(i,10),n=t?a.to.left:a.to.top;return"auto"===i?n+"px":s+n+"px"})})),t.effects.removeWrapper(a),i()}})},t.effects.effect.scale=function(e,i){var s=t(this),n=t.extend(!0,{},e),o=t.effects.setMode(s,e.mode||"effect"),a=parseInt(e.percent,10)||(0===parseInt(e.percent,10)?0:"hide"===o?0:100),r=e.direction||"both",h=e.origin,l={height:s.height(),width:s.width(),outerHeight:s.outerHeight(),outerWidth:s.outerWidth()},c={y:"horizontal"!==r?a/100:1,x:"vertical"!==r?a/100:1};n.effect="size",n.queue=!1,n.complete=i,"effect"!==o&&(n.origin=h||["middle","center"],n.restore=!0),n.from=e.from||("show"===o?{height:0,width:0,outerHeight:0,outerWidth:0}:l),n.to={height:l.height*c.y,width:l.width*c.x,outerHeight:l.outerHeight*c.y,outerWidth:l.outerWidth*c.x},n.fade&&("show"===o&&(n.from.opacity=0,n.to.opacity=1),"hide"===o&&(n.from.opacity=1,n.to.opacity=0)),s.effect(n)},t.effects.effect.puff=function(e,i){var s=t(this),n=t.effects.setMode(s,e.mode||"hide"),o="hide"===n,a=parseInt(e.percent,10)||150,r=a/100,h={height:s.height(),width:s.width(),outerHeight:s.outerHeight(),outerWidth:s.outerWidth()};t.extend(e,{effect:"scale",queue:!1,fade:!0,mode:n,complete:i,percent:o?a:100,from:o?h:{height:h.height*r,width:h.width*r,outerHeight:h.outerHeight*r,outerWidth:h.outerWidth*r}}),s.effect(e)},t.effects.effect.pulsate=function(e,i){var s,n=t(this),o=t.effects.setMode(n,e.mode||"show"),a="show"===o,r="hide"===o,h=a||"hide"===o,l=2*(e.times||5)+(h?1:0),c=e.duration/l,u=0,d=n.queue(),p=d.length;for((a||!n.is(":visible"))&&(n.css("opacity",0).show(),u=1),s=1;l>s;s++)n.animate({opacity:u},c,e.easing),u=1-u;n.animate({opacity:u},c,e.easing),n.queue(function(){r&&n.hide(),i()}),p>1&&d.splice.apply(d,[1,0].concat(d.splice(p,l+1))),n.dequeue()},t.effects.effect.shake=function(e,i){var s,n=t(this),o=["position","top","bottom","left","right","height","width"],a=t.effects.setMode(n,e.mode||"effect"),r=e.direction||"left",h=e.distance||20,l=e.times||3,c=2*l+1,u=Math.round(e.duration/c),d="up"===r||"down"===r?"top":"left",p="up"===r||"left"===r,f={},g={},m={},_=n.queue(),v=_.length;for(t.effects.save(n,o),n.show(),t.effects.createWrapper(n),f[d]=(p?"-=":"+=")+h,g[d]=(p?"+=":"-=")+2*h,m[d]=(p?"-=":"+=")+2*h,n.animate(f,u,e.easing),s=1;l>s;s++)n.animate(g,u,e.easing).animate(m,u,e.easing);n.animate(g,u,e.easing).animate(f,u/2,e.easing).queue(function(){"hide"===a&&n.hide(),t.effects.restore(n,o),t.effects.removeWrapper(n),i()}),v>1&&_.splice.apply(_,[1,0].concat(_.splice(v,c+1))),n.dequeue()},t.effects.effect.slide=function(e,i){var s,n=t(this),o=["position","top","bottom","left","right","width","height"],a=t.effects.setMode(n,e.mode||"show"),r="show"===a,h=e.direction||"left",l="up"===h||"down"===h?"top":"left",c="up"===h||"left"===h,u={};t.effects.save(n,o),n.show(),s=e.distance||n["top"===l?"outerHeight":"outerWidth"](!0),t.effects.createWrapper(n).css({overflow:"hidden"}),r&&n.css(l,c?isNaN(s)?"-"+s:-s:s),u[l]=(r?c?"+=":"-=":c?"-=":"+=")+s,n.animate(u,{queue:!1,duration:e.duration,easing:e.easing,complete:function(){"hide"===a&&n.hide(),t.effects.restore(n,o),t.effects.removeWrapper(n),i()}})},t.effects.effect.transfer=function(e,i){var s=t(this),n=t(e.to),o="fixed"===n.css("position"),a=t("body"),r=o?a.scrollTop():0,h=o?a.scrollLeft():0,l=n.offset(),c={top:l.top-r,left:l.left-h,height:n.innerHeight(),width:n.innerWidth()},u=s.offset(),d=t("
").appendTo(document.body).addClass(e.className).css({top:u.top-r,left:u.left-h,height:s.innerHeight(),width:s.innerWidth(),position:o?"fixed":"absolute"}).animate(c,e.duration,e.easing,function(){d.remove(),i()})}});
\ No newline at end of file
diff --git a/public/static/lib/jquery-ui-1.11.4.custom/jquery-ui.structure.css b/public/static/lib/jquery-ui-1.11.4.custom/jquery-ui.structure.css
new file mode 100755
index 00000000..8184e152
--- /dev/null
+++ b/public/static/lib/jquery-ui-1.11.4.custom/jquery-ui.structure.css
@@ -0,0 +1,833 @@
+/*!
+ * jQuery UI CSS Framework 1.11.4
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/category/theming/
+ */
+
+/* Layout helpers
+----------------------------------*/
+.ui-helper-hidden {
+ display: none;
+}
+.ui-helper-hidden-accessible {
+ border: 0;
+ clip: rect(0 0 0 0);
+ height: 1px;
+ margin: -1px;
+ overflow: hidden;
+ padding: 0;
+ position: absolute;
+ width: 1px;
+}
+.ui-helper-reset {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ outline: 0;
+ line-height: 1.3;
+ text-decoration: none;
+ font-size: 100%;
+ list-style: none;
+}
+.ui-helper-clearfix:before,
+.ui-helper-clearfix:after {
+ content: "";
+ display: table;
+ border-collapse: collapse;
+}
+.ui-helper-clearfix:after {
+ clear: both;
+}
+.ui-helper-clearfix {
+ min-height: 0; /* support: IE7 */
+}
+.ui-helper-zfix {
+ width: 100%;
+ height: 100%;
+ top: 0;
+ left: 0;
+ position: absolute;
+ opacity: 0;
+ filter:Alpha(Opacity=0); /* support: IE8 */
+}
+
+.ui-front {
+ z-index: 100;
+}
+
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-disabled {
+ cursor: default !important;
+}
+
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon {
+ display: block;
+ text-indent: -99999px;
+ overflow: hidden;
+ background-repeat: no-repeat;
+}
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Overlays */
+.ui-widget-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+}
+.ui-draggable-handle {
+ -ms-touch-action: none;
+ touch-action: none;
+}
+.ui-resizable {
+ position: relative;
+}
+.ui-resizable-handle {
+ position: absolute;
+ font-size: 0.1px;
+ display: block;
+ -ms-touch-action: none;
+ touch-action: none;
+}
+.ui-resizable-disabled .ui-resizable-handle,
+.ui-resizable-autohide .ui-resizable-handle {
+ display: none;
+}
+.ui-resizable-n {
+ cursor: n-resize;
+ height: 7px;
+ width: 100%;
+ top: -5px;
+ left: 0;
+}
+.ui-resizable-s {
+ cursor: s-resize;
+ height: 7px;
+ width: 100%;
+ bottom: -5px;
+ left: 0;
+}
+.ui-resizable-e {
+ cursor: e-resize;
+ width: 7px;
+ right: -5px;
+ top: 0;
+ height: 100%;
+}
+.ui-resizable-w {
+ cursor: w-resize;
+ width: 7px;
+ left: -5px;
+ top: 0;
+ height: 100%;
+}
+.ui-resizable-se {
+ cursor: se-resize;
+ width: 12px;
+ height: 12px;
+ right: 1px;
+ bottom: 1px;
+}
+.ui-resizable-sw {
+ cursor: sw-resize;
+ width: 9px;
+ height: 9px;
+ left: -5px;
+ bottom: -5px;
+}
+.ui-resizable-nw {
+ cursor: nw-resize;
+ width: 9px;
+ height: 9px;
+ left: -5px;
+ top: -5px;
+}
+.ui-resizable-ne {
+ cursor: ne-resize;
+ width: 9px;
+ height: 9px;
+ right: -5px;
+ top: -5px;
+}
+.ui-selectable {
+ -ms-touch-action: none;
+ touch-action: none;
+}
+.ui-selectable-helper {
+ position: absolute;
+ z-index: 100;
+ border: 1px dotted black;
+}
+.ui-sortable-handle {
+ -ms-touch-action: none;
+ touch-action: none;
+}
+.ui-accordion .ui-accordion-header {
+ display: block;
+ cursor: pointer;
+ position: relative;
+ margin: 2px 0 0 0;
+ padding: .5em .5em .5em .7em;
+ min-height: 0; /* support: IE7 */
+ font-size: 100%;
+}
+.ui-accordion .ui-accordion-icons {
+ padding-left: 2.2em;
+}
+.ui-accordion .ui-accordion-icons .ui-accordion-icons {
+ padding-left: 2.2em;
+}
+.ui-accordion .ui-accordion-header .ui-accordion-header-icon {
+ position: absolute;
+ left: .5em;
+ top: 50%;
+ margin-top: -8px;
+}
+.ui-accordion .ui-accordion-content {
+ padding: 1em 2.2em;
+ border-top: 0;
+ overflow: auto;
+}
+.ui-autocomplete {
+ position: absolute;
+ top: 0;
+ left: 0;
+ cursor: default;
+}
+.ui-button {
+ display: inline-block;
+ position: relative;
+ padding: 0;
+ line-height: normal;
+ margin-right: .1em;
+ cursor: pointer;
+ vertical-align: middle;
+ text-align: center;
+ overflow: visible; /* removes extra width in IE */
+}
+.ui-button,
+.ui-button:link,
+.ui-button:visited,
+.ui-button:hover,
+.ui-button:active {
+ text-decoration: none;
+}
+/* to make room for the icon, a width needs to be set here */
+.ui-button-icon-only {
+ width: 2.2em;
+}
+/* button elements seem to need a little more width */
+button.ui-button-icon-only {
+ width: 2.4em;
+}
+.ui-button-icons-only {
+ width: 3.4em;
+}
+button.ui-button-icons-only {
+ width: 3.7em;
+}
+
+/* button text element */
+.ui-button .ui-button-text {
+ display: block;
+ line-height: normal;
+}
+.ui-button-text-only .ui-button-text {
+ padding: .4em 1em;
+}
+.ui-button-icon-only .ui-button-text,
+.ui-button-icons-only .ui-button-text {
+ padding: .4em;
+ text-indent: -9999999px;
+}
+.ui-button-text-icon-primary .ui-button-text,
+.ui-button-text-icons .ui-button-text {
+ padding: .4em 1em .4em 2.1em;
+}
+.ui-button-text-icon-secondary .ui-button-text,
+.ui-button-text-icons .ui-button-text {
+ padding: .4em 2.1em .4em 1em;
+}
+.ui-button-text-icons .ui-button-text {
+ padding-left: 2.1em;
+ padding-right: 2.1em;
+}
+/* no icon support for input elements, provide padding by default */
+input.ui-button {
+ padding: .4em 1em;
+}
+
+/* button icon element(s) */
+.ui-button-icon-only .ui-icon,
+.ui-button-text-icon-primary .ui-icon,
+.ui-button-text-icon-secondary .ui-icon,
+.ui-button-text-icons .ui-icon,
+.ui-button-icons-only .ui-icon {
+ position: absolute;
+ top: 50%;
+ margin-top: -8px;
+}
+.ui-button-icon-only .ui-icon {
+ left: 50%;
+ margin-left: -8px;
+}
+.ui-button-text-icon-primary .ui-button-icon-primary,
+.ui-button-text-icons .ui-button-icon-primary,
+.ui-button-icons-only .ui-button-icon-primary {
+ left: .5em;
+}
+.ui-button-text-icon-secondary .ui-button-icon-secondary,
+.ui-button-text-icons .ui-button-icon-secondary,
+.ui-button-icons-only .ui-button-icon-secondary {
+ right: .5em;
+}
+
+/* button sets */
+.ui-buttonset {
+ margin-right: 7px;
+}
+.ui-buttonset .ui-button {
+ margin-left: 0;
+ margin-right: -.3em;
+}
+
+/* workarounds */
+/* reset extra padding in Firefox, see h5bp.com/l */
+input.ui-button::-moz-focus-inner,
+button.ui-button::-moz-focus-inner {
+ border: 0;
+ padding: 0;
+}
+.ui-datepicker {
+ width: 17em;
+ padding: .2em .2em 0;
+ display: none;
+}
+.ui-datepicker .ui-datepicker-header {
+ position: relative;
+ padding: .2em 0;
+}
+.ui-datepicker .ui-datepicker-prev,
+.ui-datepicker .ui-datepicker-next {
+ position: absolute;
+ top: 2px;
+ width: 1.8em;
+ height: 1.8em;
+}
+.ui-datepicker .ui-datepicker-prev-hover,
+.ui-datepicker .ui-datepicker-next-hover {
+ top: 1px;
+}
+.ui-datepicker .ui-datepicker-prev {
+ left: 2px;
+}
+.ui-datepicker .ui-datepicker-next {
+ right: 2px;
+}
+.ui-datepicker .ui-datepicker-prev-hover {
+ left: 1px;
+}
+.ui-datepicker .ui-datepicker-next-hover {
+ right: 1px;
+}
+.ui-datepicker .ui-datepicker-prev span,
+.ui-datepicker .ui-datepicker-next span {
+ display: block;
+ position: absolute;
+ left: 50%;
+ margin-left: -8px;
+ top: 50%;
+ margin-top: -8px;
+}
+.ui-datepicker .ui-datepicker-title {
+ margin: 0 2.3em;
+ line-height: 1.8em;
+ text-align: center;
+}
+.ui-datepicker .ui-datepicker-title select {
+ font-size: 1em;
+ margin: 1px 0;
+}
+.ui-datepicker select.ui-datepicker-month,
+.ui-datepicker select.ui-datepicker-year {
+ width: 45%;
+}
+.ui-datepicker table {
+ width: 100%;
+ font-size: .9em;
+ border-collapse: collapse;
+ margin: 0 0 .4em;
+}
+.ui-datepicker th {
+ padding: .7em .3em;
+ text-align: center;
+ font-weight: bold;
+ border: 0;
+}
+.ui-datepicker td {
+ border: 0;
+ padding: 1px;
+}
+.ui-datepicker td span,
+.ui-datepicker td a {
+ display: block;
+ padding: .2em;
+ text-align: right;
+ text-decoration: none;
+}
+.ui-datepicker .ui-datepicker-buttonpane {
+ background-image: none;
+ margin: .7em 0 0 0;
+ padding: 0 .2em;
+ border-left: 0;
+ border-right: 0;
+ border-bottom: 0;
+}
+.ui-datepicker .ui-datepicker-buttonpane button {
+ float: right;
+ margin: .5em .2em .4em;
+ cursor: pointer;
+ padding: .2em .6em .3em .6em;
+ width: auto;
+ overflow: visible;
+}
+.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current {
+ float: left;
+}
+
+/* with multiple calendars */
+.ui-datepicker.ui-datepicker-multi {
+ width: auto;
+}
+.ui-datepicker-multi .ui-datepicker-group {
+ float: left;
+}
+.ui-datepicker-multi .ui-datepicker-group table {
+ width: 95%;
+ margin: 0 auto .4em;
+}
+.ui-datepicker-multi-2 .ui-datepicker-group {
+ width: 50%;
+}
+.ui-datepicker-multi-3 .ui-datepicker-group {
+ width: 33.3%;
+}
+.ui-datepicker-multi-4 .ui-datepicker-group {
+ width: 25%;
+}
+.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header,
+.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header {
+ border-left-width: 0;
+}
+.ui-datepicker-multi .ui-datepicker-buttonpane {
+ clear: left;
+}
+.ui-datepicker-row-break {
+ clear: both;
+ width: 100%;
+ font-size: 0;
+}
+
+/* RTL support */
+.ui-datepicker-rtl {
+ direction: rtl;
+}
+.ui-datepicker-rtl .ui-datepicker-prev {
+ right: 2px;
+ left: auto;
+}
+.ui-datepicker-rtl .ui-datepicker-next {
+ left: 2px;
+ right: auto;
+}
+.ui-datepicker-rtl .ui-datepicker-prev:hover {
+ right: 1px;
+ left: auto;
+}
+.ui-datepicker-rtl .ui-datepicker-next:hover {
+ left: 1px;
+ right: auto;
+}
+.ui-datepicker-rtl .ui-datepicker-buttonpane {
+ clear: right;
+}
+.ui-datepicker-rtl .ui-datepicker-buttonpane button {
+ float: left;
+}
+.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current,
+.ui-datepicker-rtl .ui-datepicker-group {
+ float: right;
+}
+.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header,
+.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header {
+ border-right-width: 0;
+ border-left-width: 1px;
+}
+.ui-dialog {
+ overflow: hidden;
+ position: absolute;
+ top: 0;
+ left: 0;
+ padding: .2em;
+ outline: 0;
+}
+.ui-dialog .ui-dialog-titlebar {
+ padding: .4em 1em;
+ position: relative;
+}
+.ui-dialog .ui-dialog-title {
+ float: left;
+ margin: .1em 0;
+ white-space: nowrap;
+ width: 90%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+.ui-dialog .ui-dialog-titlebar-close {
+ position: absolute;
+ right: .3em;
+ top: 50%;
+ width: 20px;
+ margin: -10px 0 0 0;
+ padding: 1px;
+ height: 20px;
+}
+.ui-dialog .ui-dialog-content {
+ position: relative;
+ border: 0;
+ padding: .5em 1em;
+ background: none;
+ overflow: auto;
+}
+.ui-dialog .ui-dialog-buttonpane {
+ text-align: left;
+ border-width: 1px 0 0 0;
+ background-image: none;
+ margin-top: .5em;
+ padding: .3em 1em .5em .4em;
+}
+.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset {
+ float: right;
+}
+.ui-dialog .ui-dialog-buttonpane button {
+ margin: .5em .4em .5em 0;
+ cursor: pointer;
+}
+.ui-dialog .ui-resizable-se {
+ width: 12px;
+ height: 12px;
+ right: -5px;
+ bottom: -5px;
+ background-position: 16px 16px;
+}
+.ui-draggable .ui-dialog-titlebar {
+ cursor: move;
+}
+.ui-menu {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ display: block;
+ outline: none;
+}
+.ui-menu .ui-menu {
+ position: absolute;
+}
+.ui-menu .ui-menu-item {
+ position: relative;
+ margin: 0;
+ padding: 3px 1em 3px .4em;
+ cursor: pointer;
+ min-height: 0; /* support: IE7 */
+ /* support: IE10, see #8844 */
+ list-style-image: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");
+}
+.ui-menu .ui-menu-divider {
+ margin: 5px 0;
+ height: 0;
+ font-size: 0;
+ line-height: 0;
+ border-width: 1px 0 0 0;
+}
+.ui-menu .ui-state-focus,
+.ui-menu .ui-state-active {
+ margin: -1px;
+}
+
+/* icon support */
+.ui-menu-icons {
+ position: relative;
+}
+.ui-menu-icons .ui-menu-item {
+ padding-left: 2em;
+}
+
+/* left-aligned */
+.ui-menu .ui-icon {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: .2em;
+ margin: auto 0;
+}
+
+/* right-aligned */
+.ui-menu .ui-menu-icon {
+ left: auto;
+ right: 0;
+}
+.ui-progressbar {
+ height: 2em;
+ text-align: left;
+ overflow: hidden;
+}
+.ui-progressbar .ui-progressbar-value {
+ margin: -1px;
+ height: 100%;
+}
+.ui-progressbar .ui-progressbar-overlay {
+ background: url("data:image/gif;base64,R0lGODlhKAAoAIABAAAAAP///yH/C05FVFNDQVBFMi4wAwEAAAAh+QQJAQABACwAAAAAKAAoAAACkYwNqXrdC52DS06a7MFZI+4FHBCKoDeWKXqymPqGqxvJrXZbMx7Ttc+w9XgU2FB3lOyQRWET2IFGiU9m1frDVpxZZc6bfHwv4c1YXP6k1Vdy292Fb6UkuvFtXpvWSzA+HycXJHUXiGYIiMg2R6W459gnWGfHNdjIqDWVqemH2ekpObkpOlppWUqZiqr6edqqWQAAIfkECQEAAQAsAAAAACgAKAAAApSMgZnGfaqcg1E2uuzDmmHUBR8Qil95hiPKqWn3aqtLsS18y7G1SzNeowWBENtQd+T1JktP05nzPTdJZlR6vUxNWWjV+vUWhWNkWFwxl9VpZRedYcflIOLafaa28XdsH/ynlcc1uPVDZxQIR0K25+cICCmoqCe5mGhZOfeYSUh5yJcJyrkZWWpaR8doJ2o4NYq62lAAACH5BAkBAAEALAAAAAAoACgAAAKVDI4Yy22ZnINRNqosw0Bv7i1gyHUkFj7oSaWlu3ovC8GxNso5fluz3qLVhBVeT/Lz7ZTHyxL5dDalQWPVOsQWtRnuwXaFTj9jVVh8pma9JjZ4zYSj5ZOyma7uuolffh+IR5aW97cHuBUXKGKXlKjn+DiHWMcYJah4N0lYCMlJOXipGRr5qdgoSTrqWSq6WFl2ypoaUAAAIfkECQEAAQAsAAAAACgAKAAAApaEb6HLgd/iO7FNWtcFWe+ufODGjRfoiJ2akShbueb0wtI50zm02pbvwfWEMWBQ1zKGlLIhskiEPm9R6vRXxV4ZzWT2yHOGpWMyorblKlNp8HmHEb/lCXjcW7bmtXP8Xt229OVWR1fod2eWqNfHuMjXCPkIGNileOiImVmCOEmoSfn3yXlJWmoHGhqp6ilYuWYpmTqKUgAAIfkECQEAAQAsAAAAACgAKAAAApiEH6kb58biQ3FNWtMFWW3eNVcojuFGfqnZqSebuS06w5V80/X02pKe8zFwP6EFWOT1lDFk8rGERh1TTNOocQ61Hm4Xm2VexUHpzjymViHrFbiELsefVrn6XKfnt2Q9G/+Xdie499XHd2g4h7ioOGhXGJboGAnXSBnoBwKYyfioubZJ2Hn0RuRZaflZOil56Zp6iioKSXpUAAAh+QQJAQABACwAAAAAKAAoAAACkoQRqRvnxuI7kU1a1UU5bd5tnSeOZXhmn5lWK3qNTWvRdQxP8qvaC+/yaYQzXO7BMvaUEmJRd3TsiMAgswmNYrSgZdYrTX6tSHGZO73ezuAw2uxuQ+BbeZfMxsexY35+/Qe4J1inV0g4x3WHuMhIl2jXOKT2Q+VU5fgoSUI52VfZyfkJGkha6jmY+aaYdirq+lQAACH5BAkBAAEALAAAAAAoACgAAAKWBIKpYe0L3YNKToqswUlvznigd4wiR4KhZrKt9Upqip61i9E3vMvxRdHlbEFiEXfk9YARYxOZZD6VQ2pUunBmtRXo1Lf8hMVVcNl8JafV38aM2/Fu5V16Bn63r6xt97j09+MXSFi4BniGFae3hzbH9+hYBzkpuUh5aZmHuanZOZgIuvbGiNeomCnaxxap2upaCZsq+1kAACH5BAkBAAEALAAAAAAoACgAAAKXjI8By5zf4kOxTVrXNVlv1X0d8IGZGKLnNpYtm8Lr9cqVeuOSvfOW79D9aDHizNhDJidFZhNydEahOaDH6nomtJjp1tutKoNWkvA6JqfRVLHU/QUfau9l2x7G54d1fl995xcIGAdXqMfBNadoYrhH+Mg2KBlpVpbluCiXmMnZ2Sh4GBqJ+ckIOqqJ6LmKSllZmsoq6wpQAAAh+QQJAQABACwAAAAAKAAoAAAClYx/oLvoxuJDkU1a1YUZbJ59nSd2ZXhWqbRa2/gF8Gu2DY3iqs7yrq+xBYEkYvFSM8aSSObE+ZgRl1BHFZNr7pRCavZ5BW2142hY3AN/zWtsmf12p9XxxFl2lpLn1rseztfXZjdIWIf2s5dItwjYKBgo9yg5pHgzJXTEeGlZuenpyPmpGQoKOWkYmSpaSnqKileI2FAAACH5BAkBAAEALAAAAAAoACgAAAKVjB+gu+jG4kORTVrVhRlsnn2dJ3ZleFaptFrb+CXmO9OozeL5VfP99HvAWhpiUdcwkpBH3825AwYdU8xTqlLGhtCosArKMpvfa1mMRae9VvWZfeB2XfPkeLmm18lUcBj+p5dnN8jXZ3YIGEhYuOUn45aoCDkp16hl5IjYJvjWKcnoGQpqyPlpOhr3aElaqrq56Bq7VAAAOw==");
+ height: 100%;
+ filter: alpha(opacity=25); /* support: IE8 */
+ opacity: 0.25;
+}
+.ui-progressbar-indeterminate .ui-progressbar-value {
+ background-image: none;
+}
+.ui-selectmenu-menu {
+ padding: 0;
+ margin: 0;
+ position: absolute;
+ top: 0;
+ left: 0;
+ display: none;
+}
+.ui-selectmenu-menu .ui-menu {
+ overflow: auto;
+ /* Support: IE7 */
+ overflow-x: hidden;
+ padding-bottom: 1px;
+}
+.ui-selectmenu-menu .ui-menu .ui-selectmenu-optgroup {
+ font-size: 1em;
+ font-weight: bold;
+ line-height: 1.5;
+ padding: 2px 0.4em;
+ margin: 0.5em 0 0 0;
+ height: auto;
+ border: 0;
+}
+.ui-selectmenu-open {
+ display: block;
+}
+.ui-selectmenu-button {
+ display: inline-block;
+ overflow: hidden;
+ position: relative;
+ text-decoration: none;
+ cursor: pointer;
+}
+.ui-selectmenu-button span.ui-icon {
+ right: 0.5em;
+ left: auto;
+ margin-top: -8px;
+ position: absolute;
+ top: 50%;
+}
+.ui-selectmenu-button span.ui-selectmenu-text {
+ text-align: left;
+ padding: 0.4em 2.1em 0.4em 1em;
+ display: block;
+ line-height: 1.4;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+.ui-slider {
+ position: relative;
+ text-align: left;
+}
+.ui-slider .ui-slider-handle {
+ position: absolute;
+ z-index: 2;
+ width: 1.2em;
+ height: 1.2em;
+ cursor: default;
+ -ms-touch-action: none;
+ touch-action: none;
+}
+.ui-slider .ui-slider-range {
+ position: absolute;
+ z-index: 1;
+ font-size: .7em;
+ display: block;
+ border: 0;
+ background-position: 0 0;
+}
+
+/* support: IE8 - See #6727 */
+.ui-slider.ui-state-disabled .ui-slider-handle,
+.ui-slider.ui-state-disabled .ui-slider-range {
+ filter: inherit;
+}
+
+.ui-slider-horizontal {
+ height: .8em;
+}
+.ui-slider-horizontal .ui-slider-handle {
+ top: -.3em;
+ margin-left: -.6em;
+}
+.ui-slider-horizontal .ui-slider-range {
+ top: 0;
+ height: 100%;
+}
+.ui-slider-horizontal .ui-slider-range-min {
+ left: 0;
+}
+.ui-slider-horizontal .ui-slider-range-max {
+ right: 0;
+}
+
+.ui-slider-vertical {
+ width: .8em;
+ height: 100px;
+}
+.ui-slider-vertical .ui-slider-handle {
+ left: -.3em;
+ margin-left: 0;
+ margin-bottom: -.6em;
+}
+.ui-slider-vertical .ui-slider-range {
+ left: 0;
+ width: 100%;
+}
+.ui-slider-vertical .ui-slider-range-min {
+ bottom: 0;
+}
+.ui-slider-vertical .ui-slider-range-max {
+ top: 0;
+}
+.ui-spinner {
+ position: relative;
+ display: inline-block;
+ overflow: hidden;
+ padding: 0;
+ vertical-align: middle;
+}
+.ui-spinner-input {
+ border: none;
+ background: none;
+ color: inherit;
+ padding: 0;
+ margin: .2em 0;
+ vertical-align: middle;
+ margin-left: .4em;
+ margin-right: 22px;
+}
+.ui-spinner-button {
+ width: 16px;
+ height: 50%;
+ font-size: .5em;
+ padding: 0;
+ margin: 0;
+ text-align: center;
+ position: absolute;
+ cursor: default;
+ display: block;
+ overflow: hidden;
+ right: 0;
+}
+/* more specificity required here to override default borders */
+.ui-spinner a.ui-spinner-button {
+ border-top: none;
+ border-bottom: none;
+ border-right: none;
+}
+/* vertically center icon */
+.ui-spinner .ui-icon {
+ position: absolute;
+ margin-top: -8px;
+ top: 50%;
+ left: 0;
+}
+.ui-spinner-up {
+ top: 0;
+}
+.ui-spinner-down {
+ bottom: 0;
+}
+
+/* TR overrides */
+.ui-spinner .ui-icon-triangle-1-s {
+ /* need to fix icons sprite */
+ background-position: -65px -16px;
+}
+.ui-tabs {
+ position: relative;/* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */
+ padding: .2em;
+}
+.ui-tabs .ui-tabs-nav {
+ margin: 0;
+ padding: .2em .2em 0;
+}
+.ui-tabs .ui-tabs-nav li {
+ list-style: none;
+ float: left;
+ position: relative;
+ top: 0;
+ margin: 1px .2em 0 0;
+ border-bottom-width: 0;
+ padding: 0;
+ white-space: nowrap;
+}
+.ui-tabs .ui-tabs-nav .ui-tabs-anchor {
+ float: left;
+ padding: .5em 1em;
+ text-decoration: none;
+}
+.ui-tabs .ui-tabs-nav li.ui-tabs-active {
+ margin-bottom: -1px;
+ padding-bottom: 1px;
+}
+.ui-tabs .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor,
+.ui-tabs .ui-tabs-nav li.ui-state-disabled .ui-tabs-anchor,
+.ui-tabs .ui-tabs-nav li.ui-tabs-loading .ui-tabs-anchor {
+ cursor: text;
+}
+.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor {
+ cursor: pointer;
+}
+.ui-tabs .ui-tabs-panel {
+ display: block;
+ border-width: 0;
+ padding: 1em 1.4em;
+ background: none;
+}
+.ui-tooltip {
+ padding: 8px;
+ position: absolute;
+ z-index: 9999;
+ max-width: 300px;
+ -webkit-box-shadow: 0 0 5px #aaa;
+ box-shadow: 0 0 5px #aaa;
+}
+body .ui-tooltip {
+ border-width: 2px;
+}
diff --git a/public/static/lib/jquery-ui-1.11.4.custom/jquery-ui.structure.min.css b/public/static/lib/jquery-ui-1.11.4.custom/jquery-ui.structure.min.css
new file mode 100755
index 00000000..36a555ab
--- /dev/null
+++ b/public/static/lib/jquery-ui-1.11.4.custom/jquery-ui.structure.min.css
@@ -0,0 +1,5 @@
+/*! jQuery UI - v1.11.4 - 2016-11-20
+* http://jqueryui.com
+* Copyright jQuery Foundation and other contributors; Licensed MIT */
+
+.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table;border-collapse:collapse}.ui-helper-clearfix:after{clear:both}.ui-helper-clearfix{min-height:0}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important}.ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-draggable-handle{-ms-touch-action:none;touch-action:none}.ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:0.1px;display:block;-ms-touch-action:none;touch-action:none}.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;left:0}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0}.ui-resizable-e{cursor:e-resize;width:7px;right:-5px;top:0;height:100%}.ui-resizable-w{cursor:w-resize;width:7px;left:-5px;top:0;height:100%}.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:1px;bottom:1px}.ui-resizable-sw{cursor:sw-resize;width:9px;height:9px;left:-5px;bottom:-5px}.ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px}.ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px}.ui-selectable{-ms-touch-action:none;touch-action:none}.ui-selectable-helper{position:absolute;z-index:100;border:1px dotted black}.ui-sortable-handle{-ms-touch-action:none;touch-action:none}.ui-accordion .ui-accordion-header{display:block;cursor:pointer;position:relative;margin:2px 0 0 0;padding:.5em .5em .5em .7em;min-height:0;font-size:100%}.ui-accordion .ui-accordion-icons{padding-left:2.2em}.ui-accordion .ui-accordion-icons .ui-accordion-icons{padding-left:2.2em}.ui-accordion .ui-accordion-header .ui-accordion-header-icon{position:absolute;left:.5em;top:50%;margin-top:-8px}.ui-accordion .ui-accordion-content{padding:1em 2.2em;border-top:0;overflow:auto}.ui-autocomplete{position:absolute;top:0;left:0;cursor:default}.ui-button{display:inline-block;position:relative;padding:0;line-height:normal;margin-right:.1em;cursor:pointer;vertical-align:middle;text-align:center;overflow:visible}.ui-button,.ui-button:link,.ui-button:visited,.ui-button:hover,.ui-button:active{text-decoration:none}.ui-button-icon-only{width:2.2em}button.ui-button-icon-only{width:2.4em}.ui-button-icons-only{width:3.4em}button.ui-button-icons-only{width:3.7em}.ui-button .ui-button-text{display:block;line-height:normal}.ui-button-text-only .ui-button-text{padding:.4em 1em}.ui-button-icon-only .ui-button-text,.ui-button-icons-only .ui-button-text{padding:.4em;text-indent:-9999999px}.ui-button-text-icon-primary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 1em .4em 2.1em}.ui-button-text-icon-secondary .ui-button-text,.ui-button-text-icons .ui-button-text{padding:.4em 2.1em .4em 1em}.ui-button-text-icons .ui-button-text{padding-left:2.1em;padding-right:2.1em}input.ui-button{padding:.4em 1em}.ui-button-icon-only .ui-icon,.ui-button-text-icon-primary .ui-icon,.ui-button-text-icon-secondary .ui-icon,.ui-button-text-icons .ui-icon,.ui-button-icons-only .ui-icon{position:absolute;top:50%;margin-top:-8px}.ui-button-icon-only .ui-icon{left:50%;margin-left:-8px}.ui-button-text-icon-primary .ui-button-icon-primary,.ui-button-text-icons .ui-button-icon-primary,.ui-button-icons-only .ui-button-icon-primary{left:.5em}.ui-button-text-icon-secondary .ui-button-icon-secondary,.ui-button-text-icons .ui-button-icon-secondary,.ui-button-icons-only .ui-button-icon-secondary{right:.5em}.ui-buttonset{margin-right:7px}.ui-buttonset .ui-button{margin-left:0;margin-right:-.3em}input.ui-button::-moz-focus-inner,button.ui-button::-moz-focus-inner{border:0;padding:0}.ui-datepicker{width:17em;padding:.2em .2em 0;display:none}.ui-datepicker .ui-datepicker-header{position:relative;padding:.2em 0}.ui-datepicker .ui-datepicker-prev,.ui-datepicker .ui-datepicker-next{position:absolute;top:2px;width:1.8em;height:1.8em}.ui-datepicker .ui-datepicker-prev-hover,.ui-datepicker .ui-datepicker-next-hover{top:1px}.ui-datepicker .ui-datepicker-prev{left:2px}.ui-datepicker .ui-datepicker-next{right:2px}.ui-datepicker .ui-datepicker-prev-hover{left:1px}.ui-datepicker .ui-datepicker-next-hover{right:1px}.ui-datepicker .ui-datepicker-prev span,.ui-datepicker .ui-datepicker-next span{display:block;position:absolute;left:50%;margin-left:-8px;top:50%;margin-top:-8px}.ui-datepicker .ui-datepicker-title{margin:0 2.3em;line-height:1.8em;text-align:center}.ui-datepicker .ui-datepicker-title select{font-size:1em;margin:1px 0}.ui-datepicker select.ui-datepicker-month,.ui-datepicker select.ui-datepicker-year{width:45%}.ui-datepicker table{width:100%;font-size:.9em;border-collapse:collapse;margin:0 0 .4em}.ui-datepicker th{padding:.7em .3em;text-align:center;font-weight:bold;border:0}.ui-datepicker td{border:0;padding:1px}.ui-datepicker td span,.ui-datepicker td a{display:block;padding:.2em;text-align:right;text-decoration:none}.ui-datepicker .ui-datepicker-buttonpane{background-image:none;margin:.7em 0 0 0;padding:0 .2em;border-left:0;border-right:0;border-bottom:0}.ui-datepicker .ui-datepicker-buttonpane button{float:right;margin:.5em .2em .4em;cursor:pointer;padding:.2em .6em .3em .6em;width:auto;overflow:visible}.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current{float:left}.ui-datepicker.ui-datepicker-multi{width:auto}.ui-datepicker-multi .ui-datepicker-group{float:left}.ui-datepicker-multi .ui-datepicker-group table{width:95%;margin:0 auto .4em}.ui-datepicker-multi-2 .ui-datepicker-group{width:50%}.ui-datepicker-multi-3 .ui-datepicker-group{width:33.3%}.ui-datepicker-multi-4 .ui-datepicker-group{width:25%}.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header{border-left-width:0}.ui-datepicker-multi .ui-datepicker-buttonpane{clear:left}.ui-datepicker-row-break{clear:both;width:100%;font-size:0}.ui-datepicker-rtl{direction:rtl}.ui-datepicker-rtl .ui-datepicker-prev{right:2px;left:auto}.ui-datepicker-rtl .ui-datepicker-next{left:2px;right:auto}.ui-datepicker-rtl .ui-datepicker-prev:hover{right:1px;left:auto}.ui-datepicker-rtl .ui-datepicker-next:hover{left:1px;right:auto}.ui-datepicker-rtl .ui-datepicker-buttonpane{clear:right}.ui-datepicker-rtl .ui-datepicker-buttonpane button{float:left}.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current,.ui-datepicker-rtl .ui-datepicker-group{float:right}.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header,.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header{border-right-width:0;border-left-width:1px}.ui-dialog{overflow:hidden;position:absolute;top:0;left:0;padding:.2em;outline:0}.ui-dialog .ui-dialog-titlebar{padding:.4em 1em;position:relative}.ui-dialog .ui-dialog-title{float:left;margin:.1em 0;white-space:nowrap;width:90%;overflow:hidden;text-overflow:ellipsis}.ui-dialog .ui-dialog-titlebar-close{position:absolute;right:.3em;top:50%;width:20px;margin:-10px 0 0 0;padding:1px;height:20px}.ui-dialog .ui-dialog-content{position:relative;border:0;padding:.5em 1em;background:none;overflow:auto}.ui-dialog .ui-dialog-buttonpane{text-align:left;border-width:1px 0 0 0;background-image:none;margin-top:.5em;padding:.3em 1em .5em .4em}.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset{float:right}.ui-dialog .ui-dialog-buttonpane button{margin:.5em .4em .5em 0;cursor:pointer}.ui-dialog .ui-resizable-se{width:12px;height:12px;right:-5px;bottom:-5px;background-position:16px 16px}.ui-draggable .ui-dialog-titlebar{cursor:move}.ui-menu{list-style:none;padding:0;margin:0;display:block;outline:none}.ui-menu .ui-menu{position:absolute}.ui-menu .ui-menu-item{position:relative;margin:0;padding:3px 1em 3px .4em;cursor:pointer;min-height:0;list-style-image:url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7")}.ui-menu .ui-menu-divider{margin:5px 0;height:0;font-size:0;line-height:0;border-width:1px 0 0 0}.ui-menu .ui-state-focus,.ui-menu .ui-state-active{margin:-1px}.ui-menu-icons{position:relative}.ui-menu-icons .ui-menu-item{padding-left:2em}.ui-menu .ui-icon{position:absolute;top:0;bottom:0;left:.2em;margin:auto 0}.ui-menu .ui-menu-icon{left:auto;right:0}.ui-progressbar{height:2em;text-align:left;overflow:hidden}.ui-progressbar .ui-progressbar-value{margin:-1px;height:100%}.ui-progressbar .ui-progressbar-overlay{background:url("data:image/gif;base64,R0lGODlhKAAoAIABAAAAAP///yH/C05FVFNDQVBFMi4wAwEAAAAh+QQJAQABACwAAAAAKAAoAAACkYwNqXrdC52DS06a7MFZI+4FHBCKoDeWKXqymPqGqxvJrXZbMx7Ttc+w9XgU2FB3lOyQRWET2IFGiU9m1frDVpxZZc6bfHwv4c1YXP6k1Vdy292Fb6UkuvFtXpvWSzA+HycXJHUXiGYIiMg2R6W459gnWGfHNdjIqDWVqemH2ekpObkpOlppWUqZiqr6edqqWQAAIfkECQEAAQAsAAAAACgAKAAAApSMgZnGfaqcg1E2uuzDmmHUBR8Qil95hiPKqWn3aqtLsS18y7G1SzNeowWBENtQd+T1JktP05nzPTdJZlR6vUxNWWjV+vUWhWNkWFwxl9VpZRedYcflIOLafaa28XdsH/ynlcc1uPVDZxQIR0K25+cICCmoqCe5mGhZOfeYSUh5yJcJyrkZWWpaR8doJ2o4NYq62lAAACH5BAkBAAEALAAAAAAoACgAAAKVDI4Yy22ZnINRNqosw0Bv7i1gyHUkFj7oSaWlu3ovC8GxNso5fluz3qLVhBVeT/Lz7ZTHyxL5dDalQWPVOsQWtRnuwXaFTj9jVVh8pma9JjZ4zYSj5ZOyma7uuolffh+IR5aW97cHuBUXKGKXlKjn+DiHWMcYJah4N0lYCMlJOXipGRr5qdgoSTrqWSq6WFl2ypoaUAAAIfkECQEAAQAsAAAAACgAKAAAApaEb6HLgd/iO7FNWtcFWe+ufODGjRfoiJ2akShbueb0wtI50zm02pbvwfWEMWBQ1zKGlLIhskiEPm9R6vRXxV4ZzWT2yHOGpWMyorblKlNp8HmHEb/lCXjcW7bmtXP8Xt229OVWR1fod2eWqNfHuMjXCPkIGNileOiImVmCOEmoSfn3yXlJWmoHGhqp6ilYuWYpmTqKUgAAIfkECQEAAQAsAAAAACgAKAAAApiEH6kb58biQ3FNWtMFWW3eNVcojuFGfqnZqSebuS06w5V80/X02pKe8zFwP6EFWOT1lDFk8rGERh1TTNOocQ61Hm4Xm2VexUHpzjymViHrFbiELsefVrn6XKfnt2Q9G/+Xdie499XHd2g4h7ioOGhXGJboGAnXSBnoBwKYyfioubZJ2Hn0RuRZaflZOil56Zp6iioKSXpUAAAh+QQJAQABACwAAAAAKAAoAAACkoQRqRvnxuI7kU1a1UU5bd5tnSeOZXhmn5lWK3qNTWvRdQxP8qvaC+/yaYQzXO7BMvaUEmJRd3TsiMAgswmNYrSgZdYrTX6tSHGZO73ezuAw2uxuQ+BbeZfMxsexY35+/Qe4J1inV0g4x3WHuMhIl2jXOKT2Q+VU5fgoSUI52VfZyfkJGkha6jmY+aaYdirq+lQAACH5BAkBAAEALAAAAAAoACgAAAKWBIKpYe0L3YNKToqswUlvznigd4wiR4KhZrKt9Upqip61i9E3vMvxRdHlbEFiEXfk9YARYxOZZD6VQ2pUunBmtRXo1Lf8hMVVcNl8JafV38aM2/Fu5V16Bn63r6xt97j09+MXSFi4BniGFae3hzbH9+hYBzkpuUh5aZmHuanZOZgIuvbGiNeomCnaxxap2upaCZsq+1kAACH5BAkBAAEALAAAAAAoACgAAAKXjI8By5zf4kOxTVrXNVlv1X0d8IGZGKLnNpYtm8Lr9cqVeuOSvfOW79D9aDHizNhDJidFZhNydEahOaDH6nomtJjp1tutKoNWkvA6JqfRVLHU/QUfau9l2x7G54d1fl995xcIGAdXqMfBNadoYrhH+Mg2KBlpVpbluCiXmMnZ2Sh4GBqJ+ckIOqqJ6LmKSllZmsoq6wpQAAAh+QQJAQABACwAAAAAKAAoAAAClYx/oLvoxuJDkU1a1YUZbJ59nSd2ZXhWqbRa2/gF8Gu2DY3iqs7yrq+xBYEkYvFSM8aSSObE+ZgRl1BHFZNr7pRCavZ5BW2142hY3AN/zWtsmf12p9XxxFl2lpLn1rseztfXZjdIWIf2s5dItwjYKBgo9yg5pHgzJXTEeGlZuenpyPmpGQoKOWkYmSpaSnqKileI2FAAACH5BAkBAAEALAAAAAAoACgAAAKVjB+gu+jG4kORTVrVhRlsnn2dJ3ZleFaptFrb+CXmO9OozeL5VfP99HvAWhpiUdcwkpBH3825AwYdU8xTqlLGhtCosArKMpvfa1mMRae9VvWZfeB2XfPkeLmm18lUcBj+p5dnN8jXZ3YIGEhYuOUn45aoCDkp16hl5IjYJvjWKcnoGQpqyPlpOhr3aElaqrq56Bq7VAAAOw==");height:100%;filter:alpha(opacity=25);opacity:0.25}.ui-progressbar-indeterminate .ui-progressbar-value{background-image:none}.ui-selectmenu-menu{padding:0;margin:0;position:absolute;top:0;left:0;display:none}.ui-selectmenu-menu .ui-menu{overflow:auto;overflow-x:hidden;padding-bottom:1px}.ui-selectmenu-menu .ui-menu .ui-selectmenu-optgroup{font-size:1em;font-weight:bold;line-height:1.5;padding:2px 0.4em;margin:0.5em 0 0 0;height:auto;border:0}.ui-selectmenu-open{display:block}.ui-selectmenu-button{display:inline-block;overflow:hidden;position:relative;text-decoration:none;cursor:pointer}.ui-selectmenu-button span.ui-icon{right:0.5em;left:auto;margin-top:-8px;position:absolute;top:50%}.ui-selectmenu-button span.ui-selectmenu-text{text-align:left;padding:0.4em 2.1em 0.4em 1em;display:block;line-height:1.4;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ui-slider{position:relative;text-align:left}.ui-slider .ui-slider-handle{position:absolute;z-index:2;width:1.2em;height:1.2em;cursor:default;-ms-touch-action:none;touch-action:none}.ui-slider .ui-slider-range{position:absolute;z-index:1;font-size:.7em;display:block;border:0;background-position:0 0}.ui-slider.ui-state-disabled .ui-slider-handle,.ui-slider.ui-state-disabled .ui-slider-range{filter:inherit}.ui-slider-horizontal{height:.8em}.ui-slider-horizontal .ui-slider-handle{top:-.3em;margin-left:-.6em}.ui-slider-horizontal .ui-slider-range{top:0;height:100%}.ui-slider-horizontal .ui-slider-range-min{left:0}.ui-slider-horizontal .ui-slider-range-max{right:0}.ui-slider-vertical{width:.8em;height:100px}.ui-slider-vertical .ui-slider-handle{left:-.3em;margin-left:0;margin-bottom:-.6em}.ui-slider-vertical .ui-slider-range{left:0;width:100%}.ui-slider-vertical .ui-slider-range-min{bottom:0}.ui-slider-vertical .ui-slider-range-max{top:0}.ui-spinner{position:relative;display:inline-block;overflow:hidden;padding:0;vertical-align:middle}.ui-spinner-input{border:none;background:none;color:inherit;padding:0;margin:.2em 0;vertical-align:middle;margin-left:.4em;margin-right:22px}.ui-spinner-button{width:16px;height:50%;font-size:.5em;padding:0;margin:0;text-align:center;position:absolute;cursor:default;display:block;overflow:hidden;right:0}.ui-spinner a.ui-spinner-button{border-top:none;border-bottom:none;border-right:none}.ui-spinner .ui-icon{position:absolute;margin-top:-8px;top:50%;left:0}.ui-spinner-up{top:0}.ui-spinner-down{bottom:0}.ui-spinner .ui-icon-triangle-1-s{background-position:-65px -16px}.ui-tabs{position:relative;padding:.2em}.ui-tabs .ui-tabs-nav{margin:0;padding:.2em .2em 0}.ui-tabs .ui-tabs-nav li{list-style:none;float:left;position:relative;top:0;margin:1px .2em 0 0;border-bottom-width:0;padding:0;white-space:nowrap}.ui-tabs .ui-tabs-nav .ui-tabs-anchor{float:left;padding:.5em 1em;text-decoration:none}.ui-tabs .ui-tabs-nav li.ui-tabs-active{margin-bottom:-1px;padding-bottom:1px}.ui-tabs .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor,.ui-tabs .ui-tabs-nav li.ui-state-disabled .ui-tabs-anchor,.ui-tabs .ui-tabs-nav li.ui-tabs-loading .ui-tabs-anchor{cursor:text}.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor{cursor:pointer}.ui-tabs .ui-tabs-panel{display:block;border-width:0;padding:1em 1.4em;background:none}.ui-tooltip{padding:8px;position:absolute;z-index:9999;max-width:300px;-webkit-box-shadow:0 0 5px #aaa;box-shadow:0 0 5px #aaa}body .ui-tooltip{border-width:2px}
\ No newline at end of file
diff --git a/public/static/lib/jquery-ui-1.11.4.custom/jquery-ui.theme.css b/public/static/lib/jquery-ui-1.11.4.custom/jquery-ui.theme.css
new file mode 100755
index 00000000..add09e19
--- /dev/null
+++ b/public/static/lib/jquery-ui-1.11.4.custom/jquery-ui.theme.css
@@ -0,0 +1,410 @@
+/*!
+ * jQuery UI CSS Framework 1.11.4
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/category/theming/
+ *
+ * To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana%2CArial%2Csans-serif&fwDefault=normal&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=cccccc&bgTextureHeader=highlight_soft&bgImgOpacityHeader=75&borderColorHeader=aaaaaa&fcHeader=222222&iconColorHeader=222222&bgColorContent=ffffff&bgTextureContent=flat&bgImgOpacityContent=75&borderColorContent=aaaaaa&fcContent=222222&iconColorContent=222222&bgColorDefault=e6e6e6&bgTextureDefault=glass&bgImgOpacityDefault=75&borderColorDefault=d3d3d3&fcDefault=555555&iconColorDefault=888888&bgColorHover=dadada&bgTextureHover=glass&bgImgOpacityHover=75&borderColorHover=999999&fcHover=212121&iconColorHover=454545&bgColorActive=ffffff&bgTextureActive=glass&bgImgOpacityActive=65&borderColorActive=aaaaaa&fcActive=212121&iconColorActive=454545&bgColorHighlight=fbf9ee&bgTextureHighlight=glass&bgImgOpacityHighlight=55&borderColorHighlight=fcefa1&fcHighlight=363636&iconColorHighlight=2e83ff&bgColorError=fef1ec&bgTextureError=glass&bgImgOpacityError=95&borderColorError=cd0a0a&fcError=cd0a0a&iconColorError=cd0a0a&bgColorOverlay=aaaaaa&bgTextureOverlay=flat&bgImgOpacityOverlay=0&opacityOverlay=30&bgColorShadow=aaaaaa&bgTextureShadow=flat&bgImgOpacityShadow=0&opacityShadow=30&thicknessShadow=8px&offsetTopShadow=-8px&offsetLeftShadow=-8px&cornerRadiusShadow=8px
+ */
+
+
+/* Component containers
+----------------------------------*/
+.ui-widget {
+ font-family: Verdana,Arial,sans-serif;
+ font-size: 1.1em;
+}
+.ui-widget .ui-widget {
+ font-size: 1em;
+}
+.ui-widget input,
+.ui-widget select,
+.ui-widget textarea,
+.ui-widget button {
+ font-family: Verdana,Arial,sans-serif;
+ font-size: 1em;
+}
+.ui-widget-content {
+ border: 1px solid #aaaaaa;
+ background: #ffffff;
+ color: #222222;
+}
+.ui-widget-content a {
+ color: #222222;
+}
+.ui-widget-header {
+ border: 1px solid #aaaaaa;
+ background: #cccccc url("images/ui-bg_highlight-soft_75_cccccc_1x100.png") 50% 50% repeat-x;
+ color: #222222;
+ font-weight: bold;
+}
+.ui-widget-header a {
+ color: #222222;
+}
+
+/* Interaction states
+----------------------------------*/
+.ui-state-default,
+.ui-widget-content .ui-state-default,
+.ui-widget-header .ui-state-default {
+ border: 1px solid #d3d3d3;
+ background: #e6e6e6 url("images/ui-bg_glass_75_e6e6e6_1x400.png") 50% 50% repeat-x;
+ font-weight: normal;
+ color: #555555;
+}
+.ui-state-default a,
+.ui-state-default a:link,
+.ui-state-default a:visited {
+ color: #555555;
+ text-decoration: none;
+}
+.ui-state-hover,
+.ui-widget-content .ui-state-hover,
+.ui-widget-header .ui-state-hover,
+.ui-state-focus,
+.ui-widget-content .ui-state-focus,
+.ui-widget-header .ui-state-focus {
+ border: 1px solid #999999;
+ background: #dadada url("images/ui-bg_glass_75_dadada_1x400.png") 50% 50% repeat-x;
+ font-weight: normal;
+ color: #212121;
+}
+.ui-state-hover a,
+.ui-state-hover a:hover,
+.ui-state-hover a:link,
+.ui-state-hover a:visited,
+.ui-state-focus a,
+.ui-state-focus a:hover,
+.ui-state-focus a:link,
+.ui-state-focus a:visited {
+ color: #212121;
+ text-decoration: none;
+}
+.ui-state-active,
+.ui-widget-content .ui-state-active,
+.ui-widget-header .ui-state-active {
+ border: 1px solid #aaaaaa;
+ background: #ffffff url("images/ui-bg_glass_65_ffffff_1x400.png") 50% 50% repeat-x;
+ font-weight: normal;
+ color: #212121;
+}
+.ui-state-active a,
+.ui-state-active a:link,
+.ui-state-active a:visited {
+ color: #212121;
+ text-decoration: none;
+}
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-highlight,
+.ui-widget-content .ui-state-highlight,
+.ui-widget-header .ui-state-highlight {
+ border: 1px solid #fcefa1;
+ background: #fbf9ee url("images/ui-bg_glass_55_fbf9ee_1x400.png") 50% 50% repeat-x;
+ color: #363636;
+}
+.ui-state-highlight a,
+.ui-widget-content .ui-state-highlight a,
+.ui-widget-header .ui-state-highlight a {
+ color: #363636;
+}
+.ui-state-error,
+.ui-widget-content .ui-state-error,
+.ui-widget-header .ui-state-error {
+ border: 1px solid #cd0a0a;
+ background: #fef1ec url("images/ui-bg_glass_95_fef1ec_1x400.png") 50% 50% repeat-x;
+ color: #cd0a0a;
+}
+.ui-state-error a,
+.ui-widget-content .ui-state-error a,
+.ui-widget-header .ui-state-error a {
+ color: #cd0a0a;
+}
+.ui-state-error-text,
+.ui-widget-content .ui-state-error-text,
+.ui-widget-header .ui-state-error-text {
+ color: #cd0a0a;
+}
+.ui-priority-primary,
+.ui-widget-content .ui-priority-primary,
+.ui-widget-header .ui-priority-primary {
+ font-weight: bold;
+}
+.ui-priority-secondary,
+.ui-widget-content .ui-priority-secondary,
+.ui-widget-header .ui-priority-secondary {
+ opacity: .7;
+ filter:Alpha(Opacity=70); /* support: IE8 */
+ font-weight: normal;
+}
+.ui-state-disabled,
+.ui-widget-content .ui-state-disabled,
+.ui-widget-header .ui-state-disabled {
+ opacity: .35;
+ filter:Alpha(Opacity=35); /* support: IE8 */
+ background-image: none;
+}
+.ui-state-disabled .ui-icon {
+ filter:Alpha(Opacity=35); /* support: IE8 - See #6059 */
+}
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon {
+ width: 16px;
+ height: 16px;
+}
+.ui-icon,
+.ui-widget-content .ui-icon {
+ background-image: url("images/ui-icons_222222_256x240.png");
+}
+.ui-widget-header .ui-icon {
+ background-image: url("images/ui-icons_222222_256x240.png");
+}
+.ui-state-default .ui-icon {
+ background-image: url("images/ui-icons_888888_256x240.png");
+}
+.ui-state-hover .ui-icon,
+.ui-state-focus .ui-icon {
+ background-image: url("images/ui-icons_454545_256x240.png");
+}
+.ui-state-active .ui-icon {
+ background-image: url("images/ui-icons_454545_256x240.png");
+}
+.ui-state-highlight .ui-icon {
+ background-image: url("images/ui-icons_2e83ff_256x240.png");
+}
+.ui-state-error .ui-icon,
+.ui-state-error-text .ui-icon {
+ background-image: url("images/ui-icons_cd0a0a_256x240.png");
+}
+
+/* positioning */
+.ui-icon-blank { background-position: 16px 16px; }
+.ui-icon-carat-1-n { background-position: 0 0; }
+.ui-icon-carat-1-ne { background-position: -16px 0; }
+.ui-icon-carat-1-e { background-position: -32px 0; }
+.ui-icon-carat-1-se { background-position: -48px 0; }
+.ui-icon-carat-1-s { background-position: -64px 0; }
+.ui-icon-carat-1-sw { background-position: -80px 0; }
+.ui-icon-carat-1-w { background-position: -96px 0; }
+.ui-icon-carat-1-nw { background-position: -112px 0; }
+.ui-icon-carat-2-n-s { background-position: -128px 0; }
+.ui-icon-carat-2-e-w { background-position: -144px 0; }
+.ui-icon-triangle-1-n { background-position: 0 -16px; }
+.ui-icon-triangle-1-ne { background-position: -16px -16px; }
+.ui-icon-triangle-1-e { background-position: -32px -16px; }
+.ui-icon-triangle-1-se { background-position: -48px -16px; }
+.ui-icon-triangle-1-s { background-position: -64px -16px; }
+.ui-icon-triangle-1-sw { background-position: -80px -16px; }
+.ui-icon-triangle-1-w { background-position: -96px -16px; }
+.ui-icon-triangle-1-nw { background-position: -112px -16px; }
+.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
+.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
+.ui-icon-arrow-1-n { background-position: 0 -32px; }
+.ui-icon-arrow-1-ne { background-position: -16px -32px; }
+.ui-icon-arrow-1-e { background-position: -32px -32px; }
+.ui-icon-arrow-1-se { background-position: -48px -32px; }
+.ui-icon-arrow-1-s { background-position: -64px -32px; }
+.ui-icon-arrow-1-sw { background-position: -80px -32px; }
+.ui-icon-arrow-1-w { background-position: -96px -32px; }
+.ui-icon-arrow-1-nw { background-position: -112px -32px; }
+.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
+.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
+.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
+.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
+.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
+.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
+.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
+.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
+.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
+.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
+.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
+.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
+.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
+.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
+.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
+.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
+.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
+.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
+.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
+.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
+.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
+.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
+.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
+.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
+.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
+.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
+.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
+.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
+.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
+.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
+.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
+.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
+.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
+.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
+.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
+.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
+.ui-icon-arrow-4 { background-position: 0 -80px; }
+.ui-icon-arrow-4-diag { background-position: -16px -80px; }
+.ui-icon-extlink { background-position: -32px -80px; }
+.ui-icon-newwin { background-position: -48px -80px; }
+.ui-icon-refresh { background-position: -64px -80px; }
+.ui-icon-shuffle { background-position: -80px -80px; }
+.ui-icon-transfer-e-w { background-position: -96px -80px; }
+.ui-icon-transferthick-e-w { background-position: -112px -80px; }
+.ui-icon-folder-collapsed { background-position: 0 -96px; }
+.ui-icon-folder-open { background-position: -16px -96px; }
+.ui-icon-document { background-position: -32px -96px; }
+.ui-icon-document-b { background-position: -48px -96px; }
+.ui-icon-note { background-position: -64px -96px; }
+.ui-icon-mail-closed { background-position: -80px -96px; }
+.ui-icon-mail-open { background-position: -96px -96px; }
+.ui-icon-suitcase { background-position: -112px -96px; }
+.ui-icon-comment { background-position: -128px -96px; }
+.ui-icon-person { background-position: -144px -96px; }
+.ui-icon-print { background-position: -160px -96px; }
+.ui-icon-trash { background-position: -176px -96px; }
+.ui-icon-locked { background-position: -192px -96px; }
+.ui-icon-unlocked { background-position: -208px -96px; }
+.ui-icon-bookmark { background-position: -224px -96px; }
+.ui-icon-tag { background-position: -240px -96px; }
+.ui-icon-home { background-position: 0 -112px; }
+.ui-icon-flag { background-position: -16px -112px; }
+.ui-icon-calendar { background-position: -32px -112px; }
+.ui-icon-cart { background-position: -48px -112px; }
+.ui-icon-pencil { background-position: -64px -112px; }
+.ui-icon-clock { background-position: -80px -112px; }
+.ui-icon-disk { background-position: -96px -112px; }
+.ui-icon-calculator { background-position: -112px -112px; }
+.ui-icon-zoomin { background-position: -128px -112px; }
+.ui-icon-zoomout { background-position: -144px -112px; }
+.ui-icon-search { background-position: -160px -112px; }
+.ui-icon-wrench { background-position: -176px -112px; }
+.ui-icon-gear { background-position: -192px -112px; }
+.ui-icon-heart { background-position: -208px -112px; }
+.ui-icon-star { background-position: -224px -112px; }
+.ui-icon-link { background-position: -240px -112px; }
+.ui-icon-cancel { background-position: 0 -128px; }
+.ui-icon-plus { background-position: -16px -128px; }
+.ui-icon-plusthick { background-position: -32px -128px; }
+.ui-icon-minus { background-position: -48px -128px; }
+.ui-icon-minusthick { background-position: -64px -128px; }
+.ui-icon-close { background-position: -80px -128px; }
+.ui-icon-closethick { background-position: -96px -128px; }
+.ui-icon-key { background-position: -112px -128px; }
+.ui-icon-lightbulb { background-position: -128px -128px; }
+.ui-icon-scissors { background-position: -144px -128px; }
+.ui-icon-clipboard { background-position: -160px -128px; }
+.ui-icon-copy { background-position: -176px -128px; }
+.ui-icon-contact { background-position: -192px -128px; }
+.ui-icon-image { background-position: -208px -128px; }
+.ui-icon-video { background-position: -224px -128px; }
+.ui-icon-script { background-position: -240px -128px; }
+.ui-icon-alert { background-position: 0 -144px; }
+.ui-icon-info { background-position: -16px -144px; }
+.ui-icon-notice { background-position: -32px -144px; }
+.ui-icon-help { background-position: -48px -144px; }
+.ui-icon-check { background-position: -64px -144px; }
+.ui-icon-bullet { background-position: -80px -144px; }
+.ui-icon-radio-on { background-position: -96px -144px; }
+.ui-icon-radio-off { background-position: -112px -144px; }
+.ui-icon-pin-w { background-position: -128px -144px; }
+.ui-icon-pin-s { background-position: -144px -144px; }
+.ui-icon-play { background-position: 0 -160px; }
+.ui-icon-pause { background-position: -16px -160px; }
+.ui-icon-seek-next { background-position: -32px -160px; }
+.ui-icon-seek-prev { background-position: -48px -160px; }
+.ui-icon-seek-end { background-position: -64px -160px; }
+.ui-icon-seek-start { background-position: -80px -160px; }
+/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
+.ui-icon-seek-first { background-position: -80px -160px; }
+.ui-icon-stop { background-position: -96px -160px; }
+.ui-icon-eject { background-position: -112px -160px; }
+.ui-icon-volume-off { background-position: -128px -160px; }
+.ui-icon-volume-on { background-position: -144px -160px; }
+.ui-icon-power { background-position: 0 -176px; }
+.ui-icon-signal-diag { background-position: -16px -176px; }
+.ui-icon-signal { background-position: -32px -176px; }
+.ui-icon-battery-0 { background-position: -48px -176px; }
+.ui-icon-battery-1 { background-position: -64px -176px; }
+.ui-icon-battery-2 { background-position: -80px -176px; }
+.ui-icon-battery-3 { background-position: -96px -176px; }
+.ui-icon-circle-plus { background-position: 0 -192px; }
+.ui-icon-circle-minus { background-position: -16px -192px; }
+.ui-icon-circle-close { background-position: -32px -192px; }
+.ui-icon-circle-triangle-e { background-position: -48px -192px; }
+.ui-icon-circle-triangle-s { background-position: -64px -192px; }
+.ui-icon-circle-triangle-w { background-position: -80px -192px; }
+.ui-icon-circle-triangle-n { background-position: -96px -192px; }
+.ui-icon-circle-arrow-e { background-position: -112px -192px; }
+.ui-icon-circle-arrow-s { background-position: -128px -192px; }
+.ui-icon-circle-arrow-w { background-position: -144px -192px; }
+.ui-icon-circle-arrow-n { background-position: -160px -192px; }
+.ui-icon-circle-zoomin { background-position: -176px -192px; }
+.ui-icon-circle-zoomout { background-position: -192px -192px; }
+.ui-icon-circle-check { background-position: -208px -192px; }
+.ui-icon-circlesmall-plus { background-position: 0 -208px; }
+.ui-icon-circlesmall-minus { background-position: -16px -208px; }
+.ui-icon-circlesmall-close { background-position: -32px -208px; }
+.ui-icon-squaresmall-plus { background-position: -48px -208px; }
+.ui-icon-squaresmall-minus { background-position: -64px -208px; }
+.ui-icon-squaresmall-close { background-position: -80px -208px; }
+.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
+.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
+.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
+.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
+.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
+.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Corner radius */
+.ui-corner-all,
+.ui-corner-top,
+.ui-corner-left,
+.ui-corner-tl {
+ border-top-left-radius: 4px;
+}
+.ui-corner-all,
+.ui-corner-top,
+.ui-corner-right,
+.ui-corner-tr {
+ border-top-right-radius: 4px;
+}
+.ui-corner-all,
+.ui-corner-bottom,
+.ui-corner-left,
+.ui-corner-bl {
+ border-bottom-left-radius: 4px;
+}
+.ui-corner-all,
+.ui-corner-bottom,
+.ui-corner-right,
+.ui-corner-br {
+ border-bottom-right-radius: 4px;
+}
+
+/* Overlays */
+.ui-widget-overlay {
+ background: #aaaaaa;
+ opacity: .3;
+ filter: Alpha(Opacity=30); /* support: IE8 */
+}
+.ui-widget-shadow {
+ margin: -8px 0 0 -8px;
+ padding: 8px;
+ background: #aaaaaa;
+ opacity: .3;
+ filter: Alpha(Opacity=30); /* support: IE8 */
+ border-radius: 8px;
+}
diff --git a/public/static/lib/jquery-ui-1.11.4.custom/jquery-ui.theme.min.css b/public/static/lib/jquery-ui-1.11.4.custom/jquery-ui.theme.min.css
new file mode 100755
index 00000000..8a3e07fc
--- /dev/null
+++ b/public/static/lib/jquery-ui-1.11.4.custom/jquery-ui.theme.min.css
@@ -0,0 +1,5 @@
+/*! jQuery UI - v1.11.4 - 2016-11-21
+* http://jqueryui.com
+* Copyright jQuery Foundation and other contributors; Licensed MIT */
+
+.ui-widget{font-family:Verdana,Arial,sans-serif;font-size:1.1em}.ui-widget .ui-widget{font-size:1em}.ui-widget input,.ui-widget select,.ui-widget textarea,.ui-widget button{font-family:Verdana,Arial,sans-serif;font-size:1em}.ui-widget-content{border:1px solid #aaa;background:#fff;color:#222}.ui-widget-content a{color:#222}.ui-widget-header{border:1px solid #aaa;background:#ccc url("images/ui-bg_highlight-soft_75_cccccc_1x100.png") 50% 50% repeat-x;color:#222;font-weight:bold}.ui-widget-header a{color:#222}.ui-state-default,.ui-widget-content .ui-state-default,.ui-widget-header .ui-state-default{border:1px solid #d3d3d3;background:#e6e6e6 url("images/ui-bg_glass_75_e6e6e6_1x400.png") 50% 50% repeat-x;font-weight:normal;color:#555}.ui-state-default a,.ui-state-default a:link,.ui-state-default a:visited{color:#555;text-decoration:none}.ui-state-hover,.ui-widget-content .ui-state-hover,.ui-widget-header .ui-state-hover,.ui-state-focus,.ui-widget-content .ui-state-focus,.ui-widget-header .ui-state-focus{border:1px solid #999;background:#dadada url("images/ui-bg_glass_75_dadada_1x400.png") 50% 50% repeat-x;font-weight:normal;color:#212121}.ui-state-hover a,.ui-state-hover a:hover,.ui-state-hover a:link,.ui-state-hover a:visited,.ui-state-focus a,.ui-state-focus a:hover,.ui-state-focus a:link,.ui-state-focus a:visited{color:#212121;text-decoration:none}.ui-state-active,.ui-widget-content .ui-state-active,.ui-widget-header .ui-state-active{border:1px solid #aaa;background:#fff url("images/ui-bg_glass_65_ffffff_1x400.png") 50% 50% repeat-x;font-weight:normal;color:#212121}.ui-state-active a,.ui-state-active a:link,.ui-state-active a:visited{color:#212121;text-decoration:none}.ui-state-highlight,.ui-widget-content .ui-state-highlight,.ui-widget-header .ui-state-highlight{border:1px solid #fcefa1;background:#fbf9ee url("images/ui-bg_glass_55_fbf9ee_1x400.png") 50% 50% repeat-x;color:#363636}.ui-state-highlight a,.ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a{color:#363636}.ui-state-error,.ui-widget-content .ui-state-error,.ui-widget-header .ui-state-error{border:1px solid #cd0a0a;background:#fef1ec url("images/ui-bg_glass_95_fef1ec_1x400.png") 50% 50% repeat-x;color:#cd0a0a}.ui-state-error a,.ui-widget-content .ui-state-error a,.ui-widget-header .ui-state-error a{color:#cd0a0a}.ui-state-error-text,.ui-widget-content .ui-state-error-text,.ui-widget-header .ui-state-error-text{color:#cd0a0a}.ui-priority-primary,.ui-widget-content .ui-priority-primary,.ui-widget-header .ui-priority-primary{font-weight:bold}.ui-priority-secondary,.ui-widget-content .ui-priority-secondary,.ui-widget-header .ui-priority-secondary{opacity:.7;filter:Alpha(Opacity=70);font-weight:normal}.ui-state-disabled,.ui-widget-content .ui-state-disabled,.ui-widget-header .ui-state-disabled{opacity:.35;filter:Alpha(Opacity=35);background-image:none}.ui-state-disabled .ui-icon{filter:Alpha(Opacity=35)}.ui-icon{width:16px;height:16px}.ui-icon,.ui-widget-content .ui-icon{background-image:url("images/ui-icons_222222_256x240.png")}.ui-widget-header .ui-icon{background-image:url("images/ui-icons_222222_256x240.png")}.ui-state-default .ui-icon{background-image:url("images/ui-icons_888888_256x240.png")}.ui-state-hover .ui-icon,.ui-state-focus .ui-icon{background-image:url("images/ui-icons_454545_256x240.png")}.ui-state-active .ui-icon{background-image:url("images/ui-icons_454545_256x240.png")}.ui-state-highlight .ui-icon{background-image:url("images/ui-icons_2e83ff_256x240.png")}.ui-state-error .ui-icon,.ui-state-error-text .ui-icon{background-image:url("images/ui-icons_cd0a0a_256x240.png")}.ui-icon-blank{background-position:16px 16px}.ui-icon-carat-1-n{background-position:0 0}.ui-icon-carat-1-ne{background-position:-16px 0}.ui-icon-carat-1-e{background-position:-32px 0}.ui-icon-carat-1-se{background-position:-48px 0}.ui-icon-carat-1-s{background-position:-64px 0}.ui-icon-carat-1-sw{background-position:-80px 0}.ui-icon-carat-1-w{background-position:-96px 0}.ui-icon-carat-1-nw{background-position:-112px 0}.ui-icon-carat-2-n-s{background-position:-128px 0}.ui-icon-carat-2-e-w{background-position:-144px 0}.ui-icon-triangle-1-n{background-position:0 -16px}.ui-icon-triangle-1-ne{background-position:-16px -16px}.ui-icon-triangle-1-e{background-position:-32px -16px}.ui-icon-triangle-1-se{background-position:-48px -16px}.ui-icon-triangle-1-s{background-position:-64px -16px}.ui-icon-triangle-1-sw{background-position:-80px -16px}.ui-icon-triangle-1-w{background-position:-96px -16px}.ui-icon-triangle-1-nw{background-position:-112px -16px}.ui-icon-triangle-2-n-s{background-position:-128px -16px}.ui-icon-triangle-2-e-w{background-position:-144px -16px}.ui-icon-arrow-1-n{background-position:0 -32px}.ui-icon-arrow-1-ne{background-position:-16px -32px}.ui-icon-arrow-1-e{background-position:-32px -32px}.ui-icon-arrow-1-se{background-position:-48px -32px}.ui-icon-arrow-1-s{background-position:-64px -32px}.ui-icon-arrow-1-sw{background-position:-80px -32px}.ui-icon-arrow-1-w{background-position:-96px -32px}.ui-icon-arrow-1-nw{background-position:-112px -32px}.ui-icon-arrow-2-n-s{background-position:-128px -32px}.ui-icon-arrow-2-ne-sw{background-position:-144px -32px}.ui-icon-arrow-2-e-w{background-position:-160px -32px}.ui-icon-arrow-2-se-nw{background-position:-176px -32px}.ui-icon-arrowstop-1-n{background-position:-192px -32px}.ui-icon-arrowstop-1-e{background-position:-208px -32px}.ui-icon-arrowstop-1-s{background-position:-224px -32px}.ui-icon-arrowstop-1-w{background-position:-240px -32px}.ui-icon-arrowthick-1-n{background-position:0 -48px}.ui-icon-arrowthick-1-ne{background-position:-16px -48px}.ui-icon-arrowthick-1-e{background-position:-32px -48px}.ui-icon-arrowthick-1-se{background-position:-48px -48px}.ui-icon-arrowthick-1-s{background-position:-64px -48px}.ui-icon-arrowthick-1-sw{background-position:-80px -48px}.ui-icon-arrowthick-1-w{background-position:-96px -48px}.ui-icon-arrowthick-1-nw{background-position:-112px -48px}.ui-icon-arrowthick-2-n-s{background-position:-128px -48px}.ui-icon-arrowthick-2-ne-sw{background-position:-144px -48px}.ui-icon-arrowthick-2-e-w{background-position:-160px -48px}.ui-icon-arrowthick-2-se-nw{background-position:-176px -48px}.ui-icon-arrowthickstop-1-n{background-position:-192px -48px}.ui-icon-arrowthickstop-1-e{background-position:-208px -48px}.ui-icon-arrowthickstop-1-s{background-position:-224px -48px}.ui-icon-arrowthickstop-1-w{background-position:-240px -48px}.ui-icon-arrowreturnthick-1-w{background-position:0 -64px}.ui-icon-arrowreturnthick-1-n{background-position:-16px -64px}.ui-icon-arrowreturnthick-1-e{background-position:-32px -64px}.ui-icon-arrowreturnthick-1-s{background-position:-48px -64px}.ui-icon-arrowreturn-1-w{background-position:-64px -64px}.ui-icon-arrowreturn-1-n{background-position:-80px -64px}.ui-icon-arrowreturn-1-e{background-position:-96px -64px}.ui-icon-arrowreturn-1-s{background-position:-112px -64px}.ui-icon-arrowrefresh-1-w{background-position:-128px -64px}.ui-icon-arrowrefresh-1-n{background-position:-144px -64px}.ui-icon-arrowrefresh-1-e{background-position:-160px -64px}.ui-icon-arrowrefresh-1-s{background-position:-176px -64px}.ui-icon-arrow-4{background-position:0 -80px}.ui-icon-arrow-4-diag{background-position:-16px -80px}.ui-icon-extlink{background-position:-32px -80px}.ui-icon-newwin{background-position:-48px -80px}.ui-icon-refresh{background-position:-64px -80px}.ui-icon-shuffle{background-position:-80px -80px}.ui-icon-transfer-e-w{background-position:-96px -80px}.ui-icon-transferthick-e-w{background-position:-112px -80px}.ui-icon-folder-collapsed{background-position:0 -96px}.ui-icon-folder-open{background-position:-16px -96px}.ui-icon-document{background-position:-32px -96px}.ui-icon-document-b{background-position:-48px -96px}.ui-icon-note{background-position:-64px -96px}.ui-icon-mail-closed{background-position:-80px -96px}.ui-icon-mail-open{background-position:-96px -96px}.ui-icon-suitcase{background-position:-112px -96px}.ui-icon-comment{background-position:-128px -96px}.ui-icon-person{background-position:-144px -96px}.ui-icon-print{background-position:-160px -96px}.ui-icon-trash{background-position:-176px -96px}.ui-icon-locked{background-position:-192px -96px}.ui-icon-unlocked{background-position:-208px -96px}.ui-icon-bookmark{background-position:-224px -96px}.ui-icon-tag{background-position:-240px -96px}.ui-icon-home{background-position:0 -112px}.ui-icon-flag{background-position:-16px -112px}.ui-icon-calendar{background-position:-32px -112px}.ui-icon-cart{background-position:-48px -112px}.ui-icon-pencil{background-position:-64px -112px}.ui-icon-clock{background-position:-80px -112px}.ui-icon-disk{background-position:-96px -112px}.ui-icon-calculator{background-position:-112px -112px}.ui-icon-zoomin{background-position:-128px -112px}.ui-icon-zoomout{background-position:-144px -112px}.ui-icon-search{background-position:-160px -112px}.ui-icon-wrench{background-position:-176px -112px}.ui-icon-gear{background-position:-192px -112px}.ui-icon-heart{background-position:-208px -112px}.ui-icon-star{background-position:-224px -112px}.ui-icon-link{background-position:-240px -112px}.ui-icon-cancel{background-position:0 -128px}.ui-icon-plus{background-position:-16px -128px}.ui-icon-plusthick{background-position:-32px -128px}.ui-icon-minus{background-position:-48px -128px}.ui-icon-minusthick{background-position:-64px -128px}.ui-icon-close{background-position:-80px -128px}.ui-icon-closethick{background-position:-96px -128px}.ui-icon-key{background-position:-112px -128px}.ui-icon-lightbulb{background-position:-128px -128px}.ui-icon-scissors{background-position:-144px -128px}.ui-icon-clipboard{background-position:-160px -128px}.ui-icon-copy{background-position:-176px -128px}.ui-icon-contact{background-position:-192px -128px}.ui-icon-image{background-position:-208px -128px}.ui-icon-video{background-position:-224px -128px}.ui-icon-script{background-position:-240px -128px}.ui-icon-alert{background-position:0 -144px}.ui-icon-info{background-position:-16px -144px}.ui-icon-notice{background-position:-32px -144px}.ui-icon-help{background-position:-48px -144px}.ui-icon-check{background-position:-64px -144px}.ui-icon-bullet{background-position:-80px -144px}.ui-icon-radio-on{background-position:-96px -144px}.ui-icon-radio-off{background-position:-112px -144px}.ui-icon-pin-w{background-position:-128px -144px}.ui-icon-pin-s{background-position:-144px -144px}.ui-icon-play{background-position:0 -160px}.ui-icon-pause{background-position:-16px -160px}.ui-icon-seek-next{background-position:-32px -160px}.ui-icon-seek-prev{background-position:-48px -160px}.ui-icon-seek-end{background-position:-64px -160px}.ui-icon-seek-start{background-position:-80px -160px}.ui-icon-seek-first{background-position:-80px -160px}.ui-icon-stop{background-position:-96px -160px}.ui-icon-eject{background-position:-112px -160px}.ui-icon-volume-off{background-position:-128px -160px}.ui-icon-volume-on{background-position:-144px -160px}.ui-icon-power{background-position:0 -176px}.ui-icon-signal-diag{background-position:-16px -176px}.ui-icon-signal{background-position:-32px -176px}.ui-icon-battery-0{background-position:-48px -176px}.ui-icon-battery-1{background-position:-64px -176px}.ui-icon-battery-2{background-position:-80px -176px}.ui-icon-battery-3{background-position:-96px -176px}.ui-icon-circle-plus{background-position:0 -192px}.ui-icon-circle-minus{background-position:-16px -192px}.ui-icon-circle-close{background-position:-32px -192px}.ui-icon-circle-triangle-e{background-position:-48px -192px}.ui-icon-circle-triangle-s{background-position:-64px -192px}.ui-icon-circle-triangle-w{background-position:-80px -192px}.ui-icon-circle-triangle-n{background-position:-96px -192px}.ui-icon-circle-arrow-e{background-position:-112px -192px}.ui-icon-circle-arrow-s{background-position:-128px -192px}.ui-icon-circle-arrow-w{background-position:-144px -192px}.ui-icon-circle-arrow-n{background-position:-160px -192px}.ui-icon-circle-zoomin{background-position:-176px -192px}.ui-icon-circle-zoomout{background-position:-192px -192px}.ui-icon-circle-check{background-position:-208px -192px}.ui-icon-circlesmall-plus{background-position:0 -208px}.ui-icon-circlesmall-minus{background-position:-16px -208px}.ui-icon-circlesmall-close{background-position:-32px -208px}.ui-icon-squaresmall-plus{background-position:-48px -208px}.ui-icon-squaresmall-minus{background-position:-64px -208px}.ui-icon-squaresmall-close{background-position:-80px -208px}.ui-icon-grip-dotted-vertical{background-position:0 -224px}.ui-icon-grip-dotted-horizontal{background-position:-16px -224px}.ui-icon-grip-solid-vertical{background-position:-32px -224px}.ui-icon-grip-solid-horizontal{background-position:-48px -224px}.ui-icon-gripsmall-diagonal-se{background-position:-64px -224px}.ui-icon-grip-diagonal-se{background-position:-80px -224px}.ui-corner-all,.ui-corner-top,.ui-corner-left,.ui-corner-tl{border-top-left-radius:4px}.ui-corner-all,.ui-corner-top,.ui-corner-right,.ui-corner-tr{border-top-right-radius:4px}.ui-corner-all,.ui-corner-bottom,.ui-corner-left,.ui-corner-bl{border-bottom-left-radius:4px}.ui-corner-all,.ui-corner-bottom,.ui-corner-right,.ui-corner-br{border-bottom-right-radius:4px}.ui-widget-overlay{background:#aaa;opacity:.3;filter:Alpha(Opacity=30)}.ui-widget-shadow{margin:-8px 0 0 -8px;padding:8px;background:#aaa;opacity:.3;filter:Alpha(Opacity=30);border-radius:8px}
\ No newline at end of file
diff --git a/public/static/lib/jqueryXML2JSON/jquery.xml2json.js b/public/static/lib/jqueryXML2JSON/jquery.xml2json.js
new file mode 100755
index 00000000..b8053baa
--- /dev/null
+++ b/public/static/lib/jqueryXML2JSON/jquery.xml2json.js
@@ -0,0 +1,193 @@
+/*
+ ### jQuery XML to JSON Plugin v1.3 - 2013-02-18 ###
+ * http://www.fyneworks.com/ - diego@fyneworks.com
+ * Licensed under http://en.wikipedia.org/wiki/MIT_License
+ ###
+ Website: http://www.fyneworks.com/jquery/xml-to-json/
+*//*
+ # INSPIRED BY: http://www.terracoder.com/
+ AND: http://www.thomasfrank.se/xml_to_json.html
+ AND: http://www.kawa.net/works/js/xml/objtree-e.html
+*//*
+ This simple script converts XML (document of code) into a JSON object. It is the combination of 2
+ 'xml to json' great parsers (see below) which allows for both 'simple' and 'extended' parsing modes.
+*/
+// Avoid collisions
+;if(window.jQuery) (function($){
+
+ // Add function to jQuery namespace
+ $.extend({
+
+ // converts xml documents and xml text to json object
+ xml2json: function(xml, extended) {
+ if(!xml) return {}; // quick fail
+
+ //### PARSER LIBRARY
+ // Core function
+ function parseXML(node, simple){
+ if(!node) return null;
+ var txt = '', obj = null, att = null;
+ var nt = node.nodeType, nn = jsVar(node.localName || node.nodeName);
+ var nv = node.text || node.nodeValue || '';
+ /*DBG*/ //if(window.console) console.log(['x2j',nn,nt,nv.length+' bytes']);
+ if(node.childNodes){
+ if(node.childNodes.length>0){
+ /*DBG*/ //if(window.console) console.log(['x2j',nn,'CHILDREN',node.childNodes]);
+ $.each(node.childNodes, function(n,cn){
+ var cnt = cn.nodeType, cnn = jsVar(cn.localName || cn.nodeName);
+ var cnv = cn.text || cn.nodeValue || '';
+ /*DBG*/ //if(window.console) console.log(['x2j',nn,'node>a',cnn,cnt,cnv]);
+ if(cnt == 8){
+ /*DBG*/ //if(window.console) console.log(['x2j',nn,'node>b',cnn,'COMMENT (ignore)']);
+ return; // ignore comment node
+ }
+ else if(cnt == 3 || cnt == 4 || !cnn){
+ // ignore white-space in between tags
+ if(cnv.match(/^\s+$/)){
+ /*DBG*/ //if(window.console) console.log(['x2j',nn,'node>c',cnn,'WHITE-SPACE (ignore)']);
+ return;
+ };
+ /*DBG*/ //if(window.console) console.log(['x2j',nn,'node>d',cnn,'TEXT']);
+ txt += cnv.replace(/^\s+/,'').replace(/\s+$/,'');
+ // make sure we ditch trailing spaces from markup
+ }
+ else{
+ /*DBG*/ //if(window.console) console.log(['x2j',nn,'node>e',cnn,'OBJECT']);
+ obj = obj || {};
+ if(obj[cnn]){
+ /*DBG*/ //if(window.console) console.log(['x2j',nn,'node>f',cnn,'ARRAY']);
+
+ // http://forum.jquery.com/topic/jquery-jquery-xml2json-problems-when-siblings-of-the-same-tagname-only-have-a-textnode-as-a-child
+ if(!obj[cnn].length) obj[cnn] = myArr(obj[cnn]);
+ obj[cnn] = myArr(obj[cnn]);
+
+ obj[cnn][ obj[cnn].length ] = parseXML(cn, true/* simple */);
+ obj[cnn].length = obj[cnn].length;
+ }
+ else{
+ /*DBG*/ //if(window.console) console.log(['x2j',nn,'node>g',cnn,'dig deeper...']);
+ obj[cnn] = parseXML(cn);
+ };
+ };
+ });
+ };//node.childNodes.length>0
+ };//node.childNodes
+ if(node.attributes){
+ if(node.attributes.length>0){
+ /*DBG*/ //if(window.console) console.log(['x2j',nn,'ATTRIBUTES',node.attributes])
+ att = {}; obj = obj || {};
+ $.each(node.attributes, function(a,at){
+ var atn = jsVar(at.name), atv = at.value;
+ att[atn] = atv;
+ if(obj[atn]){
+ /*DBG*/ //if(window.console) console.log(['x2j',nn,'attr>',atn,'ARRAY']);
+
+ // http://forum.jquery.com/topic/jquery-jquery-xml2json-problems-when-siblings-of-the-same-tagname-only-have-a-textnode-as-a-child
+ //if(!obj[atn].length) obj[atn] = myArr(obj[atn]);//[ obj[ atn ] ];
+ obj[cnn] = myArr(obj[cnn]);
+
+ obj[atn][ obj[atn].length ] = atv;
+ obj[atn].length = obj[atn].length;
+ }
+ else{
+ /*DBG*/ //if(window.console) console.log(['x2j',nn,'attr>',atn,'TEXT']);
+ obj[atn] = atv;
+ };
+ });
+ //obj['attributes'] = att;
+ };//node.attributes.length>0
+ };//node.attributes
+ if(obj){
+ obj = $.extend( (txt!='' ? new String(txt) : {}),/* {text:txt},*/ obj || {}/*, att || {}*/);
+ //txt = (obj.text) ? (typeof(obj.text)=='object' ? obj.text : [obj.text || '']).concat([txt]) : txt;
+ txt = (obj.text) ? ([obj.text || '']).concat([txt]) : txt;
+ if(txt) obj.text = txt;
+ txt = '';
+ };
+ var out = obj || txt;
+ //console.log([extended, simple, out]);
+ if(extended){
+ if(txt) out = {};//new String(out);
+ txt = out.text || txt || '';
+ if(txt) out.text = txt;
+ if(!simple) out = myArr(out);
+ };
+ return out;
+ };// parseXML
+ // Core Function End
+ // Utility functions
+ var jsVar = function(s){ return String(s || '').replace(/-/g,"_"); };
+
+ // NEW isNum function: 01/09/2010
+ // Thanks to Emile Grau, GigaTecnologies S.L., www.gigatransfer.com, www.mygigamail.com
+ function isNum(s){
+ // based on utility function isNum from xml2json plugin (http://www.fyneworks.com/ - diego@fyneworks.com)
+ // few bugs corrected from original function :
+ // - syntax error : regexp.test(string) instead of string.test(reg)
+ // - regexp modified to accept comma as decimal mark (latin syntax : 25,24 )
+ // - regexp modified to reject if no number before decimal mark : ".7" is not accepted
+ // - string is "trimmed", allowing to accept space at the beginning and end of string
+ var regexp=/^((-)?([0-9]+)(([\.\,]{0,1})([0-9]+))?$)/
+ return (typeof s == "number") || regexp.test(String((s && typeof s == "string") ? jQuery.trim(s) : ''));
+ };
+ // OLD isNum function: (for reference only)
+ //var isNum = function(s){ return (typeof s == "number") || String((s && typeof s == "string") ? s : '').test(/^((-)?([0-9]*)((\.{0,1})([0-9]+))?$)/); };
+
+ var myArr = function(o){
+
+ // http://forum.jquery.com/topic/jquery-jquery-xml2json-problems-when-siblings-of-the-same-tagname-only-have-a-textnode-as-a-child
+ //if(!o.length) o = [ o ]; o.length=o.length;
+ if(!$.isArray(o)) o = [ o ]; o.length=o.length;
+
+ // here is where you can attach additional functionality, such as searching and sorting...
+ return o;
+ };
+ // Utility functions End
+ //### PARSER LIBRARY END
+
+ // Convert plain text to xml
+ if(typeof xml=='string') xml = $.text2xml(xml);
+
+ // Quick fail if not xml (or if this is a node)
+ if(!xml.nodeType) return;
+ if(xml.nodeType == 3 || xml.nodeType == 4) return xml.nodeValue;
+
+ // Find xml root node
+ var root = (xml.nodeType == 9) ? xml.documentElement : xml;
+
+ // Convert xml to json
+ var out = parseXML(root, true /* simple */);
+
+ // Clean-up memory
+ xml = null; root = null;
+
+ // Send output
+ return out;
+ },
+
+ // Convert text to XML DOM
+ text2xml: function(str) {
+ // NOTE: I'd like to use jQuery for this, but jQuery makes all tags uppercase
+ //return $(xml)[0];
+
+ /* prior to jquery 1.9 */
+ /*
+ var out;
+ try{
+ var xml = ((!$.support.opacity && !$.support.style))?new ActiveXObject("Microsoft.XMLDOM"):new DOMParser();
+ xml.async = false;
+ }catch(e){ throw new Error("XML Parser could not be instantiated") };
+ try{
+ if((!$.support.opacity && !$.support.style)) out = (xml.loadXML(str))?xml:false;
+ else out = xml.parseFromString(str, "text/xml");
+ }catch(e){ throw new Error("Error parsing XML string") };
+ return out;
+ */
+
+ /* jquery 1.9+ */
+ return $.parseXML(str);
+ }
+
+ }); // extend $
+
+})(jQuery);
diff --git a/public/static/lib/jstree/jstree.js b/public/static/lib/jstree/jstree.js
new file mode 100755
index 00000000..b6509d46
--- /dev/null
+++ b/public/static/lib/jstree/jstree.js
@@ -0,0 +1,8305 @@
+/*globals jQuery, define, module, exports, require, window, document, postMessage */
+(function (factory) {
+ "use strict";
+ if (typeof define === 'function' && define.amd) {
+ define(['jquery'], factory);
+ }
+ else if(typeof module !== 'undefined' && module.exports) {
+ module.exports = factory(require('jquery'));
+ }
+ else {
+ factory(jQuery);
+ }
+}(function ($, undefined) {
+ "use strict";
+/*!
+ * jsTree 3.3.3
+ * http://jstree.com/
+ *
+ * Copyright (c) 2014 Ivan Bozhanov (http://vakata.com)
+ *
+ * Licensed same as jquery - under the terms of the MIT License
+ * http://www.opensource.org/licenses/mit-license.php
+ */
+/*!
+ * if using jslint please allow for the jQuery global and use following options:
+ * jslint: loopfunc: true, browser: true, ass: true, bitwise: true, continue: true, nomen: true, plusplus: true, regexp: true, unparam: true, todo: true, white: true
+ */
+/*jshint -W083 */
+
+ // prevent another load? maybe there is a better way?
+ if($.jstree) {
+ return;
+ }
+
+ /**
+ * ### jsTree core functionality
+ */
+
+ // internal variables
+ var instance_counter = 0,
+ ccp_node = false,
+ ccp_mode = false,
+ ccp_inst = false,
+ themes_loaded = [],
+ src = $('script:last').attr('src'),
+ document = window.document; // local variable is always faster to access then a global
+
+ /**
+ * holds all jstree related functions and variables, including the actual class and methods to create, access and manipulate instances.
+ * @name $.jstree
+ */
+ $.jstree = {
+ /**
+ * specifies the jstree version in use
+ * @name $.jstree.version
+ */
+ version : '3.3.3',
+ /**
+ * holds all the default options used when creating new instances
+ * @name $.jstree.defaults
+ */
+ defaults : {
+ /**
+ * configure which plugins will be active on an instance. Should be an array of strings, where each element is a plugin name. The default is `[]`
+ * @name $.jstree.defaults.plugins
+ */
+ plugins : []
+ },
+ /**
+ * stores all loaded jstree plugins (used internally)
+ * @name $.jstree.plugins
+ */
+ plugins : {},
+ path : src && src.indexOf('/') !== -1 ? src.replace(/\/[^\/]+$/,'') : '',
+ idregex : /[\\:&!^|()\[\]<>@*'+~#";.,=\- \/${}%?`]/g,
+ root : '#'
+ };
+
+ /**
+ * creates a jstree instance
+ * @name $.jstree.create(el [, options])
+ * @param {DOMElement|jQuery|String} el the element to create the instance on, can be jQuery extended or a selector
+ * @param {Object} options options for this instance (extends `$.jstree.defaults`)
+ * @return {jsTree} the new instance
+ */
+ $.jstree.create = function (el, options) {
+ var tmp = new $.jstree.core(++instance_counter),
+ opt = options;
+ options = $.extend(true, {}, $.jstree.defaults, options);
+ if(opt && opt.plugins) {
+ options.plugins = opt.plugins;
+ }
+ $.each(options.plugins, function (i, k) {
+ if(i !== 'core') {
+ tmp = tmp.plugin(k, options[k]);
+ }
+ });
+ $(el).data('jstree', tmp);
+ tmp.init(el, options);
+ return tmp;
+ };
+ /**
+ * remove all traces of jstree from the DOM and destroy all instances
+ * @name $.jstree.destroy()
+ */
+ $.jstree.destroy = function () {
+ $('.jstree:jstree').jstree('destroy');
+ $(document).off('.jstree');
+ };
+ /**
+ * the jstree class constructor, used only internally
+ * @private
+ * @name $.jstree.core(id)
+ * @param {Number} id this instance's index
+ */
+ $.jstree.core = function (id) {
+ this._id = id;
+ this._cnt = 0;
+ this._wrk = null;
+ this._data = {
+ core : {
+ themes : {
+ name : false,
+ dots : false,
+ icons : false,
+ ellipsis : false
+ },
+ selected : [],
+ last_error : {},
+ working : false,
+ worker_queue : [],
+ focused : null
+ }
+ };
+ };
+ /**
+ * get a reference to an existing instance
+ *
+ * __Examples__
+ *
+ * // provided a container with an ID of "tree", and a nested node with an ID of "branch"
+ * // all of there will return the same instance
+ * $.jstree.reference('tree');
+ * $.jstree.reference('#tree');
+ * $.jstree.reference($('#tree'));
+ * $.jstree.reference(document.getElementByID('tree'));
+ * $.jstree.reference('branch');
+ * $.jstree.reference('#branch');
+ * $.jstree.reference($('#branch'));
+ * $.jstree.reference(document.getElementByID('branch'));
+ *
+ * @name $.jstree.reference(needle)
+ * @param {DOMElement|jQuery|String} needle
+ * @return {jsTree|null} the instance or `null` if not found
+ */
+ $.jstree.reference = function (needle) {
+ var tmp = null,
+ obj = null;
+ if(needle && needle.id && (!needle.tagName || !needle.nodeType)) { needle = needle.id; }
+
+ if(!obj || !obj.length) {
+ try { obj = $(needle); } catch (ignore) { }
+ }
+ if(!obj || !obj.length) {
+ try { obj = $('#' + needle.replace($.jstree.idregex,'\\$&')); } catch (ignore) { }
+ }
+ if(obj && obj.length && (obj = obj.closest('.jstree')).length && (obj = obj.data('jstree'))) {
+ tmp = obj;
+ }
+ else {
+ $('.jstree').each(function () {
+ var inst = $(this).data('jstree');
+ if(inst && inst._model.data[needle]) {
+ tmp = inst;
+ return false;
+ }
+ });
+ }
+ return tmp;
+ };
+ /**
+ * Create an instance, get an instance or invoke a command on a instance.
+ *
+ * If there is no instance associated with the current node a new one is created and `arg` is used to extend `$.jstree.defaults` for this new instance. There would be no return value (chaining is not broken).
+ *
+ * If there is an existing instance and `arg` is a string the command specified by `arg` is executed on the instance, with any additional arguments passed to the function. If the function returns a value it will be returned (chaining could break depending on function).
+ *
+ * If there is an existing instance and `arg` is not a string the instance itself is returned (similar to `$.jstree.reference`).
+ *
+ * In any other case - nothing is returned and chaining is not broken.
+ *
+ * __Examples__
+ *
+ * $('#tree1').jstree(); // creates an instance
+ * $('#tree2').jstree({ plugins : [] }); // create an instance with some options
+ * $('#tree1').jstree('open_node', '#branch_1'); // call a method on an existing instance, passing additional arguments
+ * $('#tree2').jstree(); // get an existing instance (or create an instance)
+ * $('#tree2').jstree(true); // get an existing instance (will not create new instance)
+ * $('#branch_1').jstree().select_node('#branch_1'); // get an instance (using a nested element and call a method)
+ *
+ * @name $().jstree([arg])
+ * @param {String|Object} arg
+ * @return {Mixed}
+ */
+ $.fn.jstree = function (arg) {
+ // check for string argument
+ var is_method = (typeof arg === 'string'),
+ args = Array.prototype.slice.call(arguments, 1),
+ result = null;
+ if(arg === true && !this.length) { return false; }
+ this.each(function () {
+ // get the instance (if there is one) and method (if it exists)
+ var instance = $.jstree.reference(this),
+ method = is_method && instance ? instance[arg] : null;
+ // if calling a method, and method is available - execute on the instance
+ result = is_method && method ?
+ method.apply(instance, args) :
+ null;
+ // if there is no instance and no method is being called - create one
+ if(!instance && !is_method && (arg === undefined || $.isPlainObject(arg))) {
+ $.jstree.create(this, arg);
+ }
+ // if there is an instance and no method is called - return the instance
+ if( (instance && !is_method) || arg === true ) {
+ result = instance || false;
+ }
+ // if there was a method call which returned a result - break and return the value
+ if(result !== null && result !== undefined) {
+ return false;
+ }
+ });
+ // if there was a method call with a valid return value - return that, otherwise continue the chain
+ return result !== null && result !== undefined ?
+ result : this;
+ };
+ /**
+ * used to find elements containing an instance
+ *
+ * __Examples__
+ *
+ * $('div:jstree').each(function () {
+ * $(this).jstree('destroy');
+ * });
+ *
+ * @name $(':jstree')
+ * @return {jQuery}
+ */
+ $.expr.pseudos.jstree = $.expr.createPseudo(function(search) {
+ return function(a) {
+ return $(a).hasClass('jstree') &&
+ $(a).data('jstree') !== undefined;
+ };
+ });
+
+ /**
+ * stores all defaults for the core
+ * @name $.jstree.defaults.core
+ */
+ $.jstree.defaults.core = {
+ /**
+ * data configuration
+ *
+ * If left as `false` the HTML inside the jstree container element is used to populate the tree (that should be an unordered list with list items).
+ *
+ * You can also pass in a HTML string or a JSON array here.
+ *
+ * It is possible to pass in a standard jQuery-like AJAX config and jstree will automatically determine if the response is JSON or HTML and use that to populate the tree.
+ * In addition to the standard jQuery ajax options here you can suppy functions for `data` and `url`, the functions will be run in the current instance's scope and a param will be passed indicating which node is being loaded, the return value of those functions will be used.
+ *
+ * The last option is to specify a function, that function will receive the node being loaded as argument and a second param which is a function which should be called with the result.
+ *
+ * __Examples__
+ *
+ * // AJAX
+ * $('#tree').jstree({
+ * 'core' : {
+ * 'data' : {
+ * 'url' : '/get/children/',
+ * 'data' : function (node) {
+ * return { 'id' : node.id };
+ * }
+ * }
+ * });
+ *
+ * // direct data
+ * $('#tree').jstree({
+ * 'core' : {
+ * 'data' : [
+ * 'Simple root node',
+ * {
+ * 'id' : 'node_2',
+ * 'text' : 'Root node with options',
+ * 'state' : { 'opened' : true, 'selected' : true },
+ * 'children' : [ { 'text' : 'Child 1' }, 'Child 2']
+ * }
+ * ]
+ * }
+ * });
+ *
+ * // function
+ * $('#tree').jstree({
+ * 'core' : {
+ * 'data' : function (obj, callback) {
+ * callback.call(this, ['Root 1', 'Root 2']);
+ * }
+ * });
+ *
+ * @name $.jstree.defaults.core.data
+ */
+ data : false,
+ /**
+ * configure the various strings used throughout the tree
+ *
+ * You can use an object where the key is the string you need to replace and the value is your replacement.
+ * Another option is to specify a function which will be called with an argument of the needed string and should return the replacement.
+ * If left as `false` no replacement is made.
+ *
+ * __Examples__
+ *
+ * $('#tree').jstree({
+ * 'core' : {
+ * 'strings' : {
+ * 'Loading ...' : 'Please wait ...'
+ * }
+ * }
+ * });
+ *
+ * @name $.jstree.defaults.core.strings
+ */
+ strings : false,
+ /**
+ * determines what happens when a user tries to modify the structure of the tree
+ * If left as `false` all operations like create, rename, delete, move or copy are prevented.
+ * You can set this to `true` to allow all interactions or use a function to have better control.
+ *
+ * __Examples__
+ *
+ * $('#tree').jstree({
+ * 'core' : {
+ * 'check_callback' : function (operation, node, node_parent, node_position, more) {
+ * // operation can be 'create_node', 'rename_node', 'delete_node', 'move_node' or 'copy_node'
+ * // in case of 'rename_node' node_position is filled with the new node name
+ * return operation === 'rename_node' ? true : false;
+ * }
+ * }
+ * });
+ *
+ * @name $.jstree.defaults.core.check_callback
+ */
+ check_callback : false,
+ /**
+ * a callback called with a single object parameter in the instance's scope when something goes wrong (operation prevented, ajax failed, etc)
+ * @name $.jstree.defaults.core.error
+ */
+ error : $.noop,
+ /**
+ * the open / close animation duration in milliseconds - set this to `false` to disable the animation (default is `200`)
+ * @name $.jstree.defaults.core.animation
+ */
+ animation : 200,
+ /**
+ * a boolean indicating if multiple nodes can be selected
+ * @name $.jstree.defaults.core.multiple
+ */
+ multiple : true,
+ /**
+ * theme configuration object
+ * @name $.jstree.defaults.core.themes
+ */
+ themes : {
+ /**
+ * the name of the theme to use (if left as `false` the default theme is used)
+ * @name $.jstree.defaults.core.themes.name
+ */
+ name : false,
+ /**
+ * the URL of the theme's CSS file, leave this as `false` if you have manually included the theme CSS (recommended). You can set this to `true` too which will try to autoload the theme.
+ * @name $.jstree.defaults.core.themes.url
+ */
+ url : false,
+ /**
+ * the location of all jstree themes - only used if `url` is set to `true`
+ * @name $.jstree.defaults.core.themes.dir
+ */
+ dir : false,
+ /**
+ * a boolean indicating if connecting dots are shown
+ * @name $.jstree.defaults.core.themes.dots
+ */
+ dots : true,
+ /**
+ * a boolean indicating if node icons are shown
+ * @name $.jstree.defaults.core.themes.icons
+ */
+ icons : true,
+ /**
+ * a boolean indicating if node ellipsis should be shown - this only works with a fixed with on the container
+ * @name $.jstree.defaults.core.themes.ellipsis
+ */
+ ellipsis : false,
+ /**
+ * a boolean indicating if the tree background is striped
+ * @name $.jstree.defaults.core.themes.stripes
+ */
+ stripes : false,
+ /**
+ * a string (or boolean `false`) specifying the theme variant to use (if the theme supports variants)
+ * @name $.jstree.defaults.core.themes.variant
+ */
+ variant : false,
+ /**
+ * a boolean specifying if a reponsive version of the theme should kick in on smaller screens (if the theme supports it). Defaults to `false`.
+ * @name $.jstree.defaults.core.themes.responsive
+ */
+ responsive : false
+ },
+ /**
+ * if left as `true` all parents of all selected nodes will be opened once the tree loads (so that all selected nodes are visible to the user)
+ * @name $.jstree.defaults.core.expand_selected_onload
+ */
+ expand_selected_onload : true,
+ /**
+ * if left as `true` web workers will be used to parse incoming JSON data where possible, so that the UI will not be blocked by large requests. Workers are however about 30% slower. Defaults to `true`
+ * @name $.jstree.defaults.core.worker
+ */
+ worker : true,
+ /**
+ * Force node text to plain text (and escape HTML). Defaults to `false`
+ * @name $.jstree.defaults.core.force_text
+ */
+ force_text : false,
+ /**
+ * Should the node should be toggled if the text is double clicked . Defaults to `true`
+ * @name $.jstree.defaults.core.dblclick_toggle
+ */
+ dblclick_toggle : true
+ };
+ $.jstree.core.prototype = {
+ /**
+ * used to decorate an instance with a plugin. Used internally.
+ * @private
+ * @name plugin(deco [, opts])
+ * @param {String} deco the plugin to decorate with
+ * @param {Object} opts options for the plugin
+ * @return {jsTree}
+ */
+ plugin : function (deco, opts) {
+ var Child = $.jstree.plugins[deco];
+ if(Child) {
+ this._data[deco] = {};
+ Child.prototype = this;
+ return new Child(opts, this);
+ }
+ return this;
+ },
+ /**
+ * initialize the instance. Used internally.
+ * @private
+ * @name init(el, optons)
+ * @param {DOMElement|jQuery|String} el the element we are transforming
+ * @param {Object} options options for this instance
+ * @trigger init.jstree, loading.jstree, loaded.jstree, ready.jstree, changed.jstree
+ */
+ init : function (el, options) {
+ this._model = {
+ data : {},
+ changed : [],
+ force_full_redraw : false,
+ redraw_timeout : false,
+ default_state : {
+ loaded : true,
+ opened : false,
+ selected : false,
+ disabled : false
+ }
+ };
+ this._model.data[$.jstree.root] = {
+ id : $.jstree.root,
+ parent : null,
+ parents : [],
+ children : [],
+ children_d : [],
+ state : { loaded : false }
+ };
+
+ this.element = $(el).addClass('jstree jstree-' + this._id);
+ this.settings = options;
+
+ this._data.core.ready = false;
+ this._data.core.loaded = false;
+ this._data.core.rtl = (this.element.css("direction") === "rtl");
+ this.element[this._data.core.rtl ? 'addClass' : 'removeClass']("jstree-rtl");
+ this.element.attr('role','tree');
+ if(this.settings.core.multiple) {
+ this.element.attr('aria-multiselectable', true);
+ }
+ if(!this.element.attr('tabindex')) {
+ this.element.attr('tabindex','0');
+ }
+
+ this.bind();
+ /**
+ * triggered after all events are bound
+ * @event
+ * @name init.jstree
+ */
+ this.trigger("init");
+
+ this._data.core.original_container_html = this.element.find(" > ul > li").clone(true);
+ this._data.core.original_container_html
+ .find("li").addBack()
+ .contents().filter(function() {
+ return this.nodeType === 3 && (!this.nodeValue || /^\s+$/.test(this.nodeValue));
+ })
+ .remove();
+ this.element.html("<"+"ul class='jstree-container-ul jstree-children' role='group'><"+"li id='j"+this._id+"_loading' class='jstree-initial-node jstree-loading jstree-leaf jstree-last' role='tree-item'>
<"+"a class='jstree-anchor' href='#'>
" + this.get_string("Loading ...") + "");
+ this.element.attr('aria-activedescendant','j' + this._id + '_loading');
+ this._data.core.li_height = this.get_container_ul().children("li").first().height() || 24;
+ this._data.core.node = this._create_prototype_node();
+ /**
+ * triggered after the loading text is shown and before loading starts
+ * @event
+ * @name loading.jstree
+ */
+ this.trigger("loading");
+ this.load_node($.jstree.root);
+ },
+ /**
+ * destroy an instance
+ * @name destroy()
+ * @param {Boolean} keep_html if not set to `true` the container will be emptied, otherwise the current DOM elements will be kept intact
+ */
+ destroy : function (keep_html) {
+ if(this._wrk) {
+ try {
+ window.URL.revokeObjectURL(this._wrk);
+ this._wrk = null;
+ }
+ catch (ignore) { }
+ }
+ if(!keep_html) { this.element.empty(); }
+ this.teardown();
+ },
+ /**
+ * Create prototype node
+ */
+ _create_prototype_node : function () {
+ var _node = document.createElement('LI'), _temp1, _temp2;
+ _node.setAttribute('role', 'treeitem');
+ _temp1 = document.createElement('I');
+ _temp1.className = 'jstree-icon jstree-ocl';
+ _temp1.setAttribute('role', 'presentation');
+ _node.appendChild(_temp1);
+ _temp1 = document.createElement('A');
+ _temp1.className = 'jstree-anchor';
+ _temp1.setAttribute('href','#');
+ _temp1.setAttribute('tabindex','-1');
+ _temp2 = document.createElement('I');
+ _temp2.className = 'jstree-icon jstree-themeicon';
+ _temp2.setAttribute('role', 'presentation');
+ _temp1.appendChild(_temp2);
+ _node.appendChild(_temp1);
+ _temp1 = _temp2 = null;
+
+ return _node;
+ },
+ /**
+ * part of the destroying of an instance. Used internally.
+ * @private
+ * @name teardown()
+ */
+ teardown : function () {
+ this.unbind();
+ this.element
+ .removeClass('jstree')
+ .removeData('jstree')
+ .find("[class^='jstree']")
+ .addBack()
+ .attr("class", function () { return this.className.replace(/jstree[^ ]*|$/ig,''); });
+ this.element = null;
+ },
+ /**
+ * bind all events. Used internally.
+ * @private
+ * @name bind()
+ */
+ bind : function () {
+ var word = '',
+ tout = null,
+ was_click = 0;
+ this.element
+ .on("dblclick.jstree", function (e) {
+ if(e.target.tagName && e.target.tagName.toLowerCase() === "input") { return true; }
+ if(document.selection && document.selection.empty) {
+ document.selection.empty();
+ }
+ else {
+ if(window.getSelection) {
+ var sel = window.getSelection();
+ try {
+ sel.removeAllRanges();
+ sel.collapse();
+ } catch (ignore) { }
+ }
+ }
+ })
+ .on("mousedown.jstree", $.proxy(function (e) {
+ if(e.target === this.element[0]) {
+ e.preventDefault(); // prevent losing focus when clicking scroll arrows (FF, Chrome)
+ was_click = +(new Date()); // ie does not allow to prevent losing focus
+ }
+ }, this))
+ .on("mousedown.jstree", ".jstree-ocl", function (e) {
+ e.preventDefault(); // prevent any node inside from losing focus when clicking the open/close icon
+ })
+ .on("click.jstree", ".jstree-ocl", $.proxy(function (e) {
+ this.toggle_node(e.target);
+ }, this))
+ .on("dblclick.jstree", ".jstree-anchor", $.proxy(function (e) {
+ if(e.target.tagName && e.target.tagName.toLowerCase() === "input") { return true; }
+ if(this.settings.core.dblclick_toggle) {
+ this.toggle_node(e.target);
+ }
+ }, this))
+ .on("click.jstree", ".jstree-anchor", $.proxy(function (e) {
+ e.preventDefault();
+ if(e.currentTarget !== document.activeElement) { $(e.currentTarget).focus(); }
+ this.activate_node(e.currentTarget, e);
+ }, this))
+ .on('keydown.jstree', '.jstree-anchor', $.proxy(function (e) {
+ if(e.target.tagName && e.target.tagName.toLowerCase() === "input") { return true; }
+ if(e.which !== 32 && e.which !== 13 && (e.shiftKey || e.ctrlKey || e.altKey || e.metaKey)) { return true; }
+ var o = null;
+ if(this._data.core.rtl) {
+ if(e.which === 37) { e.which = 39; }
+ else if(e.which === 39) { e.which = 37; }
+ }
+ switch(e.which) {
+ case 32: // aria defines space only with Ctrl
+ if(e.ctrlKey) {
+ e.type = "click";
+ $(e.currentTarget).trigger(e);
+ }
+ break;
+ case 13: // enter
+ e.type = "click";
+ $(e.currentTarget).trigger(e);
+ break;
+ case 37: // left
+ e.preventDefault();
+ if(this.is_open(e.currentTarget)) {
+ this.close_node(e.currentTarget);
+ }
+ else {
+ o = this.get_parent(e.currentTarget);
+ if(o && o.id !== $.jstree.root) { this.get_node(o, true).children('.jstree-anchor').focus(); }
+ }
+ break;
+ case 38: // up
+ e.preventDefault();
+ o = this.get_prev_dom(e.currentTarget);
+ if(o && o.length) { o.children('.jstree-anchor').focus(); }
+ break;
+ case 39: // right
+ e.preventDefault();
+ if(this.is_closed(e.currentTarget)) {
+ this.open_node(e.currentTarget, function (o) { this.get_node(o, true).children('.jstree-anchor').focus(); });
+ }
+ else if (this.is_open(e.currentTarget)) {
+ o = this.get_node(e.currentTarget, true).children('.jstree-children')[0];
+ if(o) { $(this._firstChild(o)).children('.jstree-anchor').focus(); }
+ }
+ break;
+ case 40: // down
+ e.preventDefault();
+ o = this.get_next_dom(e.currentTarget);
+ if(o && o.length) { o.children('.jstree-anchor').focus(); }
+ break;
+ case 106: // aria defines * on numpad as open_all - not very common
+ this.open_all();
+ break;
+ case 36: // home
+ e.preventDefault();
+ o = this._firstChild(this.get_container_ul()[0]);
+ if(o) { $(o).children('.jstree-anchor').filter(':visible').focus(); }
+ break;
+ case 35: // end
+ e.preventDefault();
+ this.element.find('.jstree-anchor').filter(':visible').last().focus();
+ break;
+ case 113: // f2 - safe to include - if check_callback is false it will fail
+ e.preventDefault();
+ this.edit(e.currentTarget);
+ break;
+ default:
+ break;
+ /*!
+ // delete
+ case 46:
+ e.preventDefault();
+ o = this.get_node(e.currentTarget);
+ if(o && o.id && o.id !== $.jstree.root) {
+ o = this.is_selected(o) ? this.get_selected() : o;
+ this.delete_node(o);
+ }
+ break;
+
+ */
+ }
+ }, this))
+ .on("load_node.jstree", $.proxy(function (e, data) {
+ if(data.status) {
+ if(data.node.id === $.jstree.root && !this._data.core.loaded) {
+ this._data.core.loaded = true;
+ if(this._firstChild(this.get_container_ul()[0])) {
+ this.element.attr('aria-activedescendant',this._firstChild(this.get_container_ul()[0]).id);
+ }
+ /**
+ * triggered after the root node is loaded for the first time
+ * @event
+ * @name loaded.jstree
+ */
+ this.trigger("loaded");
+ }
+ if(!this._data.core.ready) {
+ setTimeout($.proxy(function() {
+ if(this.element && !this.get_container_ul().find('.jstree-loading').length) {
+ this._data.core.ready = true;
+ if(this._data.core.selected.length) {
+ if(this.settings.core.expand_selected_onload) {
+ var tmp = [], i, j;
+ for(i = 0, j = this._data.core.selected.length; i < j; i++) {
+ tmp = tmp.concat(this._model.data[this._data.core.selected[i]].parents);
+ }
+ tmp = $.vakata.array_unique(tmp);
+ for(i = 0, j = tmp.length; i < j; i++) {
+ this.open_node(tmp[i], false, 0);
+ }
+ }
+ this.trigger('changed', { 'action' : 'ready', 'selected' : this._data.core.selected });
+ }
+ /**
+ * triggered after all nodes are finished loading
+ * @event
+ * @name ready.jstree
+ */
+ this.trigger("ready");
+ }
+ }, this), 0);
+ }
+ }
+ }, this))
+ // quick searching when the tree is focused
+ .on('keypress.jstree', $.proxy(function (e) {
+ if(e.target.tagName && e.target.tagName.toLowerCase() === "input") { return true; }
+ if(tout) { clearTimeout(tout); }
+ tout = setTimeout(function () {
+ word = '';
+ }, 500);
+
+ var chr = String.fromCharCode(e.which).toLowerCase(),
+ col = this.element.find('.jstree-anchor').filter(':visible'),
+ ind = col.index(document.activeElement) || 0,
+ end = false;
+ word += chr;
+
+ // match for whole word from current node down (including the current node)
+ if(word.length > 1) {
+ col.slice(ind).each($.proxy(function (i, v) {
+ if($(v).text().toLowerCase().indexOf(word) === 0) {
+ $(v).focus();
+ end = true;
+ return false;
+ }
+ }, this));
+ if(end) { return; }
+
+ // match for whole word from the beginning of the tree
+ col.slice(0, ind).each($.proxy(function (i, v) {
+ if($(v).text().toLowerCase().indexOf(word) === 0) {
+ $(v).focus();
+ end = true;
+ return false;
+ }
+ }, this));
+ if(end) { return; }
+ }
+ // list nodes that start with that letter (only if word consists of a single char)
+ if(new RegExp('^' + chr.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&') + '+$').test(word)) {
+ // search for the next node starting with that letter
+ col.slice(ind + 1).each($.proxy(function (i, v) {
+ if($(v).text().toLowerCase().charAt(0) === chr) {
+ $(v).focus();
+ end = true;
+ return false;
+ }
+ }, this));
+ if(end) { return; }
+
+ // search from the beginning
+ col.slice(0, ind + 1).each($.proxy(function (i, v) {
+ if($(v).text().toLowerCase().charAt(0) === chr) {
+ $(v).focus();
+ end = true;
+ return false;
+ }
+ }, this));
+ if(end) { return; }
+ }
+ }, this))
+ // THEME RELATED
+ .on("init.jstree", $.proxy(function () {
+ var s = this.settings.core.themes;
+ this._data.core.themes.dots = s.dots;
+ this._data.core.themes.stripes = s.stripes;
+ this._data.core.themes.icons = s.icons;
+ this._data.core.themes.ellipsis = s.ellipsis;
+ this.set_theme(s.name || "default", s.url);
+ this.set_theme_variant(s.variant);
+ }, this))
+ .on("loading.jstree", $.proxy(function () {
+ this[ this._data.core.themes.dots ? "show_dots" : "hide_dots" ]();
+ this[ this._data.core.themes.icons ? "show_icons" : "hide_icons" ]();
+ this[ this._data.core.themes.stripes ? "show_stripes" : "hide_stripes" ]();
+ this[ this._data.core.themes.ellipsis ? "show_ellipsis" : "hide_ellipsis" ]();
+ }, this))
+ .on('blur.jstree', '.jstree-anchor', $.proxy(function (e) {
+ this._data.core.focused = null;
+ $(e.currentTarget).filter('.jstree-hovered').mouseleave();
+ this.element.attr('tabindex', '0');
+ }, this))
+ .on('focus.jstree', '.jstree-anchor', $.proxy(function (e) {
+ var tmp = this.get_node(e.currentTarget);
+ if(tmp && tmp.id) {
+ this._data.core.focused = tmp.id;
+ }
+ this.element.find('.jstree-hovered').not(e.currentTarget).mouseleave();
+ $(e.currentTarget).mouseenter();
+ this.element.attr('tabindex', '-1');
+ }, this))
+ .on('focus.jstree', $.proxy(function () {
+ if(+(new Date()) - was_click > 500 && !this._data.core.focused) {
+ was_click = 0;
+ var act = this.get_node(this.element.attr('aria-activedescendant'), true);
+ if(act) {
+ act.find('> .jstree-anchor').focus();
+ }
+ }
+ }, this))
+ .on('mouseenter.jstree', '.jstree-anchor', $.proxy(function (e) {
+ this.hover_node(e.currentTarget);
+ }, this))
+ .on('mouseleave.jstree', '.jstree-anchor', $.proxy(function (e) {
+ this.dehover_node(e.currentTarget);
+ }, this));
+ },
+ /**
+ * part of the destroying of an instance. Used internally.
+ * @private
+ * @name unbind()
+ */
+ unbind : function () {
+ this.element.off('.jstree');
+ $(document).off('.jstree-' + this._id);
+ },
+ /**
+ * trigger an event. Used internally.
+ * @private
+ * @name trigger(ev [, data])
+ * @param {String} ev the name of the event to trigger
+ * @param {Object} data additional data to pass with the event
+ */
+ trigger : function (ev, data) {
+ if(!data) {
+ data = {};
+ }
+ data.instance = this;
+ this.element.triggerHandler(ev.replace('.jstree','') + '.jstree', data);
+ },
+ /**
+ * returns the jQuery extended instance container
+ * @name get_container()
+ * @return {jQuery}
+ */
+ get_container : function () {
+ return this.element;
+ },
+ /**
+ * returns the jQuery extended main UL node inside the instance container. Used internally.
+ * @private
+ * @name get_container_ul()
+ * @return {jQuery}
+ */
+ get_container_ul : function () {
+ return this.element.children(".jstree-children").first();
+ },
+ /**
+ * gets string replacements (localization). Used internally.
+ * @private
+ * @name get_string(key)
+ * @param {String} key
+ * @return {String}
+ */
+ get_string : function (key) {
+ var a = this.settings.core.strings;
+ if($.isFunction(a)) { return a.call(this, key); }
+ if(a && a[key]) { return a[key]; }
+ return key;
+ },
+ /**
+ * gets the first child of a DOM node. Used internally.
+ * @private
+ * @name _firstChild(dom)
+ * @param {DOMElement} dom
+ * @return {DOMElement}
+ */
+ _firstChild : function (dom) {
+ dom = dom ? dom.firstChild : null;
+ while(dom !== null && dom.nodeType !== 1) {
+ dom = dom.nextSibling;
+ }
+ return dom;
+ },
+ /**
+ * gets the next sibling of a DOM node. Used internally.
+ * @private
+ * @name _nextSibling(dom)
+ * @param {DOMElement} dom
+ * @return {DOMElement}
+ */
+ _nextSibling : function (dom) {
+ dom = dom ? dom.nextSibling : null;
+ while(dom !== null && dom.nodeType !== 1) {
+ dom = dom.nextSibling;
+ }
+ return dom;
+ },
+ /**
+ * gets the previous sibling of a DOM node. Used internally.
+ * @private
+ * @name _previousSibling(dom)
+ * @param {DOMElement} dom
+ * @return {DOMElement}
+ */
+ _previousSibling : function (dom) {
+ dom = dom ? dom.previousSibling : null;
+ while(dom !== null && dom.nodeType !== 1) {
+ dom = dom.previousSibling;
+ }
+ return dom;
+ },
+ /**
+ * get the JSON representation of a node (or the actual jQuery extended DOM node) by using any input (child DOM element, ID string, selector, etc)
+ * @name get_node(obj [, as_dom])
+ * @param {mixed} obj
+ * @param {Boolean} as_dom
+ * @return {Object|jQuery}
+ */
+ get_node : function (obj, as_dom) {
+ if(obj && obj.id) {
+ obj = obj.id;
+ }
+ var dom;
+ try {
+ if(this._model.data[obj]) {
+ obj = this._model.data[obj];
+ }
+ else if(typeof obj === "string" && this._model.data[obj.replace(/^#/, '')]) {
+ obj = this._model.data[obj.replace(/^#/, '')];
+ }
+ else if(typeof obj === "string" && (dom = $('#' + obj.replace($.jstree.idregex,'\\$&'), this.element)).length && this._model.data[dom.closest('.jstree-node').attr('id')]) {
+ obj = this._model.data[dom.closest('.jstree-node').attr('id')];
+ }
+ else if((dom = $(obj, this.element)).length && this._model.data[dom.closest('.jstree-node').attr('id')]) {
+ obj = this._model.data[dom.closest('.jstree-node').attr('id')];
+ }
+ else if((dom = $(obj, this.element)).length && dom.hasClass('jstree')) {
+ obj = this._model.data[$.jstree.root];
+ }
+ else {
+ return false;
+ }
+
+ if(as_dom) {
+ obj = obj.id === $.jstree.root ? this.element : $('#' + obj.id.replace($.jstree.idregex,'\\$&'), this.element);
+ }
+ return obj;
+ } catch (ex) { return false; }
+ },
+ /**
+ * get the path to a node, either consisting of node texts, or of node IDs, optionally glued together (otherwise an array)
+ * @name get_path(obj [, glue, ids])
+ * @param {mixed} obj the node
+ * @param {String} glue if you want the path as a string - pass the glue here (for example '/'), if a falsy value is supplied here, an array is returned
+ * @param {Boolean} ids if set to true build the path using ID, otherwise node text is used
+ * @return {mixed}
+ */
+ get_path : function (obj, glue, ids) {
+ obj = obj.parents ? obj : this.get_node(obj);
+ if(!obj || obj.id === $.jstree.root || !obj.parents) {
+ return false;
+ }
+ var i, j, p = [];
+ p.push(ids ? obj.id : obj.text);
+ for(i = 0, j = obj.parents.length; i < j; i++) {
+ p.push(ids ? obj.parents[i] : this.get_text(obj.parents[i]));
+ }
+ p = p.reverse().slice(1);
+ return glue ? p.join(glue) : p;
+ },
+ /**
+ * get the next visible node that is below the `obj` node. If `strict` is set to `true` only sibling nodes are returned.
+ * @name get_next_dom(obj [, strict])
+ * @param {mixed} obj
+ * @param {Boolean} strict
+ * @return {jQuery}
+ */
+ get_next_dom : function (obj, strict) {
+ var tmp;
+ obj = this.get_node(obj, true);
+ if(obj[0] === this.element[0]) {
+ tmp = this._firstChild(this.get_container_ul()[0]);
+ while (tmp && tmp.offsetHeight === 0) {
+ tmp = this._nextSibling(tmp);
+ }
+ return tmp ? $(tmp) : false;
+ }
+ if(!obj || !obj.length) {
+ return false;
+ }
+ if(strict) {
+ tmp = obj[0];
+ do {
+ tmp = this._nextSibling(tmp);
+ } while (tmp && tmp.offsetHeight === 0);
+ return tmp ? $(tmp) : false;
+ }
+ if(obj.hasClass("jstree-open")) {
+ tmp = this._firstChild(obj.children('.jstree-children')[0]);
+ while (tmp && tmp.offsetHeight === 0) {
+ tmp = this._nextSibling(tmp);
+ }
+ if(tmp !== null) {
+ return $(tmp);
+ }
+ }
+ tmp = obj[0];
+ do {
+ tmp = this._nextSibling(tmp);
+ } while (tmp && tmp.offsetHeight === 0);
+ if(tmp !== null) {
+ return $(tmp);
+ }
+ return obj.parentsUntil(".jstree",".jstree-node").nextAll(".jstree-node:visible").first();
+ },
+ /**
+ * get the previous visible node that is above the `obj` node. If `strict` is set to `true` only sibling nodes are returned.
+ * @name get_prev_dom(obj [, strict])
+ * @param {mixed} obj
+ * @param {Boolean} strict
+ * @return {jQuery}
+ */
+ get_prev_dom : function (obj, strict) {
+ var tmp;
+ obj = this.get_node(obj, true);
+ if(obj[0] === this.element[0]) {
+ tmp = this.get_container_ul()[0].lastChild;
+ while (tmp && tmp.offsetHeight === 0) {
+ tmp = this._previousSibling(tmp);
+ }
+ return tmp ? $(tmp) : false;
+ }
+ if(!obj || !obj.length) {
+ return false;
+ }
+ if(strict) {
+ tmp = obj[0];
+ do {
+ tmp = this._previousSibling(tmp);
+ } while (tmp && tmp.offsetHeight === 0);
+ return tmp ? $(tmp) : false;
+ }
+ tmp = obj[0];
+ do {
+ tmp = this._previousSibling(tmp);
+ } while (tmp && tmp.offsetHeight === 0);
+ if(tmp !== null) {
+ obj = $(tmp);
+ while(obj.hasClass("jstree-open")) {
+ obj = obj.children(".jstree-children").first().children(".jstree-node:visible:last");
+ }
+ return obj;
+ }
+ tmp = obj[0].parentNode.parentNode;
+ return tmp && tmp.className && tmp.className.indexOf('jstree-node') !== -1 ? $(tmp) : false;
+ },
+ /**
+ * get the parent ID of a node
+ * @name get_parent(obj)
+ * @param {mixed} obj
+ * @return {String}
+ */
+ get_parent : function (obj) {
+ obj = this.get_node(obj);
+ if(!obj || obj.id === $.jstree.root) {
+ return false;
+ }
+ return obj.parent;
+ },
+ /**
+ * get a jQuery collection of all the children of a node (node must be rendered)
+ * @name get_children_dom(obj)
+ * @param {mixed} obj
+ * @return {jQuery}
+ */
+ get_children_dom : function (obj) {
+ obj = this.get_node(obj, true);
+ if(obj[0] === this.element[0]) {
+ return this.get_container_ul().children(".jstree-node");
+ }
+ if(!obj || !obj.length) {
+ return false;
+ }
+ return obj.children(".jstree-children").children(".jstree-node");
+ },
+ /**
+ * checks if a node has children
+ * @name is_parent(obj)
+ * @param {mixed} obj
+ * @return {Boolean}
+ */
+ is_parent : function (obj) {
+ obj = this.get_node(obj);
+ return obj && (obj.state.loaded === false || obj.children.length > 0);
+ },
+ /**
+ * checks if a node is loaded (its children are available)
+ * @name is_loaded(obj)
+ * @param {mixed} obj
+ * @return {Boolean}
+ */
+ is_loaded : function (obj) {
+ obj = this.get_node(obj);
+ return obj && obj.state.loaded;
+ },
+ /**
+ * check if a node is currently loading (fetching children)
+ * @name is_loading(obj)
+ * @param {mixed} obj
+ * @return {Boolean}
+ */
+ is_loading : function (obj) {
+ obj = this.get_node(obj);
+ return obj && obj.state && obj.state.loading;
+ },
+ /**
+ * check if a node is opened
+ * @name is_open(obj)
+ * @param {mixed} obj
+ * @return {Boolean}
+ */
+ is_open : function (obj) {
+ obj = this.get_node(obj);
+ return obj && obj.state.opened;
+ },
+ /**
+ * check if a node is in a closed state
+ * @name is_closed(obj)
+ * @param {mixed} obj
+ * @return {Boolean}
+ */
+ is_closed : function (obj) {
+ obj = this.get_node(obj);
+ return obj && this.is_parent(obj) && !obj.state.opened;
+ },
+ /**
+ * check if a node has no children
+ * @name is_leaf(obj)
+ * @param {mixed} obj
+ * @return {Boolean}
+ */
+ is_leaf : function (obj) {
+ return !this.is_parent(obj);
+ },
+ /**
+ * loads a node (fetches its children using the `core.data` setting). Multiple nodes can be passed to by using an array.
+ * @name load_node(obj [, callback])
+ * @param {mixed} obj
+ * @param {function} callback a function to be executed once loading is complete, the function is executed in the instance's scope and receives two arguments - the node and a boolean status
+ * @return {Boolean}
+ * @trigger load_node.jstree
+ */
+ load_node : function (obj, callback) {
+ var k, l, i, j, c;
+ if($.isArray(obj)) {
+ this._load_nodes(obj.slice(), callback);
+ return true;
+ }
+ obj = this.get_node(obj);
+ if(!obj) {
+ if(callback) { callback.call(this, obj, false); }
+ return false;
+ }
+ // if(obj.state.loading) { } // the node is already loading - just wait for it to load and invoke callback? but if called implicitly it should be loaded again?
+ if(obj.state.loaded) {
+ obj.state.loaded = false;
+ for(i = 0, j = obj.parents.length; i < j; i++) {
+ this._model.data[obj.parents[i]].children_d = $.vakata.array_filter(this._model.data[obj.parents[i]].children_d, function (v) {
+ return $.inArray(v, obj.children_d) === -1;
+ });
+ }
+ for(k = 0, l = obj.children_d.length; k < l; k++) {
+ if(this._model.data[obj.children_d[k]].state.selected) {
+ c = true;
+ }
+ delete this._model.data[obj.children_d[k]];
+ }
+ if (c) {
+ this._data.core.selected = $.vakata.array_filter(this._data.core.selected, function (v) {
+ return $.inArray(v, obj.children_d) === -1;
+ });
+ }
+ obj.children = [];
+ obj.children_d = [];
+ if(c) {
+ this.trigger('changed', { 'action' : 'load_node', 'node' : obj, 'selected' : this._data.core.selected });
+ }
+ }
+ obj.state.failed = false;
+ obj.state.loading = true;
+ this.get_node(obj, true).addClass("jstree-loading").attr('aria-busy',true);
+ this._load_node(obj, $.proxy(function (status) {
+ obj = this._model.data[obj.id];
+ obj.state.loading = false;
+ obj.state.loaded = status;
+ obj.state.failed = !obj.state.loaded;
+ var dom = this.get_node(obj, true), i = 0, j = 0, m = this._model.data, has_children = false;
+ for(i = 0, j = obj.children.length; i < j; i++) {
+ if(m[obj.children[i]] && !m[obj.children[i]].state.hidden) {
+ has_children = true;
+ break;
+ }
+ }
+ if(obj.state.loaded && dom && dom.length) {
+ dom.removeClass('jstree-closed jstree-open jstree-leaf');
+ if (!has_children) {
+ dom.addClass('jstree-leaf');
+ }
+ else {
+ if (obj.id !== '#') {
+ dom.addClass(obj.state.opened ? 'jstree-open' : 'jstree-closed');
+ }
+ }
+ }
+ dom.removeClass("jstree-loading").attr('aria-busy',false);
+ /**
+ * triggered after a node is loaded
+ * @event
+ * @name load_node.jstree
+ * @param {Object} node the node that was loading
+ * @param {Boolean} status was the node loaded successfully
+ */
+ this.trigger('load_node', { "node" : obj, "status" : status });
+ if(callback) {
+ callback.call(this, obj, status);
+ }
+ }, this));
+ return true;
+ },
+ /**
+ * load an array of nodes (will also load unavailable nodes as soon as the appear in the structure). Used internally.
+ * @private
+ * @name _load_nodes(nodes [, callback])
+ * @param {array} nodes
+ * @param {function} callback a function to be executed once loading is complete, the function is executed in the instance's scope and receives one argument - the array passed to _load_nodes
+ */
+ _load_nodes : function (nodes, callback, is_callback, force_reload) {
+ var r = true,
+ c = function () { this._load_nodes(nodes, callback, true); },
+ m = this._model.data, i, j, tmp = [];
+ for(i = 0, j = nodes.length; i < j; i++) {
+ if(m[nodes[i]] && ( (!m[nodes[i]].state.loaded && !m[nodes[i]].state.failed) || (!is_callback && force_reload) )) {
+ if(!this.is_loading(nodes[i])) {
+ this.load_node(nodes[i], c);
+ }
+ r = false;
+ }
+ }
+ if(r) {
+ for(i = 0, j = nodes.length; i < j; i++) {
+ if(m[nodes[i]] && m[nodes[i]].state.loaded) {
+ tmp.push(nodes[i]);
+ }
+ }
+ if(callback && !callback.done) {
+ callback.call(this, tmp);
+ callback.done = true;
+ }
+ }
+ },
+ /**
+ * loads all unloaded nodes
+ * @name load_all([obj, callback])
+ * @param {mixed} obj the node to load recursively, omit to load all nodes in the tree
+ * @param {function} callback a function to be executed once loading all the nodes is complete,
+ * @trigger load_all.jstree
+ */
+ load_all : function (obj, callback) {
+ if(!obj) { obj = $.jstree.root; }
+ obj = this.get_node(obj);
+ if(!obj) { return false; }
+ var to_load = [],
+ m = this._model.data,
+ c = m[obj.id].children_d,
+ i, j;
+ if(obj.state && !obj.state.loaded) {
+ to_load.push(obj.id);
+ }
+ for(i = 0, j = c.length; i < j; i++) {
+ if(m[c[i]] && m[c[i]].state && !m[c[i]].state.loaded) {
+ to_load.push(c[i]);
+ }
+ }
+ if(to_load.length) {
+ this._load_nodes(to_load, function () {
+ this.load_all(obj, callback);
+ });
+ }
+ else {
+ /**
+ * triggered after a load_all call completes
+ * @event
+ * @name load_all.jstree
+ * @param {Object} node the recursively loaded node
+ */
+ if(callback) { callback.call(this, obj); }
+ this.trigger('load_all', { "node" : obj });
+ }
+ },
+ /**
+ * handles the actual loading of a node. Used only internally.
+ * @private
+ * @name _load_node(obj [, callback])
+ * @param {mixed} obj
+ * @param {function} callback a function to be executed once loading is complete, the function is executed in the instance's scope and receives one argument - a boolean status
+ * @return {Boolean}
+ */
+ _load_node : function (obj, callback) {
+ var s = this.settings.core.data, t;
+ var notTextOrCommentNode = function notTextOrCommentNode () {
+ return this.nodeType !== 3 && this.nodeType !== 8;
+ };
+ // use original HTML
+ if(!s) {
+ if(obj.id === $.jstree.root) {
+ return this._append_html_data(obj, this._data.core.original_container_html.clone(true), function (status) {
+ callback.call(this, status);
+ });
+ }
+ else {
+ return callback.call(this, false);
+ }
+ // return callback.call(this, obj.id === $.jstree.root ? this._append_html_data(obj, this._data.core.original_container_html.clone(true)) : false);
+ }
+ if($.isFunction(s)) {
+ return s.call(this, obj, $.proxy(function (d) {
+ if(d === false) {
+ callback.call(this, false);
+ }
+ else {
+ this[typeof d === 'string' ? '_append_html_data' : '_append_json_data'](obj, typeof d === 'string' ? $($.parseHTML(d)).filter(notTextOrCommentNode) : d, function (status) {
+ callback.call(this, status);
+ });
+ }
+ // return d === false ? callback.call(this, false) : callback.call(this, this[typeof d === 'string' ? '_append_html_data' : '_append_json_data'](obj, typeof d === 'string' ? $(d) : d));
+ }, this));
+ }
+ if(typeof s === 'object') {
+ if(s.url) {
+ s = $.extend(true, {}, s);
+ if($.isFunction(s.url)) {
+ s.url = s.url.call(this, obj);
+ }
+ if($.isFunction(s.data)) {
+ s.data = s.data.call(this, obj);
+ }
+ return $.ajax(s)
+ .done($.proxy(function (d,t,x) {
+ var type = x.getResponseHeader('Content-Type');
+ if((type && type.indexOf('json') !== -1) || typeof d === "object") {
+ return this._append_json_data(obj, d, function (status) { callback.call(this, status); });
+ //return callback.call(this, this._append_json_data(obj, d));
+ }
+ if((type && type.indexOf('html') !== -1) || typeof d === "string") {
+ return this._append_html_data(obj, $($.parseHTML(d)).filter(notTextOrCommentNode), function (status) { callback.call(this, status); });
+ // return callback.call(this, this._append_html_data(obj, $(d)));
+ }
+ this._data.core.last_error = { 'error' : 'ajax', 'plugin' : 'core', 'id' : 'core_04', 'reason' : 'Could not load node', 'data' : JSON.stringify({ 'id' : obj.id, 'xhr' : x }) };
+ this.settings.core.error.call(this, this._data.core.last_error);
+ return callback.call(this, false);
+ }, this))
+ .fail($.proxy(function (f) {
+ callback.call(this, false);
+ this._data.core.last_error = { 'error' : 'ajax', 'plugin' : 'core', 'id' : 'core_04', 'reason' : 'Could not load node', 'data' : JSON.stringify({ 'id' : obj.id, 'xhr' : f }) };
+ this.settings.core.error.call(this, this._data.core.last_error);
+ }, this));
+ }
+ t = ($.isArray(s) || $.isPlainObject(s)) ? JSON.parse(JSON.stringify(s)) : s;
+ if(obj.id === $.jstree.root) {
+ return this._append_json_data(obj, t, function (status) {
+ callback.call(this, status);
+ });
+ }
+ else {
+ this._data.core.last_error = { 'error' : 'nodata', 'plugin' : 'core', 'id' : 'core_05', 'reason' : 'Could not load node', 'data' : JSON.stringify({ 'id' : obj.id }) };
+ this.settings.core.error.call(this, this._data.core.last_error);
+ return callback.call(this, false);
+ }
+ //return callback.call(this, (obj.id === $.jstree.root ? this._append_json_data(obj, t) : false) );
+ }
+ if(typeof s === 'string') {
+ if(obj.id === $.jstree.root) {
+ return this._append_html_data(obj, $($.parseHTML(s)).filter(notTextOrCommentNode), function (status) {
+ callback.call(this, status);
+ });
+ }
+ else {
+ this._data.core.last_error = { 'error' : 'nodata', 'plugin' : 'core', 'id' : 'core_06', 'reason' : 'Could not load node', 'data' : JSON.stringify({ 'id' : obj.id }) };
+ this.settings.core.error.call(this, this._data.core.last_error);
+ return callback.call(this, false);
+ }
+ //return callback.call(this, (obj.id === $.jstree.root ? this._append_html_data(obj, $(s)) : false) );
+ }
+ return callback.call(this, false);
+ },
+ /**
+ * adds a node to the list of nodes to redraw. Used only internally.
+ * @private
+ * @name _node_changed(obj [, callback])
+ * @param {mixed} obj
+ */
+ _node_changed : function (obj) {
+ obj = this.get_node(obj);
+ if(obj) {
+ this._model.changed.push(obj.id);
+ }
+ },
+ /**
+ * appends HTML content to the tree. Used internally.
+ * @private
+ * @name _append_html_data(obj, data)
+ * @param {mixed} obj the node to append to
+ * @param {String} data the HTML string to parse and append
+ * @trigger model.jstree, changed.jstree
+ */
+ _append_html_data : function (dom, data, cb) {
+ dom = this.get_node(dom);
+ dom.children = [];
+ dom.children_d = [];
+ var dat = data.is('ul') ? data.children() : data,
+ par = dom.id,
+ chd = [],
+ dpc = [],
+ m = this._model.data,
+ p = m[par],
+ s = this._data.core.selected.length,
+ tmp, i, j;
+ dat.each($.proxy(function (i, v) {
+ tmp = this._parse_model_from_html($(v), par, p.parents.concat());
+ if(tmp) {
+ chd.push(tmp);
+ dpc.push(tmp);
+ if(m[tmp].children_d.length) {
+ dpc = dpc.concat(m[tmp].children_d);
+ }
+ }
+ }, this));
+ p.children = chd;
+ p.children_d = dpc;
+ for(i = 0, j = p.parents.length; i < j; i++) {
+ m[p.parents[i]].children_d = m[p.parents[i]].children_d.concat(dpc);
+ }
+ /**
+ * triggered when new data is inserted to the tree model
+ * @event
+ * @name model.jstree
+ * @param {Array} nodes an array of node IDs
+ * @param {String} parent the parent ID of the nodes
+ */
+ this.trigger('model', { "nodes" : dpc, 'parent' : par });
+ if(par !== $.jstree.root) {
+ this._node_changed(par);
+ this.redraw();
+ }
+ else {
+ this.get_container_ul().children('.jstree-initial-node').remove();
+ this.redraw(true);
+ }
+ if(this._data.core.selected.length !== s) {
+ this.trigger('changed', { 'action' : 'model', 'selected' : this._data.core.selected });
+ }
+ cb.call(this, true);
+ },
+ /**
+ * appends JSON content to the tree. Used internally.
+ * @private
+ * @name _append_json_data(obj, data)
+ * @param {mixed} obj the node to append to
+ * @param {String} data the JSON object to parse and append
+ * @param {Boolean} force_processing internal param - do not set
+ * @trigger model.jstree, changed.jstree
+ */
+ _append_json_data : function (dom, data, cb, force_processing) {
+ if(this.element === null) { return; }
+ dom = this.get_node(dom);
+ dom.children = [];
+ dom.children_d = [];
+ // *%$@!!!
+ if(data.d) {
+ data = data.d;
+ if(typeof data === "string") {
+ data = JSON.parse(data);
+ }
+ }
+ if(!$.isArray(data)) { data = [data]; }
+ var w = null,
+ args = {
+ 'df' : this._model.default_state,
+ 'dat' : data,
+ 'par' : dom.id,
+ 'm' : this._model.data,
+ 't_id' : this._id,
+ 't_cnt' : this._cnt,
+ 'sel' : this._data.core.selected
+ },
+ func = function (data, undefined) {
+ if(data.data) { data = data.data; }
+ var dat = data.dat,
+ par = data.par,
+ chd = [],
+ dpc = [],
+ add = [],
+ df = data.df,
+ t_id = data.t_id,
+ t_cnt = data.t_cnt,
+ m = data.m,
+ p = m[par],
+ sel = data.sel,
+ tmp, i, j, rslt,
+ parse_flat = function (d, p, ps) {
+ if(!ps) { ps = []; }
+ else { ps = ps.concat(); }
+ if(p) { ps.unshift(p); }
+ var tid = d.id.toString(),
+ i, j, c, e,
+ tmp = {
+ id : tid,
+ text : d.text || '',
+ icon : d.icon !== undefined ? d.icon : true,
+ parent : p,
+ parents : ps,
+ children : d.children || [],
+ children_d : d.children_d || [],
+ data : d.data,
+ state : { },
+ li_attr : { id : false },
+ a_attr : { href : '#' },
+ original : false
+ };
+ for(i in df) {
+ if(df.hasOwnProperty(i)) {
+ tmp.state[i] = df[i];
+ }
+ }
+ if(d && d.data && d.data.jstree && d.data.jstree.icon) {
+ tmp.icon = d.data.jstree.icon;
+ }
+ if(tmp.icon === undefined || tmp.icon === null || tmp.icon === "") {
+ tmp.icon = true;
+ }
+ if(d && d.data) {
+ tmp.data = d.data;
+ if(d.data.jstree) {
+ for(i in d.data.jstree) {
+ if(d.data.jstree.hasOwnProperty(i)) {
+ tmp.state[i] = d.data.jstree[i];
+ }
+ }
+ }
+ }
+ if(d && typeof d.state === 'object') {
+ for (i in d.state) {
+ if(d.state.hasOwnProperty(i)) {
+ tmp.state[i] = d.state[i];
+ }
+ }
+ }
+ if(d && typeof d.li_attr === 'object') {
+ for (i in d.li_attr) {
+ if(d.li_attr.hasOwnProperty(i)) {
+ tmp.li_attr[i] = d.li_attr[i];
+ }
+ }
+ }
+ if(!tmp.li_attr.id) {
+ tmp.li_attr.id = tid;
+ }
+ if(d && typeof d.a_attr === 'object') {
+ for (i in d.a_attr) {
+ if(d.a_attr.hasOwnProperty(i)) {
+ tmp.a_attr[i] = d.a_attr[i];
+ }
+ }
+ }
+ if(d && d.children && d.children === true) {
+ tmp.state.loaded = false;
+ tmp.children = [];
+ tmp.children_d = [];
+ }
+ m[tmp.id] = tmp;
+ for(i = 0, j = tmp.children.length; i < j; i++) {
+ c = parse_flat(m[tmp.children[i]], tmp.id, ps);
+ e = m[c];
+ tmp.children_d.push(c);
+ if(e.children_d.length) {
+ tmp.children_d = tmp.children_d.concat(e.children_d);
+ }
+ }
+ delete d.data;
+ delete d.children;
+ m[tmp.id].original = d;
+ if(tmp.state.selected) {
+ add.push(tmp.id);
+ }
+ return tmp.id;
+ },
+ parse_nest = function (d, p, ps) {
+ if(!ps) { ps = []; }
+ else { ps = ps.concat(); }
+ if(p) { ps.unshift(p); }
+ var tid = false, i, j, c, e, tmp;
+ do {
+ tid = 'j' + t_id + '_' + (++t_cnt);
+ } while(m[tid]);
+
+ tmp = {
+ id : false,
+ text : typeof d === 'string' ? d : '',
+ icon : typeof d === 'object' && d.icon !== undefined ? d.icon : true,
+ parent : p,
+ parents : ps,
+ children : [],
+ children_d : [],
+ data : null,
+ state : { },
+ li_attr : { id : false },
+ a_attr : { href : '#' },
+ original : false
+ };
+ for(i in df) {
+ if(df.hasOwnProperty(i)) {
+ tmp.state[i] = df[i];
+ }
+ }
+ if(d && d.id) { tmp.id = d.id.toString(); }
+ if(d && d.text) { tmp.text = d.text; }
+ if(d && d.data && d.data.jstree && d.data.jstree.icon) {
+ tmp.icon = d.data.jstree.icon;
+ }
+ if(tmp.icon === undefined || tmp.icon === null || tmp.icon === "") {
+ tmp.icon = true;
+ }
+ if(d && d.data) {
+ tmp.data = d.data;
+ if(d.data.jstree) {
+ for(i in d.data.jstree) {
+ if(d.data.jstree.hasOwnProperty(i)) {
+ tmp.state[i] = d.data.jstree[i];
+ }
+ }
+ }
+ }
+ if(d && typeof d.state === 'object') {
+ for (i in d.state) {
+ if(d.state.hasOwnProperty(i)) {
+ tmp.state[i] = d.state[i];
+ }
+ }
+ }
+ if(d && typeof d.li_attr === 'object') {
+ for (i in d.li_attr) {
+ if(d.li_attr.hasOwnProperty(i)) {
+ tmp.li_attr[i] = d.li_attr[i];
+ }
+ }
+ }
+ if(tmp.li_attr.id && !tmp.id) {
+ tmp.id = tmp.li_attr.id.toString();
+ }
+ if(!tmp.id) {
+ tmp.id = tid;
+ }
+ if(!tmp.li_attr.id) {
+ tmp.li_attr.id = tmp.id;
+ }
+ if(d && typeof d.a_attr === 'object') {
+ for (i in d.a_attr) {
+ if(d.a_attr.hasOwnProperty(i)) {
+ tmp.a_attr[i] = d.a_attr[i];
+ }
+ }
+ }
+ if(d && d.children && d.children.length) {
+ for(i = 0, j = d.children.length; i < j; i++) {
+ c = parse_nest(d.children[i], tmp.id, ps);
+ e = m[c];
+ tmp.children.push(c);
+ if(e.children_d.length) {
+ tmp.children_d = tmp.children_d.concat(e.children_d);
+ }
+ }
+ tmp.children_d = tmp.children_d.concat(tmp.children);
+ }
+ if(d && d.children && d.children === true) {
+ tmp.state.loaded = false;
+ tmp.children = [];
+ tmp.children_d = [];
+ }
+ delete d.data;
+ delete d.children;
+ tmp.original = d;
+ m[tmp.id] = tmp;
+ if(tmp.state.selected) {
+ add.push(tmp.id);
+ }
+ return tmp.id;
+ };
+
+ if(dat.length && dat[0].id !== undefined && dat[0].parent !== undefined) {
+ // Flat JSON support (for easy import from DB):
+ // 1) convert to object (foreach)
+ for(i = 0, j = dat.length; i < j; i++) {
+ if(!dat[i].children) {
+ dat[i].children = [];
+ }
+ m[dat[i].id.toString()] = dat[i];
+ }
+ // 2) populate children (foreach)
+ for(i = 0, j = dat.length; i < j; i++) {
+ m[dat[i].parent.toString()].children.push(dat[i].id.toString());
+ // populate parent.children_d
+ p.children_d.push(dat[i].id.toString());
+ }
+ // 3) normalize && populate parents and children_d with recursion
+ for(i = 0, j = p.children.length; i < j; i++) {
+ tmp = parse_flat(m[p.children[i]], par, p.parents.concat());
+ dpc.push(tmp);
+ if(m[tmp].children_d.length) {
+ dpc = dpc.concat(m[tmp].children_d);
+ }
+ }
+ for(i = 0, j = p.parents.length; i < j; i++) {
+ m[p.parents[i]].children_d = m[p.parents[i]].children_d.concat(dpc);
+ }
+ // ?) three_state selection - p.state.selected && t - (if three_state foreach(dat => ch) -> foreach(parents) if(parent.selected) child.selected = true;
+ rslt = {
+ 'cnt' : t_cnt,
+ 'mod' : m,
+ 'sel' : sel,
+ 'par' : par,
+ 'dpc' : dpc,
+ 'add' : add
+ };
+ }
+ else {
+ for(i = 0, j = dat.length; i < j; i++) {
+ tmp = parse_nest(dat[i], par, p.parents.concat());
+ if(tmp) {
+ chd.push(tmp);
+ dpc.push(tmp);
+ if(m[tmp].children_d.length) {
+ dpc = dpc.concat(m[tmp].children_d);
+ }
+ }
+ }
+ p.children = chd;
+ p.children_d = dpc;
+ for(i = 0, j = p.parents.length; i < j; i++) {
+ m[p.parents[i]].children_d = m[p.parents[i]].children_d.concat(dpc);
+ }
+ rslt = {
+ 'cnt' : t_cnt,
+ 'mod' : m,
+ 'sel' : sel,
+ 'par' : par,
+ 'dpc' : dpc,
+ 'add' : add
+ };
+ }
+ if(typeof window === 'undefined' || typeof window.document === 'undefined') {
+ postMessage(rslt);
+ }
+ else {
+ return rslt;
+ }
+ },
+ rslt = function (rslt, worker) {
+ if(this.element === null) { return; }
+ this._cnt = rslt.cnt;
+ var i, m = this._model.data;
+ for (i in m) {
+ if (m.hasOwnProperty(i) && m[i].state && m[i].state.loading && rslt.mod[i]) {
+ rslt.mod[i].state.loading = true;
+ }
+ }
+ this._model.data = rslt.mod; // breaks the reference in load_node - careful
+
+ if(worker) {
+ var j, a = rslt.add, r = rslt.sel, s = this._data.core.selected.slice();
+ m = this._model.data;
+ // if selection was changed while calculating in worker
+ if(r.length !== s.length || $.vakata.array_unique(r.concat(s)).length !== r.length) {
+ // deselect nodes that are no longer selected
+ for(i = 0, j = r.length; i < j; i++) {
+ if($.inArray(r[i], a) === -1 && $.inArray(r[i], s) === -1) {
+ m[r[i]].state.selected = false;
+ }
+ }
+ // select nodes that were selected in the mean time
+ for(i = 0, j = s.length; i < j; i++) {
+ if($.inArray(s[i], r) === -1) {
+ m[s[i]].state.selected = true;
+ }
+ }
+ }
+ }
+ if(rslt.add.length) {
+ this._data.core.selected = this._data.core.selected.concat(rslt.add);
+ }
+
+ this.trigger('model', { "nodes" : rslt.dpc, 'parent' : rslt.par });
+
+ if(rslt.par !== $.jstree.root) {
+ this._node_changed(rslt.par);
+ this.redraw();
+ }
+ else {
+ // this.get_container_ul().children('.jstree-initial-node').remove();
+ this.redraw(true);
+ }
+ if(rslt.add.length) {
+ this.trigger('changed', { 'action' : 'model', 'selected' : this._data.core.selected });
+ }
+ cb.call(this, true);
+ };
+ if(this.settings.core.worker && window.Blob && window.URL && window.Worker) {
+ try {
+ if(this._wrk === null) {
+ this._wrk = window.URL.createObjectURL(
+ new window.Blob(
+ ['self.onmessage = ' + func.toString()],
+ {type:"text/javascript"}
+ )
+ );
+ }
+ if(!this._data.core.working || force_processing) {
+ this._data.core.working = true;
+ w = new window.Worker(this._wrk);
+ w.onmessage = $.proxy(function (e) {
+ rslt.call(this, e.data, true);
+ try { w.terminate(); w = null; } catch(ignore) { }
+ if(this._data.core.worker_queue.length) {
+ this._append_json_data.apply(this, this._data.core.worker_queue.shift());
+ }
+ else {
+ this._data.core.working = false;
+ }
+ }, this);
+ if(!args.par) {
+ if(this._data.core.worker_queue.length) {
+ this._append_json_data.apply(this, this._data.core.worker_queue.shift());
+ }
+ else {
+ this._data.core.working = false;
+ }
+ }
+ else {
+ w.postMessage(args);
+ }
+ }
+ else {
+ this._data.core.worker_queue.push([dom, data, cb, true]);
+ }
+ }
+ catch(e) {
+ rslt.call(this, func(args), false);
+ if(this._data.core.worker_queue.length) {
+ this._append_json_data.apply(this, this._data.core.worker_queue.shift());
+ }
+ else {
+ this._data.core.working = false;
+ }
+ }
+ }
+ else {
+ rslt.call(this, func(args), false);
+ }
+ },
+ /**
+ * parses a node from a jQuery object and appends them to the in memory tree model. Used internally.
+ * @private
+ * @name _parse_model_from_html(d [, p, ps])
+ * @param {jQuery} d the jQuery object to parse
+ * @param {String} p the parent ID
+ * @param {Array} ps list of all parents
+ * @return {String} the ID of the object added to the model
+ */
+ _parse_model_from_html : function (d, p, ps) {
+ if(!ps) { ps = []; }
+ else { ps = [].concat(ps); }
+ if(p) { ps.unshift(p); }
+ var c, e, m = this._model.data,
+ data = {
+ id : false,
+ text : false,
+ icon : true,
+ parent : p,
+ parents : ps,
+ children : [],
+ children_d : [],
+ data : null,
+ state : { },
+ li_attr : { id : false },
+ a_attr : { href : '#' },
+ original : false
+ }, i, tmp, tid;
+ for(i in this._model.default_state) {
+ if(this._model.default_state.hasOwnProperty(i)) {
+ data.state[i] = this._model.default_state[i];
+ }
+ }
+ tmp = $.vakata.attributes(d, true);
+ $.each(tmp, function (i, v) {
+ v = $.trim(v);
+ if(!v.length) { return true; }
+ data.li_attr[i] = v;
+ if(i === 'id') {
+ data.id = v.toString();
+ }
+ });
+ tmp = d.children('a').first();
+ if(tmp.length) {
+ tmp = $.vakata.attributes(tmp, true);
+ $.each(tmp, function (i, v) {
+ v = $.trim(v);
+ if(v.length) {
+ data.a_attr[i] = v;
+ }
+ });
+ }
+ tmp = d.children("a").first().length ? d.children("a").first().clone() : d.clone();
+ tmp.children("ins, i, ul").remove();
+ tmp = tmp.html();
+ tmp = $('
').html(tmp);
+ data.text = this.settings.core.force_text ? tmp.text() : tmp.html();
+ tmp = d.data();
+ data.data = tmp ? $.extend(true, {}, tmp) : null;
+ data.state.opened = d.hasClass('jstree-open');
+ data.state.selected = d.children('a').hasClass('jstree-clicked');
+ data.state.disabled = d.children('a').hasClass('jstree-disabled');
+ if(data.data && data.data.jstree) {
+ for(i in data.data.jstree) {
+ if(data.data.jstree.hasOwnProperty(i)) {
+ data.state[i] = data.data.jstree[i];
+ }
+ }
+ }
+ tmp = d.children("a").children(".jstree-themeicon");
+ if(tmp.length) {
+ data.icon = tmp.hasClass('jstree-themeicon-hidden') ? false : tmp.attr('rel');
+ }
+ if(data.state.icon !== undefined) {
+ data.icon = data.state.icon;
+ }
+ if(data.icon === undefined || data.icon === null || data.icon === "") {
+ data.icon = true;
+ }
+ tmp = d.children("ul").children("li");
+ do {
+ tid = 'j' + this._id + '_' + (++this._cnt);
+ } while(m[tid]);
+ data.id = data.li_attr.id ? data.li_attr.id.toString() : tid;
+ if(tmp.length) {
+ tmp.each($.proxy(function (i, v) {
+ c = this._parse_model_from_html($(v), data.id, ps);
+ e = this._model.data[c];
+ data.children.push(c);
+ if(e.children_d.length) {
+ data.children_d = data.children_d.concat(e.children_d);
+ }
+ }, this));
+ data.children_d = data.children_d.concat(data.children);
+ }
+ else {
+ if(d.hasClass('jstree-closed')) {
+ data.state.loaded = false;
+ }
+ }
+ if(data.li_attr['class']) {
+ data.li_attr['class'] = data.li_attr['class'].replace('jstree-closed','').replace('jstree-open','');
+ }
+ if(data.a_attr['class']) {
+ data.a_attr['class'] = data.a_attr['class'].replace('jstree-clicked','').replace('jstree-disabled','');
+ }
+ m[data.id] = data;
+ if(data.state.selected) {
+ this._data.core.selected.push(data.id);
+ }
+ return data.id;
+ },
+ /**
+ * parses a node from a JSON object (used when dealing with flat data, which has no nesting of children, but has id and parent properties) and appends it to the in memory tree model. Used internally.
+ * @private
+ * @name _parse_model_from_flat_json(d [, p, ps])
+ * @param {Object} d the JSON object to parse
+ * @param {String} p the parent ID
+ * @param {Array} ps list of all parents
+ * @return {String} the ID of the object added to the model
+ */
+ _parse_model_from_flat_json : function (d, p, ps) {
+ if(!ps) { ps = []; }
+ else { ps = ps.concat(); }
+ if(p) { ps.unshift(p); }
+ var tid = d.id.toString(),
+ m = this._model.data,
+ df = this._model.default_state,
+ i, j, c, e,
+ tmp = {
+ id : tid,
+ text : d.text || '',
+ icon : d.icon !== undefined ? d.icon : true,
+ parent : p,
+ parents : ps,
+ children : d.children || [],
+ children_d : d.children_d || [],
+ data : d.data,
+ state : { },
+ li_attr : { id : false },
+ a_attr : { href : '#' },
+ original : false
+ };
+ for(i in df) {
+ if(df.hasOwnProperty(i)) {
+ tmp.state[i] = df[i];
+ }
+ }
+ if(d && d.data && d.data.jstree && d.data.jstree.icon) {
+ tmp.icon = d.data.jstree.icon;
+ }
+ if(tmp.icon === undefined || tmp.icon === null || tmp.icon === "") {
+ tmp.icon = true;
+ }
+ if(d && d.data) {
+ tmp.data = d.data;
+ if(d.data.jstree) {
+ for(i in d.data.jstree) {
+ if(d.data.jstree.hasOwnProperty(i)) {
+ tmp.state[i] = d.data.jstree[i];
+ }
+ }
+ }
+ }
+ if(d && typeof d.state === 'object') {
+ for (i in d.state) {
+ if(d.state.hasOwnProperty(i)) {
+ tmp.state[i] = d.state[i];
+ }
+ }
+ }
+ if(d && typeof d.li_attr === 'object') {
+ for (i in d.li_attr) {
+ if(d.li_attr.hasOwnProperty(i)) {
+ tmp.li_attr[i] = d.li_attr[i];
+ }
+ }
+ }
+ if(!tmp.li_attr.id) {
+ tmp.li_attr.id = tid;
+ }
+ if(d && typeof d.a_attr === 'object') {
+ for (i in d.a_attr) {
+ if(d.a_attr.hasOwnProperty(i)) {
+ tmp.a_attr[i] = d.a_attr[i];
+ }
+ }
+ }
+ if(d && d.children && d.children === true) {
+ tmp.state.loaded = false;
+ tmp.children = [];
+ tmp.children_d = [];
+ }
+ m[tmp.id] = tmp;
+ for(i = 0, j = tmp.children.length; i < j; i++) {
+ c = this._parse_model_from_flat_json(m[tmp.children[i]], tmp.id, ps);
+ e = m[c];
+ tmp.children_d.push(c);
+ if(e.children_d.length) {
+ tmp.children_d = tmp.children_d.concat(e.children_d);
+ }
+ }
+ delete d.data;
+ delete d.children;
+ m[tmp.id].original = d;
+ if(tmp.state.selected) {
+ this._data.core.selected.push(tmp.id);
+ }
+ return tmp.id;
+ },
+ /**
+ * parses a node from a JSON object and appends it to the in memory tree model. Used internally.
+ * @private
+ * @name _parse_model_from_json(d [, p, ps])
+ * @param {Object} d the JSON object to parse
+ * @param {String} p the parent ID
+ * @param {Array} ps list of all parents
+ * @return {String} the ID of the object added to the model
+ */
+ _parse_model_from_json : function (d, p, ps) {
+ if(!ps) { ps = []; }
+ else { ps = ps.concat(); }
+ if(p) { ps.unshift(p); }
+ var tid = false, i, j, c, e, m = this._model.data, df = this._model.default_state, tmp;
+ do {
+ tid = 'j' + this._id + '_' + (++this._cnt);
+ } while(m[tid]);
+
+ tmp = {
+ id : false,
+ text : typeof d === 'string' ? d : '',
+ icon : typeof d === 'object' && d.icon !== undefined ? d.icon : true,
+ parent : p,
+ parents : ps,
+ children : [],
+ children_d : [],
+ data : null,
+ state : { },
+ li_attr : { id : false },
+ a_attr : { href : '#' },
+ original : false
+ };
+ for(i in df) {
+ if(df.hasOwnProperty(i)) {
+ tmp.state[i] = df[i];
+ }
+ }
+ if(d && d.id) { tmp.id = d.id.toString(); }
+ if(d && d.text) { tmp.text = d.text; }
+ if(d && d.data && d.data.jstree && d.data.jstree.icon) {
+ tmp.icon = d.data.jstree.icon;
+ }
+ if(tmp.icon === undefined || tmp.icon === null || tmp.icon === "") {
+ tmp.icon = true;
+ }
+ if(d && d.data) {
+ tmp.data = d.data;
+ if(d.data.jstree) {
+ for(i in d.data.jstree) {
+ if(d.data.jstree.hasOwnProperty(i)) {
+ tmp.state[i] = d.data.jstree[i];
+ }
+ }
+ }
+ }
+ if(d && typeof d.state === 'object') {
+ for (i in d.state) {
+ if(d.state.hasOwnProperty(i)) {
+ tmp.state[i] = d.state[i];
+ }
+ }
+ }
+ if(d && typeof d.li_attr === 'object') {
+ for (i in d.li_attr) {
+ if(d.li_attr.hasOwnProperty(i)) {
+ tmp.li_attr[i] = d.li_attr[i];
+ }
+ }
+ }
+ if(tmp.li_attr.id && !tmp.id) {
+ tmp.id = tmp.li_attr.id.toString();
+ }
+ if(!tmp.id) {
+ tmp.id = tid;
+ }
+ if(!tmp.li_attr.id) {
+ tmp.li_attr.id = tmp.id;
+ }
+ if(d && typeof d.a_attr === 'object') {
+ for (i in d.a_attr) {
+ if(d.a_attr.hasOwnProperty(i)) {
+ tmp.a_attr[i] = d.a_attr[i];
+ }
+ }
+ }
+ if(d && d.children && d.children.length) {
+ for(i = 0, j = d.children.length; i < j; i++) {
+ c = this._parse_model_from_json(d.children[i], tmp.id, ps);
+ e = m[c];
+ tmp.children.push(c);
+ if(e.children_d.length) {
+ tmp.children_d = tmp.children_d.concat(e.children_d);
+ }
+ }
+ tmp.children_d = tmp.children_d.concat(tmp.children);
+ }
+ if(d && d.children && d.children === true) {
+ tmp.state.loaded = false;
+ tmp.children = [];
+ tmp.children_d = [];
+ }
+ delete d.data;
+ delete d.children;
+ tmp.original = d;
+ m[tmp.id] = tmp;
+ if(tmp.state.selected) {
+ this._data.core.selected.push(tmp.id);
+ }
+ return tmp.id;
+ },
+ /**
+ * redraws all nodes that need to be redrawn. Used internally.
+ * @private
+ * @name _redraw()
+ * @trigger redraw.jstree
+ */
+ _redraw : function () {
+ var nodes = this._model.force_full_redraw ? this._model.data[$.jstree.root].children.concat([]) : this._model.changed.concat([]),
+ f = document.createElement('UL'), tmp, i, j, fe = this._data.core.focused;
+ for(i = 0, j = nodes.length; i < j; i++) {
+ tmp = this.redraw_node(nodes[i], true, this._model.force_full_redraw);
+ if(tmp && this._model.force_full_redraw) {
+ f.appendChild(tmp);
+ }
+ }
+ if(this._model.force_full_redraw) {
+ f.className = this.get_container_ul()[0].className;
+ f.setAttribute('role','group');
+ this.element.empty().append(f);
+ //this.get_container_ul()[0].appendChild(f);
+ }
+ if(fe !== null) {
+ tmp = this.get_node(fe, true);
+ if(tmp && tmp.length && tmp.children('.jstree-anchor')[0] !== document.activeElement) {
+ tmp.children('.jstree-anchor').focus();
+ }
+ else {
+ this._data.core.focused = null;
+ }
+ }
+ this._model.force_full_redraw = false;
+ this._model.changed = [];
+ /**
+ * triggered after nodes are redrawn
+ * @event
+ * @name redraw.jstree
+ * @param {array} nodes the redrawn nodes
+ */
+ this.trigger('redraw', { "nodes" : nodes });
+ },
+ /**
+ * redraws all nodes that need to be redrawn or optionally - the whole tree
+ * @name redraw([full])
+ * @param {Boolean} full if set to `true` all nodes are redrawn.
+ */
+ redraw : function (full) {
+ if(full) {
+ this._model.force_full_redraw = true;
+ }
+ //if(this._model.redraw_timeout) {
+ // clearTimeout(this._model.redraw_timeout);
+ //}
+ //this._model.redraw_timeout = setTimeout($.proxy(this._redraw, this),0);
+ this._redraw();
+ },
+ /**
+ * redraws a single node's children. Used internally.
+ * @private
+ * @name draw_children(node)
+ * @param {mixed} node the node whose children will be redrawn
+ */
+ draw_children : function (node) {
+ var obj = this.get_node(node),
+ i = false,
+ j = false,
+ k = false,
+ d = document;
+ if(!obj) { return false; }
+ if(obj.id === $.jstree.root) { return this.redraw(true); }
+ node = this.get_node(node, true);
+ if(!node || !node.length) { return false; } // TODO: quick toggle
+
+ node.children('.jstree-children').remove();
+ node = node[0];
+ if(obj.children.length && obj.state.loaded) {
+ k = d.createElement('UL');
+ k.setAttribute('role', 'group');
+ k.className = 'jstree-children';
+ for(i = 0, j = obj.children.length; i < j; i++) {
+ k.appendChild(this.redraw_node(obj.children[i], true, true));
+ }
+ node.appendChild(k);
+ }
+ },
+ /**
+ * redraws a single node. Used internally.
+ * @private
+ * @name redraw_node(node, deep, is_callback, force_render)
+ * @param {mixed} node the node to redraw
+ * @param {Boolean} deep should child nodes be redrawn too
+ * @param {Boolean} is_callback is this a recursion call
+ * @param {Boolean} force_render should children of closed parents be drawn anyway
+ */
+ redraw_node : function (node, deep, is_callback, force_render) {
+ var obj = this.get_node(node),
+ par = false,
+ ind = false,
+ old = false,
+ i = false,
+ j = false,
+ k = false,
+ c = '',
+ d = document,
+ m = this._model.data,
+ f = false,
+ s = false,
+ tmp = null,
+ t = 0,
+ l = 0,
+ has_children = false,
+ last_sibling = false;
+ if(!obj) { return false; }
+ if(obj.id === $.jstree.root) { return this.redraw(true); }
+ deep = deep || obj.children.length === 0;
+ node = !document.querySelector ? document.getElementById(obj.id) : this.element[0].querySelector('#' + ("0123456789".indexOf(obj.id[0]) !== -1 ? '\\3' + obj.id[0] + ' ' + obj.id.substr(1).replace($.jstree.idregex,'\\$&') : obj.id.replace($.jstree.idregex,'\\$&')) ); //, this.element);
+ if(!node) {
+ deep = true;
+ //node = d.createElement('LI');
+ if(!is_callback) {
+ par = obj.parent !== $.jstree.root ? $('#' + obj.parent.replace($.jstree.idregex,'\\$&'), this.element)[0] : null;
+ if(par !== null && (!par || !m[obj.parent].state.opened)) {
+ return false;
+ }
+ ind = $.inArray(obj.id, par === null ? m[$.jstree.root].children : m[obj.parent].children);
+ }
+ }
+ else {
+ node = $(node);
+ if(!is_callback) {
+ par = node.parent().parent()[0];
+ if(par === this.element[0]) {
+ par = null;
+ }
+ ind = node.index();
+ }
+ // m[obj.id].data = node.data(); // use only node's data, no need to touch jquery storage
+ if(!deep && obj.children.length && !node.children('.jstree-children').length) {
+ deep = true;
+ }
+ if(!deep) {
+ old = node.children('.jstree-children')[0];
+ }
+ f = node.children('.jstree-anchor')[0] === document.activeElement;
+ node.remove();
+ //node = d.createElement('LI');
+ //node = node[0];
+ }
+ node = this._data.core.node.cloneNode(true);
+ // node is DOM, deep is boolean
+
+ c = 'jstree-node ';
+ for(i in obj.li_attr) {
+ if(obj.li_attr.hasOwnProperty(i)) {
+ if(i === 'id') { continue; }
+ if(i !== 'class') {
+ node.setAttribute(i, obj.li_attr[i]);
+ }
+ else {
+ c += obj.li_attr[i];
+ }
+ }
+ }
+ if(!obj.a_attr.id) {
+ obj.a_attr.id = obj.id + '_anchor';
+ }
+ node.setAttribute('aria-selected', !!obj.state.selected);
+ node.setAttribute('aria-level', obj.parents.length);
+ node.setAttribute('aria-labelledby', obj.a_attr.id);
+ if(obj.state.disabled) {
+ node.setAttribute('aria-disabled', true);
+ }
+
+ for(i = 0, j = obj.children.length; i < j; i++) {
+ if(!m[obj.children[i]].state.hidden) {
+ has_children = true;
+ break;
+ }
+ }
+ if(obj.parent !== null && m[obj.parent] && !obj.state.hidden) {
+ i = $.inArray(obj.id, m[obj.parent].children);
+ last_sibling = obj.id;
+ if(i !== -1) {
+ i++;
+ for(j = m[obj.parent].children.length; i < j; i++) {
+ if(!m[m[obj.parent].children[i]].state.hidden) {
+ last_sibling = m[obj.parent].children[i];
+ }
+ if(last_sibling !== obj.id) {
+ break;
+ }
+ }
+ }
+ }
+
+ if(obj.state.hidden) {
+ c += ' jstree-hidden';
+ }
+ if(obj.state.loaded && !has_children) {
+ c += ' jstree-leaf';
+ }
+ else {
+ c += obj.state.opened && obj.state.loaded ? ' jstree-open' : ' jstree-closed';
+ node.setAttribute('aria-expanded', (obj.state.opened && obj.state.loaded) );
+ }
+ if(last_sibling === obj.id) {
+ c += ' jstree-last';
+ }
+ node.id = obj.id;
+ node.className = c;
+ c = ( obj.state.selected ? ' jstree-clicked' : '') + ( obj.state.disabled ? ' jstree-disabled' : '');
+ for(j in obj.a_attr) {
+ if(obj.a_attr.hasOwnProperty(j)) {
+ if(j === 'href' && obj.a_attr[j] === '#') { continue; }
+ if(j !== 'class') {
+ node.childNodes[1].setAttribute(j, obj.a_attr[j]);
+ }
+ else {
+ c += ' ' + obj.a_attr[j];
+ }
+ }
+ }
+ if(c.length) {
+ node.childNodes[1].className = 'jstree-anchor ' + c;
+ }
+ if((obj.icon && obj.icon !== true) || obj.icon === false) {
+ if(obj.icon === false) {
+ node.childNodes[1].childNodes[0].className += ' jstree-themeicon-hidden';
+ }
+ else if(obj.icon.indexOf('/') === -1 && obj.icon.indexOf('.') === -1) {
+ node.childNodes[1].childNodes[0].className += ' ' + obj.icon + ' jstree-themeicon-custom';
+ }
+ else {
+ node.childNodes[1].childNodes[0].style.backgroundImage = 'url("'+obj.icon+'")';
+ node.childNodes[1].childNodes[0].style.backgroundPosition = 'center center';
+ node.childNodes[1].childNodes[0].style.backgroundSize = 'auto';
+ node.childNodes[1].childNodes[0].className += ' jstree-themeicon-custom';
+ }
+ }
+
+ if(this.settings.core.force_text) {
+ node.childNodes[1].appendChild(d.createTextNode(obj.text));
+ }
+ else {
+ node.childNodes[1].innerHTML += obj.text;
+ }
+
+
+ if(deep && obj.children.length && (obj.state.opened || force_render) && obj.state.loaded) {
+ k = d.createElement('UL');
+ k.setAttribute('role', 'group');
+ k.className = 'jstree-children';
+ for(i = 0, j = obj.children.length; i < j; i++) {
+ k.appendChild(this.redraw_node(obj.children[i], deep, true));
+ }
+ node.appendChild(k);
+ }
+ if(old) {
+ node.appendChild(old);
+ }
+ if(!is_callback) {
+ // append back using par / ind
+ if(!par) {
+ par = this.element[0];
+ }
+ for(i = 0, j = par.childNodes.length; i < j; i++) {
+ if(par.childNodes[i] && par.childNodes[i].className && par.childNodes[i].className.indexOf('jstree-children') !== -1) {
+ tmp = par.childNodes[i];
+ break;
+ }
+ }
+ if(!tmp) {
+ tmp = d.createElement('UL');
+ tmp.setAttribute('role', 'group');
+ tmp.className = 'jstree-children';
+ par.appendChild(tmp);
+ }
+ par = tmp;
+
+ if(ind < par.childNodes.length) {
+ par.insertBefore(node, par.childNodes[ind]);
+ }
+ else {
+ par.appendChild(node);
+ }
+ if(f) {
+ t = this.element[0].scrollTop;
+ l = this.element[0].scrollLeft;
+ node.childNodes[1].focus();
+ this.element[0].scrollTop = t;
+ this.element[0].scrollLeft = l;
+ }
+ }
+ if(obj.state.opened && !obj.state.loaded) {
+ obj.state.opened = false;
+ setTimeout($.proxy(function () {
+ this.open_node(obj.id, false, 0);
+ }, this), 0);
+ }
+ return node;
+ },
+ /**
+ * opens a node, revaling its children. If the node is not loaded it will be loaded and opened once ready.
+ * @name open_node(obj [, callback, animation])
+ * @param {mixed} obj the node to open
+ * @param {Function} callback a function to execute once the node is opened
+ * @param {Number} animation the animation duration in milliseconds when opening the node (overrides the `core.animation` setting). Use `false` for no animation.
+ * @trigger open_node.jstree, after_open.jstree, before_open.jstree
+ */
+ open_node : function (obj, callback, animation) {
+ var t1, t2, d, t;
+ if($.isArray(obj)) {
+ obj = obj.slice();
+ for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
+ this.open_node(obj[t1], callback, animation);
+ }
+ return true;
+ }
+ obj = this.get_node(obj);
+ if(!obj || obj.id === $.jstree.root) {
+ return false;
+ }
+ animation = animation === undefined ? this.settings.core.animation : animation;
+ if(!this.is_closed(obj)) {
+ if(callback) {
+ callback.call(this, obj, false);
+ }
+ return false;
+ }
+ if(!this.is_loaded(obj)) {
+ if(this.is_loading(obj)) {
+ return setTimeout($.proxy(function () {
+ this.open_node(obj, callback, animation);
+ }, this), 500);
+ }
+ this.load_node(obj, function (o, ok) {
+ return ok ? this.open_node(o, callback, animation) : (callback ? callback.call(this, o, false) : false);
+ });
+ }
+ else {
+ d = this.get_node(obj, true);
+ t = this;
+ if(d.length) {
+ if(animation && d.children(".jstree-children").length) {
+ d.children(".jstree-children").stop(true, true);
+ }
+ if(obj.children.length && !this._firstChild(d.children('.jstree-children')[0])) {
+ this.draw_children(obj);
+ //d = this.get_node(obj, true);
+ }
+ if(!animation) {
+ this.trigger('before_open', { "node" : obj });
+ d[0].className = d[0].className.replace('jstree-closed', 'jstree-open');
+ d[0].setAttribute("aria-expanded", true);
+ }
+ else {
+ this.trigger('before_open', { "node" : obj });
+ d
+ .children(".jstree-children").css("display","none").end()
+ .removeClass("jstree-closed").addClass("jstree-open").attr("aria-expanded", true)
+ .children(".jstree-children").stop(true, true)
+ .slideDown(animation, function () {
+ this.style.display = "";
+ if (t.element) {
+ t.trigger("after_open", { "node" : obj });
+ }
+ });
+ }
+ }
+ obj.state.opened = true;
+ if(callback) {
+ callback.call(this, obj, true);
+ }
+ if(!d.length) {
+ /**
+ * triggered when a node is about to be opened (if the node is supposed to be in the DOM, it will be, but it won't be visible yet)
+ * @event
+ * @name before_open.jstree
+ * @param {Object} node the opened node
+ */
+ this.trigger('before_open', { "node" : obj });
+ }
+ /**
+ * triggered when a node is opened (if there is an animation it will not be completed yet)
+ * @event
+ * @name open_node.jstree
+ * @param {Object} node the opened node
+ */
+ this.trigger('open_node', { "node" : obj });
+ if(!animation || !d.length) {
+ /**
+ * triggered when a node is opened and the animation is complete
+ * @event
+ * @name after_open.jstree
+ * @param {Object} node the opened node
+ */
+ this.trigger("after_open", { "node" : obj });
+ }
+ return true;
+ }
+ },
+ /**
+ * opens every parent of a node (node should be loaded)
+ * @name _open_to(obj)
+ * @param {mixed} obj the node to reveal
+ * @private
+ */
+ _open_to : function (obj) {
+ obj = this.get_node(obj);
+ if(!obj || obj.id === $.jstree.root) {
+ return false;
+ }
+ var i, j, p = obj.parents;
+ for(i = 0, j = p.length; i < j; i+=1) {
+ if(i !== $.jstree.root) {
+ this.open_node(p[i], false, 0);
+ }
+ }
+ return $('#' + obj.id.replace($.jstree.idregex,'\\$&'), this.element);
+ },
+ /**
+ * closes a node, hiding its children
+ * @name close_node(obj [, animation])
+ * @param {mixed} obj the node to close
+ * @param {Number} animation the animation duration in milliseconds when closing the node (overrides the `core.animation` setting). Use `false` for no animation.
+ * @trigger close_node.jstree, after_close.jstree
+ */
+ close_node : function (obj, animation) {
+ var t1, t2, t, d;
+ if($.isArray(obj)) {
+ obj = obj.slice();
+ for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
+ this.close_node(obj[t1], animation);
+ }
+ return true;
+ }
+ obj = this.get_node(obj);
+ if(!obj || obj.id === $.jstree.root) {
+ return false;
+ }
+ if(this.is_closed(obj)) {
+ return false;
+ }
+ animation = animation === undefined ? this.settings.core.animation : animation;
+ t = this;
+ d = this.get_node(obj, true);
+
+ obj.state.opened = false;
+ /**
+ * triggered when a node is closed (if there is an animation it will not be complete yet)
+ * @event
+ * @name close_node.jstree
+ * @param {Object} node the closed node
+ */
+ this.trigger('close_node',{ "node" : obj });
+ if(!d.length) {
+ /**
+ * triggered when a node is closed and the animation is complete
+ * @event
+ * @name after_close.jstree
+ * @param {Object} node the closed node
+ */
+ this.trigger("after_close", { "node" : obj });
+ }
+ else {
+ if(!animation) {
+ d[0].className = d[0].className.replace('jstree-open', 'jstree-closed');
+ d.attr("aria-expanded", false).children('.jstree-children').remove();
+ this.trigger("after_close", { "node" : obj });
+ }
+ else {
+ d
+ .children(".jstree-children").attr("style","display:block !important").end()
+ .removeClass("jstree-open").addClass("jstree-closed").attr("aria-expanded", false)
+ .children(".jstree-children").stop(true, true).slideUp(animation, function () {
+ this.style.display = "";
+ d.children('.jstree-children').remove();
+ if (t.element) {
+ t.trigger("after_close", { "node" : obj });
+ }
+ });
+ }
+ }
+ },
+ /**
+ * toggles a node - closing it if it is open, opening it if it is closed
+ * @name toggle_node(obj)
+ * @param {mixed} obj the node to toggle
+ */
+ toggle_node : function (obj) {
+ var t1, t2;
+ if($.isArray(obj)) {
+ obj = obj.slice();
+ for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
+ this.toggle_node(obj[t1]);
+ }
+ return true;
+ }
+ if(this.is_closed(obj)) {
+ return this.open_node(obj);
+ }
+ if(this.is_open(obj)) {
+ return this.close_node(obj);
+ }
+ },
+ /**
+ * opens all nodes within a node (or the tree), revaling their children. If the node is not loaded it will be loaded and opened once ready.
+ * @name open_all([obj, animation, original_obj])
+ * @param {mixed} obj the node to open recursively, omit to open all nodes in the tree
+ * @param {Number} animation the animation duration in milliseconds when opening the nodes, the default is no animation
+ * @param {jQuery} reference to the node that started the process (internal use)
+ * @trigger open_all.jstree
+ */
+ open_all : function (obj, animation, original_obj) {
+ if(!obj) { obj = $.jstree.root; }
+ obj = this.get_node(obj);
+ if(!obj) { return false; }
+ var dom = obj.id === $.jstree.root ? this.get_container_ul() : this.get_node(obj, true), i, j, _this;
+ if(!dom.length) {
+ for(i = 0, j = obj.children_d.length; i < j; i++) {
+ if(this.is_closed(this._model.data[obj.children_d[i]])) {
+ this._model.data[obj.children_d[i]].state.opened = true;
+ }
+ }
+ return this.trigger('open_all', { "node" : obj });
+ }
+ original_obj = original_obj || dom;
+ _this = this;
+ dom = this.is_closed(obj) ? dom.find('.jstree-closed').addBack() : dom.find('.jstree-closed');
+ dom.each(function () {
+ _this.open_node(
+ this,
+ function(node, status) { if(status && this.is_parent(node)) { this.open_all(node, animation, original_obj); } },
+ animation || 0
+ );
+ });
+ if(original_obj.find('.jstree-closed').length === 0) {
+ /**
+ * triggered when an `open_all` call completes
+ * @event
+ * @name open_all.jstree
+ * @param {Object} node the opened node
+ */
+ this.trigger('open_all', { "node" : this.get_node(original_obj) });
+ }
+ },
+ /**
+ * closes all nodes within a node (or the tree), revaling their children
+ * @name close_all([obj, animation])
+ * @param {mixed} obj the node to close recursively, omit to close all nodes in the tree
+ * @param {Number} animation the animation duration in milliseconds when closing the nodes, the default is no animation
+ * @trigger close_all.jstree
+ */
+ close_all : function (obj, animation) {
+ if(!obj) { obj = $.jstree.root; }
+ obj = this.get_node(obj);
+ if(!obj) { return false; }
+ var dom = obj.id === $.jstree.root ? this.get_container_ul() : this.get_node(obj, true),
+ _this = this, i, j;
+ if(dom.length) {
+ dom = this.is_open(obj) ? dom.find('.jstree-open').addBack() : dom.find('.jstree-open');
+ $(dom.get().reverse()).each(function () { _this.close_node(this, animation || 0); });
+ }
+ for(i = 0, j = obj.children_d.length; i < j; i++) {
+ this._model.data[obj.children_d[i]].state.opened = false;
+ }
+ /**
+ * triggered when an `close_all` call completes
+ * @event
+ * @name close_all.jstree
+ * @param {Object} node the closed node
+ */
+ this.trigger('close_all', { "node" : obj });
+ },
+ /**
+ * checks if a node is disabled (not selectable)
+ * @name is_disabled(obj)
+ * @param {mixed} obj
+ * @return {Boolean}
+ */
+ is_disabled : function (obj) {
+ obj = this.get_node(obj);
+ return obj && obj.state && obj.state.disabled;
+ },
+ /**
+ * enables a node - so that it can be selected
+ * @name enable_node(obj)
+ * @param {mixed} obj the node to enable
+ * @trigger enable_node.jstree
+ */
+ enable_node : function (obj) {
+ var t1, t2;
+ if($.isArray(obj)) {
+ obj = obj.slice();
+ for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
+ this.enable_node(obj[t1]);
+ }
+ return true;
+ }
+ obj = this.get_node(obj);
+ if(!obj || obj.id === $.jstree.root) {
+ return false;
+ }
+ obj.state.disabled = false;
+ this.get_node(obj,true).children('.jstree-anchor').removeClass('jstree-disabled').attr('aria-disabled', false);
+ /**
+ * triggered when an node is enabled
+ * @event
+ * @name enable_node.jstree
+ * @param {Object} node the enabled node
+ */
+ this.trigger('enable_node', { 'node' : obj });
+ },
+ /**
+ * disables a node - so that it can not be selected
+ * @name disable_node(obj)
+ * @param {mixed} obj the node to disable
+ * @trigger disable_node.jstree
+ */
+ disable_node : function (obj) {
+ var t1, t2;
+ if($.isArray(obj)) {
+ obj = obj.slice();
+ for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
+ this.disable_node(obj[t1]);
+ }
+ return true;
+ }
+ obj = this.get_node(obj);
+ if(!obj || obj.id === $.jstree.root) {
+ return false;
+ }
+ obj.state.disabled = true;
+ this.get_node(obj,true).children('.jstree-anchor').addClass('jstree-disabled').attr('aria-disabled', true);
+ /**
+ * triggered when an node is disabled
+ * @event
+ * @name disable_node.jstree
+ * @param {Object} node the disabled node
+ */
+ this.trigger('disable_node', { 'node' : obj });
+ },
+ /**
+ * determines if a node is hidden
+ * @name is_hidden(obj)
+ * @param {mixed} obj the node
+ */
+ is_hidden : function (obj) {
+ obj = this.get_node(obj);
+ return obj.state.hidden === true;
+ },
+ /**
+ * hides a node - it is still in the structure but will not be visible
+ * @name hide_node(obj)
+ * @param {mixed} obj the node to hide
+ * @param {Boolean} skip_redraw internal parameter controlling if redraw is called
+ * @trigger hide_node.jstree
+ */
+ hide_node : function (obj, skip_redraw) {
+ var t1, t2;
+ if($.isArray(obj)) {
+ obj = obj.slice();
+ for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
+ this.hide_node(obj[t1], true);
+ }
+ if (!skip_redraw) {
+ this.redraw();
+ }
+ return true;
+ }
+ obj = this.get_node(obj);
+ if(!obj || obj.id === $.jstree.root) {
+ return false;
+ }
+ if(!obj.state.hidden) {
+ obj.state.hidden = true;
+ this._node_changed(obj.parent);
+ if(!skip_redraw) {
+ this.redraw();
+ }
+ /**
+ * triggered when an node is hidden
+ * @event
+ * @name hide_node.jstree
+ * @param {Object} node the hidden node
+ */
+ this.trigger('hide_node', { 'node' : obj });
+ }
+ },
+ /**
+ * shows a node
+ * @name show_node(obj)
+ * @param {mixed} obj the node to show
+ * @param {Boolean} skip_redraw internal parameter controlling if redraw is called
+ * @trigger show_node.jstree
+ */
+ show_node : function (obj, skip_redraw) {
+ var t1, t2;
+ if($.isArray(obj)) {
+ obj = obj.slice();
+ for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
+ this.show_node(obj[t1], true);
+ }
+ if (!skip_redraw) {
+ this.redraw();
+ }
+ return true;
+ }
+ obj = this.get_node(obj);
+ if(!obj || obj.id === $.jstree.root) {
+ return false;
+ }
+ if(obj.state.hidden) {
+ obj.state.hidden = false;
+ this._node_changed(obj.parent);
+ if(!skip_redraw) {
+ this.redraw();
+ }
+ /**
+ * triggered when an node is shown
+ * @event
+ * @name show_node.jstree
+ * @param {Object} node the shown node
+ */
+ this.trigger('show_node', { 'node' : obj });
+ }
+ },
+ /**
+ * hides all nodes
+ * @name hide_all()
+ * @trigger hide_all.jstree
+ */
+ hide_all : function (skip_redraw) {
+ var i, m = this._model.data, ids = [];
+ for(i in m) {
+ if(m.hasOwnProperty(i) && i !== $.jstree.root && !m[i].state.hidden) {
+ m[i].state.hidden = true;
+ ids.push(i);
+ }
+ }
+ this._model.force_full_redraw = true;
+ if(!skip_redraw) {
+ this.redraw();
+ }
+ /**
+ * triggered when all nodes are hidden
+ * @event
+ * @name hide_all.jstree
+ * @param {Array} nodes the IDs of all hidden nodes
+ */
+ this.trigger('hide_all', { 'nodes' : ids });
+ return ids;
+ },
+ /**
+ * shows all nodes
+ * @name show_all()
+ * @trigger show_all.jstree
+ */
+ show_all : function (skip_redraw) {
+ var i, m = this._model.data, ids = [];
+ for(i in m) {
+ if(m.hasOwnProperty(i) && i !== $.jstree.root && m[i].state.hidden) {
+ m[i].state.hidden = false;
+ ids.push(i);
+ }
+ }
+ this._model.force_full_redraw = true;
+ if(!skip_redraw) {
+ this.redraw();
+ }
+ /**
+ * triggered when all nodes are shown
+ * @event
+ * @name show_all.jstree
+ * @param {Array} nodes the IDs of all shown nodes
+ */
+ this.trigger('show_all', { 'nodes' : ids });
+ return ids;
+ },
+ /**
+ * called when a node is selected by the user. Used internally.
+ * @private
+ * @name activate_node(obj, e)
+ * @param {mixed} obj the node
+ * @param {Object} e the related event
+ * @trigger activate_node.jstree, changed.jstree
+ */
+ activate_node : function (obj, e) {
+ if(this.is_disabled(obj)) {
+ return false;
+ }
+ if(!e || typeof e !== 'object') {
+ e = {};
+ }
+
+ // ensure last_clicked is still in the DOM, make it fresh (maybe it was moved?) and make sure it is still selected, if not - make last_clicked the last selected node
+ this._data.core.last_clicked = this._data.core.last_clicked && this._data.core.last_clicked.id !== undefined ? this.get_node(this._data.core.last_clicked.id) : null;
+ if(this._data.core.last_clicked && !this._data.core.last_clicked.state.selected) { this._data.core.last_clicked = null; }
+ if(!this._data.core.last_clicked && this._data.core.selected.length) { this._data.core.last_clicked = this.get_node(this._data.core.selected[this._data.core.selected.length - 1]); }
+
+ if(!this.settings.core.multiple || (!e.metaKey && !e.ctrlKey && !e.shiftKey) || (e.shiftKey && (!this._data.core.last_clicked || !this.get_parent(obj) || this.get_parent(obj) !== this._data.core.last_clicked.parent ) )) {
+ if(!this.settings.core.multiple && (e.metaKey || e.ctrlKey || e.shiftKey) && this.is_selected(obj)) {
+ this.deselect_node(obj, false, e);
+ }
+ else {
+ this.deselect_all(true);
+ this.select_node(obj, false, false, e);
+ this._data.core.last_clicked = this.get_node(obj);
+ }
+ }
+ else {
+ if(e.shiftKey) {
+ var o = this.get_node(obj).id,
+ l = this._data.core.last_clicked.id,
+ p = this.get_node(this._data.core.last_clicked.parent).children,
+ c = false,
+ i, j;
+ for(i = 0, j = p.length; i < j; i += 1) {
+ // separate IFs work whem o and l are the same
+ if(p[i] === o) {
+ c = !c;
+ }
+ if(p[i] === l) {
+ c = !c;
+ }
+ if(!this.is_disabled(p[i]) && (c || p[i] === o || p[i] === l)) {
+ if (!this.is_hidden(p[i])) {
+ this.select_node(p[i], true, false, e);
+ }
+ }
+ else {
+ this.deselect_node(p[i], true, e);
+ }
+ }
+ this.trigger('changed', { 'action' : 'select_node', 'node' : this.get_node(obj), 'selected' : this._data.core.selected, 'event' : e });
+ }
+ else {
+ if(!this.is_selected(obj)) {
+ this.select_node(obj, false, false, e);
+ }
+ else {
+ this.deselect_node(obj, false, e);
+ }
+ }
+ }
+ /**
+ * triggered when an node is clicked or intercated with by the user
+ * @event
+ * @name activate_node.jstree
+ * @param {Object} node
+ * @param {Object} event the ooriginal event (if any) which triggered the call (may be an empty object)
+ */
+ this.trigger('activate_node', { 'node' : this.get_node(obj), 'event' : e });
+ },
+ /**
+ * applies the hover state on a node, called when a node is hovered by the user. Used internally.
+ * @private
+ * @name hover_node(obj)
+ * @param {mixed} obj
+ * @trigger hover_node.jstree
+ */
+ hover_node : function (obj) {
+ obj = this.get_node(obj, true);
+ if(!obj || !obj.length || obj.children('.jstree-hovered').length) {
+ return false;
+ }
+ var o = this.element.find('.jstree-hovered'), t = this.element;
+ if(o && o.length) { this.dehover_node(o); }
+
+ obj.children('.jstree-anchor').addClass('jstree-hovered');
+ /**
+ * triggered when an node is hovered
+ * @event
+ * @name hover_node.jstree
+ * @param {Object} node
+ */
+ this.trigger('hover_node', { 'node' : this.get_node(obj) });
+ setTimeout(function () { t.attr('aria-activedescendant', obj[0].id); }, 0);
+ },
+ /**
+ * removes the hover state from a nodecalled when a node is no longer hovered by the user. Used internally.
+ * @private
+ * @name dehover_node(obj)
+ * @param {mixed} obj
+ * @trigger dehover_node.jstree
+ */
+ dehover_node : function (obj) {
+ obj = this.get_node(obj, true);
+ if(!obj || !obj.length || !obj.children('.jstree-hovered').length) {
+ return false;
+ }
+ obj.children('.jstree-anchor').removeClass('jstree-hovered');
+ /**
+ * triggered when an node is no longer hovered
+ * @event
+ * @name dehover_node.jstree
+ * @param {Object} node
+ */
+ this.trigger('dehover_node', { 'node' : this.get_node(obj) });
+ },
+ /**
+ * select a node
+ * @name select_node(obj [, supress_event, prevent_open])
+ * @param {mixed} obj an array can be used to select multiple nodes
+ * @param {Boolean} supress_event if set to `true` the `changed.jstree` event won't be triggered
+ * @param {Boolean} prevent_open if set to `true` parents of the selected node won't be opened
+ * @trigger select_node.jstree, changed.jstree
+ */
+ select_node : function (obj, supress_event, prevent_open, e) {
+ var dom, t1, t2, th;
+ if($.isArray(obj)) {
+ obj = obj.slice();
+ for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
+ this.select_node(obj[t1], supress_event, prevent_open, e);
+ }
+ return true;
+ }
+ obj = this.get_node(obj);
+ if(!obj || obj.id === $.jstree.root) {
+ return false;
+ }
+ dom = this.get_node(obj, true);
+ if(!obj.state.selected) {
+ obj.state.selected = true;
+ this._data.core.selected.push(obj.id);
+ if(!prevent_open) {
+ dom = this._open_to(obj);
+ }
+ if(dom && dom.length) {
+ dom.attr('aria-selected', true).children('.jstree-anchor').addClass('jstree-clicked');
+ }
+ /**
+ * triggered when an node is selected
+ * @event
+ * @name select_node.jstree
+ * @param {Object} node
+ * @param {Array} selected the current selection
+ * @param {Object} event the event (if any) that triggered this select_node
+ */
+ this.trigger('select_node', { 'node' : obj, 'selected' : this._data.core.selected, 'event' : e });
+ if(!supress_event) {
+ /**
+ * triggered when selection changes
+ * @event
+ * @name changed.jstree
+ * @param {Object} node
+ * @param {Object} action the action that caused the selection to change
+ * @param {Array} selected the current selection
+ * @param {Object} event the event (if any) that triggered this changed event
+ */
+ this.trigger('changed', { 'action' : 'select_node', 'node' : obj, 'selected' : this._data.core.selected, 'event' : e });
+ }
+ }
+ },
+ /**
+ * deselect a node
+ * @name deselect_node(obj [, supress_event])
+ * @param {mixed} obj an array can be used to deselect multiple nodes
+ * @param {Boolean} supress_event if set to `true` the `changed.jstree` event won't be triggered
+ * @trigger deselect_node.jstree, changed.jstree
+ */
+ deselect_node : function (obj, supress_event, e) {
+ var t1, t2, dom;
+ if($.isArray(obj)) {
+ obj = obj.slice();
+ for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
+ this.deselect_node(obj[t1], supress_event, e);
+ }
+ return true;
+ }
+ obj = this.get_node(obj);
+ if(!obj || obj.id === $.jstree.root) {
+ return false;
+ }
+ dom = this.get_node(obj, true);
+ if(obj.state.selected) {
+ obj.state.selected = false;
+ this._data.core.selected = $.vakata.array_remove_item(this._data.core.selected, obj.id);
+ if(dom.length) {
+ dom.attr('aria-selected', false).children('.jstree-anchor').removeClass('jstree-clicked');
+ }
+ /**
+ * triggered when an node is deselected
+ * @event
+ * @name deselect_node.jstree
+ * @param {Object} node
+ * @param {Array} selected the current selection
+ * @param {Object} event the event (if any) that triggered this deselect_node
+ */
+ this.trigger('deselect_node', { 'node' : obj, 'selected' : this._data.core.selected, 'event' : e });
+ if(!supress_event) {
+ this.trigger('changed', { 'action' : 'deselect_node', 'node' : obj, 'selected' : this._data.core.selected, 'event' : e });
+ }
+ }
+ },
+ /**
+ * select all nodes in the tree
+ * @name select_all([supress_event])
+ * @param {Boolean} supress_event if set to `true` the `changed.jstree` event won't be triggered
+ * @trigger select_all.jstree, changed.jstree
+ */
+ select_all : function (supress_event) {
+ var tmp = this._data.core.selected.concat([]), i, j;
+ this._data.core.selected = this._model.data[$.jstree.root].children_d.concat();
+ for(i = 0, j = this._data.core.selected.length; i < j; i++) {
+ if(this._model.data[this._data.core.selected[i]]) {
+ this._model.data[this._data.core.selected[i]].state.selected = true;
+ }
+ }
+ this.redraw(true);
+ /**
+ * triggered when all nodes are selected
+ * @event
+ * @name select_all.jstree
+ * @param {Array} selected the current selection
+ */
+ this.trigger('select_all', { 'selected' : this._data.core.selected });
+ if(!supress_event) {
+ this.trigger('changed', { 'action' : 'select_all', 'selected' : this._data.core.selected, 'old_selection' : tmp });
+ }
+ },
+ /**
+ * deselect all selected nodes
+ * @name deselect_all([supress_event])
+ * @param {Boolean} supress_event if set to `true` the `changed.jstree` event won't be triggered
+ * @trigger deselect_all.jstree, changed.jstree
+ */
+ deselect_all : function (supress_event) {
+ var tmp = this._data.core.selected.concat([]), i, j;
+ for(i = 0, j = this._data.core.selected.length; i < j; i++) {
+ if(this._model.data[this._data.core.selected[i]]) {
+ this._model.data[this._data.core.selected[i]].state.selected = false;
+ }
+ }
+ this._data.core.selected = [];
+ this.element.find('.jstree-clicked').removeClass('jstree-clicked').parent().attr('aria-selected', false);
+ /**
+ * triggered when all nodes are deselected
+ * @event
+ * @name deselect_all.jstree
+ * @param {Object} node the previous selection
+ * @param {Array} selected the current selection
+ */
+ this.trigger('deselect_all', { 'selected' : this._data.core.selected, 'node' : tmp });
+ if(!supress_event) {
+ this.trigger('changed', { 'action' : 'deselect_all', 'selected' : this._data.core.selected, 'old_selection' : tmp });
+ }
+ },
+ /**
+ * checks if a node is selected
+ * @name is_selected(obj)
+ * @param {mixed} obj
+ * @return {Boolean}
+ */
+ is_selected : function (obj) {
+ obj = this.get_node(obj);
+ if(!obj || obj.id === $.jstree.root) {
+ return false;
+ }
+ return obj.state.selected;
+ },
+ /**
+ * get an array of all selected nodes
+ * @name get_selected([full])
+ * @param {mixed} full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned
+ * @return {Array}
+ */
+ get_selected : function (full) {
+ return full ? $.map(this._data.core.selected, $.proxy(function (i) { return this.get_node(i); }, this)) : this._data.core.selected.slice();
+ },
+ /**
+ * get an array of all top level selected nodes (ignoring children of selected nodes)
+ * @name get_top_selected([full])
+ * @param {mixed} full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned
+ * @return {Array}
+ */
+ get_top_selected : function (full) {
+ var tmp = this.get_selected(true),
+ obj = {}, i, j, k, l;
+ for(i = 0, j = tmp.length; i < j; i++) {
+ obj[tmp[i].id] = tmp[i];
+ }
+ for(i = 0, j = tmp.length; i < j; i++) {
+ for(k = 0, l = tmp[i].children_d.length; k < l; k++) {
+ if(obj[tmp[i].children_d[k]]) {
+ delete obj[tmp[i].children_d[k]];
+ }
+ }
+ }
+ tmp = [];
+ for(i in obj) {
+ if(obj.hasOwnProperty(i)) {
+ tmp.push(i);
+ }
+ }
+ return full ? $.map(tmp, $.proxy(function (i) { return this.get_node(i); }, this)) : tmp;
+ },
+ /**
+ * get an array of all bottom level selected nodes (ignoring selected parents)
+ * @name get_bottom_selected([full])
+ * @param {mixed} full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned
+ * @return {Array}
+ */
+ get_bottom_selected : function (full) {
+ var tmp = this.get_selected(true),
+ obj = [], i, j;
+ for(i = 0, j = tmp.length; i < j; i++) {
+ if(!tmp[i].children.length) {
+ obj.push(tmp[i].id);
+ }
+ }
+ return full ? $.map(obj, $.proxy(function (i) { return this.get_node(i); }, this)) : obj;
+ },
+ /**
+ * gets the current state of the tree so that it can be restored later with `set_state(state)`. Used internally.
+ * @name get_state()
+ * @private
+ * @return {Object}
+ */
+ get_state : function () {
+ var state = {
+ 'core' : {
+ 'open' : [],
+ 'scroll' : {
+ 'left' : this.element.scrollLeft(),
+ 'top' : this.element.scrollTop()
+ },
+ /*!
+ 'themes' : {
+ 'name' : this.get_theme(),
+ 'icons' : this._data.core.themes.icons,
+ 'dots' : this._data.core.themes.dots
+ },
+ */
+ 'selected' : []
+ }
+ }, i;
+ for(i in this._model.data) {
+ if(this._model.data.hasOwnProperty(i)) {
+ if(i !== $.jstree.root) {
+ if(this._model.data[i].state.opened) {
+ state.core.open.push(i);
+ }
+ if(this._model.data[i].state.selected) {
+ state.core.selected.push(i);
+ }
+ }
+ }
+ }
+ return state;
+ },
+ /**
+ * sets the state of the tree. Used internally.
+ * @name set_state(state [, callback])
+ * @private
+ * @param {Object} state the state to restore. Keep in mind this object is passed by reference and jstree will modify it.
+ * @param {Function} callback an optional function to execute once the state is restored.
+ * @trigger set_state.jstree
+ */
+ set_state : function (state, callback) {
+ if(state) {
+ if(state.core) {
+ var res, n, t, _this, i;
+ if(state.core.open) {
+ if(!$.isArray(state.core.open) || !state.core.open.length) {
+ delete state.core.open;
+ this.set_state(state, callback);
+ }
+ else {
+ this._load_nodes(state.core.open, function (nodes) {
+ this.open_node(nodes, false, 0);
+ delete state.core.open;
+ this.set_state(state, callback);
+ });
+ }
+ return false;
+ }
+ if(state.core.scroll) {
+ if(state.core.scroll && state.core.scroll.left !== undefined) {
+ this.element.scrollLeft(state.core.scroll.left);
+ }
+ if(state.core.scroll && state.core.scroll.top !== undefined) {
+ this.element.scrollTop(state.core.scroll.top);
+ }
+ delete state.core.scroll;
+ this.set_state(state, callback);
+ return false;
+ }
+ if(state.core.selected) {
+ _this = this;
+ this.deselect_all();
+ $.each(state.core.selected, function (i, v) {
+ _this.select_node(v, false, true);
+ });
+ delete state.core.selected;
+ this.set_state(state, callback);
+ return false;
+ }
+ for(i in state) {
+ if(state.hasOwnProperty(i) && i !== "core" && $.inArray(i, this.settings.plugins) === -1) {
+ delete state[i];
+ }
+ }
+ if($.isEmptyObject(state.core)) {
+ delete state.core;
+ this.set_state(state, callback);
+ return false;
+ }
+ }
+ if($.isEmptyObject(state)) {
+ state = null;
+ if(callback) { callback.call(this); }
+ /**
+ * triggered when a `set_state` call completes
+ * @event
+ * @name set_state.jstree
+ */
+ this.trigger('set_state');
+ return false;
+ }
+ return true;
+ }
+ return false;
+ },
+ /**
+ * refreshes the tree - all nodes are reloaded with calls to `load_node`.
+ * @name refresh()
+ * @param {Boolean} skip_loading an option to skip showing the loading indicator
+ * @param {Mixed} forget_state if set to `true` state will not be reapplied, if set to a function (receiving the current state as argument) the result of that function will be used as state
+ * @trigger refresh.jstree
+ */
+ refresh : function (skip_loading, forget_state) {
+ this._data.core.state = forget_state === true ? {} : this.get_state();
+ if(forget_state && $.isFunction(forget_state)) { this._data.core.state = forget_state.call(this, this._data.core.state); }
+ this._cnt = 0;
+ this._model.data = {};
+ this._model.data[$.jstree.root] = {
+ id : $.jstree.root,
+ parent : null,
+ parents : [],
+ children : [],
+ children_d : [],
+ state : { loaded : false }
+ };
+ this._data.core.selected = [];
+ this._data.core.last_clicked = null;
+ this._data.core.focused = null;
+
+ var c = this.get_container_ul()[0].className;
+ if(!skip_loading) {
+ this.element.html("<"+"ul class='"+c+"' role='group'><"+"li class='jstree-initial-node jstree-loading jstree-leaf jstree-last' role='treeitem' id='j"+this._id+"_loading'>
<"+"a class='jstree-anchor' href='#'>
" + this.get_string("Loading ...") + "");
+ this.element.attr('aria-activedescendant','j'+this._id+'_loading');
+ }
+ this.load_node($.jstree.root, function (o, s) {
+ if(s) {
+ this.get_container_ul()[0].className = c;
+ if(this._firstChild(this.get_container_ul()[0])) {
+ this.element.attr('aria-activedescendant',this._firstChild(this.get_container_ul()[0]).id);
+ }
+ this.set_state($.extend(true, {}, this._data.core.state), function () {
+ /**
+ * triggered when a `refresh` call completes
+ * @event
+ * @name refresh.jstree
+ */
+ this.trigger('refresh');
+ });
+ }
+ this._data.core.state = null;
+ });
+ },
+ /**
+ * refreshes a node in the tree (reload its children) all opened nodes inside that node are reloaded with calls to `load_node`.
+ * @name refresh_node(obj)
+ * @param {mixed} obj the node
+ * @trigger refresh_node.jstree
+ */
+ refresh_node : function (obj) {
+ obj = this.get_node(obj);
+ if(!obj || obj.id === $.jstree.root) { return false; }
+ var opened = [], to_load = [], s = this._data.core.selected.concat([]);
+ to_load.push(obj.id);
+ if(obj.state.opened === true) { opened.push(obj.id); }
+ this.get_node(obj, true).find('.jstree-open').each(function() { to_load.push(this.id); opened.push(this.id); });
+ this._load_nodes(to_load, $.proxy(function (nodes) {
+ this.open_node(opened, false, 0);
+ this.select_node(s);
+ /**
+ * triggered when a node is refreshed
+ * @event
+ * @name refresh_node.jstree
+ * @param {Object} node - the refreshed node
+ * @param {Array} nodes - an array of the IDs of the nodes that were reloaded
+ */
+ this.trigger('refresh_node', { 'node' : obj, 'nodes' : nodes });
+ }, this), false, true);
+ },
+ /**
+ * set (change) the ID of a node
+ * @name set_id(obj, id)
+ * @param {mixed} obj the node
+ * @param {String} id the new ID
+ * @return {Boolean}
+ * @trigger set_id.jstree
+ */
+ set_id : function (obj, id) {
+ obj = this.get_node(obj);
+ if(!obj || obj.id === $.jstree.root) { return false; }
+ var i, j, m = this._model.data, old = obj.id;
+ id = id.toString();
+ // update parents (replace current ID with new one in children and children_d)
+ m[obj.parent].children[$.inArray(obj.id, m[obj.parent].children)] = id;
+ for(i = 0, j = obj.parents.length; i < j; i++) {
+ m[obj.parents[i]].children_d[$.inArray(obj.id, m[obj.parents[i]].children_d)] = id;
+ }
+ // update children (replace current ID with new one in parent and parents)
+ for(i = 0, j = obj.children.length; i < j; i++) {
+ m[obj.children[i]].parent = id;
+ }
+ for(i = 0, j = obj.children_d.length; i < j; i++) {
+ m[obj.children_d[i]].parents[$.inArray(obj.id, m[obj.children_d[i]].parents)] = id;
+ }
+ i = $.inArray(obj.id, this._data.core.selected);
+ if(i !== -1) { this._data.core.selected[i] = id; }
+ // update model and obj itself (obj.id, this._model.data[KEY])
+ i = this.get_node(obj.id, true);
+ if(i) {
+ i.attr('id', id); //.children('.jstree-anchor').attr('id', id + '_anchor').end().attr('aria-labelledby', id + '_anchor');
+ if(this.element.attr('aria-activedescendant') === obj.id) {
+ this.element.attr('aria-activedescendant', id);
+ }
+ }
+ delete m[obj.id];
+ obj.id = id;
+ obj.li_attr.id = id;
+ m[id] = obj;
+ /**
+ * triggered when a node id value is changed
+ * @event
+ * @name set_id.jstree
+ * @param {Object} node
+ * @param {String} old the old id
+ */
+ this.trigger('set_id',{ "node" : obj, "new" : obj.id, "old" : old });
+ return true;
+ },
+ /**
+ * get the text value of a node
+ * @name get_text(obj)
+ * @param {mixed} obj the node
+ * @return {String}
+ */
+ get_text : function (obj) {
+ obj = this.get_node(obj);
+ return (!obj || obj.id === $.jstree.root) ? false : obj.text;
+ },
+ /**
+ * set the text value of a node. Used internally, please use `rename_node(obj, val)`.
+ * @private
+ * @name set_text(obj, val)
+ * @param {mixed} obj the node, you can pass an array to set the text on multiple nodes
+ * @param {String} val the new text value
+ * @return {Boolean}
+ * @trigger set_text.jstree
+ */
+ set_text : function (obj, val) {
+ var t1, t2;
+ if($.isArray(obj)) {
+ obj = obj.slice();
+ for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
+ this.set_text(obj[t1], val);
+ }
+ return true;
+ }
+ obj = this.get_node(obj);
+ if(!obj || obj.id === $.jstree.root) { return false; }
+ obj.text = val;
+ if(this.get_node(obj, true).length) {
+ this.redraw_node(obj.id);
+ }
+ /**
+ * triggered when a node text value is changed
+ * @event
+ * @name set_text.jstree
+ * @param {Object} obj
+ * @param {String} text the new value
+ */
+ this.trigger('set_text',{ "obj" : obj, "text" : val });
+ return true;
+ },
+ /**
+ * gets a JSON representation of a node (or the whole tree)
+ * @name get_json([obj, options])
+ * @param {mixed} obj
+ * @param {Object} options
+ * @param {Boolean} options.no_state do not return state information
+ * @param {Boolean} options.no_id do not return ID
+ * @param {Boolean} options.no_children do not include children
+ * @param {Boolean} options.no_data do not include node data
+ * @param {Boolean} options.no_li_attr do not include LI attributes
+ * @param {Boolean} options.no_a_attr do not include A attributes
+ * @param {Boolean} options.flat return flat JSON instead of nested
+ * @return {Object}
+ */
+ get_json : function (obj, options, flat) {
+ obj = this.get_node(obj || $.jstree.root);
+ if(!obj) { return false; }
+ if(options && options.flat && !flat) { flat = []; }
+ var tmp = {
+ 'id' : obj.id,
+ 'text' : obj.text,
+ 'icon' : this.get_icon(obj),
+ 'li_attr' : $.extend(true, {}, obj.li_attr),
+ 'a_attr' : $.extend(true, {}, obj.a_attr),
+ 'state' : {},
+ 'data' : options && options.no_data ? false : $.extend(true, {}, obj.data)
+ //( this.get_node(obj, true).length ? this.get_node(obj, true).data() : obj.data ),
+ }, i, j;
+ if(options && options.flat) {
+ tmp.parent = obj.parent;
+ }
+ else {
+ tmp.children = [];
+ }
+ if(!options || !options.no_state) {
+ for(i in obj.state) {
+ if(obj.state.hasOwnProperty(i)) {
+ tmp.state[i] = obj.state[i];
+ }
+ }
+ } else {
+ delete tmp.state;
+ }
+ if(options && options.no_li_attr) {
+ delete tmp.li_attr;
+ }
+ if(options && options.no_a_attr) {
+ delete tmp.a_attr;
+ }
+ if(options && options.no_id) {
+ delete tmp.id;
+ if(tmp.li_attr && tmp.li_attr.id) {
+ delete tmp.li_attr.id;
+ }
+ if(tmp.a_attr && tmp.a_attr.id) {
+ delete tmp.a_attr.id;
+ }
+ }
+ if(options && options.flat && obj.id !== $.jstree.root) {
+ flat.push(tmp);
+ }
+ if(!options || !options.no_children) {
+ for(i = 0, j = obj.children.length; i < j; i++) {
+ if(options && options.flat) {
+ this.get_json(obj.children[i], options, flat);
+ }
+ else {
+ tmp.children.push(this.get_json(obj.children[i], options));
+ }
+ }
+ }
+ return options && options.flat ? flat : (obj.id === $.jstree.root ? tmp.children : tmp);
+ },
+ /**
+ * create a new node (do not confuse with load_node)
+ * @name create_node([par, node, pos, callback, is_loaded])
+ * @param {mixed} par the parent node (to create a root node use either "#" (string) or `null`)
+ * @param {mixed} node the data for the new node (a valid JSON object, or a simple string with the name)
+ * @param {mixed} pos the index at which to insert the node, "first" and "last" are also supported, default is "last"
+ * @param {Function} callback a function to be called once the node is created
+ * @param {Boolean} is_loaded internal argument indicating if the parent node was succesfully loaded
+ * @return {String} the ID of the newly create node
+ * @trigger model.jstree, create_node.jstree
+ */
+ create_node : function (par, node, pos, callback, is_loaded) {
+ if(par === null) { par = $.jstree.root; }
+ par = this.get_node(par);
+ if(!par) { return false; }
+ pos = pos === undefined ? "last" : pos;
+ if(!pos.toString().match(/^(before|after)$/) && !is_loaded && !this.is_loaded(par)) {
+ return this.load_node(par, function () { this.create_node(par, node, pos, callback, true); });
+ }
+ if(!node) { node = { "text" : this.get_string('New node') }; }
+ if(typeof node === "string") { node = { "text" : node }; }
+ if(node.text === undefined) { node.text = this.get_string('New node'); }
+ var tmp, dpc, i, j;
+
+ if(par.id === $.jstree.root) {
+ if(pos === "before") { pos = "first"; }
+ if(pos === "after") { pos = "last"; }
+ }
+ switch(pos) {
+ case "before":
+ tmp = this.get_node(par.parent);
+ pos = $.inArray(par.id, tmp.children);
+ par = tmp;
+ break;
+ case "after" :
+ tmp = this.get_node(par.parent);
+ pos = $.inArray(par.id, tmp.children) + 1;
+ par = tmp;
+ break;
+ case "inside":
+ case "first":
+ pos = 0;
+ break;
+ case "last":
+ pos = par.children.length;
+ break;
+ default:
+ if(!pos) { pos = 0; }
+ break;
+ }
+ if(pos > par.children.length) { pos = par.children.length; }
+ if(!node.id) { node.id = true; }
+ if(!this.check("create_node", node, par, pos)) {
+ this.settings.core.error.call(this, this._data.core.last_error);
+ return false;
+ }
+ if(node.id === true) { delete node.id; }
+ node = this._parse_model_from_json(node, par.id, par.parents.concat());
+ if(!node) { return false; }
+ tmp = this.get_node(node);
+ dpc = [];
+ dpc.push(node);
+ dpc = dpc.concat(tmp.children_d);
+ this.trigger('model', { "nodes" : dpc, "parent" : par.id });
+
+ par.children_d = par.children_d.concat(dpc);
+ for(i = 0, j = par.parents.length; i < j; i++) {
+ this._model.data[par.parents[i]].children_d = this._model.data[par.parents[i]].children_d.concat(dpc);
+ }
+ node = tmp;
+ tmp = [];
+ for(i = 0, j = par.children.length; i < j; i++) {
+ tmp[i >= pos ? i+1 : i] = par.children[i];
+ }
+ tmp[pos] = node.id;
+ par.children = tmp;
+
+ this.redraw_node(par, true);
+ if(callback) { callback.call(this, this.get_node(node)); }
+ /**
+ * triggered when a node is created
+ * @event
+ * @name create_node.jstree
+ * @param {Object} node
+ * @param {String} parent the parent's ID
+ * @param {Number} position the position of the new node among the parent's children
+ */
+ this.trigger('create_node', { "node" : this.get_node(node), "parent" : par.id, "position" : pos });
+ return node.id;
+ },
+ /**
+ * set the text value of a node
+ * @name rename_node(obj, val)
+ * @param {mixed} obj the node, you can pass an array to rename multiple nodes to the same name
+ * @param {String} val the new text value
+ * @return {Boolean}
+ * @trigger rename_node.jstree
+ */
+ rename_node : function (obj, val) {
+ var t1, t2, old;
+ if($.isArray(obj)) {
+ obj = obj.slice();
+ for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
+ this.rename_node(obj[t1], val);
+ }
+ return true;
+ }
+ obj = this.get_node(obj);
+ if(!obj || obj.id === $.jstree.root) { return false; }
+ old = obj.text;
+ if(!this.check("rename_node", obj, this.get_parent(obj), val)) {
+ this.settings.core.error.call(this, this._data.core.last_error);
+ return false;
+ }
+ this.set_text(obj, val); // .apply(this, Array.prototype.slice.call(arguments))
+ /**
+ * triggered when a node is renamed
+ * @event
+ * @name rename_node.jstree
+ * @param {Object} node
+ * @param {String} text the new value
+ * @param {String} old the old value
+ */
+ this.trigger('rename_node', { "node" : obj, "text" : val, "old" : old });
+ return true;
+ },
+ /**
+ * remove a node
+ * @name delete_node(obj)
+ * @param {mixed} obj the node, you can pass an array to delete multiple nodes
+ * @return {Boolean}
+ * @trigger delete_node.jstree, changed.jstree
+ */
+ delete_node : function (obj) {
+ var t1, t2, par, pos, tmp, i, j, k, l, c, top, lft;
+ if($.isArray(obj)) {
+ obj = obj.slice();
+ for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
+ this.delete_node(obj[t1]);
+ }
+ return true;
+ }
+ obj = this.get_node(obj);
+ if(!obj || obj.id === $.jstree.root) { return false; }
+ par = this.get_node(obj.parent);
+ pos = $.inArray(obj.id, par.children);
+ c = false;
+ if(!this.check("delete_node", obj, par, pos)) {
+ this.settings.core.error.call(this, this._data.core.last_error);
+ return false;
+ }
+ if(pos !== -1) {
+ par.children = $.vakata.array_remove(par.children, pos);
+ }
+ tmp = obj.children_d.concat([]);
+ tmp.push(obj.id);
+ for(i = 0, j = obj.parents.length; i < j; i++) {
+ this._model.data[obj.parents[i]].children_d = $.vakata.array_filter(this._model.data[obj.parents[i]].children_d, function (v) {
+ return $.inArray(v, tmp) === -1;
+ });
+ }
+ for(k = 0, l = tmp.length; k < l; k++) {
+ if(this._model.data[tmp[k]].state.selected) {
+ c = true;
+ break;
+ }
+ }
+ if (c) {
+ this._data.core.selected = $.vakata.array_filter(this._data.core.selected, function (v) {
+ return $.inArray(v, tmp) === -1;
+ });
+ }
+ /**
+ * triggered when a node is deleted
+ * @event
+ * @name delete_node.jstree
+ * @param {Object} node
+ * @param {String} parent the parent's ID
+ */
+ this.trigger('delete_node', { "node" : obj, "parent" : par.id });
+ if(c) {
+ this.trigger('changed', { 'action' : 'delete_node', 'node' : obj, 'selected' : this._data.core.selected, 'parent' : par.id });
+ }
+ for(k = 0, l = tmp.length; k < l; k++) {
+ delete this._model.data[tmp[k]];
+ }
+ if($.inArray(this._data.core.focused, tmp) !== -1) {
+ this._data.core.focused = null;
+ top = this.element[0].scrollTop;
+ lft = this.element[0].scrollLeft;
+ if(par.id === $.jstree.root) {
+ if (this._model.data[$.jstree.root].children[0]) {
+ this.get_node(this._model.data[$.jstree.root].children[0], true).children('.jstree-anchor').focus();
+ }
+ }
+ else {
+ this.get_node(par, true).children('.jstree-anchor').focus();
+ }
+ this.element[0].scrollTop = top;
+ this.element[0].scrollLeft = lft;
+ }
+ this.redraw_node(par, true);
+ return true;
+ },
+ /**
+ * check if an operation is premitted on the tree. Used internally.
+ * @private
+ * @name check(chk, obj, par, pos)
+ * @param {String} chk the operation to check, can be "create_node", "rename_node", "delete_node", "copy_node" or "move_node"
+ * @param {mixed} obj the node
+ * @param {mixed} par the parent
+ * @param {mixed} pos the position to insert at, or if "rename_node" - the new name
+ * @param {mixed} more some various additional information, for example if a "move_node" operations is triggered by DND this will be the hovered node
+ * @return {Boolean}
+ */
+ check : function (chk, obj, par, pos, more) {
+ obj = obj && obj.id ? obj : this.get_node(obj);
+ par = par && par.id ? par : this.get_node(par);
+ var tmp = chk.match(/^move_node|copy_node|create_node$/i) ? par : obj,
+ chc = this.settings.core.check_callback;
+ if(chk === "move_node" || chk === "copy_node") {
+ if((!more || !more.is_multi) && (obj.id === par.id || (chk === "move_node" && $.inArray(obj.id, par.children) === pos) || $.inArray(par.id, obj.children_d) !== -1)) {
+ this._data.core.last_error = { 'error' : 'check', 'plugin' : 'core', 'id' : 'core_01', 'reason' : 'Moving parent inside child', 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
+ return false;
+ }
+ }
+ if(tmp && tmp.data) { tmp = tmp.data; }
+ if(tmp && tmp.functions && (tmp.functions[chk] === false || tmp.functions[chk] === true)) {
+ if(tmp.functions[chk] === false) {
+ this._data.core.last_error = { 'error' : 'check', 'plugin' : 'core', 'id' : 'core_02', 'reason' : 'Node data prevents function: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
+ }
+ return tmp.functions[chk];
+ }
+ if(chc === false || ($.isFunction(chc) && chc.call(this, chk, obj, par, pos, more) === false) || (chc && chc[chk] === false)) {
+ this._data.core.last_error = { 'error' : 'check', 'plugin' : 'core', 'id' : 'core_03', 'reason' : 'User config for core.check_callback prevents function: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
+ return false;
+ }
+ return true;
+ },
+ /**
+ * get the last error
+ * @name last_error()
+ * @return {Object}
+ */
+ last_error : function () {
+ return this._data.core.last_error;
+ },
+ /**
+ * move a node to a new parent
+ * @name move_node(obj, par [, pos, callback, is_loaded])
+ * @param {mixed} obj the node to move, pass an array to move multiple nodes
+ * @param {mixed} par the new parent
+ * @param {mixed} pos the position to insert at (besides integer values, "first" and "last" are supported, as well as "before" and "after"), defaults to integer `0`
+ * @param {function} callback a function to call once the move is completed, receives 3 arguments - the node, the new parent and the position
+ * @param {Boolean} is_loaded internal parameter indicating if the parent node has been loaded
+ * @param {Boolean} skip_redraw internal parameter indicating if the tree should be redrawn
+ * @param {Boolean} instance internal parameter indicating if the node comes from another instance
+ * @trigger move_node.jstree
+ */
+ move_node : function (obj, par, pos, callback, is_loaded, skip_redraw, origin) {
+ var t1, t2, old_par, old_pos, new_par, old_ins, is_multi, dpc, tmp, i, j, k, l, p;
+
+ par = this.get_node(par);
+ pos = pos === undefined ? 0 : pos;
+ if(!par) { return false; }
+ if(!pos.toString().match(/^(before|after)$/) && !is_loaded && !this.is_loaded(par)) {
+ return this.load_node(par, function () { this.move_node(obj, par, pos, callback, true, false, origin); });
+ }
+
+ if($.isArray(obj)) {
+ if(obj.length === 1) {
+ obj = obj[0];
+ }
+ else {
+ //obj = obj.slice();
+ for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
+ if((tmp = this.move_node(obj[t1], par, pos, callback, is_loaded, false, origin))) {
+ par = tmp;
+ pos = "after";
+ }
+ }
+ this.redraw();
+ return true;
+ }
+ }
+ obj = obj && obj.id ? obj : this.get_node(obj);
+
+ if(!obj || obj.id === $.jstree.root) { return false; }
+
+ old_par = (obj.parent || $.jstree.root).toString();
+ new_par = (!pos.toString().match(/^(before|after)$/) || par.id === $.jstree.root) ? par : this.get_node(par.parent);
+ old_ins = origin ? origin : (this._model.data[obj.id] ? this : $.jstree.reference(obj.id));
+ is_multi = !old_ins || !old_ins._id || (this._id !== old_ins._id);
+ old_pos = old_ins && old_ins._id && old_par && old_ins._model.data[old_par] && old_ins._model.data[old_par].children ? $.inArray(obj.id, old_ins._model.data[old_par].children) : -1;
+ if(old_ins && old_ins._id) {
+ obj = old_ins._model.data[obj.id];
+ }
+
+ if(is_multi) {
+ if((tmp = this.copy_node(obj, par, pos, callback, is_loaded, false, origin))) {
+ if(old_ins) { old_ins.delete_node(obj); }
+ return tmp;
+ }
+ return false;
+ }
+ //var m = this._model.data;
+ if(par.id === $.jstree.root) {
+ if(pos === "before") { pos = "first"; }
+ if(pos === "after") { pos = "last"; }
+ }
+ switch(pos) {
+ case "before":
+ pos = $.inArray(par.id, new_par.children);
+ break;
+ case "after" :
+ pos = $.inArray(par.id, new_par.children) + 1;
+ break;
+ case "inside":
+ case "first":
+ pos = 0;
+ break;
+ case "last":
+ pos = new_par.children.length;
+ break;
+ default:
+ if(!pos) { pos = 0; }
+ break;
+ }
+ if(pos > new_par.children.length) { pos = new_par.children.length; }
+ if(!this.check("move_node", obj, new_par, pos, { 'core' : true, 'origin' : origin, 'is_multi' : (old_ins && old_ins._id && old_ins._id !== this._id), 'is_foreign' : (!old_ins || !old_ins._id) })) {
+ this.settings.core.error.call(this, this._data.core.last_error);
+ return false;
+ }
+ if(obj.parent === new_par.id) {
+ dpc = new_par.children.concat();
+ tmp = $.inArray(obj.id, dpc);
+ if(tmp !== -1) {
+ dpc = $.vakata.array_remove(dpc, tmp);
+ if(pos > tmp) { pos--; }
+ }
+ tmp = [];
+ for(i = 0, j = dpc.length; i < j; i++) {
+ tmp[i >= pos ? i+1 : i] = dpc[i];
+ }
+ tmp[pos] = obj.id;
+ new_par.children = tmp;
+ this._node_changed(new_par.id);
+ this.redraw(new_par.id === $.jstree.root);
+ }
+ else {
+ // clean old parent and up
+ tmp = obj.children_d.concat();
+ tmp.push(obj.id);
+ for(i = 0, j = obj.parents.length; i < j; i++) {
+ dpc = [];
+ p = old_ins._model.data[obj.parents[i]].children_d;
+ for(k = 0, l = p.length; k < l; k++) {
+ if($.inArray(p[k], tmp) === -1) {
+ dpc.push(p[k]);
+ }
+ }
+ old_ins._model.data[obj.parents[i]].children_d = dpc;
+ }
+ old_ins._model.data[old_par].children = $.vakata.array_remove_item(old_ins._model.data[old_par].children, obj.id);
+
+ // insert into new parent and up
+ for(i = 0, j = new_par.parents.length; i < j; i++) {
+ this._model.data[new_par.parents[i]].children_d = this._model.data[new_par.parents[i]].children_d.concat(tmp);
+ }
+ dpc = [];
+ for(i = 0, j = new_par.children.length; i < j; i++) {
+ dpc[i >= pos ? i+1 : i] = new_par.children[i];
+ }
+ dpc[pos] = obj.id;
+ new_par.children = dpc;
+ new_par.children_d.push(obj.id);
+ new_par.children_d = new_par.children_d.concat(obj.children_d);
+
+ // update object
+ obj.parent = new_par.id;
+ tmp = new_par.parents.concat();
+ tmp.unshift(new_par.id);
+ p = obj.parents.length;
+ obj.parents = tmp;
+
+ // update object children
+ tmp = tmp.concat();
+ for(i = 0, j = obj.children_d.length; i < j; i++) {
+ this._model.data[obj.children_d[i]].parents = this._model.data[obj.children_d[i]].parents.slice(0,p*-1);
+ Array.prototype.push.apply(this._model.data[obj.children_d[i]].parents, tmp);
+ }
+
+ if(old_par === $.jstree.root || new_par.id === $.jstree.root) {
+ this._model.force_full_redraw = true;
+ }
+ if(!this._model.force_full_redraw) {
+ this._node_changed(old_par);
+ this._node_changed(new_par.id);
+ }
+ if(!skip_redraw) {
+ this.redraw();
+ }
+ }
+ if(callback) { callback.call(this, obj, new_par, pos); }
+ /**
+ * triggered when a node is moved
+ * @event
+ * @name move_node.jstree
+ * @param {Object} node
+ * @param {String} parent the parent's ID
+ * @param {Number} position the position of the node among the parent's children
+ * @param {String} old_parent the old parent of the node
+ * @param {Number} old_position the old position of the node
+ * @param {Boolean} is_multi do the node and new parent belong to different instances
+ * @param {jsTree} old_instance the instance the node came from
+ * @param {jsTree} new_instance the instance of the new parent
+ */
+ this.trigger('move_node', { "node" : obj, "parent" : new_par.id, "position" : pos, "old_parent" : old_par, "old_position" : old_pos, 'is_multi' : (old_ins && old_ins._id && old_ins._id !== this._id), 'is_foreign' : (!old_ins || !old_ins._id), 'old_instance' : old_ins, 'new_instance' : this });
+ return obj.id;
+ },
+ /**
+ * copy a node to a new parent
+ * @name copy_node(obj, par [, pos, callback, is_loaded])
+ * @param {mixed} obj the node to copy, pass an array to copy multiple nodes
+ * @param {mixed} par the new parent
+ * @param {mixed} pos the position to insert at (besides integer values, "first" and "last" are supported, as well as "before" and "after"), defaults to integer `0`
+ * @param {function} callback a function to call once the move is completed, receives 3 arguments - the node, the new parent and the position
+ * @param {Boolean} is_loaded internal parameter indicating if the parent node has been loaded
+ * @param {Boolean} skip_redraw internal parameter indicating if the tree should be redrawn
+ * @param {Boolean} instance internal parameter indicating if the node comes from another instance
+ * @trigger model.jstree copy_node.jstree
+ */
+ copy_node : function (obj, par, pos, callback, is_loaded, skip_redraw, origin) {
+ var t1, t2, dpc, tmp, i, j, node, old_par, new_par, old_ins, is_multi;
+
+ par = this.get_node(par);
+ pos = pos === undefined ? 0 : pos;
+ if(!par) { return false; }
+ if(!pos.toString().match(/^(before|after)$/) && !is_loaded && !this.is_loaded(par)) {
+ return this.load_node(par, function () { this.copy_node(obj, par, pos, callback, true, false, origin); });
+ }
+
+ if($.isArray(obj)) {
+ if(obj.length === 1) {
+ obj = obj[0];
+ }
+ else {
+ //obj = obj.slice();
+ for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
+ if((tmp = this.copy_node(obj[t1], par, pos, callback, is_loaded, true, origin))) {
+ par = tmp;
+ pos = "after";
+ }
+ }
+ this.redraw();
+ return true;
+ }
+ }
+ obj = obj && obj.id ? obj : this.get_node(obj);
+ if(!obj || obj.id === $.jstree.root) { return false; }
+
+ old_par = (obj.parent || $.jstree.root).toString();
+ new_par = (!pos.toString().match(/^(before|after)$/) || par.id === $.jstree.root) ? par : this.get_node(par.parent);
+ old_ins = origin ? origin : (this._model.data[obj.id] ? this : $.jstree.reference(obj.id));
+ is_multi = !old_ins || !old_ins._id || (this._id !== old_ins._id);
+
+ if(old_ins && old_ins._id) {
+ obj = old_ins._model.data[obj.id];
+ }
+
+ if(par.id === $.jstree.root) {
+ if(pos === "before") { pos = "first"; }
+ if(pos === "after") { pos = "last"; }
+ }
+ switch(pos) {
+ case "before":
+ pos = $.inArray(par.id, new_par.children);
+ break;
+ case "after" :
+ pos = $.inArray(par.id, new_par.children) + 1;
+ break;
+ case "inside":
+ case "first":
+ pos = 0;
+ break;
+ case "last":
+ pos = new_par.children.length;
+ break;
+ default:
+ if(!pos) { pos = 0; }
+ break;
+ }
+ if(pos > new_par.children.length) { pos = new_par.children.length; }
+ if(!this.check("copy_node", obj, new_par, pos, { 'core' : true, 'origin' : origin, 'is_multi' : (old_ins && old_ins._id && old_ins._id !== this._id), 'is_foreign' : (!old_ins || !old_ins._id) })) {
+ this.settings.core.error.call(this, this._data.core.last_error);
+ return false;
+ }
+ node = old_ins ? old_ins.get_json(obj, { no_id : true, no_data : true, no_state : true }) : obj;
+ if(!node) { return false; }
+ if(node.id === true) { delete node.id; }
+ node = this._parse_model_from_json(node, new_par.id, new_par.parents.concat());
+ if(!node) { return false; }
+ tmp = this.get_node(node);
+ if(obj && obj.state && obj.state.loaded === false) { tmp.state.loaded = false; }
+ dpc = [];
+ dpc.push(node);
+ dpc = dpc.concat(tmp.children_d);
+ this.trigger('model', { "nodes" : dpc, "parent" : new_par.id });
+
+ // insert into new parent and up
+ for(i = 0, j = new_par.parents.length; i < j; i++) {
+ this._model.data[new_par.parents[i]].children_d = this._model.data[new_par.parents[i]].children_d.concat(dpc);
+ }
+ dpc = [];
+ for(i = 0, j = new_par.children.length; i < j; i++) {
+ dpc[i >= pos ? i+1 : i] = new_par.children[i];
+ }
+ dpc[pos] = tmp.id;
+ new_par.children = dpc;
+ new_par.children_d.push(tmp.id);
+ new_par.children_d = new_par.children_d.concat(tmp.children_d);
+
+ if(new_par.id === $.jstree.root) {
+ this._model.force_full_redraw = true;
+ }
+ if(!this._model.force_full_redraw) {
+ this._node_changed(new_par.id);
+ }
+ if(!skip_redraw) {
+ this.redraw(new_par.id === $.jstree.root);
+ }
+ if(callback) { callback.call(this, tmp, new_par, pos); }
+ /**
+ * triggered when a node is copied
+ * @event
+ * @name copy_node.jstree
+ * @param {Object} node the copied node
+ * @param {Object} original the original node
+ * @param {String} parent the parent's ID
+ * @param {Number} position the position of the node among the parent's children
+ * @param {String} old_parent the old parent of the node
+ * @param {Number} old_position the position of the original node
+ * @param {Boolean} is_multi do the node and new parent belong to different instances
+ * @param {jsTree} old_instance the instance the node came from
+ * @param {jsTree} new_instance the instance of the new parent
+ */
+ this.trigger('copy_node', { "node" : tmp, "original" : obj, "parent" : new_par.id, "position" : pos, "old_parent" : old_par, "old_position" : old_ins && old_ins._id && old_par && old_ins._model.data[old_par] && old_ins._model.data[old_par].children ? $.inArray(obj.id, old_ins._model.data[old_par].children) : -1,'is_multi' : (old_ins && old_ins._id && old_ins._id !== this._id), 'is_foreign' : (!old_ins || !old_ins._id), 'old_instance' : old_ins, 'new_instance' : this });
+ return tmp.id;
+ },
+ /**
+ * cut a node (a later call to `paste(obj)` would move the node)
+ * @name cut(obj)
+ * @param {mixed} obj multiple objects can be passed using an array
+ * @trigger cut.jstree
+ */
+ cut : function (obj) {
+ if(!obj) { obj = this._data.core.selected.concat(); }
+ if(!$.isArray(obj)) { obj = [obj]; }
+ if(!obj.length) { return false; }
+ var tmp = [], o, t1, t2;
+ for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
+ o = this.get_node(obj[t1]);
+ if(o && o.id && o.id !== $.jstree.root) { tmp.push(o); }
+ }
+ if(!tmp.length) { return false; }
+ ccp_node = tmp;
+ ccp_inst = this;
+ ccp_mode = 'move_node';
+ /**
+ * triggered when nodes are added to the buffer for moving
+ * @event
+ * @name cut.jstree
+ * @param {Array} node
+ */
+ this.trigger('cut', { "node" : obj });
+ },
+ /**
+ * copy a node (a later call to `paste(obj)` would copy the node)
+ * @name copy(obj)
+ * @param {mixed} obj multiple objects can be passed using an array
+ * @trigger copy.jstree
+ */
+ copy : function (obj) {
+ if(!obj) { obj = this._data.core.selected.concat(); }
+ if(!$.isArray(obj)) { obj = [obj]; }
+ if(!obj.length) { return false; }
+ var tmp = [], o, t1, t2;
+ for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
+ o = this.get_node(obj[t1]);
+ if(o && o.id && o.id !== $.jstree.root) { tmp.push(o); }
+ }
+ if(!tmp.length) { return false; }
+ ccp_node = tmp;
+ ccp_inst = this;
+ ccp_mode = 'copy_node';
+ /**
+ * triggered when nodes are added to the buffer for copying
+ * @event
+ * @name copy.jstree
+ * @param {Array} node
+ */
+ this.trigger('copy', { "node" : obj });
+ },
+ /**
+ * get the current buffer (any nodes that are waiting for a paste operation)
+ * @name get_buffer()
+ * @return {Object} an object consisting of `mode` ("copy_node" or "move_node"), `node` (an array of objects) and `inst` (the instance)
+ */
+ get_buffer : function () {
+ return { 'mode' : ccp_mode, 'node' : ccp_node, 'inst' : ccp_inst };
+ },
+ /**
+ * check if there is something in the buffer to paste
+ * @name can_paste()
+ * @return {Boolean}
+ */
+ can_paste : function () {
+ return ccp_mode !== false && ccp_node !== false; // && ccp_inst._model.data[ccp_node];
+ },
+ /**
+ * copy or move the previously cut or copied nodes to a new parent
+ * @name paste(obj [, pos])
+ * @param {mixed} obj the new parent
+ * @param {mixed} pos the position to insert at (besides integer, "first" and "last" are supported), defaults to integer `0`
+ * @trigger paste.jstree
+ */
+ paste : function (obj, pos) {
+ obj = this.get_node(obj);
+ if(!obj || !ccp_mode || !ccp_mode.match(/^(copy_node|move_node)$/) || !ccp_node) { return false; }
+ if(this[ccp_mode](ccp_node, obj, pos, false, false, false, ccp_inst)) {
+ /**
+ * triggered when paste is invoked
+ * @event
+ * @name paste.jstree
+ * @param {String} parent the ID of the receiving node
+ * @param {Array} node the nodes in the buffer
+ * @param {String} mode the performed operation - "copy_node" or "move_node"
+ */
+ this.trigger('paste', { "parent" : obj.id, "node" : ccp_node, "mode" : ccp_mode });
+ }
+ ccp_node = false;
+ ccp_mode = false;
+ ccp_inst = false;
+ },
+ /**
+ * clear the buffer of previously copied or cut nodes
+ * @name clear_buffer()
+ * @trigger clear_buffer.jstree
+ */
+ clear_buffer : function () {
+ ccp_node = false;
+ ccp_mode = false;
+ ccp_inst = false;
+ /**
+ * triggered when the copy / cut buffer is cleared
+ * @event
+ * @name clear_buffer.jstree
+ */
+ this.trigger('clear_buffer');
+ },
+ /**
+ * put a node in edit mode (input field to rename the node)
+ * @name edit(obj [, default_text, callback])
+ * @param {mixed} obj
+ * @param {String} default_text the text to populate the input with (if omitted or set to a non-string value the node's text value is used)
+ * @param {Function} callback a function to be called once the text box is blurred, it is called in the instance's scope and receives the node, a status parameter (true if the rename is successful, false otherwise) and a boolean indicating if the user cancelled the edit. You can access the node's title using .text
+ */
+ edit : function (obj, default_text, callback) {
+ var rtl, w, a, s, t, h1, h2, fn, tmp, cancel = false;
+ obj = this.get_node(obj);
+ if(!obj) { return false; }
+ if(this.settings.core.check_callback === false) {
+ this._data.core.last_error = { 'error' : 'check', 'plugin' : 'core', 'id' : 'core_07', 'reason' : 'Could not edit node because of check_callback' };
+ this.settings.core.error.call(this, this._data.core.last_error);
+ return false;
+ }
+ tmp = obj;
+ default_text = typeof default_text === 'string' ? default_text : obj.text;
+ this.set_text(obj, "");
+ obj = this._open_to(obj);
+ tmp.text = default_text;
+
+ rtl = this._data.core.rtl;
+ w = this.element.width();
+ this._data.core.focused = tmp.id;
+ a = obj.children('.jstree-anchor').focus();
+ s = $('
');
+ /*!
+ oi = obj.children("i:visible"),
+ ai = a.children("i:visible"),
+ w1 = oi.width() * oi.length,
+ w2 = ai.width() * ai.length,
+ */
+ t = default_text;
+ h1 = $("<"+"div />", { css : { "position" : "absolute", "top" : "-200px", "left" : (rtl ? "0px" : "-1000px"), "visibility" : "hidden" } }).appendTo("body");
+ h2 = $("<"+"input />", {
+ "value" : t,
+ "class" : "jstree-rename-input",
+ // "size" : t.length,
+ "css" : {
+ "padding" : "0",
+ "border" : "1px solid silver",
+ "box-sizing" : "border-box",
+ "display" : "inline-block",
+ "height" : (this._data.core.li_height) + "px",
+ "lineHeight" : (this._data.core.li_height) + "px",
+ "width" : "150px" // will be set a bit further down
+ },
+ "blur" : $.proxy(function (e) {
+ e.stopImmediatePropagation();
+ e.preventDefault();
+ var i = s.children(".jstree-rename-input"),
+ v = i.val(),
+ f = this.settings.core.force_text,
+ nv;
+ if(v === "") { v = t; }
+ h1.remove();
+ s.replaceWith(a);
+ s.remove();
+ t = f ? t : $('').append($.parseHTML(t)).html();
+ this.set_text(obj, t);
+ nv = !!this.rename_node(obj, f ? $('').text(v).text() : $('').append($.parseHTML(v)).html());
+ if(!nv) {
+ this.set_text(obj, t); // move this up? and fix #483
+ }
+ this._data.core.focused = tmp.id;
+ setTimeout($.proxy(function () {
+ var node = this.get_node(tmp.id, true);
+ if(node.length) {
+ this._data.core.focused = tmp.id;
+ node.children('.jstree-anchor').focus();
+ }
+ }, this), 0);
+ if(callback) {
+ callback.call(this, tmp, nv, cancel);
+ }
+ h2 = null;
+ }, this),
+ "keydown" : function (e) {
+ var key = e.which;
+ if(key === 27) {
+ cancel = true;
+ this.value = t;
+ }
+ if(key === 27 || key === 13 || key === 37 || key === 38 || key === 39 || key === 40 || key === 32) {
+ e.stopImmediatePropagation();
+ }
+ if(key === 27 || key === 13) {
+ e.preventDefault();
+ this.blur();
+ }
+ },
+ "click" : function (e) { e.stopImmediatePropagation(); },
+ "mousedown" : function (e) { e.stopImmediatePropagation(); },
+ "keyup" : function (e) {
+ h2.width(Math.min(h1.text("pW" + this.value).width(),w));
+ },
+ "keypress" : function(e) {
+ if(e.which === 13) { return false; }
+ }
+ });
+ fn = {
+ fontFamily : a.css('fontFamily') || '',
+ fontSize : a.css('fontSize') || '',
+ fontWeight : a.css('fontWeight') || '',
+ fontStyle : a.css('fontStyle') || '',
+ fontStretch : a.css('fontStretch') || '',
+ fontVariant : a.css('fontVariant') || '',
+ letterSpacing : a.css('letterSpacing') || '',
+ wordSpacing : a.css('wordSpacing') || ''
+ };
+ s.attr('class', a.attr('class')).append(a.contents().clone()).append(h2);
+ a.replaceWith(s);
+ h1.css(fn);
+ h2.css(fn).width(Math.min(h1.text("pW" + h2[0].value).width(),w))[0].select();
+ $(document).one('mousedown.jstree touchstart.jstree dnd_start.vakata', function (e) {
+ if (h2 && e.target !== h2) {
+ $(h2).blur();
+ }
+ });
+ },
+
+
+ /**
+ * changes the theme
+ * @name set_theme(theme_name [, theme_url])
+ * @param {String} theme_name the name of the new theme to apply
+ * @param {mixed} theme_url the location of the CSS file for this theme. Omit or set to `false` if you manually included the file. Set to `true` to autoload from the `core.themes.dir` directory.
+ * @trigger set_theme.jstree
+ */
+ set_theme : function (theme_name, theme_url) {
+ if(!theme_name) { return false; }
+ if(theme_url === true) {
+ var dir = this.settings.core.themes.dir;
+ if(!dir) { dir = $.jstree.path + '/themes'; }
+ theme_url = dir + '/' + theme_name + '/style.css';
+ }
+ if(theme_url && $.inArray(theme_url, themes_loaded) === -1) {
+ $('head').append('<'+'link rel="stylesheet" href="' + theme_url + '" type="text/css" />');
+ themes_loaded.push(theme_url);
+ }
+ if(this._data.core.themes.name) {
+ this.element.removeClass('jstree-' + this._data.core.themes.name);
+ }
+ this._data.core.themes.name = theme_name;
+ this.element.addClass('jstree-' + theme_name);
+ this.element[this.settings.core.themes.responsive ? 'addClass' : 'removeClass' ]('jstree-' + theme_name + '-responsive');
+ /**
+ * triggered when a theme is set
+ * @event
+ * @name set_theme.jstree
+ * @param {String} theme the new theme
+ */
+ this.trigger('set_theme', { 'theme' : theme_name });
+ },
+ /**
+ * gets the name of the currently applied theme name
+ * @name get_theme()
+ * @return {String}
+ */
+ get_theme : function () { return this._data.core.themes.name; },
+ /**
+ * changes the theme variant (if the theme has variants)
+ * @name set_theme_variant(variant_name)
+ * @param {String|Boolean} variant_name the variant to apply (if `false` is used the current variant is removed)
+ */
+ set_theme_variant : function (variant_name) {
+ if(this._data.core.themes.variant) {
+ this.element.removeClass('jstree-' + this._data.core.themes.name + '-' + this._data.core.themes.variant);
+ }
+ this._data.core.themes.variant = variant_name;
+ if(variant_name) {
+ this.element.addClass('jstree-' + this._data.core.themes.name + '-' + this._data.core.themes.variant);
+ }
+ },
+ /**
+ * gets the name of the currently applied theme variant
+ * @name get_theme()
+ * @return {String}
+ */
+ get_theme_variant : function () { return this._data.core.themes.variant; },
+ /**
+ * shows a striped background on the container (if the theme supports it)
+ * @name show_stripes()
+ */
+ show_stripes : function () {
+ this._data.core.themes.stripes = true;
+ this.get_container_ul().addClass("jstree-striped");
+ /**
+ * triggered when stripes are shown
+ * @event
+ * @name show_stripes.jstree
+ */
+ this.trigger('show_stripes');
+ },
+ /**
+ * hides the striped background on the container
+ * @name hide_stripes()
+ */
+ hide_stripes : function () {
+ this._data.core.themes.stripes = false;
+ this.get_container_ul().removeClass("jstree-striped");
+ /**
+ * triggered when stripes are hidden
+ * @event
+ * @name hide_stripes.jstree
+ */
+ this.trigger('hide_stripes');
+ },
+ /**
+ * toggles the striped background on the container
+ * @name toggle_stripes()
+ */
+ toggle_stripes : function () { if(this._data.core.themes.stripes) { this.hide_stripes(); } else { this.show_stripes(); } },
+ /**
+ * shows the connecting dots (if the theme supports it)
+ * @name show_dots()
+ */
+ show_dots : function () {
+ this._data.core.themes.dots = true;
+ this.get_container_ul().removeClass("jstree-no-dots");
+ /**
+ * triggered when dots are shown
+ * @event
+ * @name show_dots.jstree
+ */
+ this.trigger('show_dots');
+ },
+ /**
+ * hides the connecting dots
+ * @name hide_dots()
+ */
+ hide_dots : function () {
+ this._data.core.themes.dots = false;
+ this.get_container_ul().addClass("jstree-no-dots");
+ /**
+ * triggered when dots are hidden
+ * @event
+ * @name hide_dots.jstree
+ */
+ this.trigger('hide_dots');
+ },
+ /**
+ * toggles the connecting dots
+ * @name toggle_dots()
+ */
+ toggle_dots : function () { if(this._data.core.themes.dots) { this.hide_dots(); } else { this.show_dots(); } },
+ /**
+ * show the node icons
+ * @name show_icons()
+ */
+ show_icons : function () {
+ this._data.core.themes.icons = true;
+ this.get_container_ul().removeClass("jstree-no-icons");
+ /**
+ * triggered when icons are shown
+ * @event
+ * @name show_icons.jstree
+ */
+ this.trigger('show_icons');
+ },
+ /**
+ * hide the node icons
+ * @name hide_icons()
+ */
+ hide_icons : function () {
+ this._data.core.themes.icons = false;
+ this.get_container_ul().addClass("jstree-no-icons");
+ /**
+ * triggered when icons are hidden
+ * @event
+ * @name hide_icons.jstree
+ */
+ this.trigger('hide_icons');
+ },
+ /**
+ * toggle the node icons
+ * @name toggle_icons()
+ */
+ toggle_icons : function () { if(this._data.core.themes.icons) { this.hide_icons(); } else { this.show_icons(); } },
+ /**
+ * show the node ellipsis
+ * @name show_icons()
+ */
+ show_ellipsis : function () {
+ this._data.core.themes.ellipsis = true;
+ this.get_container_ul().addClass("jstree-ellipsis");
+ /**
+ * triggered when ellisis is shown
+ * @event
+ * @name show_ellipsis.jstree
+ */
+ this.trigger('show_ellipsis');
+ },
+ /**
+ * hide the node ellipsis
+ * @name hide_ellipsis()
+ */
+ hide_ellipsis : function () {
+ this._data.core.themes.ellipsis = false;
+ this.get_container_ul().removeClass("jstree-ellipsis");
+ /**
+ * triggered when ellisis is hidden
+ * @event
+ * @name hide_ellipsis.jstree
+ */
+ this.trigger('hide_ellipsis');
+ },
+ /**
+ * toggle the node ellipsis
+ * @name toggle_icons()
+ */
+ toggle_ellipsis : function () { if(this._data.core.themes.ellipsis) { this.hide_ellipsis(); } else { this.show_ellipsis(); } },
+ /**
+ * set the node icon for a node
+ * @name set_icon(obj, icon)
+ * @param {mixed} obj
+ * @param {String} icon the new icon - can be a path to an icon or a className, if using an image that is in the current directory use a `./` prefix, otherwise it will be detected as a class
+ */
+ set_icon : function (obj, icon) {
+ var t1, t2, dom, old;
+ if($.isArray(obj)) {
+ obj = obj.slice();
+ for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
+ this.set_icon(obj[t1], icon);
+ }
+ return true;
+ }
+ obj = this.get_node(obj);
+ if(!obj || obj.id === $.jstree.root) { return false; }
+ old = obj.icon;
+ obj.icon = icon === true || icon === null || icon === undefined || icon === '' ? true : icon;
+ dom = this.get_node(obj, true).children(".jstree-anchor").children(".jstree-themeicon");
+ if(icon === false) {
+ this.hide_icon(obj);
+ }
+ else if(icon === true || icon === null || icon === undefined || icon === '') {
+ dom.removeClass('jstree-themeicon-custom ' + old).css("background","").removeAttr("rel");
+ if(old === false) { this.show_icon(obj); }
+ }
+ else if(icon.indexOf("/") === -1 && icon.indexOf(".") === -1) {
+ dom.removeClass(old).css("background","");
+ dom.addClass(icon + ' jstree-themeicon-custom').attr("rel",icon);
+ if(old === false) { this.show_icon(obj); }
+ }
+ else {
+ dom.removeClass(old).css("background","");
+ dom.addClass('jstree-themeicon-custom').css("background", "url('" + icon + "') center center no-repeat").attr("rel",icon);
+ if(old === false) { this.show_icon(obj); }
+ }
+ return true;
+ },
+ /**
+ * get the node icon for a node
+ * @name get_icon(obj)
+ * @param {mixed} obj
+ * @return {String}
+ */
+ get_icon : function (obj) {
+ obj = this.get_node(obj);
+ return (!obj || obj.id === $.jstree.root) ? false : obj.icon;
+ },
+ /**
+ * hide the icon on an individual node
+ * @name hide_icon(obj)
+ * @param {mixed} obj
+ */
+ hide_icon : function (obj) {
+ var t1, t2;
+ if($.isArray(obj)) {
+ obj = obj.slice();
+ for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
+ this.hide_icon(obj[t1]);
+ }
+ return true;
+ }
+ obj = this.get_node(obj);
+ if(!obj || obj === $.jstree.root) { return false; }
+ obj.icon = false;
+ this.get_node(obj, true).children(".jstree-anchor").children(".jstree-themeicon").addClass('jstree-themeicon-hidden');
+ return true;
+ },
+ /**
+ * show the icon on an individual node
+ * @name show_icon(obj)
+ * @param {mixed} obj
+ */
+ show_icon : function (obj) {
+ var t1, t2, dom;
+ if($.isArray(obj)) {
+ obj = obj.slice();
+ for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
+ this.show_icon(obj[t1]);
+ }
+ return true;
+ }
+ obj = this.get_node(obj);
+ if(!obj || obj === $.jstree.root) { return false; }
+ dom = this.get_node(obj, true);
+ obj.icon = dom.length ? dom.children(".jstree-anchor").children(".jstree-themeicon").attr('rel') : true;
+ if(!obj.icon) { obj.icon = true; }
+ dom.children(".jstree-anchor").children(".jstree-themeicon").removeClass('jstree-themeicon-hidden');
+ return true;
+ }
+ };
+
+ // helpers
+ $.vakata = {};
+ // collect attributes
+ $.vakata.attributes = function(node, with_values) {
+ node = $(node)[0];
+ var attr = with_values ? {} : [];
+ if(node && node.attributes) {
+ $.each(node.attributes, function (i, v) {
+ if($.inArray(v.name.toLowerCase(),['style','contenteditable','hasfocus','tabindex']) !== -1) { return; }
+ if(v.value !== null && $.trim(v.value) !== '') {
+ if(with_values) { attr[v.name] = v.value; }
+ else { attr.push(v.name); }
+ }
+ });
+ }
+ return attr;
+ };
+ $.vakata.array_unique = function(array) {
+ var a = [], i, j, l, o = {};
+ for(i = 0, l = array.length; i < l; i++) {
+ if(o[array[i]] === undefined) {
+ a.push(array[i]);
+ o[array[i]] = true;
+ }
+ }
+ return a;
+ };
+ // remove item from array
+ $.vakata.array_remove = function(array, from) {
+ array.splice(from, 1);
+ return array;
+ //var rest = array.slice((to || from) + 1 || array.length);
+ //array.length = from < 0 ? array.length + from : from;
+ //array.push.apply(array, rest);
+ //return array;
+ };
+ // remove item from array
+ $.vakata.array_remove_item = function(array, item) {
+ var tmp = $.inArray(item, array);
+ return tmp !== -1 ? $.vakata.array_remove(array, tmp) : array;
+ };
+ $.vakata.array_filter = function(c,a,b,d,e) {
+ if (c.filter) {
+ return c.filter(a, b);
+ }
+ d=[];
+ for (e in c) {
+ if (~~e+''===e+'' && e>=0 && a.call(b,c[e],+e,c)) {
+ d.push(c[e]);
+ }
+ }
+ return d;
+ };
+
+
+/**
+ * ### Changed plugin
+ *
+ * This plugin adds more information to the `changed.jstree` event. The new data is contained in the `changed` event data property, and contains a lists of `selected` and `deselected` nodes.
+ */
+
+ $.jstree.plugins.changed = function (options, parent) {
+ var last = [];
+ this.trigger = function (ev, data) {
+ var i, j;
+ if(!data) {
+ data = {};
+ }
+ if(ev.replace('.jstree','') === 'changed') {
+ data.changed = { selected : [], deselected : [] };
+ var tmp = {};
+ for(i = 0, j = last.length; i < j; i++) {
+ tmp[last[i]] = 1;
+ }
+ for(i = 0, j = data.selected.length; i < j; i++) {
+ if(!tmp[data.selected[i]]) {
+ data.changed.selected.push(data.selected[i]);
+ }
+ else {
+ tmp[data.selected[i]] = 2;
+ }
+ }
+ for(i = 0, j = last.length; i < j; i++) {
+ if(tmp[last[i]] === 1) {
+ data.changed.deselected.push(last[i]);
+ }
+ }
+ last = data.selected.slice();
+ }
+ /**
+ * triggered when selection changes (the "changed" plugin enhances the original event with more data)
+ * @event
+ * @name changed.jstree
+ * @param {Object} node
+ * @param {Object} action the action that caused the selection to change
+ * @param {Array} selected the current selection
+ * @param {Object} changed an object containing two properties `selected` and `deselected` - both arrays of node IDs, which were selected or deselected since the last changed event
+ * @param {Object} event the event (if any) that triggered this changed event
+ * @plugin changed
+ */
+ parent.trigger.call(this, ev, data);
+ };
+ this.refresh = function (skip_loading, forget_state) {
+ last = [];
+ return parent.refresh.apply(this, arguments);
+ };
+ };
+
+/**
+ * ### Checkbox plugin
+ *
+ * This plugin renders checkbox icons in front of each node, making multiple selection much easier.
+ * It also supports tri-state behavior, meaning that if a node has a few of its children checked it will be rendered as undetermined, and state will be propagated up.
+ */
+
+ var _i = document.createElement('I');
+ _i.className = 'jstree-icon jstree-checkbox';
+ _i.setAttribute('role', 'presentation');
+ /**
+ * stores all defaults for the checkbox plugin
+ * @name $.jstree.defaults.checkbox
+ * @plugin checkbox
+ */
+ $.jstree.defaults.checkbox = {
+ /**
+ * a boolean indicating if checkboxes should be visible (can be changed at a later time using `show_checkboxes()` and `hide_checkboxes`). Defaults to `true`.
+ * @name $.jstree.defaults.checkbox.visible
+ * @plugin checkbox
+ */
+ visible : true,
+ /**
+ * a boolean indicating if checkboxes should cascade down and have an undetermined state. Defaults to `true`.
+ * @name $.jstree.defaults.checkbox.three_state
+ * @plugin checkbox
+ */
+ three_state : true,
+ /**
+ * a boolean indicating if clicking anywhere on the node should act as clicking on the checkbox. Defaults to `true`.
+ * @name $.jstree.defaults.checkbox.whole_node
+ * @plugin checkbox
+ */
+ whole_node : true,
+ /**
+ * a boolean indicating if the selected style of a node should be kept, or removed. Defaults to `true`.
+ * @name $.jstree.defaults.checkbox.keep_selected_style
+ * @plugin checkbox
+ */
+ keep_selected_style : true,
+ /**
+ * This setting controls how cascading and undetermined nodes are applied.
+ * If 'up' is in the string - cascading up is enabled, if 'down' is in the string - cascading down is enabled, if 'undetermined' is in the string - undetermined nodes will be used.
+ * If `three_state` is set to `true` this setting is automatically set to 'up+down+undetermined'. Defaults to ''.
+ * @name $.jstree.defaults.checkbox.cascade
+ * @plugin checkbox
+ */
+ cascade : '',
+ /**
+ * This setting controls if checkbox are bound to the general tree selection or to an internal array maintained by the checkbox plugin. Defaults to `true`, only set to `false` if you know exactly what you are doing.
+ * @name $.jstree.defaults.checkbox.tie_selection
+ * @plugin checkbox
+ */
+ tie_selection : true
+ };
+ $.jstree.plugins.checkbox = function (options, parent) {
+ this.bind = function () {
+ parent.bind.call(this);
+ this._data.checkbox.uto = false;
+ this._data.checkbox.selected = [];
+ if(this.settings.checkbox.three_state) {
+ this.settings.checkbox.cascade = 'up+down+undetermined';
+ }
+ this.element
+ .on("init.jstree", $.proxy(function () {
+ this._data.checkbox.visible = this.settings.checkbox.visible;
+ if(!this.settings.checkbox.keep_selected_style) {
+ this.element.addClass('jstree-checkbox-no-clicked');
+ }
+ if(this.settings.checkbox.tie_selection) {
+ this.element.addClass('jstree-checkbox-selection');
+ }
+ }, this))
+ .on("loading.jstree", $.proxy(function () {
+ this[ this._data.checkbox.visible ? 'show_checkboxes' : 'hide_checkboxes' ]();
+ }, this));
+ if(this.settings.checkbox.cascade.indexOf('undetermined') !== -1) {
+ this.element
+ .on('changed.jstree uncheck_node.jstree check_node.jstree uncheck_all.jstree check_all.jstree move_node.jstree copy_node.jstree redraw.jstree open_node.jstree', $.proxy(function () {
+ // only if undetermined is in setting
+ if(this._data.checkbox.uto) { clearTimeout(this._data.checkbox.uto); }
+ this._data.checkbox.uto = setTimeout($.proxy(this._undetermined, this), 50);
+ }, this));
+ }
+ if(!this.settings.checkbox.tie_selection) {
+ this.element
+ .on('model.jstree', $.proxy(function (e, data) {
+ var m = this._model.data,
+ p = m[data.parent],
+ dpc = data.nodes,
+ i, j;
+ for(i = 0, j = dpc.length; i < j; i++) {
+ m[dpc[i]].state.checked = m[dpc[i]].state.checked || (m[dpc[i]].original && m[dpc[i]].original.state && m[dpc[i]].original.state.checked);
+ if(m[dpc[i]].state.checked) {
+ this._data.checkbox.selected.push(dpc[i]);
+ }
+ }
+ }, this));
+ }
+ if(this.settings.checkbox.cascade.indexOf('up') !== -1 || this.settings.checkbox.cascade.indexOf('down') !== -1) {
+ this.element
+ .on('model.jstree', $.proxy(function (e, data) {
+ var m = this._model.data,
+ p = m[data.parent],
+ dpc = data.nodes,
+ chd = [],
+ c, i, j, k, l, tmp, s = this.settings.checkbox.cascade, t = this.settings.checkbox.tie_selection;
+
+ if(s.indexOf('down') !== -1) {
+ // apply down
+ if(p.state[ t ? 'selected' : 'checked' ]) {
+ for(i = 0, j = dpc.length; i < j; i++) {
+ m[dpc[i]].state[ t ? 'selected' : 'checked' ] = true;
+ }
+ this._data[ t ? 'core' : 'checkbox' ].selected = this._data[ t ? 'core' : 'checkbox' ].selected.concat(dpc);
+ }
+ else {
+ for(i = 0, j = dpc.length; i < j; i++) {
+ if(m[dpc[i]].state[ t ? 'selected' : 'checked' ]) {
+ for(k = 0, l = m[dpc[i]].children_d.length; k < l; k++) {
+ m[m[dpc[i]].children_d[k]].state[ t ? 'selected' : 'checked' ] = true;
+ }
+ this._data[ t ? 'core' : 'checkbox' ].selected = this._data[ t ? 'core' : 'checkbox' ].selected.concat(m[dpc[i]].children_d);
+ }
+ }
+ }
+ }
+
+ if(s.indexOf('up') !== -1) {
+ // apply up
+ for(i = 0, j = p.children_d.length; i < j; i++) {
+ if(!m[p.children_d[i]].children.length) {
+ chd.push(m[p.children_d[i]].parent);
+ }
+ }
+ chd = $.vakata.array_unique(chd);
+ for(k = 0, l = chd.length; k < l; k++) {
+ p = m[chd[k]];
+ while(p && p.id !== $.jstree.root) {
+ c = 0;
+ for(i = 0, j = p.children.length; i < j; i++) {
+ c += m[p.children[i]].state[ t ? 'selected' : 'checked' ];
+ }
+ if(c === j) {
+ p.state[ t ? 'selected' : 'checked' ] = true;
+ this._data[ t ? 'core' : 'checkbox' ].selected.push(p.id);
+ tmp = this.get_node(p, true);
+ if(tmp && tmp.length) {
+ tmp.attr('aria-selected', true).children('.jstree-anchor').addClass( t ? 'jstree-clicked' : 'jstree-checked');
+ }
+ }
+ else {
+ break;
+ }
+ p = this.get_node(p.parent);
+ }
+ }
+ }
+
+ this._data[ t ? 'core' : 'checkbox' ].selected = $.vakata.array_unique(this._data[ t ? 'core' : 'checkbox' ].selected);
+ }, this))
+ .on(this.settings.checkbox.tie_selection ? 'select_node.jstree' : 'check_node.jstree', $.proxy(function (e, data) {
+ var obj = data.node,
+ m = this._model.data,
+ par = this.get_node(obj.parent),
+ dom = this.get_node(obj, true),
+ i, j, c, tmp, s = this.settings.checkbox.cascade, t = this.settings.checkbox.tie_selection,
+ sel = {}, cur = this._data[ t ? 'core' : 'checkbox' ].selected;
+
+ for (i = 0, j = cur.length; i < j; i++) {
+ sel[cur[i]] = true;
+ }
+ // apply down
+ if(s.indexOf('down') !== -1) {
+ //this._data[ t ? 'core' : 'checkbox' ].selected = $.vakata.array_unique(this._data[ t ? 'core' : 'checkbox' ].selected.concat(obj.children_d));
+ for(i = 0, j = obj.children_d.length; i < j; i++) {
+ sel[obj.children_d[i]] = true;
+ tmp = m[obj.children_d[i]];
+ tmp.state[ t ? 'selected' : 'checked' ] = true;
+ if(tmp && tmp.original && tmp.original.state && tmp.original.state.undetermined) {
+ tmp.original.state.undetermined = false;
+ }
+ }
+ }
+
+ // apply up
+ if(s.indexOf('up') !== -1) {
+ while(par && par.id !== $.jstree.root) {
+ c = 0;
+ for(i = 0, j = par.children.length; i < j; i++) {
+ c += m[par.children[i]].state[ t ? 'selected' : 'checked' ];
+ }
+ if(c === j) {
+ par.state[ t ? 'selected' : 'checked' ] = true;
+ sel[par.id] = true;
+ //this._data[ t ? 'core' : 'checkbox' ].selected.push(par.id);
+ tmp = this.get_node(par, true);
+ if(tmp && tmp.length) {
+ tmp.attr('aria-selected', true).children('.jstree-anchor').addClass(t ? 'jstree-clicked' : 'jstree-checked');
+ }
+ }
+ else {
+ break;
+ }
+ par = this.get_node(par.parent);
+ }
+ }
+
+ cur = [];
+ for (i in sel) {
+ if (sel.hasOwnProperty(i)) {
+ cur.push(i);
+ }
+ }
+ this._data[ t ? 'core' : 'checkbox' ].selected = cur;
+
+ // apply down (process .children separately?)
+ if(s.indexOf('down') !== -1 && dom.length) {
+ dom.find('.jstree-anchor').addClass(t ? 'jstree-clicked' : 'jstree-checked').parent().attr('aria-selected', true);
+ }
+ }, this))
+ .on(this.settings.checkbox.tie_selection ? 'deselect_all.jstree' : 'uncheck_all.jstree', $.proxy(function (e, data) {
+ var obj = this.get_node($.jstree.root),
+ m = this._model.data,
+ i, j, tmp;
+ for(i = 0, j = obj.children_d.length; i < j; i++) {
+ tmp = m[obj.children_d[i]];
+ if(tmp && tmp.original && tmp.original.state && tmp.original.state.undetermined) {
+ tmp.original.state.undetermined = false;
+ }
+ }
+ }, this))
+ .on(this.settings.checkbox.tie_selection ? 'deselect_node.jstree' : 'uncheck_node.jstree', $.proxy(function (e, data) {
+ var obj = data.node,
+ dom = this.get_node(obj, true),
+ i, j, tmp, s = this.settings.checkbox.cascade, t = this.settings.checkbox.tie_selection,
+ cur = this._data[ t ? 'core' : 'checkbox' ].selected, sel = {};
+ if(obj && obj.original && obj.original.state && obj.original.state.undetermined) {
+ obj.original.state.undetermined = false;
+ }
+
+ // apply down
+ if(s.indexOf('down') !== -1) {
+ for(i = 0, j = obj.children_d.length; i < j; i++) {
+ tmp = this._model.data[obj.children_d[i]];
+ tmp.state[ t ? 'selected' : 'checked' ] = false;
+ if(tmp && tmp.original && tmp.original.state && tmp.original.state.undetermined) {
+ tmp.original.state.undetermined = false;
+ }
+ }
+ }
+
+ // apply up
+ if(s.indexOf('up') !== -1) {
+ for(i = 0, j = obj.parents.length; i < j; i++) {
+ tmp = this._model.data[obj.parents[i]];
+ tmp.state[ t ? 'selected' : 'checked' ] = false;
+ if(tmp && tmp.original && tmp.original.state && tmp.original.state.undetermined) {
+ tmp.original.state.undetermined = false;
+ }
+ tmp = this.get_node(obj.parents[i], true);
+ if(tmp && tmp.length) {
+ tmp.attr('aria-selected', false).children('.jstree-anchor').removeClass(t ? 'jstree-clicked' : 'jstree-checked');
+ }
+ }
+ }
+ sel = {};
+ for(i = 0, j = cur.length; i < j; i++) {
+ // apply down + apply up
+ if(
+ (s.indexOf('down') === -1 || $.inArray(cur[i], obj.children_d) === -1) &&
+ (s.indexOf('up') === -1 || $.inArray(cur[i], obj.parents) === -1)
+ ) {
+ sel[cur[i]] = true;
+ }
+ }
+ cur = [];
+ for (i in sel) {
+ if (sel.hasOwnProperty(i)) {
+ cur.push(i);
+ }
+ }
+ this._data[ t ? 'core' : 'checkbox' ].selected = cur;
+
+ // apply down (process .children separately?)
+ if(s.indexOf('down') !== -1 && dom.length) {
+ dom.find('.jstree-anchor').removeClass(t ? 'jstree-clicked' : 'jstree-checked').parent().attr('aria-selected', false);
+ }
+ }, this));
+ }
+ if(this.settings.checkbox.cascade.indexOf('up') !== -1) {
+ this.element
+ .on('delete_node.jstree', $.proxy(function (e, data) {
+ // apply up (whole handler)
+ var p = this.get_node(data.parent),
+ m = this._model.data,
+ i, j, c, tmp, t = this.settings.checkbox.tie_selection;
+ while(p && p.id !== $.jstree.root && !p.state[ t ? 'selected' : 'checked' ]) {
+ c = 0;
+ for(i = 0, j = p.children.length; i < j; i++) {
+ c += m[p.children[i]].state[ t ? 'selected' : 'checked' ];
+ }
+ if(j > 0 && c === j) {
+ p.state[ t ? 'selected' : 'checked' ] = true;
+ this._data[ t ? 'core' : 'checkbox' ].selected.push(p.id);
+ tmp = this.get_node(p, true);
+ if(tmp && tmp.length) {
+ tmp.attr('aria-selected', true).children('.jstree-anchor').addClass(t ? 'jstree-clicked' : 'jstree-checked');
+ }
+ }
+ else {
+ break;
+ }
+ p = this.get_node(p.parent);
+ }
+ }, this))
+ .on('move_node.jstree', $.proxy(function (e, data) {
+ // apply up (whole handler)
+ var is_multi = data.is_multi,
+ old_par = data.old_parent,
+ new_par = this.get_node(data.parent),
+ m = this._model.data,
+ p, c, i, j, tmp, t = this.settings.checkbox.tie_selection;
+ if(!is_multi) {
+ p = this.get_node(old_par);
+ while(p && p.id !== $.jstree.root && !p.state[ t ? 'selected' : 'checked' ]) {
+ c = 0;
+ for(i = 0, j = p.children.length; i < j; i++) {
+ c += m[p.children[i]].state[ t ? 'selected' : 'checked' ];
+ }
+ if(j > 0 && c === j) {
+ p.state[ t ? 'selected' : 'checked' ] = true;
+ this._data[ t ? 'core' : 'checkbox' ].selected.push(p.id);
+ tmp = this.get_node(p, true);
+ if(tmp && tmp.length) {
+ tmp.attr('aria-selected', true).children('.jstree-anchor').addClass(t ? 'jstree-clicked' : 'jstree-checked');
+ }
+ }
+ else {
+ break;
+ }
+ p = this.get_node(p.parent);
+ }
+ }
+ p = new_par;
+ while(p && p.id !== $.jstree.root) {
+ c = 0;
+ for(i = 0, j = p.children.length; i < j; i++) {
+ c += m[p.children[i]].state[ t ? 'selected' : 'checked' ];
+ }
+ if(c === j) {
+ if(!p.state[ t ? 'selected' : 'checked' ]) {
+ p.state[ t ? 'selected' : 'checked' ] = true;
+ this._data[ t ? 'core' : 'checkbox' ].selected.push(p.id);
+ tmp = this.get_node(p, true);
+ if(tmp && tmp.length) {
+ tmp.attr('aria-selected', true).children('.jstree-anchor').addClass(t ? 'jstree-clicked' : 'jstree-checked');
+ }
+ }
+ }
+ else {
+ if(p.state[ t ? 'selected' : 'checked' ]) {
+ p.state[ t ? 'selected' : 'checked' ] = false;
+ this._data[ t ? 'core' : 'checkbox' ].selected = $.vakata.array_remove_item(this._data[ t ? 'core' : 'checkbox' ].selected, p.id);
+ tmp = this.get_node(p, true);
+ if(tmp && tmp.length) {
+ tmp.attr('aria-selected', false).children('.jstree-anchor').removeClass(t ? 'jstree-clicked' : 'jstree-checked');
+ }
+ }
+ else {
+ break;
+ }
+ }
+ p = this.get_node(p.parent);
+ }
+ }, this));
+ }
+ };
+ /**
+ * set the undetermined state where and if necessary. Used internally.
+ * @private
+ * @name _undetermined()
+ * @plugin checkbox
+ */
+ this._undetermined = function () {
+ if(this.element === null) { return; }
+ var i, j, k, l, o = {}, m = this._model.data, t = this.settings.checkbox.tie_selection, s = this._data[ t ? 'core' : 'checkbox' ].selected, p = [], tt = this;
+ for(i = 0, j = s.length; i < j; i++) {
+ if(m[s[i]] && m[s[i]].parents) {
+ for(k = 0, l = m[s[i]].parents.length; k < l; k++) {
+ if(o[m[s[i]].parents[k]] !== undefined) {
+ break;
+ }
+ if(m[s[i]].parents[k] !== $.jstree.root) {
+ o[m[s[i]].parents[k]] = true;
+ p.push(m[s[i]].parents[k]);
+ }
+ }
+ }
+ }
+ // attempt for server side undetermined state
+ this.element.find('.jstree-closed').not(':has(.jstree-children)')
+ .each(function () {
+ var tmp = tt.get_node(this), tmp2;
+ if(!tmp.state.loaded) {
+ if(tmp.original && tmp.original.state && tmp.original.state.undetermined && tmp.original.state.undetermined === true) {
+ if(o[tmp.id] === undefined && tmp.id !== $.jstree.root) {
+ o[tmp.id] = true;
+ p.push(tmp.id);
+ }
+ for(k = 0, l = tmp.parents.length; k < l; k++) {
+ if(o[tmp.parents[k]] === undefined && tmp.parents[k] !== $.jstree.root) {
+ o[tmp.parents[k]] = true;
+ p.push(tmp.parents[k]);
+ }
+ }
+ }
+ }
+ else {
+ for(i = 0, j = tmp.children_d.length; i < j; i++) {
+ tmp2 = m[tmp.children_d[i]];
+ if(!tmp2.state.loaded && tmp2.original && tmp2.original.state && tmp2.original.state.undetermined && tmp2.original.state.undetermined === true) {
+ if(o[tmp2.id] === undefined && tmp2.id !== $.jstree.root) {
+ o[tmp2.id] = true;
+ p.push(tmp2.id);
+ }
+ for(k = 0, l = tmp2.parents.length; k < l; k++) {
+ if(o[tmp2.parents[k]] === undefined && tmp2.parents[k] !== $.jstree.root) {
+ o[tmp2.parents[k]] = true;
+ p.push(tmp2.parents[k]);
+ }
+ }
+ }
+ }
+ }
+ });
+
+ this.element.find('.jstree-undetermined').removeClass('jstree-undetermined');
+ for(i = 0, j = p.length; i < j; i++) {
+ if(!m[p[i]].state[ t ? 'selected' : 'checked' ]) {
+ s = this.get_node(p[i], true);
+ if(s && s.length) {
+ s.children('.jstree-anchor').children('.jstree-checkbox').addClass('jstree-undetermined');
+ }
+ }
+ }
+ };
+ this.redraw_node = function(obj, deep, is_callback, force_render) {
+ obj = parent.redraw_node.apply(this, arguments);
+ if(obj) {
+ var i, j, tmp = null, icon = null;
+ for(i = 0, j = obj.childNodes.length; i < j; i++) {
+ if(obj.childNodes[i] && obj.childNodes[i].className && obj.childNodes[i].className.indexOf("jstree-anchor") !== -1) {
+ tmp = obj.childNodes[i];
+ break;
+ }
+ }
+ if(tmp) {
+ if(!this.settings.checkbox.tie_selection && this._model.data[obj.id].state.checked) { tmp.className += ' jstree-checked'; }
+ icon = _i.cloneNode(false);
+ if(this._model.data[obj.id].state.checkbox_disabled) { icon.className += ' jstree-checkbox-disabled'; }
+ tmp.insertBefore(icon, tmp.childNodes[0]);
+ }
+ }
+ if(!is_callback && this.settings.checkbox.cascade.indexOf('undetermined') !== -1) {
+ if(this._data.checkbox.uto) { clearTimeout(this._data.checkbox.uto); }
+ this._data.checkbox.uto = setTimeout($.proxy(this._undetermined, this), 50);
+ }
+ return obj;
+ };
+ /**
+ * show the node checkbox icons
+ * @name show_checkboxes()
+ * @plugin checkbox
+ */
+ this.show_checkboxes = function () { this._data.core.themes.checkboxes = true; this.get_container_ul().removeClass("jstree-no-checkboxes"); };
+ /**
+ * hide the node checkbox icons
+ * @name hide_checkboxes()
+ * @plugin checkbox
+ */
+ this.hide_checkboxes = function () { this._data.core.themes.checkboxes = false; this.get_container_ul().addClass("jstree-no-checkboxes"); };
+ /**
+ * toggle the node icons
+ * @name toggle_checkboxes()
+ * @plugin checkbox
+ */
+ this.toggle_checkboxes = function () { if(this._data.core.themes.checkboxes) { this.hide_checkboxes(); } else { this.show_checkboxes(); } };
+ /**
+ * checks if a node is in an undetermined state
+ * @name is_undetermined(obj)
+ * @param {mixed} obj
+ * @return {Boolean}
+ */
+ this.is_undetermined = function (obj) {
+ obj = this.get_node(obj);
+ var s = this.settings.checkbox.cascade, i, j, t = this.settings.checkbox.tie_selection, d = this._data[ t ? 'core' : 'checkbox' ].selected, m = this._model.data;
+ if(!obj || obj.state[ t ? 'selected' : 'checked' ] === true || s.indexOf('undetermined') === -1 || (s.indexOf('down') === -1 && s.indexOf('up') === -1)) {
+ return false;
+ }
+ if(!obj.state.loaded && obj.original.state.undetermined === true) {
+ return true;
+ }
+ for(i = 0, j = obj.children_d.length; i < j; i++) {
+ if($.inArray(obj.children_d[i], d) !== -1 || (!m[obj.children_d[i]].state.loaded && m[obj.children_d[i]].original.state.undetermined)) {
+ return true;
+ }
+ }
+ return false;
+ };
+ /**
+ * disable a node's checkbox
+ * @name disable_checkbox(obj)
+ * @param {mixed} obj an array can be used too
+ * @trigger disable_checkbox.jstree
+ * @plugin checkbox
+ */
+ this.disable_checkbox = function (obj) {
+ var t1, t2, dom;
+ if($.isArray(obj)) {
+ obj = obj.slice();
+ for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
+ this.disable_checkbox(obj[t1]);
+ }
+ return true;
+ }
+ obj = this.get_node(obj);
+ if(!obj || obj.id === $.jstree.root) {
+ return false;
+ }
+ dom = this.get_node(obj, true);
+ if(!obj.state.checkbox_disabled) {
+ obj.state.checkbox_disabled = true;
+ if(dom && dom.length) {
+ dom.children('.jstree-anchor').children('.jstree-checkbox').addClass('jstree-checkbox-disabled');
+ }
+ /**
+ * triggered when an node's checkbox is disabled
+ * @event
+ * @name disable_checkbox.jstree
+ * @param {Object} node
+ * @plugin checkbox
+ */
+ this.trigger('disable_checkbox', { 'node' : obj });
+ }
+ };
+ /**
+ * enable a node's checkbox
+ * @name disable_checkbox(obj)
+ * @param {mixed} obj an array can be used too
+ * @trigger enable_checkbox.jstree
+ * @plugin checkbox
+ */
+ this.enable_checkbox = function (obj) {
+ var t1, t2, dom;
+ if($.isArray(obj)) {
+ obj = obj.slice();
+ for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
+ this.enable_checkbox(obj[t1]);
+ }
+ return true;
+ }
+ obj = this.get_node(obj);
+ if(!obj || obj.id === $.jstree.root) {
+ return false;
+ }
+ dom = this.get_node(obj, true);
+ if(obj.state.checkbox_disabled) {
+ obj.state.checkbox_disabled = false;
+ if(dom && dom.length) {
+ dom.children('.jstree-anchor').children('.jstree-checkbox').removeClass('jstree-checkbox-disabled');
+ }
+ /**
+ * triggered when an node's checkbox is enabled
+ * @event
+ * @name enable_checkbox.jstree
+ * @param {Object} node
+ * @plugin checkbox
+ */
+ this.trigger('enable_checkbox', { 'node' : obj });
+ }
+ };
+
+ this.activate_node = function (obj, e) {
+ if($(e.target).hasClass('jstree-checkbox-disabled')) {
+ return false;
+ }
+ if(this.settings.checkbox.tie_selection && (this.settings.checkbox.whole_node || $(e.target).hasClass('jstree-checkbox'))) {
+ e.ctrlKey = true;
+ }
+ if(this.settings.checkbox.tie_selection || (!this.settings.checkbox.whole_node && !$(e.target).hasClass('jstree-checkbox'))) {
+ return parent.activate_node.call(this, obj, e);
+ }
+ if(this.is_disabled(obj)) {
+ return false;
+ }
+ if(this.is_checked(obj)) {
+ this.uncheck_node(obj, e);
+ }
+ else {
+ this.check_node(obj, e);
+ }
+ this.trigger('activate_node', { 'node' : this.get_node(obj) });
+ };
+
+ /**
+ * check a node (only if tie_selection in checkbox settings is false, otherwise select_node will be called internally)
+ * @name check_node(obj)
+ * @param {mixed} obj an array can be used to check multiple nodes
+ * @trigger check_node.jstree
+ * @plugin checkbox
+ */
+ this.check_node = function (obj, e) {
+ if(this.settings.checkbox.tie_selection) { return this.select_node(obj, false, true, e); }
+ var dom, t1, t2, th;
+ if($.isArray(obj)) {
+ obj = obj.slice();
+ for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
+ this.check_node(obj[t1], e);
+ }
+ return true;
+ }
+ obj = this.get_node(obj);
+ if(!obj || obj.id === $.jstree.root) {
+ return false;
+ }
+ dom = this.get_node(obj, true);
+ if(!obj.state.checked) {
+ obj.state.checked = true;
+ this._data.checkbox.selected.push(obj.id);
+ if(dom && dom.length) {
+ dom.children('.jstree-anchor').addClass('jstree-checked');
+ }
+ /**
+ * triggered when an node is checked (only if tie_selection in checkbox settings is false)
+ * @event
+ * @name check_node.jstree
+ * @param {Object} node
+ * @param {Array} selected the current selection
+ * @param {Object} event the event (if any) that triggered this check_node
+ * @plugin checkbox
+ */
+ this.trigger('check_node', { 'node' : obj, 'selected' : this._data.checkbox.selected, 'event' : e });
+ }
+ };
+ /**
+ * uncheck a node (only if tie_selection in checkbox settings is false, otherwise deselect_node will be called internally)
+ * @name uncheck_node(obj)
+ * @param {mixed} obj an array can be used to uncheck multiple nodes
+ * @trigger uncheck_node.jstree
+ * @plugin checkbox
+ */
+ this.uncheck_node = function (obj, e) {
+ if(this.settings.checkbox.tie_selection) { return this.deselect_node(obj, false, e); }
+ var t1, t2, dom;
+ if($.isArray(obj)) {
+ obj = obj.slice();
+ for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
+ this.uncheck_node(obj[t1], e);
+ }
+ return true;
+ }
+ obj = this.get_node(obj);
+ if(!obj || obj.id === $.jstree.root) {
+ return false;
+ }
+ dom = this.get_node(obj, true);
+ if(obj.state.checked) {
+ obj.state.checked = false;
+ this._data.checkbox.selected = $.vakata.array_remove_item(this._data.checkbox.selected, obj.id);
+ if(dom.length) {
+ dom.children('.jstree-anchor').removeClass('jstree-checked');
+ }
+ /**
+ * triggered when an node is unchecked (only if tie_selection in checkbox settings is false)
+ * @event
+ * @name uncheck_node.jstree
+ * @param {Object} node
+ * @param {Array} selected the current selection
+ * @param {Object} event the event (if any) that triggered this uncheck_node
+ * @plugin checkbox
+ */
+ this.trigger('uncheck_node', { 'node' : obj, 'selected' : this._data.checkbox.selected, 'event' : e });
+ }
+ };
+ /**
+ * checks all nodes in the tree (only if tie_selection in checkbox settings is false, otherwise select_all will be called internally)
+ * @name check_all()
+ * @trigger check_all.jstree, changed.jstree
+ * @plugin checkbox
+ */
+ this.check_all = function () {
+ if(this.settings.checkbox.tie_selection) { return this.select_all(); }
+ var tmp = this._data.checkbox.selected.concat([]), i, j;
+ this._data.checkbox.selected = this._model.data[$.jstree.root].children_d.concat();
+ for(i = 0, j = this._data.checkbox.selected.length; i < j; i++) {
+ if(this._model.data[this._data.checkbox.selected[i]]) {
+ this._model.data[this._data.checkbox.selected[i]].state.checked = true;
+ }
+ }
+ this.redraw(true);
+ /**
+ * triggered when all nodes are checked (only if tie_selection in checkbox settings is false)
+ * @event
+ * @name check_all.jstree
+ * @param {Array} selected the current selection
+ * @plugin checkbox
+ */
+ this.trigger('check_all', { 'selected' : this._data.checkbox.selected });
+ };
+ /**
+ * uncheck all checked nodes (only if tie_selection in checkbox settings is false, otherwise deselect_all will be called internally)
+ * @name uncheck_all()
+ * @trigger uncheck_all.jstree
+ * @plugin checkbox
+ */
+ this.uncheck_all = function () {
+ if(this.settings.checkbox.tie_selection) { return this.deselect_all(); }
+ var tmp = this._data.checkbox.selected.concat([]), i, j;
+ for(i = 0, j = this._data.checkbox.selected.length; i < j; i++) {
+ if(this._model.data[this._data.checkbox.selected[i]]) {
+ this._model.data[this._data.checkbox.selected[i]].state.checked = false;
+ }
+ }
+ this._data.checkbox.selected = [];
+ this.element.find('.jstree-checked').removeClass('jstree-checked');
+ /**
+ * triggered when all nodes are unchecked (only if tie_selection in checkbox settings is false)
+ * @event
+ * @name uncheck_all.jstree
+ * @param {Object} node the previous selection
+ * @param {Array} selected the current selection
+ * @plugin checkbox
+ */
+ this.trigger('uncheck_all', { 'selected' : this._data.checkbox.selected, 'node' : tmp });
+ };
+ /**
+ * checks if a node is checked (if tie_selection is on in the settings this function will return the same as is_selected)
+ * @name is_checked(obj)
+ * @param {mixed} obj
+ * @return {Boolean}
+ * @plugin checkbox
+ */
+ this.is_checked = function (obj) {
+ if(this.settings.checkbox.tie_selection) { return this.is_selected(obj); }
+ obj = this.get_node(obj);
+ if(!obj || obj.id === $.jstree.root) { return false; }
+ return obj.state.checked;
+ };
+ /**
+ * get an array of all checked nodes (if tie_selection is on in the settings this function will return the same as get_selected)
+ * @name get_checked([full])
+ * @param {mixed} full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned
+ * @return {Array}
+ * @plugin checkbox
+ */
+ this.get_checked = function (full) {
+ if(this.settings.checkbox.tie_selection) { return this.get_selected(full); }
+ return full ? $.map(this._data.checkbox.selected, $.proxy(function (i) { return this.get_node(i); }, this)) : this._data.checkbox.selected;
+ };
+ /**
+ * get an array of all top level checked nodes (ignoring children of checked nodes) (if tie_selection is on in the settings this function will return the same as get_top_selected)
+ * @name get_top_checked([full])
+ * @param {mixed} full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned
+ * @return {Array}
+ * @plugin checkbox
+ */
+ this.get_top_checked = function (full) {
+ if(this.settings.checkbox.tie_selection) { return this.get_top_selected(full); }
+ var tmp = this.get_checked(true),
+ obj = {}, i, j, k, l;
+ for(i = 0, j = tmp.length; i < j; i++) {
+ obj[tmp[i].id] = tmp[i];
+ }
+ for(i = 0, j = tmp.length; i < j; i++) {
+ for(k = 0, l = tmp[i].children_d.length; k < l; k++) {
+ if(obj[tmp[i].children_d[k]]) {
+ delete obj[tmp[i].children_d[k]];
+ }
+ }
+ }
+ tmp = [];
+ for(i in obj) {
+ if(obj.hasOwnProperty(i)) {
+ tmp.push(i);
+ }
+ }
+ return full ? $.map(tmp, $.proxy(function (i) { return this.get_node(i); }, this)) : tmp;
+ };
+ /**
+ * get an array of all bottom level checked nodes (ignoring selected parents) (if tie_selection is on in the settings this function will return the same as get_bottom_selected)
+ * @name get_bottom_checked([full])
+ * @param {mixed} full if set to `true` the returned array will consist of the full node objects, otherwise - only IDs will be returned
+ * @return {Array}
+ * @plugin checkbox
+ */
+ this.get_bottom_checked = function (full) {
+ if(this.settings.checkbox.tie_selection) { return this.get_bottom_selected(full); }
+ var tmp = this.get_checked(true),
+ obj = [], i, j;
+ for(i = 0, j = tmp.length; i < j; i++) {
+ if(!tmp[i].children.length) {
+ obj.push(tmp[i].id);
+ }
+ }
+ return full ? $.map(obj, $.proxy(function (i) { return this.get_node(i); }, this)) : obj;
+ };
+ this.load_node = function (obj, callback) {
+ var k, l, i, j, c, tmp;
+ if(!$.isArray(obj) && !this.settings.checkbox.tie_selection) {
+ tmp = this.get_node(obj);
+ if(tmp && tmp.state.loaded) {
+ for(k = 0, l = tmp.children_d.length; k < l; k++) {
+ if(this._model.data[tmp.children_d[k]].state.checked) {
+ c = true;
+ this._data.checkbox.selected = $.vakata.array_remove_item(this._data.checkbox.selected, tmp.children_d[k]);
+ }
+ }
+ }
+ }
+ return parent.load_node.apply(this, arguments);
+ };
+ this.get_state = function () {
+ var state = parent.get_state.apply(this, arguments);
+ if(this.settings.checkbox.tie_selection) { return state; }
+ state.checkbox = this._data.checkbox.selected.slice();
+ return state;
+ };
+ this.set_state = function (state, callback) {
+ var res = parent.set_state.apply(this, arguments);
+ if(res && state.checkbox) {
+ if(!this.settings.checkbox.tie_selection) {
+ this.uncheck_all();
+ var _this = this;
+ $.each(state.checkbox, function (i, v) {
+ _this.check_node(v);
+ });
+ }
+ delete state.checkbox;
+ this.set_state(state, callback);
+ return false;
+ }
+ return res;
+ };
+ this.refresh = function (skip_loading, forget_state) {
+ if(!this.settings.checkbox.tie_selection) {
+ this._data.checkbox.selected = [];
+ }
+ return parent.refresh.apply(this, arguments);
+ };
+ };
+
+ // include the checkbox plugin by default
+ // $.jstree.defaults.plugins.push("checkbox");
+
+/**
+ * ### Conditionalselect plugin
+ *
+ * This plugin allows defining a callback to allow or deny node selection by user input (activate node method).
+ */
+
+ /**
+ * a callback (function) which is invoked in the instance's scope and receives two arguments - the node and the event that triggered the `activate_node` call. Returning false prevents working with the node, returning true allows invoking activate_node. Defaults to returning `true`.
+ * @name $.jstree.defaults.checkbox.visible
+ * @plugin checkbox
+ */
+ $.jstree.defaults.conditionalselect = function () { return true; };
+ $.jstree.plugins.conditionalselect = function (options, parent) {
+ // own function
+ this.activate_node = function (obj, e) {
+ if(this.settings.conditionalselect.call(this, this.get_node(obj), e)) {
+ parent.activate_node.call(this, obj, e);
+ }
+ };
+ };
+
+
+/**
+ * ### Contextmenu plugin
+ *
+ * Shows a context menu when a node is right-clicked.
+ */
+
+ /**
+ * stores all defaults for the contextmenu plugin
+ * @name $.jstree.defaults.contextmenu
+ * @plugin contextmenu
+ */
+ $.jstree.defaults.contextmenu = {
+ /**
+ * a boolean indicating if the node should be selected when the context menu is invoked on it. Defaults to `true`.
+ * @name $.jstree.defaults.contextmenu.select_node
+ * @plugin contextmenu
+ */
+ select_node : true,
+ /**
+ * a boolean indicating if the menu should be shown aligned with the node. Defaults to `true`, otherwise the mouse coordinates are used.
+ * @name $.jstree.defaults.contextmenu.show_at_node
+ * @plugin contextmenu
+ */
+ show_at_node : true,
+ /**
+ * an object of actions, or a function that accepts a node and a callback function and calls the callback function with an object of actions available for that node (you can also return the items too).
+ *
+ * Each action consists of a key (a unique name) and a value which is an object with the following properties (only label and action are required). Once a menu item is activated the `action` function will be invoked with an object containing the following keys: item - the contextmenu item definition as seen below, reference - the DOM node that was used (the tree node), element - the contextmenu DOM element, position - an object with x/y properties indicating the position of the menu.
+ *
+ * * `separator_before` - a boolean indicating if there should be a separator before this item
+ * * `separator_after` - a boolean indicating if there should be a separator after this item
+ * * `_disabled` - a boolean indicating if this action should be disabled
+ * * `label` - a string - the name of the action (could be a function returning a string)
+ * * `title` - a string - an optional tooltip for the item
+ * * `action` - a function to be executed if this item is chosen, the function will receive
+ * * `icon` - a string, can be a path to an icon or a className, if using an image that is in the current directory use a `./` prefix, otherwise it will be detected as a class
+ * * `shortcut` - keyCode which will trigger the action if the menu is open (for example `113` for rename, which equals F2)
+ * * `shortcut_label` - shortcut label (like for example `F2` for rename)
+ * * `submenu` - an object with the same structure as $.jstree.defaults.contextmenu.items which can be used to create a submenu - each key will be rendered as a separate option in a submenu that will appear once the current item is hovered
+ *
+ * @name $.jstree.defaults.contextmenu.items
+ * @plugin contextmenu
+ */
+ items : function (o, cb) { // Could be an object directly
+ return {
+ "create" : {
+ "separator_before" : false,
+ "separator_after" : true,
+ "_disabled" : false, //(this.check("create_node", data.reference, {}, "last")),
+ "label" : "Create",
+ "action" : function (data) {
+ var inst = $.jstree.reference(data.reference),
+ obj = inst.get_node(data.reference);
+ inst.create_node(obj, {}, "last", function (new_node) {
+ setTimeout(function () { inst.edit(new_node); },0);
+ });
+ }
+ },
+ "rename" : {
+ "separator_before" : false,
+ "separator_after" : false,
+ "_disabled" : false, //(this.check("rename_node", data.reference, this.get_parent(data.reference), "")),
+ "label" : "Rename",
+ /*!
+ "shortcut" : 113,
+ "shortcut_label" : 'F2',
+ "icon" : "glyphicon glyphicon-leaf",
+ */
+ "action" : function (data) {
+ var inst = $.jstree.reference(data.reference),
+ obj = inst.get_node(data.reference);
+ inst.edit(obj);
+ }
+ },
+ "remove" : {
+ "separator_before" : false,
+ "icon" : false,
+ "separator_after" : false,
+ "_disabled" : false, //(this.check("delete_node", data.reference, this.get_parent(data.reference), "")),
+ "label" : "Delete",
+ "action" : function (data) {
+ var inst = $.jstree.reference(data.reference),
+ obj = inst.get_node(data.reference);
+ if(inst.is_selected(obj)) {
+ inst.delete_node(inst.get_selected());
+ }
+ else {
+ inst.delete_node(obj);
+ }
+ }
+ },
+ "ccp" : {
+ "separator_before" : true,
+ "icon" : false,
+ "separator_after" : false,
+ "label" : "Edit",
+ "action" : false,
+ "submenu" : {
+ "cut" : {
+ "separator_before" : false,
+ "separator_after" : false,
+ "label" : "Cut",
+ "action" : function (data) {
+ var inst = $.jstree.reference(data.reference),
+ obj = inst.get_node(data.reference);
+ if(inst.is_selected(obj)) {
+ inst.cut(inst.get_top_selected());
+ }
+ else {
+ inst.cut(obj);
+ }
+ }
+ },
+ "copy" : {
+ "separator_before" : false,
+ "icon" : false,
+ "separator_after" : false,
+ "label" : "Copy",
+ "action" : function (data) {
+ var inst = $.jstree.reference(data.reference),
+ obj = inst.get_node(data.reference);
+ if(inst.is_selected(obj)) {
+ inst.copy(inst.get_top_selected());
+ }
+ else {
+ inst.copy(obj);
+ }
+ }
+ },
+ "paste" : {
+ "separator_before" : false,
+ "icon" : false,
+ "_disabled" : function (data) {
+ return !$.jstree.reference(data.reference).can_paste();
+ },
+ "separator_after" : false,
+ "label" : "Paste",
+ "action" : function (data) {
+ var inst = $.jstree.reference(data.reference),
+ obj = inst.get_node(data.reference);
+ inst.paste(obj);
+ }
+ }
+ }
+ }
+ };
+ }
+ };
+
+ $.jstree.plugins.contextmenu = function (options, parent) {
+ this.bind = function () {
+ parent.bind.call(this);
+
+ var last_ts = 0, cto = null, ex, ey;
+ this.element
+ .on("contextmenu.jstree", ".jstree-anchor", $.proxy(function (e, data) {
+ if (e.target.tagName.toLowerCase() === 'input') {
+ return;
+ }
+ e.preventDefault();
+ last_ts = e.ctrlKey ? +new Date() : 0;
+ if(data || cto) {
+ last_ts = (+new Date()) + 10000;
+ }
+ if(cto) {
+ clearTimeout(cto);
+ }
+ if(!this.is_loading(e.currentTarget)) {
+ this.show_contextmenu(e.currentTarget, e.pageX, e.pageY, e);
+ }
+ }, this))
+ .on("click.jstree", ".jstree-anchor", $.proxy(function (e) {
+ if(this._data.contextmenu.visible && (!last_ts || (+new Date()) - last_ts > 250)) { // work around safari & macOS ctrl+click
+ $.vakata.context.hide();
+ }
+ last_ts = 0;
+ }, this))
+ .on("touchstart.jstree", ".jstree-anchor", function (e) {
+ if(!e.originalEvent || !e.originalEvent.changedTouches || !e.originalEvent.changedTouches[0]) {
+ return;
+ }
+ ex = e.originalEvent.changedTouches[0].clientX;
+ ey = e.originalEvent.changedTouches[0].clientY;
+ cto = setTimeout(function () {
+ $(e.currentTarget).trigger('contextmenu', true);
+ }, 750);
+ })
+ .on('touchmove.vakata.jstree', function (e) {
+ if(cto && e.originalEvent && e.originalEvent.changedTouches && e.originalEvent.changedTouches[0] && (Math.abs(ex - e.originalEvent.changedTouches[0].clientX) > 50 || Math.abs(ey - e.originalEvent.changedTouches[0].clientY) > 50)) {
+ clearTimeout(cto);
+ }
+ })
+ .on('touchend.vakata.jstree', function (e) {
+ if(cto) {
+ clearTimeout(cto);
+ }
+ });
+
+ /*!
+ if(!('oncontextmenu' in document.body) && ('ontouchstart' in document.body)) {
+ var el = null, tm = null;
+ this.element
+ .on("touchstart", ".jstree-anchor", function (e) {
+ el = e.currentTarget;
+ tm = +new Date();
+ $(document).one("touchend", function (e) {
+ e.target = document.elementFromPoint(e.originalEvent.targetTouches[0].pageX - window.pageXOffset, e.originalEvent.targetTouches[0].pageY - window.pageYOffset);
+ e.currentTarget = e.target;
+ tm = ((+(new Date())) - tm);
+ if(e.target === el && tm > 600 && tm < 1000) {
+ e.preventDefault();
+ $(el).trigger('contextmenu', e);
+ }
+ el = null;
+ tm = null;
+ });
+ });
+ }
+ */
+ $(document).on("context_hide.vakata.jstree", $.proxy(function (e, data) {
+ this._data.contextmenu.visible = false;
+ $(data.reference).removeClass('jstree-context');
+ }, this));
+ };
+ this.teardown = function () {
+ if(this._data.contextmenu.visible) {
+ $.vakata.context.hide();
+ }
+ parent.teardown.call(this);
+ };
+
+ /**
+ * prepare and show the context menu for a node
+ * @name show_contextmenu(obj [, x, y])
+ * @param {mixed} obj the node
+ * @param {Number} x the x-coordinate relative to the document to show the menu at
+ * @param {Number} y the y-coordinate relative to the document to show the menu at
+ * @param {Object} e the event if available that triggered the contextmenu
+ * @plugin contextmenu
+ * @trigger show_contextmenu.jstree
+ */
+ this.show_contextmenu = function (obj, x, y, e) {
+ obj = this.get_node(obj);
+ if(!obj || obj.id === $.jstree.root) { return false; }
+ var s = this.settings.contextmenu,
+ d = this.get_node(obj, true),
+ a = d.children(".jstree-anchor"),
+ o = false,
+ i = false;
+ if(s.show_at_node || x === undefined || y === undefined) {
+ o = a.offset();
+ x = o.left;
+ y = o.top + this._data.core.li_height;
+ }
+ if(this.settings.contextmenu.select_node && !this.is_selected(obj)) {
+ this.activate_node(obj, e);
+ }
+
+ i = s.items;
+ if($.isFunction(i)) {
+ i = i.call(this, obj, $.proxy(function (i) {
+ this._show_contextmenu(obj, x, y, i);
+ }, this));
+ }
+ if($.isPlainObject(i)) {
+ this._show_contextmenu(obj, x, y, i);
+ }
+ };
+ /**
+ * show the prepared context menu for a node
+ * @name _show_contextmenu(obj, x, y, i)
+ * @param {mixed} obj the node
+ * @param {Number} x the x-coordinate relative to the document to show the menu at
+ * @param {Number} y the y-coordinate relative to the document to show the menu at
+ * @param {Number} i the object of items to show
+ * @plugin contextmenu
+ * @trigger show_contextmenu.jstree
+ * @private
+ */
+ this._show_contextmenu = function (obj, x, y, i) {
+ var d = this.get_node(obj, true),
+ a = d.children(".jstree-anchor");
+ $(document).one("context_show.vakata.jstree", $.proxy(function (e, data) {
+ var cls = 'jstree-contextmenu jstree-' + this.get_theme() + '-contextmenu';
+ $(data.element).addClass(cls);
+ a.addClass('jstree-context');
+ }, this));
+ this._data.contextmenu.visible = true;
+ $.vakata.context.show(a, { 'x' : x, 'y' : y }, i);
+ /**
+ * triggered when the contextmenu is shown for a node
+ * @event
+ * @name show_contextmenu.jstree
+ * @param {Object} node the node
+ * @param {Number} x the x-coordinate of the menu relative to the document
+ * @param {Number} y the y-coordinate of the menu relative to the document
+ * @plugin contextmenu
+ */
+ this.trigger('show_contextmenu', { "node" : obj, "x" : x, "y" : y });
+ };
+ };
+
+ // contextmenu helper
+ (function ($) {
+ var right_to_left = false,
+ vakata_context = {
+ element : false,
+ reference : false,
+ position_x : 0,
+ position_y : 0,
+ items : [],
+ html : "",
+ is_visible : false
+ };
+
+ $.vakata.context = {
+ settings : {
+ hide_onmouseleave : 0,
+ icons : true
+ },
+ _trigger : function (event_name) {
+ $(document).triggerHandler("context_" + event_name + ".vakata", {
+ "reference" : vakata_context.reference,
+ "element" : vakata_context.element,
+ "position" : {
+ "x" : vakata_context.position_x,
+ "y" : vakata_context.position_y
+ }
+ });
+ },
+ _execute : function (i) {
+ i = vakata_context.items[i];
+ return i && (!i._disabled || ($.isFunction(i._disabled) && !i._disabled({ "item" : i, "reference" : vakata_context.reference, "element" : vakata_context.element }))) && i.action ? i.action.call(null, {
+ "item" : i,
+ "reference" : vakata_context.reference,
+ "element" : vakata_context.element,
+ "position" : {
+ "x" : vakata_context.position_x,
+ "y" : vakata_context.position_y
+ }
+ }) : false;
+ },
+ _parse : function (o, is_callback) {
+ if(!o) { return false; }
+ if(!is_callback) {
+ vakata_context.html = "";
+ vakata_context.items = [];
+ }
+ var str = "",
+ sep = false,
+ tmp;
+
+ if(is_callback) { str += "<"+"ul>"; }
+ $.each(o, function (i, val) {
+ if(!val) { return true; }
+ vakata_context.items.push(val);
+ if(!sep && val.separator_before) {
+ str += "<"+"li class='vakata-context-separator'><"+"a href='#' " + ($.vakata.context.settings.icons ? '' : 'style="margin-left:0px;"') + "> <"+"/a><"+"/li>";
+ }
+ sep = false;
+ str += "<"+"li class='" + (val._class || "") + (val._disabled === true || ($.isFunction(val._disabled) && val._disabled({ "item" : val, "reference" : vakata_context.reference, "element" : vakata_context.element })) ? " vakata-contextmenu-disabled " : "") + "' "+(val.shortcut?" data-shortcut='"+val.shortcut+"' ":'')+">";
+ str += "<"+"a href='#' rel='" + (vakata_context.items.length - 1) + "' " + (val.title ? "title='" + val.title + "'" : "") + ">";
+ if($.vakata.context.settings.icons) {
+ str += "<"+"i ";
+ if(val.icon) {
+ if(val.icon.indexOf("/") !== -1 || val.icon.indexOf(".") !== -1) { str += " style='background:url(\"" + val.icon + "\") center center no-repeat' "; }
+ else { str += " class='" + val.icon + "' "; }
+ }
+ str += "><"+"/i><"+"span class='vakata-contextmenu-sep'> <"+"/span>";
+ }
+ str += ($.isFunction(val.label) ? val.label({ "item" : i, "reference" : vakata_context.reference, "element" : vakata_context.element }) : val.label) + (val.shortcut?' ':'') + "<"+"/a>";
+ if(val.submenu) {
+ tmp = $.vakata.context._parse(val.submenu, true);
+ if(tmp) { str += tmp; }
+ }
+ str += "<"+"/li>";
+ if(val.separator_after) {
+ str += "<"+"li class='vakata-context-separator'><"+"a href='#' " + ($.vakata.context.settings.icons ? '' : 'style="margin-left:0px;"') + "> <"+"/a><"+"/li>";
+ sep = true;
+ }
+ });
+ str = str.replace(/<\/li\>$/,"");
+ if(is_callback) { str += ""; }
+ /**
+ * triggered on the document when the contextmenu is parsed (HTML is built)
+ * @event
+ * @plugin contextmenu
+ * @name context_parse.vakata
+ * @param {jQuery} reference the element that was right clicked
+ * @param {jQuery} element the DOM element of the menu itself
+ * @param {Object} position the x & y coordinates of the menu
+ */
+ if(!is_callback) { vakata_context.html = str; $.vakata.context._trigger("parse"); }
+ return str.length > 10 ? str : false;
+ },
+ _show_submenu : function (o) {
+ o = $(o);
+ if(!o.length || !o.children("ul").length) { return; }
+ var e = o.children("ul"),
+ xl = o.offset().left,
+ x = xl + o.outerWidth(),
+ y = o.offset().top,
+ w = e.width(),
+ h = e.height(),
+ dw = $(window).width() + $(window).scrollLeft(),
+ dh = $(window).height() + $(window).scrollTop();
+ // може да се спести е една проверка - дали няма някой от класовете вече нагоре
+ if(right_to_left) {
+ o[x - (w + 10 + o.outerWidth()) < 0 ? "addClass" : "removeClass"]("vakata-context-left");
+ }
+ else {
+ o[x + w > dw && xl > dw - x ? "addClass" : "removeClass"]("vakata-context-right");
+ }
+ if(y + h + 10 > dh) {
+ e.css("bottom","-1px");
+ }
+
+ //if does not fit - stick it to the side
+ if (o.hasClass('vakata-context-right')) {
+ if (xl < w) {
+ e.css("margin-right", xl - w);
+ }
+ } else {
+ if (dw - x < w) {
+ e.css("margin-left", dw - x - w);
+ }
+ }
+
+ e.show();
+ },
+ show : function (reference, position, data) {
+ var o, e, x, y, w, h, dw, dh, cond = true;
+ if(vakata_context.element && vakata_context.element.length) {
+ vakata_context.element.width('');
+ }
+ switch(cond) {
+ case (!position && !reference):
+ return false;
+ case (!!position && !!reference):
+ vakata_context.reference = reference;
+ vakata_context.position_x = position.x;
+ vakata_context.position_y = position.y;
+ break;
+ case (!position && !!reference):
+ vakata_context.reference = reference;
+ o = reference.offset();
+ vakata_context.position_x = o.left + reference.outerHeight();
+ vakata_context.position_y = o.top;
+ break;
+ case (!!position && !reference):
+ vakata_context.position_x = position.x;
+ vakata_context.position_y = position.y;
+ break;
+ }
+ if(!!reference && !data && $(reference).data('vakata_contextmenu')) {
+ data = $(reference).data('vakata_contextmenu');
+ }
+ if($.vakata.context._parse(data)) {
+ vakata_context.element.html(vakata_context.html);
+ }
+ if(vakata_context.items.length) {
+ vakata_context.element.appendTo("body");
+ e = vakata_context.element;
+ x = vakata_context.position_x;
+ y = vakata_context.position_y;
+ w = e.width();
+ h = e.height();
+ dw = $(window).width() + $(window).scrollLeft();
+ dh = $(window).height() + $(window).scrollTop();
+ if(right_to_left) {
+ x -= (e.outerWidth() - $(reference).outerWidth());
+ if(x < $(window).scrollLeft() + 20) {
+ x = $(window).scrollLeft() + 20;
+ }
+ }
+ if(x + w + 20 > dw) {
+ x = dw - (w + 20);
+ }
+ if(y + h + 20 > dh) {
+ y = dh - (h + 20);
+ }
+
+ vakata_context.element
+ .css({ "left" : x, "top" : y })
+ .show()
+ .find('a').first().focus().parent().addClass("vakata-context-hover");
+ vakata_context.is_visible = true;
+ /**
+ * triggered on the document when the contextmenu is shown
+ * @event
+ * @plugin contextmenu
+ * @name context_show.vakata
+ * @param {jQuery} reference the element that was right clicked
+ * @param {jQuery} element the DOM element of the menu itself
+ * @param {Object} position the x & y coordinates of the menu
+ */
+ $.vakata.context._trigger("show");
+ }
+ },
+ hide : function () {
+ if(vakata_context.is_visible) {
+ vakata_context.element.hide().find("ul").hide().end().find(':focus').blur().end().detach();
+ vakata_context.is_visible = false;
+ /**
+ * triggered on the document when the contextmenu is hidden
+ * @event
+ * @plugin contextmenu
+ * @name context_hide.vakata
+ * @param {jQuery} reference the element that was right clicked
+ * @param {jQuery} element the DOM element of the menu itself
+ * @param {Object} position the x & y coordinates of the menu
+ */
+ $.vakata.context._trigger("hide");
+ }
+ }
+ };
+ $(function () {
+ right_to_left = $("body").css("direction") === "rtl";
+ var to = false;
+
+ vakata_context.element = $("");
+ vakata_context.element
+ .on("mouseenter", "li", function (e) {
+ e.stopImmediatePropagation();
+
+ if($.contains(this, e.relatedTarget)) {
+ // премахнато заради delegate mouseleave по-долу
+ // $(this).find(".vakata-context-hover").removeClass("vakata-context-hover");
+ return;
+ }
+
+ if(to) { clearTimeout(to); }
+ vakata_context.element.find(".vakata-context-hover").removeClass("vakata-context-hover").end();
+
+ $(this)
+ .siblings().find("ul").hide().end().end()
+ .parentsUntil(".vakata-context", "li").addBack().addClass("vakata-context-hover");
+ $.vakata.context._show_submenu(this);
+ })
+ // тестово - дали не натоварва?
+ .on("mouseleave", "li", function (e) {
+ if($.contains(this, e.relatedTarget)) { return; }
+ $(this).find(".vakata-context-hover").addBack().removeClass("vakata-context-hover");
+ })
+ .on("mouseleave", function (e) {
+ $(this).find(".vakata-context-hover").removeClass("vakata-context-hover");
+ if($.vakata.context.settings.hide_onmouseleave) {
+ to = setTimeout(
+ (function (t) {
+ return function () { $.vakata.context.hide(); };
+ }(this)), $.vakata.context.settings.hide_onmouseleave);
+ }
+ })
+ .on("click", "a", function (e) {
+ e.preventDefault();
+ //})
+ //.on("mouseup", "a", function (e) {
+ if(!$(this).blur().parent().hasClass("vakata-context-disabled") && $.vakata.context._execute($(this).attr("rel")) !== false) {
+ $.vakata.context.hide();
+ }
+ })
+ .on('keydown', 'a', function (e) {
+ var o = null;
+ switch(e.which) {
+ case 13:
+ case 32:
+ e.type = "click";
+ e.preventDefault();
+ $(e.currentTarget).trigger(e);
+ break;
+ case 37:
+ if(vakata_context.is_visible) {
+ vakata_context.element.find(".vakata-context-hover").last().closest("li").first().find("ul").hide().find(".vakata-context-hover").removeClass("vakata-context-hover").end().end().children('a').focus();
+ e.stopImmediatePropagation();
+ e.preventDefault();
+ }
+ break;
+ case 38:
+ if(vakata_context.is_visible) {
+ o = vakata_context.element.find("ul:visible").addBack().last().children(".vakata-context-hover").removeClass("vakata-context-hover").prevAll("li:not(.vakata-context-separator)").first();
+ if(!o.length) { o = vakata_context.element.find("ul:visible").addBack().last().children("li:not(.vakata-context-separator)").last(); }
+ o.addClass("vakata-context-hover").children('a').focus();
+ e.stopImmediatePropagation();
+ e.preventDefault();
+ }
+ break;
+ case 39:
+ if(vakata_context.is_visible) {
+ vakata_context.element.find(".vakata-context-hover").last().children("ul").show().children("li:not(.vakata-context-separator)").removeClass("vakata-context-hover").first().addClass("vakata-context-hover").children('a').focus();
+ e.stopImmediatePropagation();
+ e.preventDefault();
+ }
+ break;
+ case 40:
+ if(vakata_context.is_visible) {
+ o = vakata_context.element.find("ul:visible").addBack().last().children(".vakata-context-hover").removeClass("vakata-context-hover").nextAll("li:not(.vakata-context-separator)").first();
+ if(!o.length) { o = vakata_context.element.find("ul:visible").addBack().last().children("li:not(.vakata-context-separator)").first(); }
+ o.addClass("vakata-context-hover").children('a').focus();
+ e.stopImmediatePropagation();
+ e.preventDefault();
+ }
+ break;
+ case 27:
+ $.vakata.context.hide();
+ e.preventDefault();
+ break;
+ default:
+ //console.log(e.which);
+ break;
+ }
+ })
+ .on('keydown', function (e) {
+ e.preventDefault();
+ var a = vakata_context.element.find('.vakata-contextmenu-shortcut-' + e.which).parent();
+ if(a.parent().not('.vakata-context-disabled')) {
+ a.click();
+ }
+ });
+
+ $(document)
+ .on("mousedown.vakata.jstree", function (e) {
+ if(vakata_context.is_visible && !$.contains(vakata_context.element[0], e.target)) {
+ $.vakata.context.hide();
+ }
+ })
+ .on("context_show.vakata.jstree", function (e, data) {
+ vakata_context.element.find("li:has(ul)").children("a").addClass("vakata-context-parent");
+ if(right_to_left) {
+ vakata_context.element.addClass("vakata-context-rtl").css("direction", "rtl");
+ }
+ // also apply a RTL class?
+ vakata_context.element.find("ul").hide().end();
+ });
+ });
+ }($));
+ // $.jstree.defaults.plugins.push("contextmenu");
+
+
+/**
+ * ### Drag'n'drop plugin
+ *
+ * Enables dragging and dropping of nodes in the tree, resulting in a move or copy operations.
+ */
+
+ /**
+ * stores all defaults for the drag'n'drop plugin
+ * @name $.jstree.defaults.dnd
+ * @plugin dnd
+ */
+ $.jstree.defaults.dnd = {
+ /**
+ * a boolean indicating if a copy should be possible while dragging (by pressint the meta key or Ctrl). Defaults to `true`.
+ * @name $.jstree.defaults.dnd.copy
+ * @plugin dnd
+ */
+ copy : true,
+ /**
+ * a number indicating how long a node should remain hovered while dragging to be opened. Defaults to `500`.
+ * @name $.jstree.defaults.dnd.open_timeout
+ * @plugin dnd
+ */
+ open_timeout : 500,
+ /**
+ * a function invoked each time a node is about to be dragged, invoked in the tree's scope and receives the nodes about to be dragged as an argument (array) and the event that started the drag - return `false` to prevent dragging
+ * @name $.jstree.defaults.dnd.is_draggable
+ * @plugin dnd
+ */
+ is_draggable : true,
+ /**
+ * a boolean indicating if checks should constantly be made while the user is dragging the node (as opposed to checking only on drop), default is `true`
+ * @name $.jstree.defaults.dnd.check_while_dragging
+ * @plugin dnd
+ */
+ check_while_dragging : true,
+ /**
+ * a boolean indicating if nodes from this tree should only be copied with dnd (as opposed to moved), default is `false`
+ * @name $.jstree.defaults.dnd.always_copy
+ * @plugin dnd
+ */
+ always_copy : false,
+ /**
+ * when dropping a node "inside", this setting indicates the position the node should go to - it can be an integer or a string: "first" (same as 0) or "last", default is `0`
+ * @name $.jstree.defaults.dnd.inside_pos
+ * @plugin dnd
+ */
+ inside_pos : 0,
+ /**
+ * when starting the drag on a node that is selected this setting controls if all selected nodes are dragged or only the single node, default is `true`, which means all selected nodes are dragged when the drag is started on a selected node
+ * @name $.jstree.defaults.dnd.drag_selection
+ * @plugin dnd
+ */
+ drag_selection : true,
+ /**
+ * controls whether dnd works on touch devices. If left as boolean true dnd will work the same as in desktop browsers, which in some cases may impair scrolling. If set to boolean false dnd will not work on touch devices. There is a special third option - string "selected" which means only selected nodes can be dragged on touch devices.
+ * @name $.jstree.defaults.dnd.touch
+ * @plugin dnd
+ */
+ touch : true,
+ /**
+ * controls whether items can be dropped anywhere on the node, not just on the anchor, by default only the node anchor is a valid drop target. Works best with the wholerow plugin. If enabled on mobile depending on the interface it might be hard for the user to cancel the drop, since the whole tree container will be a valid drop target.
+ * @name $.jstree.defaults.dnd.large_drop_target
+ * @plugin dnd
+ */
+ large_drop_target : false,
+ /**
+ * controls whether a drag can be initiated from any part of the node and not just the text/icon part, works best with the wholerow plugin. Keep in mind it can cause problems with tree scrolling on mobile depending on the interface - in that case set the touch option to "selected".
+ * @name $.jstree.defaults.dnd.large_drag_target
+ * @plugin dnd
+ */
+ large_drag_target : false,
+ /**
+ * controls whether use HTML5 dnd api instead of classical. That will allow better integration of dnd events with other HTML5 controls.
+ * @reference http://caniuse.com/#feat=dragndrop
+ * @name $.jstree.defaults.dnd.use_html5
+ * @plugin dnd
+ */
+ use_html5: false
+ };
+ var drg, elm;
+ // TODO: now check works by checking for each node individually, how about max_children, unique, etc?
+ $.jstree.plugins.dnd = function (options, parent) {
+ this.init = function (el, options) {
+ parent.init.call(this, el, options);
+ this.settings.dnd.use_html5 = this.settings.dnd.use_html5 && ('draggable' in document.createElement('span'));
+ };
+ this.bind = function () {
+ parent.bind.call(this);
+
+ this.element
+ .on(this.settings.dnd.use_html5 ? 'dragstart.jstree' : 'mousedown.jstree touchstart.jstree', this.settings.dnd.large_drag_target ? '.jstree-node' : '.jstree-anchor', $.proxy(function (e) {
+ if(this.settings.dnd.large_drag_target && $(e.target).closest('.jstree-node')[0] !== e.currentTarget) {
+ return true;
+ }
+ if(e.type === "touchstart" && (!this.settings.dnd.touch || (this.settings.dnd.touch === 'selected' && !$(e.currentTarget).closest('.jstree-node').children('.jstree-anchor').hasClass('jstree-clicked')))) {
+ return true;
+ }
+ var obj = this.get_node(e.target),
+ mlt = this.is_selected(obj) && this.settings.dnd.drag_selection ? this.get_top_selected().length : 1,
+ txt = (mlt > 1 ? mlt + ' ' + this.get_string('nodes') : this.get_text(e.currentTarget));
+ if(this.settings.core.force_text) {
+ txt = $.vakata.html.escape(txt);
+ }
+ if(obj && obj.id && obj.id !== $.jstree.root && (e.which === 1 || e.type === "touchstart" || e.type === "dragstart") &&
+ (this.settings.dnd.is_draggable === true || ($.isFunction(this.settings.dnd.is_draggable) && this.settings.dnd.is_draggable.call(this, (mlt > 1 ? this.get_top_selected(true) : [obj]), e)))
+ ) {
+ drg = { 'jstree' : true, 'origin' : this, 'obj' : this.get_node(obj,true), 'nodes' : mlt > 1 ? this.get_top_selected() : [obj.id] };
+ elm = e.currentTarget;
+ if (this.settings.dnd.use_html5) {
+ $.vakata.dnd._trigger('start', e, { 'helper': $(), 'element': elm, 'data': drg });
+ } else {
+ this.element.trigger('mousedown.jstree');
+ return $.vakata.dnd.start(e, drg, '' + txt + '+
');
+ }
+ }
+ }, this));
+ if (this.settings.dnd.use_html5) {
+ this.element
+ .on('dragover.jstree', function (e) {
+ e.preventDefault();
+ $.vakata.dnd._trigger('move', e, { 'helper': $(), 'element': elm, 'data': drg });
+ return false;
+ })
+ //.on('dragenter.jstree', this.settings.dnd.large_drop_target ? '.jstree-node' : '.jstree-anchor', $.proxy(function (e) {
+ // e.preventDefault();
+ // $.vakata.dnd._trigger('move', e, { 'helper': $(), 'element': elm, 'data': drg });
+ // return false;
+ // }, this))
+ .on('drop.jstree', $.proxy(function (e) {
+ e.preventDefault();
+ $.vakata.dnd._trigger('stop', e, { 'helper': $(), 'element': elm, 'data': drg });
+ return false;
+ }, this));
+ }
+ };
+ this.redraw_node = function(obj, deep, callback, force_render) {
+ obj = parent.redraw_node.apply(this, arguments);
+ if (obj && this.settings.dnd.use_html5) {
+ if (this.settings.dnd.large_drag_target) {
+ obj.setAttribute('draggable', true);
+ } else {
+ var i, j, tmp = null;
+ for(i = 0, j = obj.childNodes.length; i < j; i++) {
+ if(obj.childNodes[i] && obj.childNodes[i].className && obj.childNodes[i].className.indexOf("jstree-anchor") !== -1) {
+ tmp = obj.childNodes[i];
+ break;
+ }
+ }
+ if(tmp) {
+ tmp.setAttribute('draggable', true);
+ }
+ }
+ }
+ return obj;
+ };
+ };
+
+ $(function() {
+ // bind only once for all instances
+ var lastmv = false,
+ laster = false,
+ lastev = false,
+ opento = false,
+ marker = $('
').hide(); //.appendTo('body');
+
+ $(document)
+ .on('dnd_start.vakata.jstree', function (e, data) {
+ lastmv = false;
+ lastev = false;
+ if(!data || !data.data || !data.data.jstree) { return; }
+ marker.appendTo('body'); //.show();
+ })
+ .on('dnd_move.vakata.jstree', function (e, data) {
+ if(opento) {
+ if (!data.event || data.event.type !== 'dragover' || data.event.target !== lastev.target) {
+ clearTimeout(opento);
+ }
+ }
+ if(!data || !data.data || !data.data.jstree) { return; }
+
+ // if we are hovering the marker image do nothing (can happen on "inside" drags)
+ if(data.event.target.id && data.event.target.id === 'jstree-marker') {
+ return;
+ }
+ lastev = data.event;
+
+ var ins = $.jstree.reference(data.event.target),
+ ref = false,
+ off = false,
+ rel = false,
+ tmp, l, t, h, p, i, o, ok, t1, t2, op, ps, pr, ip, tm, is_copy, pn;
+ // if we are over an instance
+ if(ins && ins._data && ins._data.dnd) {
+ marker.attr('class', 'jstree-' + ins.get_theme() + ( ins.settings.core.themes.responsive ? ' jstree-dnd-responsive' : '' ));
+ is_copy = data.data.origin && (data.data.origin.settings.dnd.always_copy || (data.data.origin.settings.dnd.copy && (data.event.metaKey || data.event.ctrlKey)));
+ data.helper
+ .children().attr('class', 'jstree-' + ins.get_theme() + ' jstree-' + ins.get_theme() + '-' + ins.get_theme_variant() + ' ' + ( ins.settings.core.themes.responsive ? ' jstree-dnd-responsive' : '' ))
+ .find('.jstree-copy').first()[ is_copy ? 'show' : 'hide' ]();
+
+ // if are hovering the container itself add a new root node
+ //console.log(data.event);
+ if( (data.event.target === ins.element[0] || data.event.target === ins.get_container_ul()[0]) && ins.get_container_ul().children().length === 0) {
+ ok = true;
+ for(t1 = 0, t2 = data.data.nodes.length; t1 < t2; t1++) {
+ ok = ok && ins.check( (data.data.origin && (data.data.origin.settings.dnd.always_copy || (data.data.origin.settings.dnd.copy && (data.event.metaKey || data.event.ctrlKey)) ) ? "copy_node" : "move_node"), (data.data.origin && data.data.origin !== ins ? data.data.origin.get_node(data.data.nodes[t1]) : data.data.nodes[t1]), $.jstree.root, 'last', { 'dnd' : true, 'ref' : ins.get_node($.jstree.root), 'pos' : 'i', 'origin' : data.data.origin, 'is_multi' : (data.data.origin && data.data.origin !== ins), 'is_foreign' : (!data.data.origin) });
+ if(!ok) { break; }
+ }
+ if(ok) {
+ lastmv = { 'ins' : ins, 'par' : $.jstree.root, 'pos' : 'last' };
+ marker.hide();
+ data.helper.find('.jstree-icon').first().removeClass('jstree-er').addClass('jstree-ok');
+ if (data.event.originalEvent && data.event.originalEvent.dataTransfer) {
+ data.event.originalEvent.dataTransfer.dropEffect = is_copy ? 'copy' : 'move';
+ }
+ return;
+ }
+ }
+ else {
+ // if we are hovering a tree node
+ ref = ins.settings.dnd.large_drop_target ? $(data.event.target).closest('.jstree-node').children('.jstree-anchor') : $(data.event.target).closest('.jstree-anchor');
+ if(ref && ref.length && ref.parent().is('.jstree-closed, .jstree-open, .jstree-leaf')) {
+ off = ref.offset();
+ rel = (data.event.pageY !== undefined ? data.event.pageY : data.event.originalEvent.pageY) - off.top;
+ h = ref.outerHeight();
+ if(rel < h / 3) {
+ o = ['b', 'i', 'a'];
+ }
+ else if(rel > h - h / 3) {
+ o = ['a', 'i', 'b'];
+ }
+ else {
+ o = rel > h / 2 ? ['i', 'a', 'b'] : ['i', 'b', 'a'];
+ }
+ $.each(o, function (j, v) {
+ switch(v) {
+ case 'b':
+ l = off.left - 6;
+ t = off.top;
+ p = ins.get_parent(ref);
+ i = ref.parent().index();
+ break;
+ case 'i':
+ ip = ins.settings.dnd.inside_pos;
+ tm = ins.get_node(ref.parent());
+ l = off.left - 2;
+ t = off.top + h / 2 + 1;
+ p = tm.id;
+ i = ip === 'first' ? 0 : (ip === 'last' ? tm.children.length : Math.min(ip, tm.children.length));
+ break;
+ case 'a':
+ l = off.left - 6;
+ t = off.top + h;
+ p = ins.get_parent(ref);
+ i = ref.parent().index() + 1;
+ break;
+ }
+ ok = true;
+ for(t1 = 0, t2 = data.data.nodes.length; t1 < t2; t1++) {
+ op = data.data.origin && (data.data.origin.settings.dnd.always_copy || (data.data.origin.settings.dnd.copy && (data.event.metaKey || data.event.ctrlKey))) ? "copy_node" : "move_node";
+ ps = i;
+ if(op === "move_node" && v === 'a' && (data.data.origin && data.data.origin === ins) && p === ins.get_parent(data.data.nodes[t1])) {
+ pr = ins.get_node(p);
+ if(ps > $.inArray(data.data.nodes[t1], pr.children)) {
+ ps -= 1;
+ }
+ }
+ ok = ok && ( (ins && ins.settings && ins.settings.dnd && ins.settings.dnd.check_while_dragging === false) || ins.check(op, (data.data.origin && data.data.origin !== ins ? data.data.origin.get_node(data.data.nodes[t1]) : data.data.nodes[t1]), p, ps, { 'dnd' : true, 'ref' : ins.get_node(ref.parent()), 'pos' : v, 'origin' : data.data.origin, 'is_multi' : (data.data.origin && data.data.origin !== ins), 'is_foreign' : (!data.data.origin) }) );
+ if(!ok) {
+ if(ins && ins.last_error) { laster = ins.last_error(); }
+ break;
+ }
+ }
+ if(v === 'i' && ref.parent().is('.jstree-closed') && ins.settings.dnd.open_timeout) {
+ opento = setTimeout((function (x, z) { return function () { x.open_node(z); }; }(ins, ref)), ins.settings.dnd.open_timeout);
+ }
+ if(ok) {
+ pn = ins.get_node(p, true);
+ if (!pn.hasClass('.jstree-dnd-parent')) {
+ $('.jstree-dnd-parent').removeClass('jstree-dnd-parent');
+ pn.addClass('jstree-dnd-parent');
+ }
+ lastmv = { 'ins' : ins, 'par' : p, 'pos' : v === 'i' && ip === 'last' && i === 0 && !ins.is_loaded(tm) ? 'last' : i };
+ marker.css({ 'left' : l + 'px', 'top' : t + 'px' }).show();
+ data.helper.find('.jstree-icon').first().removeClass('jstree-er').addClass('jstree-ok');
+ if (data.event.originalEvent && data.event.originalEvent.dataTransfer) {
+ data.event.originalEvent.dataTransfer.dropEffect = is_copy ? 'copy' : 'move';
+ }
+ laster = {};
+ o = true;
+ return false;
+ }
+ });
+ if(o === true) { return; }
+ }
+ }
+ }
+ $('.jstree-dnd-parent').removeClass('jstree-dnd-parent');
+ lastmv = false;
+ data.helper.find('.jstree-icon').removeClass('jstree-ok').addClass('jstree-er');
+ if (data.event.originalEvent && data.event.originalEvent.dataTransfer) {
+ data.event.originalEvent.dataTransfer.dropEffect = 'none';
+ }
+ marker.hide();
+ })
+ .on('dnd_scroll.vakata.jstree', function (e, data) {
+ if(!data || !data.data || !data.data.jstree) { return; }
+ marker.hide();
+ lastmv = false;
+ lastev = false;
+ data.helper.find('.jstree-icon').first().removeClass('jstree-ok').addClass('jstree-er');
+ })
+ .on('dnd_stop.vakata.jstree', function (e, data) {
+ $('.jstree-dnd-parent').removeClass('jstree-dnd-parent');
+ if(opento) { clearTimeout(opento); }
+ if(!data || !data.data || !data.data.jstree) { return; }
+ marker.hide().detach();
+ var i, j, nodes = [];
+ if(lastmv) {
+ for(i = 0, j = data.data.nodes.length; i < j; i++) {
+ nodes[i] = data.data.origin ? data.data.origin.get_node(data.data.nodes[i]) : data.data.nodes[i];
+ }
+ lastmv.ins[ data.data.origin && (data.data.origin.settings.dnd.always_copy || (data.data.origin.settings.dnd.copy && (data.event.metaKey || data.event.ctrlKey))) ? 'copy_node' : 'move_node' ](nodes, lastmv.par, lastmv.pos, false, false, false, data.data.origin);
+ }
+ else {
+ i = $(data.event.target).closest('.jstree');
+ if(i.length && laster && laster.error && laster.error === 'check') {
+ i = i.jstree(true);
+ if(i) {
+ i.settings.core.error.call(this, laster);
+ }
+ }
+ }
+ lastev = false;
+ lastmv = false;
+ })
+ .on('keyup.jstree keydown.jstree', function (e, data) {
+ data = $.vakata.dnd._get();
+ if(data && data.data && data.data.jstree) {
+ if (e.type === "keyup" && e.which === 27) {
+ if (opento) { clearTimeout(opento); }
+ lastmv = false;
+ laster = false;
+ lastev = false;
+ opento = false;
+ marker.hide().detach();
+ $.vakata.dnd._clean();
+ } else {
+ data.helper.find('.jstree-copy').first()[ data.data.origin && (data.data.origin.settings.dnd.always_copy || (data.data.origin.settings.dnd.copy && (e.metaKey || e.ctrlKey))) ? 'show' : 'hide' ]();
+ if(lastev) {
+ lastev.metaKey = e.metaKey;
+ lastev.ctrlKey = e.ctrlKey;
+ $.vakata.dnd._trigger('move', lastev);
+ }
+ }
+ }
+ });
+ });
+
+ // helpers
+ (function ($) {
+ $.vakata.html = {
+ div : $(''),
+ escape : function (str) {
+ return $.vakata.html.div.text(str).html();
+ },
+ strip : function (str) {
+ return $.vakata.html.div.empty().append($.parseHTML(str)).text();
+ }
+ };
+ // private variable
+ var vakata_dnd = {
+ element : false,
+ target : false,
+ is_down : false,
+ is_drag : false,
+ helper : false,
+ helper_w: 0,
+ data : false,
+ init_x : 0,
+ init_y : 0,
+ scroll_l: 0,
+ scroll_t: 0,
+ scroll_e: false,
+ scroll_i: false,
+ is_touch: false
+ };
+ $.vakata.dnd = {
+ settings : {
+ scroll_speed : 10,
+ scroll_proximity : 20,
+ helper_left : 5,
+ helper_top : 10,
+ threshold : 5,
+ threshold_touch : 50
+ },
+ _trigger : function (event_name, e, data) {
+ if (data === undefined) {
+ data = $.vakata.dnd._get();
+ }
+ data.event = e;
+ $(document).triggerHandler("dnd_" + event_name + ".vakata", data);
+ },
+ _get : function () {
+ return {
+ "data" : vakata_dnd.data,
+ "element" : vakata_dnd.element,
+ "helper" : vakata_dnd.helper
+ };
+ },
+ _clean : function () {
+ if(vakata_dnd.helper) { vakata_dnd.helper.remove(); }
+ if(vakata_dnd.scroll_i) { clearInterval(vakata_dnd.scroll_i); vakata_dnd.scroll_i = false; }
+ vakata_dnd = {
+ element : false,
+ target : false,
+ is_down : false,
+ is_drag : false,
+ helper : false,
+ helper_w: 0,
+ data : false,
+ init_x : 0,
+ init_y : 0,
+ scroll_l: 0,
+ scroll_t: 0,
+ scroll_e: false,
+ scroll_i: false,
+ is_touch: false
+ };
+ $(document).off("mousemove.vakata.jstree touchmove.vakata.jstree", $.vakata.dnd.drag);
+ $(document).off("mouseup.vakata.jstree touchend.vakata.jstree", $.vakata.dnd.stop);
+ },
+ _scroll : function (init_only) {
+ if(!vakata_dnd.scroll_e || (!vakata_dnd.scroll_l && !vakata_dnd.scroll_t)) {
+ if(vakata_dnd.scroll_i) { clearInterval(vakata_dnd.scroll_i); vakata_dnd.scroll_i = false; }
+ return false;
+ }
+ if(!vakata_dnd.scroll_i) {
+ vakata_dnd.scroll_i = setInterval($.vakata.dnd._scroll, 100);
+ return false;
+ }
+ if(init_only === true) { return false; }
+
+ var i = vakata_dnd.scroll_e.scrollTop(),
+ j = vakata_dnd.scroll_e.scrollLeft();
+ vakata_dnd.scroll_e.scrollTop(i + vakata_dnd.scroll_t * $.vakata.dnd.settings.scroll_speed);
+ vakata_dnd.scroll_e.scrollLeft(j + vakata_dnd.scroll_l * $.vakata.dnd.settings.scroll_speed);
+ if(i !== vakata_dnd.scroll_e.scrollTop() || j !== vakata_dnd.scroll_e.scrollLeft()) {
+ /**
+ * triggered on the document when a drag causes an element to scroll
+ * @event
+ * @plugin dnd
+ * @name dnd_scroll.vakata
+ * @param {Mixed} data any data supplied with the call to $.vakata.dnd.start
+ * @param {DOM} element the DOM element being dragged
+ * @param {jQuery} helper the helper shown next to the mouse
+ * @param {jQuery} event the element that is scrolling
+ */
+ $.vakata.dnd._trigger("scroll", vakata_dnd.scroll_e);
+ }
+ },
+ start : function (e, data, html) {
+ if(e.type === "touchstart" && e.originalEvent && e.originalEvent.changedTouches && e.originalEvent.changedTouches[0]) {
+ e.pageX = e.originalEvent.changedTouches[0].pageX;
+ e.pageY = e.originalEvent.changedTouches[0].pageY;
+ e.target = document.elementFromPoint(e.originalEvent.changedTouches[0].pageX - window.pageXOffset, e.originalEvent.changedTouches[0].pageY - window.pageYOffset);
+ }
+ if(vakata_dnd.is_drag) { $.vakata.dnd.stop({}); }
+ try {
+ e.currentTarget.unselectable = "on";
+ e.currentTarget.onselectstart = function() { return false; };
+ if(e.currentTarget.style) {
+ e.currentTarget.style.touchAction = "none";
+ e.currentTarget.style.msTouchAction = "none";
+ e.currentTarget.style.MozUserSelect = "none";
+ }
+ } catch(ignore) { }
+ vakata_dnd.init_x = e.pageX;
+ vakata_dnd.init_y = e.pageY;
+ vakata_dnd.data = data;
+ vakata_dnd.is_down = true;
+ vakata_dnd.element = e.currentTarget;
+ vakata_dnd.target = e.target;
+ vakata_dnd.is_touch = e.type === "touchstart";
+ if(html !== false) {
+ vakata_dnd.helper = $("").html(html).css({
+ "display" : "block",
+ "margin" : "0",
+ "padding" : "0",
+ "position" : "absolute",
+ "top" : "-2000px",
+ "lineHeight" : "16px",
+ "zIndex" : "10000"
+ });
+ }
+ $(document).on("mousemove.vakata.jstree touchmove.vakata.jstree", $.vakata.dnd.drag);
+ $(document).on("mouseup.vakata.jstree touchend.vakata.jstree", $.vakata.dnd.stop);
+ return false;
+ },
+ drag : function (e) {
+ if(e.type === "touchmove" && e.originalEvent && e.originalEvent.changedTouches && e.originalEvent.changedTouches[0]) {
+ e.pageX = e.originalEvent.changedTouches[0].pageX;
+ e.pageY = e.originalEvent.changedTouches[0].pageY;
+ e.target = document.elementFromPoint(e.originalEvent.changedTouches[0].pageX - window.pageXOffset, e.originalEvent.changedTouches[0].pageY - window.pageYOffset);
+ }
+ if(!vakata_dnd.is_down) { return; }
+ if(!vakata_dnd.is_drag) {
+ if(
+ Math.abs(e.pageX - vakata_dnd.init_x) > (vakata_dnd.is_touch ? $.vakata.dnd.settings.threshold_touch : $.vakata.dnd.settings.threshold) ||
+ Math.abs(e.pageY - vakata_dnd.init_y) > (vakata_dnd.is_touch ? $.vakata.dnd.settings.threshold_touch : $.vakata.dnd.settings.threshold)
+ ) {
+ if(vakata_dnd.helper) {
+ vakata_dnd.helper.appendTo("body");
+ vakata_dnd.helper_w = vakata_dnd.helper.outerWidth();
+ }
+ vakata_dnd.is_drag = true;
+ $(vakata_dnd.target).one('click.vakata', false);
+ /**
+ * triggered on the document when a drag starts
+ * @event
+ * @plugin dnd
+ * @name dnd_start.vakata
+ * @param {Mixed} data any data supplied with the call to $.vakata.dnd.start
+ * @param {DOM} element the DOM element being dragged
+ * @param {jQuery} helper the helper shown next to the mouse
+ * @param {Object} event the event that caused the start (probably mousemove)
+ */
+ $.vakata.dnd._trigger("start", e);
+ }
+ else { return; }
+ }
+
+ var d = false, w = false,
+ dh = false, wh = false,
+ dw = false, ww = false,
+ dt = false, dl = false,
+ ht = false, hl = false;
+
+ vakata_dnd.scroll_t = 0;
+ vakata_dnd.scroll_l = 0;
+ vakata_dnd.scroll_e = false;
+ $($(e.target).parentsUntil("body").addBack().get().reverse())
+ .filter(function () {
+ return (/^auto|scroll$/).test($(this).css("overflow")) &&
+ (this.scrollHeight > this.offsetHeight || this.scrollWidth > this.offsetWidth);
+ })
+ .each(function () {
+ var t = $(this), o = t.offset();
+ if(this.scrollHeight > this.offsetHeight) {
+ if(o.top + t.height() - e.pageY < $.vakata.dnd.settings.scroll_proximity) { vakata_dnd.scroll_t = 1; }
+ if(e.pageY - o.top < $.vakata.dnd.settings.scroll_proximity) { vakata_dnd.scroll_t = -1; }
+ }
+ if(this.scrollWidth > this.offsetWidth) {
+ if(o.left + t.width() - e.pageX < $.vakata.dnd.settings.scroll_proximity) { vakata_dnd.scroll_l = 1; }
+ if(e.pageX - o.left < $.vakata.dnd.settings.scroll_proximity) { vakata_dnd.scroll_l = -1; }
+ }
+ if(vakata_dnd.scroll_t || vakata_dnd.scroll_l) {
+ vakata_dnd.scroll_e = $(this);
+ return false;
+ }
+ });
+
+ if(!vakata_dnd.scroll_e) {
+ d = $(document); w = $(window);
+ dh = d.height(); wh = w.height();
+ dw = d.width(); ww = w.width();
+ dt = d.scrollTop(); dl = d.scrollLeft();
+ if(dh > wh && e.pageY - dt < $.vakata.dnd.settings.scroll_proximity) { vakata_dnd.scroll_t = -1; }
+ if(dh > wh && wh - (e.pageY - dt) < $.vakata.dnd.settings.scroll_proximity) { vakata_dnd.scroll_t = 1; }
+ if(dw > ww && e.pageX - dl < $.vakata.dnd.settings.scroll_proximity) { vakata_dnd.scroll_l = -1; }
+ if(dw > ww && ww - (e.pageX - dl) < $.vakata.dnd.settings.scroll_proximity) { vakata_dnd.scroll_l = 1; }
+ if(vakata_dnd.scroll_t || vakata_dnd.scroll_l) {
+ vakata_dnd.scroll_e = d;
+ }
+ }
+ if(vakata_dnd.scroll_e) { $.vakata.dnd._scroll(true); }
+
+ if(vakata_dnd.helper) {
+ ht = parseInt(e.pageY + $.vakata.dnd.settings.helper_top, 10);
+ hl = parseInt(e.pageX + $.vakata.dnd.settings.helper_left, 10);
+ if(dh && ht + 25 > dh) { ht = dh - 50; }
+ if(dw && hl + vakata_dnd.helper_w > dw) { hl = dw - (vakata_dnd.helper_w + 2); }
+ vakata_dnd.helper.css({
+ left : hl + "px",
+ top : ht + "px"
+ });
+ }
+ /**
+ * triggered on the document when a drag is in progress
+ * @event
+ * @plugin dnd
+ * @name dnd_move.vakata
+ * @param {Mixed} data any data supplied with the call to $.vakata.dnd.start
+ * @param {DOM} element the DOM element being dragged
+ * @param {jQuery} helper the helper shown next to the mouse
+ * @param {Object} event the event that caused this to trigger (most likely mousemove)
+ */
+ $.vakata.dnd._trigger("move", e);
+ return false;
+ },
+ stop : function (e) {
+ if(e.type === "touchend" && e.originalEvent && e.originalEvent.changedTouches && e.originalEvent.changedTouches[0]) {
+ e.pageX = e.originalEvent.changedTouches[0].pageX;
+ e.pageY = e.originalEvent.changedTouches[0].pageY;
+ e.target = document.elementFromPoint(e.originalEvent.changedTouches[0].pageX - window.pageXOffset, e.originalEvent.changedTouches[0].pageY - window.pageYOffset);
+ }
+ if(vakata_dnd.is_drag) {
+ /**
+ * triggered on the document when a drag stops (the dragged element is dropped)
+ * @event
+ * @plugin dnd
+ * @name dnd_stop.vakata
+ * @param {Mixed} data any data supplied with the call to $.vakata.dnd.start
+ * @param {DOM} element the DOM element being dragged
+ * @param {jQuery} helper the helper shown next to the mouse
+ * @param {Object} event the event that caused the stop
+ */
+ if (e.target !== vakata_dnd.target) {
+ $(vakata_dnd.target).off('click.vakata');
+ }
+ $.vakata.dnd._trigger("stop", e);
+ }
+ else {
+ if(e.type === "touchend" && e.target === vakata_dnd.target) {
+ var to = setTimeout(function () { $(e.target).click(); }, 100);
+ $(e.target).one('click', function() { if(to) { clearTimeout(to); } });
+ }
+ }
+ $.vakata.dnd._clean();
+ return false;
+ }
+ };
+ }($));
+
+ // include the dnd plugin by default
+ // $.jstree.defaults.plugins.push("dnd");
+
+
+/**
+ * ### Massload plugin
+ *
+ * Adds massload functionality to jsTree, so that multiple nodes can be loaded in a single request (only useful with lazy loading).
+ */
+
+ /**
+ * massload configuration
+ *
+ * It is possible to set this to a standard jQuery-like AJAX config.
+ * In addition to the standard jQuery ajax options here you can supply functions for `data` and `url`, the functions will be run in the current instance's scope and a param will be passed indicating which node IDs need to be loaded, the return value of those functions will be used.
+ *
+ * You can also set this to a function, that function will receive the node IDs being loaded as argument and a second param which is a function (callback) which should be called with the result.
+ *
+ * Both the AJAX and the function approach rely on the same return value - an object where the keys are the node IDs, and the value is the children of that node as an array.
+ *
+ * {
+ * "id1" : [{ "text" : "Child of ID1", "id" : "c1" }, { "text" : "Another child of ID1", "id" : "c2" }],
+ * "id2" : [{ "text" : "Child of ID2", "id" : "c3" }]
+ * }
+ *
+ * @name $.jstree.defaults.massload
+ * @plugin massload
+ */
+ $.jstree.defaults.massload = null;
+ $.jstree.plugins.massload = function (options, parent) {
+ this.init = function (el, options) {
+ this._data.massload = {};
+ parent.init.call(this, el, options);
+ };
+ this._load_nodes = function (nodes, callback, is_callback, force_reload) {
+ var s = this.settings.massload,
+ nodesString = JSON.stringify(nodes),
+ toLoad = [],
+ m = this._model.data,
+ i, j, dom;
+ if (!is_callback) {
+ for(i = 0, j = nodes.length; i < j; i++) {
+ if(!m[nodes[i]] || ( (!m[nodes[i]].state.loaded && !m[nodes[i]].state.failed) || force_reload) ) {
+ toLoad.push(nodes[i]);
+ dom = this.get_node(nodes[i], true);
+ if (dom && dom.length) {
+ dom.addClass("jstree-loading").attr('aria-busy',true);
+ }
+ }
+ }
+ this._data.massload = {};
+ if (toLoad.length) {
+ if($.isFunction(s)) {
+ return s.call(this, toLoad, $.proxy(function (data) {
+ var i, j;
+ if(data) {
+ for(i in data) {
+ if(data.hasOwnProperty(i)) {
+ this._data.massload[i] = data[i];
+ }
+ }
+ }
+ for(i = 0, j = nodes.length; i < j; i++) {
+ dom = this.get_node(nodes[i], true);
+ if (dom && dom.length) {
+ dom.removeClass("jstree-loading").attr('aria-busy',false);
+ }
+ }
+ parent._load_nodes.call(this, nodes, callback, is_callback, force_reload);
+ }, this));
+ }
+ if(typeof s === 'object' && s && s.url) {
+ s = $.extend(true, {}, s);
+ if($.isFunction(s.url)) {
+ s.url = s.url.call(this, toLoad);
+ }
+ if($.isFunction(s.data)) {
+ s.data = s.data.call(this, toLoad);
+ }
+ return $.ajax(s)
+ .done($.proxy(function (data,t,x) {
+ var i, j;
+ if(data) {
+ for(i in data) {
+ if(data.hasOwnProperty(i)) {
+ this._data.massload[i] = data[i];
+ }
+ }
+ }
+ for(i = 0, j = nodes.length; i < j; i++) {
+ dom = this.get_node(nodes[i], true);
+ if (dom && dom.length) {
+ dom.removeClass("jstree-loading").attr('aria-busy',false);
+ }
+ }
+ parent._load_nodes.call(this, nodes, callback, is_callback, force_reload);
+ }, this))
+ .fail($.proxy(function (f) {
+ parent._load_nodes.call(this, nodes, callback, is_callback, force_reload);
+ }, this));
+ }
+ }
+ }
+ return parent._load_nodes.call(this, nodes, callback, is_callback, force_reload);
+ };
+ this._load_node = function (obj, callback) {
+ var data = this._data.massload[obj.id],
+ rslt = null, dom;
+ if(data) {
+ rslt = this[typeof data === 'string' ? '_append_html_data' : '_append_json_data'](
+ obj,
+ typeof data === 'string' ? $($.parseHTML(data)).filter(function () { return this.nodeType !== 3; }) : data,
+ function (status) { callback.call(this, status); }
+ );
+ dom = this.get_node(obj.id, true);
+ if (dom && dom.length) {
+ dom.removeClass("jstree-loading").attr('aria-busy',false);
+ }
+ delete this._data.massload[obj.id];
+ return rslt;
+ }
+ return parent._load_node.call(this, obj, callback);
+ };
+ };
+
+/**
+ * ### Search plugin
+ *
+ * Adds search functionality to jsTree.
+ */
+
+ /**
+ * stores all defaults for the search plugin
+ * @name $.jstree.defaults.search
+ * @plugin search
+ */
+ $.jstree.defaults.search = {
+ /**
+ * a jQuery-like AJAX config, which jstree uses if a server should be queried for results.
+ *
+ * A `str` (which is the search string) parameter will be added with the request, an optional `inside` parameter will be added if the search is limited to a node id. The expected result is a JSON array with nodes that need to be opened so that matching nodes will be revealed.
+ * Leave this setting as `false` to not query the server. You can also set this to a function, which will be invoked in the instance's scope and receive 3 parameters - the search string, the callback to call with the array of nodes to load, and the optional node ID to limit the search to
+ * @name $.jstree.defaults.search.ajax
+ * @plugin search
+ */
+ ajax : false,
+ /**
+ * Indicates if the search should be fuzzy or not (should `chnd3` match `child node 3`). Default is `false`.
+ * @name $.jstree.defaults.search.fuzzy
+ * @plugin search
+ */
+ fuzzy : false,
+ /**
+ * Indicates if the search should be case sensitive. Default is `false`.
+ * @name $.jstree.defaults.search.case_sensitive
+ * @plugin search
+ */
+ case_sensitive : false,
+ /**
+ * Indicates if the tree should be filtered (by default) to show only matching nodes (keep in mind this can be a heavy on large trees in old browsers).
+ * This setting can be changed at runtime when calling the search method. Default is `false`.
+ * @name $.jstree.defaults.search.show_only_matches
+ * @plugin search
+ */
+ show_only_matches : false,
+ /**
+ * Indicates if the children of matched element are shown (when show_only_matches is true)
+ * This setting can be changed at runtime when calling the search method. Default is `false`.
+ * @name $.jstree.defaults.search.show_only_matches_children
+ * @plugin search
+ */
+ show_only_matches_children : false,
+ /**
+ * Indicates if all nodes opened to reveal the search result, should be closed when the search is cleared or a new search is performed. Default is `true`.
+ * @name $.jstree.defaults.search.close_opened_onclear
+ * @plugin search
+ */
+ close_opened_onclear : true,
+ /**
+ * Indicates if only leaf nodes should be included in search results. Default is `false`.
+ * @name $.jstree.defaults.search.search_leaves_only
+ * @plugin search
+ */
+ search_leaves_only : false,
+ /**
+ * If set to a function it wil be called in the instance's scope with two arguments - search string and node (where node will be every node in the structure, so use with caution).
+ * If the function returns a truthy value the node will be considered a match (it might not be displayed if search_only_leaves is set to true and the node is not a leaf). Default is `false`.
+ * @name $.jstree.defaults.search.search_callback
+ * @plugin search
+ */
+ search_callback : false
+ };
+
+ $.jstree.plugins.search = function (options, parent) {
+ this.bind = function () {
+ parent.bind.call(this);
+
+ this._data.search.str = "";
+ this._data.search.dom = $();
+ this._data.search.res = [];
+ this._data.search.opn = [];
+ this._data.search.som = false;
+ this._data.search.smc = false;
+ this._data.search.hdn = [];
+
+ this.element
+ .on("search.jstree", $.proxy(function (e, data) {
+ if(this._data.search.som && data.res.length) {
+ var m = this._model.data, i, j, p = [], k, l;
+ for(i = 0, j = data.res.length; i < j; i++) {
+ if(m[data.res[i]] && !m[data.res[i]].state.hidden) {
+ p.push(data.res[i]);
+ p = p.concat(m[data.res[i]].parents);
+ if(this._data.search.smc) {
+ for (k = 0, l = m[data.res[i]].children_d.length; k < l; k++) {
+ if (m[m[data.res[i]].children_d[k]] && !m[m[data.res[i]].children_d[k]].state.hidden) {
+ p.push(m[data.res[i]].children_d[k]);
+ }
+ }
+ }
+ }
+ }
+ p = $.vakata.array_remove_item($.vakata.array_unique(p), $.jstree.root);
+ this._data.search.hdn = this.hide_all(true);
+ this.show_node(p, true);
+ this.redraw(true);
+ }
+ }, this))
+ .on("clear_search.jstree", $.proxy(function (e, data) {
+ if(this._data.search.som && data.res.length) {
+ this.show_node(this._data.search.hdn, true);
+ this.redraw(true);
+ }
+ }, this));
+ };
+ /**
+ * used to search the tree nodes for a given string
+ * @name search(str [, skip_async])
+ * @param {String} str the search string
+ * @param {Boolean} skip_async if set to true server will not be queried even if configured
+ * @param {Boolean} show_only_matches if set to true only matching nodes will be shown (keep in mind this can be very slow on large trees or old browsers)
+ * @param {mixed} inside an optional node to whose children to limit the search
+ * @param {Boolean} append if set to true the results of this search are appended to the previous search
+ * @plugin search
+ * @trigger search.jstree
+ */
+ this.search = function (str, skip_async, show_only_matches, inside, append, show_only_matches_children) {
+ if(str === false || $.trim(str.toString()) === "") {
+ return this.clear_search();
+ }
+ inside = this.get_node(inside);
+ inside = inside && inside.id ? inside.id : null;
+ str = str.toString();
+ var s = this.settings.search,
+ a = s.ajax ? s.ajax : false,
+ m = this._model.data,
+ f = null,
+ r = [],
+ p = [], i, j;
+ if(this._data.search.res.length && !append) {
+ this.clear_search();
+ }
+ if(show_only_matches === undefined) {
+ show_only_matches = s.show_only_matches;
+ }
+ if(show_only_matches_children === undefined) {
+ show_only_matches_children = s.show_only_matches_children;
+ }
+ if(!skip_async && a !== false) {
+ if($.isFunction(a)) {
+ return a.call(this, str, $.proxy(function (d) {
+ if(d && d.d) { d = d.d; }
+ this._load_nodes(!$.isArray(d) ? [] : $.vakata.array_unique(d), function () {
+ this.search(str, true, show_only_matches, inside, append, show_only_matches_children);
+ });
+ }, this), inside);
+ }
+ else {
+ a = $.extend({}, a);
+ if(!a.data) { a.data = {}; }
+ a.data.str = str;
+ if(inside) {
+ a.data.inside = inside;
+ }
+ if (this._data.search.lastRequest) {
+ this._data.search.lastRequest.abort();
+ }
+ this._data.search.lastRequest = $.ajax(a)
+ .fail($.proxy(function () {
+ this._data.core.last_error = { 'error' : 'ajax', 'plugin' : 'search', 'id' : 'search_01', 'reason' : 'Could not load search parents', 'data' : JSON.stringify(a) };
+ this.settings.core.error.call(this, this._data.core.last_error);
+ }, this))
+ .done($.proxy(function (d) {
+ if(d && d.d) { d = d.d; }
+ this._load_nodes(!$.isArray(d) ? [] : $.vakata.array_unique(d), function () {
+ this.search(str, true, show_only_matches, inside, append, show_only_matches_children);
+ });
+ }, this));
+ return this._data.search.lastRequest;
+ }
+ }
+ if(!append) {
+ this._data.search.str = str;
+ this._data.search.dom = $();
+ this._data.search.res = [];
+ this._data.search.opn = [];
+ this._data.search.som = show_only_matches;
+ this._data.search.smc = show_only_matches_children;
+ }
+
+ f = new $.vakata.search(str, true, { caseSensitive : s.case_sensitive, fuzzy : s.fuzzy });
+ $.each(m[inside ? inside : $.jstree.root].children_d, function (ii, i) {
+ var v = m[i];
+ if(v.text && !v.state.hidden && (!s.search_leaves_only || (v.state.loaded && v.children.length === 0)) && ( (s.search_callback && s.search_callback.call(this, str, v)) || (!s.search_callback && f.search(v.text).isMatch) ) ) {
+ r.push(i);
+ p = p.concat(v.parents);
+ }
+ });
+ if(r.length) {
+ p = $.vakata.array_unique(p);
+ for(i = 0, j = p.length; i < j; i++) {
+ if(p[i] !== $.jstree.root && m[p[i]] && this.open_node(p[i], null, 0) === true) {
+ this._data.search.opn.push(p[i]);
+ }
+ }
+ if(!append) {
+ this._data.search.dom = $(this.element[0].querySelectorAll('#' + $.map(r, function (v) { return "0123456789".indexOf(v[0]) !== -1 ? '\\3' + v[0] + ' ' + v.substr(1).replace($.jstree.idregex,'\\$&') : v.replace($.jstree.idregex,'\\$&'); }).join(', #')));
+ this._data.search.res = r;
+ }
+ else {
+ this._data.search.dom = this._data.search.dom.add($(this.element[0].querySelectorAll('#' + $.map(r, function (v) { return "0123456789".indexOf(v[0]) !== -1 ? '\\3' + v[0] + ' ' + v.substr(1).replace($.jstree.idregex,'\\$&') : v.replace($.jstree.idregex,'\\$&'); }).join(', #'))));
+ this._data.search.res = $.vakata.array_unique(this._data.search.res.concat(r));
+ }
+ this._data.search.dom.children(".jstree-anchor").addClass('jstree-search');
+ }
+ /**
+ * triggered after search is complete
+ * @event
+ * @name search.jstree
+ * @param {jQuery} nodes a jQuery collection of matching nodes
+ * @param {String} str the search string
+ * @param {Array} res a collection of objects represeing the matching nodes
+ * @plugin search
+ */
+ this.trigger('search', { nodes : this._data.search.dom, str : str, res : this._data.search.res, show_only_matches : show_only_matches });
+ };
+ /**
+ * used to clear the last search (removes classes and shows all nodes if filtering is on)
+ * @name clear_search()
+ * @plugin search
+ * @trigger clear_search.jstree
+ */
+ this.clear_search = function () {
+ if(this.settings.search.close_opened_onclear) {
+ this.close_node(this._data.search.opn, 0);
+ }
+ /**
+ * triggered after search is complete
+ * @event
+ * @name clear_search.jstree
+ * @param {jQuery} nodes a jQuery collection of matching nodes (the result from the last search)
+ * @param {String} str the search string (the last search string)
+ * @param {Array} res a collection of objects represeing the matching nodes (the result from the last search)
+ * @plugin search
+ */
+ this.trigger('clear_search', { 'nodes' : this._data.search.dom, str : this._data.search.str, res : this._data.search.res });
+ if(this._data.search.res.length) {
+ this._data.search.dom = $(this.element[0].querySelectorAll('#' + $.map(this._data.search.res, function (v) {
+ return "0123456789".indexOf(v[0]) !== -1 ? '\\3' + v[0] + ' ' + v.substr(1).replace($.jstree.idregex,'\\$&') : v.replace($.jstree.idregex,'\\$&');
+ }).join(', #')));
+ this._data.search.dom.children(".jstree-anchor").removeClass("jstree-search");
+ }
+ this._data.search.str = "";
+ this._data.search.res = [];
+ this._data.search.opn = [];
+ this._data.search.dom = $();
+ };
+
+ this.redraw_node = function(obj, deep, callback, force_render) {
+ obj = parent.redraw_node.apply(this, arguments);
+ if(obj) {
+ if($.inArray(obj.id, this._data.search.res) !== -1) {
+ var i, j, tmp = null;
+ for(i = 0, j = obj.childNodes.length; i < j; i++) {
+ if(obj.childNodes[i] && obj.childNodes[i].className && obj.childNodes[i].className.indexOf("jstree-anchor") !== -1) {
+ tmp = obj.childNodes[i];
+ break;
+ }
+ }
+ if(tmp) {
+ tmp.className += ' jstree-search';
+ }
+ }
+ }
+ return obj;
+ };
+ };
+
+ // helpers
+ (function ($) {
+ // from http://kiro.me/projects/fuse.html
+ $.vakata.search = function(pattern, txt, options) {
+ options = options || {};
+ options = $.extend({}, $.vakata.search.defaults, options);
+ if(options.fuzzy !== false) {
+ options.fuzzy = true;
+ }
+ pattern = options.caseSensitive ? pattern : pattern.toLowerCase();
+ var MATCH_LOCATION = options.location,
+ MATCH_DISTANCE = options.distance,
+ MATCH_THRESHOLD = options.threshold,
+ patternLen = pattern.length,
+ matchmask, pattern_alphabet, match_bitapScore, search;
+ if(patternLen > 32) {
+ options.fuzzy = false;
+ }
+ if(options.fuzzy) {
+ matchmask = 1 << (patternLen - 1);
+ pattern_alphabet = (function () {
+ var mask = {},
+ i = 0;
+ for (i = 0; i < patternLen; i++) {
+ mask[pattern.charAt(i)] = 0;
+ }
+ for (i = 0; i < patternLen; i++) {
+ mask[pattern.charAt(i)] |= 1 << (patternLen - i - 1);
+ }
+ return mask;
+ }());
+ match_bitapScore = function (e, x) {
+ var accuracy = e / patternLen,
+ proximity = Math.abs(MATCH_LOCATION - x);
+ if(!MATCH_DISTANCE) {
+ return proximity ? 1.0 : accuracy;
+ }
+ return accuracy + (proximity / MATCH_DISTANCE);
+ };
+ }
+ search = function (text) {
+ text = options.caseSensitive ? text : text.toLowerCase();
+ if(pattern === text || text.indexOf(pattern) !== -1) {
+ return {
+ isMatch: true,
+ score: 0
+ };
+ }
+ if(!options.fuzzy) {
+ return {
+ isMatch: false,
+ score: 1
+ };
+ }
+ var i, j,
+ textLen = text.length,
+ scoreThreshold = MATCH_THRESHOLD,
+ bestLoc = text.indexOf(pattern, MATCH_LOCATION),
+ binMin, binMid,
+ binMax = patternLen + textLen,
+ lastRd, start, finish, rd, charMatch,
+ score = 1,
+ locations = [];
+ if (bestLoc !== -1) {
+ scoreThreshold = Math.min(match_bitapScore(0, bestLoc), scoreThreshold);
+ bestLoc = text.lastIndexOf(pattern, MATCH_LOCATION + patternLen);
+ if (bestLoc !== -1) {
+ scoreThreshold = Math.min(match_bitapScore(0, bestLoc), scoreThreshold);
+ }
+ }
+ bestLoc = -1;
+ for (i = 0; i < patternLen; i++) {
+ binMin = 0;
+ binMid = binMax;
+ while (binMin < binMid) {
+ if (match_bitapScore(i, MATCH_LOCATION + binMid) <= scoreThreshold) {
+ binMin = binMid;
+ } else {
+ binMax = binMid;
+ }
+ binMid = Math.floor((binMax - binMin) / 2 + binMin);
+ }
+ binMax = binMid;
+ start = Math.max(1, MATCH_LOCATION - binMid + 1);
+ finish = Math.min(MATCH_LOCATION + binMid, textLen) + patternLen;
+ rd = new Array(finish + 2);
+ rd[finish + 1] = (1 << i) - 1;
+ for (j = finish; j >= start; j--) {
+ charMatch = pattern_alphabet[text.charAt(j - 1)];
+ if (i === 0) {
+ rd[j] = ((rd[j + 1] << 1) | 1) & charMatch;
+ } else {
+ rd[j] = ((rd[j + 1] << 1) | 1) & charMatch | (((lastRd[j + 1] | lastRd[j]) << 1) | 1) | lastRd[j + 1];
+ }
+ if (rd[j] & matchmask) {
+ score = match_bitapScore(i, j - 1);
+ if (score <= scoreThreshold) {
+ scoreThreshold = score;
+ bestLoc = j - 1;
+ locations.push(bestLoc);
+ if (bestLoc > MATCH_LOCATION) {
+ start = Math.max(1, 2 * MATCH_LOCATION - bestLoc);
+ } else {
+ break;
+ }
+ }
+ }
+ }
+ if (match_bitapScore(i + 1, MATCH_LOCATION) > scoreThreshold) {
+ break;
+ }
+ lastRd = rd;
+ }
+ return {
+ isMatch: bestLoc >= 0,
+ score: score
+ };
+ };
+ return txt === true ? { 'search' : search } : search(txt);
+ };
+ $.vakata.search.defaults = {
+ location : 0,
+ distance : 100,
+ threshold : 0.6,
+ fuzzy : false,
+ caseSensitive : false
+ };
+ }($));
+
+ // include the search plugin by default
+ // $.jstree.defaults.plugins.push("search");
+
+
+/**
+ * ### Sort plugin
+ *
+ * Automatically sorts all siblings in the tree according to a sorting function.
+ */
+
+ /**
+ * the settings function used to sort the nodes.
+ * It is executed in the tree's context, accepts two nodes as arguments and should return `1` or `-1`.
+ * @name $.jstree.defaults.sort
+ * @plugin sort
+ */
+ $.jstree.defaults.sort = function (a, b) {
+ //return this.get_type(a) === this.get_type(b) ? (this.get_text(a) > this.get_text(b) ? 1 : -1) : this.get_type(a) >= this.get_type(b);
+ return this.get_text(a) > this.get_text(b) ? 1 : -1;
+ };
+ $.jstree.plugins.sort = function (options, parent) {
+ this.bind = function () {
+ parent.bind.call(this);
+ this.element
+ .on("model.jstree", $.proxy(function (e, data) {
+ this.sort(data.parent, true);
+ }, this))
+ .on("rename_node.jstree create_node.jstree", $.proxy(function (e, data) {
+ this.sort(data.parent || data.node.parent, false);
+ this.redraw_node(data.parent || data.node.parent, true);
+ }, this))
+ .on("move_node.jstree copy_node.jstree", $.proxy(function (e, data) {
+ this.sort(data.parent, false);
+ this.redraw_node(data.parent, true);
+ }, this));
+ };
+ /**
+ * used to sort a node's children
+ * @private
+ * @name sort(obj [, deep])
+ * @param {mixed} obj the node
+ * @param {Boolean} deep if set to `true` nodes are sorted recursively.
+ * @plugin sort
+ * @trigger search.jstree
+ */
+ this.sort = function (obj, deep) {
+ var i, j;
+ obj = this.get_node(obj);
+ if(obj && obj.children && obj.children.length) {
+ obj.children.sort($.proxy(this.settings.sort, this));
+ if(deep) {
+ for(i = 0, j = obj.children_d.length; i < j; i++) {
+ this.sort(obj.children_d[i], false);
+ }
+ }
+ }
+ };
+ };
+
+ // include the sort plugin by default
+ // $.jstree.defaults.plugins.push("sort");
+
+/**
+ * ### State plugin
+ *
+ * Saves the state of the tree (selected nodes, opened nodes) on the user's computer using available options (localStorage, cookies, etc)
+ */
+
+ var to = false;
+ /**
+ * stores all defaults for the state plugin
+ * @name $.jstree.defaults.state
+ * @plugin state
+ */
+ $.jstree.defaults.state = {
+ /**
+ * A string for the key to use when saving the current tree (change if using multiple trees in your project). Defaults to `jstree`.
+ * @name $.jstree.defaults.state.key
+ * @plugin state
+ */
+ key : 'jstree',
+ /**
+ * A space separated list of events that trigger a state save. Defaults to `changed.jstree open_node.jstree close_node.jstree`.
+ * @name $.jstree.defaults.state.events
+ * @plugin state
+ */
+ events : 'changed.jstree open_node.jstree close_node.jstree check_node.jstree uncheck_node.jstree',
+ /**
+ * Time in milliseconds after which the state will expire. Defaults to 'false' meaning - no expire.
+ * @name $.jstree.defaults.state.ttl
+ * @plugin state
+ */
+ ttl : false,
+ /**
+ * A function that will be executed prior to restoring state with one argument - the state object. Can be used to clear unwanted parts of the state.
+ * @name $.jstree.defaults.state.filter
+ * @plugin state
+ */
+ filter : false
+ };
+ $.jstree.plugins.state = function (options, parent) {
+ this.bind = function () {
+ parent.bind.call(this);
+ var bind = $.proxy(function () {
+ this.element.on(this.settings.state.events, $.proxy(function () {
+ if(to) { clearTimeout(to); }
+ to = setTimeout($.proxy(function () { this.save_state(); }, this), 100);
+ }, this));
+ /**
+ * triggered when the state plugin is finished restoring the state (and immediately after ready if there is no state to restore).
+ * @event
+ * @name state_ready.jstree
+ * @plugin state
+ */
+ this.trigger('state_ready');
+ }, this);
+ this.element
+ .on("ready.jstree", $.proxy(function (e, data) {
+ this.element.one("restore_state.jstree", bind);
+ if(!this.restore_state()) { bind(); }
+ }, this));
+ };
+ /**
+ * save the state
+ * @name save_state()
+ * @plugin state
+ */
+ this.save_state = function () {
+ var st = { 'state' : this.get_state(), 'ttl' : this.settings.state.ttl, 'sec' : +(new Date()) };
+ $.vakata.storage.set(this.settings.state.key, JSON.stringify(st));
+ };
+ /**
+ * restore the state from the user's computer
+ * @name restore_state()
+ * @plugin state
+ */
+ this.restore_state = function () {
+ var k = $.vakata.storage.get(this.settings.state.key);
+ if(!!k) { try { k = JSON.parse(k); } catch(ex) { return false; } }
+ if(!!k && k.ttl && k.sec && +(new Date()) - k.sec > k.ttl) { return false; }
+ if(!!k && k.state) { k = k.state; }
+ if(!!k && $.isFunction(this.settings.state.filter)) { k = this.settings.state.filter.call(this, k); }
+ if(!!k) {
+ this.element.one("set_state.jstree", function (e, data) { data.instance.trigger('restore_state', { 'state' : $.extend(true, {}, k) }); });
+ this.set_state(k);
+ return true;
+ }
+ return false;
+ };
+ /**
+ * clear the state on the user's computer
+ * @name clear_state()
+ * @plugin state
+ */
+ this.clear_state = function () {
+ return $.vakata.storage.del(this.settings.state.key);
+ };
+ };
+
+ (function ($, undefined) {
+ $.vakata.storage = {
+ // simply specifying the functions in FF throws an error
+ set : function (key, val) { return window.localStorage.setItem(key, val); },
+ get : function (key) { return window.localStorage.getItem(key); },
+ del : function (key) { return window.localStorage.removeItem(key); }
+ };
+ }($));
+
+ // include the state plugin by default
+ // $.jstree.defaults.plugins.push("state");
+
+/**
+ * ### Types plugin
+ *
+ * Makes it possible to add predefined types for groups of nodes, which make it possible to easily control nesting rules and icon for each group.
+ */
+
+ /**
+ * An object storing all types as key value pairs, where the key is the type name and the value is an object that could contain following keys (all optional).
+ *
+ * * `max_children` the maximum number of immediate children this node type can have. Do not specify or set to `-1` for unlimited.
+ * * `max_depth` the maximum number of nesting this node type can have. A value of `1` would mean that the node can have children, but no grandchildren. Do not specify or set to `-1` for unlimited.
+ * * `valid_children` an array of node type strings, that nodes of this type can have as children. Do not specify or set to `-1` for no limits.
+ * * `icon` a string - can be a path to an icon or a className, if using an image that is in the current directory use a `./` prefix, otherwise it will be detected as a class. Omit to use the default icon from your theme.
+ * * `li_attr` an object of values which will be used to add HTML attributes on the resulting LI DOM node (merged with the node's own data)
+ * * `a_attr` an object of values which will be used to add HTML attributes on the resulting A DOM node (merged with the node's own data)
+ *
+ * There are two predefined types:
+ *
+ * * `#` represents the root of the tree, for example `max_children` would control the maximum number of root nodes.
+ * * `default` represents the default node - any settings here will be applied to all nodes that do not have a type specified.
+ *
+ * @name $.jstree.defaults.types
+ * @plugin types
+ */
+ $.jstree.defaults.types = {
+ 'default' : {}
+ };
+ $.jstree.defaults.types[$.jstree.root] = {};
+
+ $.jstree.plugins.types = function (options, parent) {
+ this.init = function (el, options) {
+ var i, j;
+ if(options && options.types && options.types['default']) {
+ for(i in options.types) {
+ if(i !== "default" && i !== $.jstree.root && options.types.hasOwnProperty(i)) {
+ for(j in options.types['default']) {
+ if(options.types['default'].hasOwnProperty(j) && options.types[i][j] === undefined) {
+ options.types[i][j] = options.types['default'][j];
+ }
+ }
+ }
+ }
+ }
+ parent.init.call(this, el, options);
+ this._model.data[$.jstree.root].type = $.jstree.root;
+ };
+ this.refresh = function (skip_loading, forget_state) {
+ parent.refresh.call(this, skip_loading, forget_state);
+ this._model.data[$.jstree.root].type = $.jstree.root;
+ };
+ this.bind = function () {
+ this.element
+ .on('model.jstree', $.proxy(function (e, data) {
+ var m = this._model.data,
+ dpc = data.nodes,
+ t = this.settings.types,
+ i, j, c = 'default', k;
+ for(i = 0, j = dpc.length; i < j; i++) {
+ c = 'default';
+ if(m[dpc[i]].original && m[dpc[i]].original.type && t[m[dpc[i]].original.type]) {
+ c = m[dpc[i]].original.type;
+ }
+ if(m[dpc[i]].data && m[dpc[i]].data.jstree && m[dpc[i]].data.jstree.type && t[m[dpc[i]].data.jstree.type]) {
+ c = m[dpc[i]].data.jstree.type;
+ }
+ m[dpc[i]].type = c;
+ if(m[dpc[i]].icon === true && t[c].icon !== undefined) {
+ m[dpc[i]].icon = t[c].icon;
+ }
+ if(t[c].li_attr !== undefined && typeof t[c].li_attr === 'object') {
+ for (k in t[c].li_attr) {
+ if (t[c].li_attr.hasOwnProperty(k)) {
+ if (k === 'id') {
+ continue;
+ }
+ else if (m[dpc[i]].li_attr[k] === undefined) {
+ m[dpc[i]].li_attr[k] = t[c].li_attr[k];
+ }
+ else if (k === 'class') {
+ m[dpc[i]].li_attr['class'] = t[c].li_attr['class'] + ' ' + m[dpc[i]].li_attr['class'];
+ }
+ }
+ }
+ }
+ if(t[c].a_attr !== undefined && typeof t[c].a_attr === 'object') {
+ for (k in t[c].a_attr) {
+ if (t[c].a_attr.hasOwnProperty(k)) {
+ if (k === 'id') {
+ continue;
+ }
+ else if (m[dpc[i]].a_attr[k] === undefined) {
+ m[dpc[i]].a_attr[k] = t[c].a_attr[k];
+ }
+ else if (k === 'href' && m[dpc[i]].a_attr[k] === '#') {
+ m[dpc[i]].a_attr['href'] = t[c].a_attr['href'];
+ }
+ else if (k === 'class') {
+ m[dpc[i]].a_attr['class'] = t[c].a_attr['class'] + ' ' + m[dpc[i]].a_attr['class'];
+ }
+ }
+ }
+ }
+ }
+ m[$.jstree.root].type = $.jstree.root;
+ }, this));
+ parent.bind.call(this);
+ };
+ this.get_json = function (obj, options, flat) {
+ var i, j,
+ m = this._model.data,
+ opt = options ? $.extend(true, {}, options, {no_id:false}) : {},
+ tmp = parent.get_json.call(this, obj, opt, flat);
+ if(tmp === false) { return false; }
+ if($.isArray(tmp)) {
+ for(i = 0, j = tmp.length; i < j; i++) {
+ tmp[i].type = tmp[i].id && m[tmp[i].id] && m[tmp[i].id].type ? m[tmp[i].id].type : "default";
+ if(options && options.no_id) {
+ delete tmp[i].id;
+ if(tmp[i].li_attr && tmp[i].li_attr.id) {
+ delete tmp[i].li_attr.id;
+ }
+ if(tmp[i].a_attr && tmp[i].a_attr.id) {
+ delete tmp[i].a_attr.id;
+ }
+ }
+ }
+ }
+ else {
+ tmp.type = tmp.id && m[tmp.id] && m[tmp.id].type ? m[tmp.id].type : "default";
+ if(options && options.no_id) {
+ tmp = this._delete_ids(tmp);
+ }
+ }
+ return tmp;
+ };
+ this._delete_ids = function (tmp) {
+ if($.isArray(tmp)) {
+ for(var i = 0, j = tmp.length; i < j; i++) {
+ tmp[i] = this._delete_ids(tmp[i]);
+ }
+ return tmp;
+ }
+ delete tmp.id;
+ if(tmp.li_attr && tmp.li_attr.id) {
+ delete tmp.li_attr.id;
+ }
+ if(tmp.a_attr && tmp.a_attr.id) {
+ delete tmp.a_attr.id;
+ }
+ if(tmp.children && $.isArray(tmp.children)) {
+ tmp.children = this._delete_ids(tmp.children);
+ }
+ return tmp;
+ };
+ this.check = function (chk, obj, par, pos, more) {
+ if(parent.check.call(this, chk, obj, par, pos, more) === false) { return false; }
+ obj = obj && obj.id ? obj : this.get_node(obj);
+ par = par && par.id ? par : this.get_node(par);
+ var m = obj && obj.id ? (more && more.origin ? more.origin : $.jstree.reference(obj.id)) : null, tmp, d, i, j;
+ m = m && m._model && m._model.data ? m._model.data : null;
+ switch(chk) {
+ case "create_node":
+ case "move_node":
+ case "copy_node":
+ if(chk !== 'move_node' || $.inArray(obj.id, par.children) === -1) {
+ tmp = this.get_rules(par);
+ if(tmp.max_children !== undefined && tmp.max_children !== -1 && tmp.max_children === par.children.length) {
+ this._data.core.last_error = { 'error' : 'check', 'plugin' : 'types', 'id' : 'types_01', 'reason' : 'max_children prevents function: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
+ return false;
+ }
+ if(tmp.valid_children !== undefined && tmp.valid_children !== -1 && $.inArray((obj.type || 'default'), tmp.valid_children) === -1) {
+ this._data.core.last_error = { 'error' : 'check', 'plugin' : 'types', 'id' : 'types_02', 'reason' : 'valid_children prevents function: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
+ return false;
+ }
+ if(m && obj.children_d && obj.parents) {
+ d = 0;
+ for(i = 0, j = obj.children_d.length; i < j; i++) {
+ d = Math.max(d, m[obj.children_d[i]].parents.length);
+ }
+ d = d - obj.parents.length + 1;
+ }
+ if(d <= 0 || d === undefined) { d = 1; }
+ do {
+ if(tmp.max_depth !== undefined && tmp.max_depth !== -1 && tmp.max_depth < d) {
+ this._data.core.last_error = { 'error' : 'check', 'plugin' : 'types', 'id' : 'types_03', 'reason' : 'max_depth prevents function: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
+ return false;
+ }
+ par = this.get_node(par.parent);
+ tmp = this.get_rules(par);
+ d++;
+ } while(par);
+ }
+ break;
+ }
+ return true;
+ };
+ /**
+ * used to retrieve the type settings object for a node
+ * @name get_rules(obj)
+ * @param {mixed} obj the node to find the rules for
+ * @return {Object}
+ * @plugin types
+ */
+ this.get_rules = function (obj) {
+ obj = this.get_node(obj);
+ if(!obj) { return false; }
+ var tmp = this.get_type(obj, true);
+ if(tmp.max_depth === undefined) { tmp.max_depth = -1; }
+ if(tmp.max_children === undefined) { tmp.max_children = -1; }
+ if(tmp.valid_children === undefined) { tmp.valid_children = -1; }
+ return tmp;
+ };
+ /**
+ * used to retrieve the type string or settings object for a node
+ * @name get_type(obj [, rules])
+ * @param {mixed} obj the node to find the rules for
+ * @param {Boolean} rules if set to `true` instead of a string the settings object will be returned
+ * @return {String|Object}
+ * @plugin types
+ */
+ this.get_type = function (obj, rules) {
+ obj = this.get_node(obj);
+ return (!obj) ? false : ( rules ? $.extend({ 'type' : obj.type }, this.settings.types[obj.type]) : obj.type);
+ };
+ /**
+ * used to change a node's type
+ * @name set_type(obj, type)
+ * @param {mixed} obj the node to change
+ * @param {String} type the new type
+ * @plugin types
+ */
+ this.set_type = function (obj, type) {
+ var m = this._model.data, t, t1, t2, old_type, old_icon, k, d, a;
+ if($.isArray(obj)) {
+ obj = obj.slice();
+ for(t1 = 0, t2 = obj.length; t1 < t2; t1++) {
+ this.set_type(obj[t1], type);
+ }
+ return true;
+ }
+ t = this.settings.types;
+ obj = this.get_node(obj);
+ if(!t[type] || !obj) { return false; }
+ d = this.get_node(obj, true);
+ if (d && d.length) {
+ a = d.children('.jstree-anchor');
+ }
+ old_type = obj.type;
+ old_icon = this.get_icon(obj);
+ obj.type = type;
+ if(old_icon === true || !t[old_type] || (t[old_type].icon !== undefined && old_icon === t[old_type].icon)) {
+ this.set_icon(obj, t[type].icon !== undefined ? t[type].icon : true);
+ }
+
+ // remove old type props
+ if(t[old_type] && t[old_type].li_attr !== undefined && typeof t[old_type].li_attr === 'object') {
+ for (k in t[old_type].li_attr) {
+ if (t[old_type].li_attr.hasOwnProperty(k)) {
+ if (k === 'id') {
+ continue;
+ }
+ else if (k === 'class') {
+ m[obj.id].li_attr['class'] = (m[obj.id].li_attr['class'] || '').replace(t[old_type].li_attr[k], '');
+ if (d) { d.removeClass(t[old_type].li_attr[k]); }
+ }
+ else if (m[obj.id].li_attr[k] === t[old_type].li_attr[k]) {
+ m[obj.id].li_attr[k] = null;
+ if (d) { d.removeAttr(k); }
+ }
+ }
+ }
+ }
+ if(t[old_type] && t[old_type].a_attr !== undefined && typeof t[old_type].a_attr === 'object') {
+ for (k in t[old_type].a_attr) {
+ if (t[old_type].a_attr.hasOwnProperty(k)) {
+ if (k === 'id') {
+ continue;
+ }
+ else if (k === 'class') {
+ m[obj.id].a_attr['class'] = (m[obj.id].a_attr['class'] || '').replace(t[old_type].a_attr[k], '');
+ if (a) { a.removeClass(t[old_type].a_attr[k]); }
+ }
+ else if (m[obj.id].a_attr[k] === t[old_type].a_attr[k]) {
+ if (k === 'href') {
+ m[obj.id].a_attr[k] = '#';
+ if (a) { a.attr('href', '#'); }
+ }
+ else {
+ delete m[obj.id].a_attr[k];
+ if (a) { a.removeAttr(k); }
+ }
+ }
+ }
+ }
+ }
+
+ // add new props
+ if(t[type].li_attr !== undefined && typeof t[type].li_attr === 'object') {
+ for (k in t[type].li_attr) {
+ if (t[type].li_attr.hasOwnProperty(k)) {
+ if (k === 'id') {
+ continue;
+ }
+ else if (m[obj.id].li_attr[k] === undefined) {
+ m[obj.id].li_attr[k] = t[type].li_attr[k];
+ if (d) {
+ if (k === 'class') {
+ d.addClass(t[type].li_attr[k]);
+ }
+ else {
+ d.attr(k, t[type].li_attr[k]);
+ }
+ }
+ }
+ else if (k === 'class') {
+ m[obj.id].li_attr['class'] = t[type].li_attr[k] + ' ' + m[obj.id].li_attr['class'];
+ if (d) { d.addClass(t[type].li_attr[k]); }
+ }
+ }
+ }
+ }
+ if(t[type].a_attr !== undefined && typeof t[type].a_attr === 'object') {
+ for (k in t[type].a_attr) {
+ if (t[type].a_attr.hasOwnProperty(k)) {
+ if (k === 'id') {
+ continue;
+ }
+ else if (m[obj.id].a_attr[k] === undefined) {
+ m[obj.id].a_attr[k] = t[type].a_attr[k];
+ if (a) {
+ if (k === 'class') {
+ a.addClass(t[type].a_attr[k]);
+ }
+ else {
+ a.attr(k, t[type].a_attr[k]);
+ }
+ }
+ }
+ else if (k === 'href' && m[obj.id].a_attr[k] === '#') {
+ m[obj.id].a_attr['href'] = t[type].a_attr['href'];
+ if (a) { a.attr('href', t[type].a_attr['href']); }
+ }
+ else if (k === 'class') {
+ m[obj.id].a_attr['class'] = t[type].a_attr['class'] + ' ' + m[obj.id].a_attr['class'];
+ if (a) { a.addClass(t[type].a_attr[k]); }
+ }
+ }
+ }
+ }
+
+ return true;
+ };
+ };
+ // include the types plugin by default
+ // $.jstree.defaults.plugins.push("types");
+
+
+/**
+ * ### Unique plugin
+ *
+ * Enforces that no nodes with the same name can coexist as siblings.
+ */
+
+ /**
+ * stores all defaults for the unique plugin
+ * @name $.jstree.defaults.unique
+ * @plugin unique
+ */
+ $.jstree.defaults.unique = {
+ /**
+ * Indicates if the comparison should be case sensitive. Default is `false`.
+ * @name $.jstree.defaults.unique.case_sensitive
+ * @plugin unique
+ */
+ case_sensitive : false,
+ /**
+ * A callback executed in the instance's scope when a new node is created and the name is already taken, the two arguments are the conflicting name and the counter. The default will produce results like `New node (2)`.
+ * @name $.jstree.defaults.unique.duplicate
+ * @plugin unique
+ */
+ duplicate : function (name, counter) {
+ return name + ' (' + counter + ')';
+ }
+ };
+
+ $.jstree.plugins.unique = function (options, parent) {
+ this.check = function (chk, obj, par, pos, more) {
+ if(parent.check.call(this, chk, obj, par, pos, more) === false) { return false; }
+ obj = obj && obj.id ? obj : this.get_node(obj);
+ par = par && par.id ? par : this.get_node(par);
+ if(!par || !par.children) { return true; }
+ var n = chk === "rename_node" ? pos : obj.text,
+ c = [],
+ s = this.settings.unique.case_sensitive,
+ m = this._model.data, i, j;
+ for(i = 0, j = par.children.length; i < j; i++) {
+ c.push(s ? m[par.children[i]].text : m[par.children[i]].text.toLowerCase());
+ }
+ if(!s) { n = n.toLowerCase(); }
+ switch(chk) {
+ case "delete_node":
+ return true;
+ case "rename_node":
+ i = ($.inArray(n, c) === -1 || (obj.text && obj.text[ s ? 'toString' : 'toLowerCase']() === n));
+ if(!i) {
+ this._data.core.last_error = { 'error' : 'check', 'plugin' : 'unique', 'id' : 'unique_01', 'reason' : 'Child with name ' + n + ' already exists. Preventing: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
+ }
+ return i;
+ case "create_node":
+ i = ($.inArray(n, c) === -1);
+ if(!i) {
+ this._data.core.last_error = { 'error' : 'check', 'plugin' : 'unique', 'id' : 'unique_04', 'reason' : 'Child with name ' + n + ' already exists. Preventing: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
+ }
+ return i;
+ case "copy_node":
+ i = ($.inArray(n, c) === -1);
+ if(!i) {
+ this._data.core.last_error = { 'error' : 'check', 'plugin' : 'unique', 'id' : 'unique_02', 'reason' : 'Child with name ' + n + ' already exists. Preventing: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
+ }
+ return i;
+ case "move_node":
+ i = ( (obj.parent === par.id && (!more || !more.is_multi)) || $.inArray(n, c) === -1);
+ if(!i) {
+ this._data.core.last_error = { 'error' : 'check', 'plugin' : 'unique', 'id' : 'unique_03', 'reason' : 'Child with name ' + n + ' already exists. Preventing: ' + chk, 'data' : JSON.stringify({ 'chk' : chk, 'pos' : pos, 'obj' : obj && obj.id ? obj.id : false, 'par' : par && par.id ? par.id : false }) };
+ }
+ return i;
+ }
+ return true;
+ };
+ this.create_node = function (par, node, pos, callback, is_loaded) {
+ if(!node || node.text === undefined) {
+ if(par === null) {
+ par = $.jstree.root;
+ }
+ par = this.get_node(par);
+ if(!par) {
+ return parent.create_node.call(this, par, node, pos, callback, is_loaded);
+ }
+ pos = pos === undefined ? "last" : pos;
+ if(!pos.toString().match(/^(before|after)$/) && !is_loaded && !this.is_loaded(par)) {
+ return parent.create_node.call(this, par, node, pos, callback, is_loaded);
+ }
+ if(!node) { node = {}; }
+ var tmp, n, dpc, i, j, m = this._model.data, s = this.settings.unique.case_sensitive, cb = this.settings.unique.duplicate;
+ n = tmp = this.get_string('New node');
+ dpc = [];
+ for(i = 0, j = par.children.length; i < j; i++) {
+ dpc.push(s ? m[par.children[i]].text : m[par.children[i]].text.toLowerCase());
+ }
+ i = 1;
+ while($.inArray(s ? n : n.toLowerCase(), dpc) !== -1) {
+ n = cb.call(this, tmp, (++i)).toString();
+ }
+ node.text = n;
+ }
+ return parent.create_node.call(this, par, node, pos, callback, is_loaded);
+ };
+ };
+
+ // include the unique plugin by default
+ // $.jstree.defaults.plugins.push("unique");
+
+
+/**
+ * ### Wholerow plugin
+ *
+ * Makes each node appear block level. Making selection easier. May cause slow down for large trees in old browsers.
+ */
+
+ var div = document.createElement('DIV');
+ div.setAttribute('unselectable','on');
+ div.setAttribute('role','presentation');
+ div.className = 'jstree-wholerow';
+ div.innerHTML = ' ';
+ $.jstree.plugins.wholerow = function (options, parent) {
+ this.bind = function () {
+ parent.bind.call(this);
+
+ this.element
+ .on('ready.jstree set_state.jstree', $.proxy(function () {
+ this.hide_dots();
+ }, this))
+ .on("init.jstree loading.jstree ready.jstree", $.proxy(function () {
+ //div.style.height = this._data.core.li_height + 'px';
+ this.get_container_ul().addClass('jstree-wholerow-ul');
+ }, this))
+ .on("deselect_all.jstree", $.proxy(function (e, data) {
+ this.element.find('.jstree-wholerow-clicked').removeClass('jstree-wholerow-clicked');
+ }, this))
+ .on("changed.jstree", $.proxy(function (e, data) {
+ this.element.find('.jstree-wholerow-clicked').removeClass('jstree-wholerow-clicked');
+ var tmp = false, i, j;
+ for(i = 0, j = data.selected.length; i < j; i++) {
+ tmp = this.get_node(data.selected[i], true);
+ if(tmp && tmp.length) {
+ tmp.children('.jstree-wholerow').addClass('jstree-wholerow-clicked');
+ }
+ }
+ }, this))
+ .on("open_node.jstree", $.proxy(function (e, data) {
+ this.get_node(data.node, true).find('.jstree-clicked').parent().children('.jstree-wholerow').addClass('jstree-wholerow-clicked');
+ }, this))
+ .on("hover_node.jstree dehover_node.jstree", $.proxy(function (e, data) {
+ if(e.type === "hover_node" && this.is_disabled(data.node)) { return; }
+ this.get_node(data.node, true).children('.jstree-wholerow')[e.type === "hover_node"?"addClass":"removeClass"]('jstree-wholerow-hovered');
+ }, this))
+ .on("contextmenu.jstree", ".jstree-wholerow", $.proxy(function (e) {
+ if (this._data.contextmenu) {
+ e.preventDefault();
+ var tmp = $.Event('contextmenu', { metaKey : e.metaKey, ctrlKey : e.ctrlKey, altKey : e.altKey, shiftKey : e.shiftKey, pageX : e.pageX, pageY : e.pageY });
+ $(e.currentTarget).closest(".jstree-node").children(".jstree-anchor").first().trigger(tmp);
+ }
+ }, this))
+ /*!
+ .on("mousedown.jstree touchstart.jstree", ".jstree-wholerow", function (e) {
+ if(e.target === e.currentTarget) {
+ var a = $(e.currentTarget).closest(".jstree-node").children(".jstree-anchor");
+ e.target = a[0];
+ a.trigger(e);
+ }
+ })
+ */
+ .on("click.jstree", ".jstree-wholerow", function (e) {
+ e.stopImmediatePropagation();
+ var tmp = $.Event('click', { metaKey : e.metaKey, ctrlKey : e.ctrlKey, altKey : e.altKey, shiftKey : e.shiftKey });
+ $(e.currentTarget).closest(".jstree-node").children(".jstree-anchor").first().trigger(tmp).focus();
+ })
+ .on("dblclick.jstree", ".jstree-wholerow", function (e) {
+ e.stopImmediatePropagation();
+ var tmp = $.Event('dblclick', { metaKey : e.metaKey, ctrlKey : e.ctrlKey, altKey : e.altKey, shiftKey : e.shiftKey });
+ $(e.currentTarget).closest(".jstree-node").children(".jstree-anchor").first().trigger(tmp).focus();
+ })
+ .on("click.jstree", ".jstree-leaf > .jstree-ocl", $.proxy(function (e) {
+ e.stopImmediatePropagation();
+ var tmp = $.Event('click', { metaKey : e.metaKey, ctrlKey : e.ctrlKey, altKey : e.altKey, shiftKey : e.shiftKey });
+ $(e.currentTarget).closest(".jstree-node").children(".jstree-anchor").first().trigger(tmp).focus();
+ }, this))
+ .on("mouseover.jstree", ".jstree-wholerow, .jstree-icon", $.proxy(function (e) {
+ e.stopImmediatePropagation();
+ if(!this.is_disabled(e.currentTarget)) {
+ this.hover_node(e.currentTarget);
+ }
+ return false;
+ }, this))
+ .on("mouseleave.jstree", ".jstree-node", $.proxy(function (e) {
+ this.dehover_node(e.currentTarget);
+ }, this));
+ };
+ this.teardown = function () {
+ if(this.settings.wholerow) {
+ this.element.find(".jstree-wholerow").remove();
+ }
+ parent.teardown.call(this);
+ };
+ this.redraw_node = function(obj, deep, callback, force_render) {
+ obj = parent.redraw_node.apply(this, arguments);
+ if(obj) {
+ var tmp = div.cloneNode(true);
+ //tmp.style.height = this._data.core.li_height + 'px';
+ if($.inArray(obj.id, this._data.core.selected) !== -1) { tmp.className += ' jstree-wholerow-clicked'; }
+ if(this._data.core.focused && this._data.core.focused === obj.id) { tmp.className += ' jstree-wholerow-hovered'; }
+ obj.insertBefore(tmp, obj.childNodes[0]);
+ }
+ return obj;
+ };
+ };
+ // include the wholerow plugin by default
+ // $.jstree.defaults.plugins.push("wholerow");
+ if(document.registerElement && Object && Object.create) {
+ var proto = Object.create(HTMLElement.prototype);
+ proto.createdCallback = function () {
+ var c = { core : {}, plugins : [] }, i;
+ for(i in $.jstree.plugins) {
+ if($.jstree.plugins.hasOwnProperty(i) && this.attributes[i]) {
+ c.plugins.push(i);
+ if(this.getAttribute(i) && JSON.parse(this.getAttribute(i))) {
+ c[i] = JSON.parse(this.getAttribute(i));
+ }
+ }
+ }
+ for(i in $.jstree.defaults.core) {
+ if($.jstree.defaults.core.hasOwnProperty(i) && this.attributes[i]) {
+ c.core[i] = JSON.parse(this.getAttribute(i)) || this.getAttribute(i);
+ }
+ }
+ $(this).jstree(c);
+ };
+ // proto.attributeChangedCallback = function (name, previous, value) { };
+ try {
+ document.registerElement("vakata-jstree", { prototype: proto });
+ } catch(ignore) { }
+ }
+
+}));
\ No newline at end of file
diff --git a/public/static/lib/jstree/themes/default-dark/32px.png b/public/static/lib/jstree/themes/default-dark/32px.png
new file mode 100755
index 00000000..d6fd7211
Binary files /dev/null and b/public/static/lib/jstree/themes/default-dark/32px.png differ
diff --git a/public/static/lib/jstree/themes/default-dark/40px.png b/public/static/lib/jstree/themes/default-dark/40px.png
new file mode 100755
index 00000000..4fc88e41
Binary files /dev/null and b/public/static/lib/jstree/themes/default-dark/40px.png differ
diff --git a/public/static/lib/jstree/themes/default-dark/style.css b/public/static/lib/jstree/themes/default-dark/style.css
new file mode 100755
index 00000000..3af003f2
--- /dev/null
+++ b/public/static/lib/jstree/themes/default-dark/style.css
@@ -0,0 +1,1146 @@
+/* jsTree default dark theme */
+.jstree-node,
+.jstree-children,
+.jstree-container-ul {
+ display: block;
+ margin: 0;
+ padding: 0;
+ list-style-type: none;
+ list-style-image: none;
+}
+.jstree-node {
+ white-space: nowrap;
+}
+.jstree-anchor {
+ display: inline-block;
+ color: black;
+ white-space: nowrap;
+ padding: 0 4px 0 1px;
+ margin: 0;
+ vertical-align: top;
+}
+.jstree-anchor:focus {
+ outline: 0;
+}
+.jstree-anchor,
+.jstree-anchor:link,
+.jstree-anchor:visited,
+.jstree-anchor:hover,
+.jstree-anchor:active {
+ text-decoration: none;
+ color: inherit;
+}
+.jstree-icon {
+ display: inline-block;
+ text-decoration: none;
+ margin: 0;
+ padding: 0;
+ vertical-align: top;
+ text-align: center;
+}
+.jstree-icon:empty {
+ display: inline-block;
+ text-decoration: none;
+ margin: 0;
+ padding: 0;
+ vertical-align: top;
+ text-align: center;
+}
+.jstree-ocl {
+ cursor: pointer;
+}
+.jstree-leaf > .jstree-ocl {
+ cursor: default;
+}
+.jstree .jstree-open > .jstree-children {
+ display: block;
+}
+.jstree .jstree-closed > .jstree-children,
+.jstree .jstree-leaf > .jstree-children {
+ display: none;
+}
+.jstree-anchor > .jstree-themeicon {
+ margin-right: 2px;
+}
+.jstree-no-icons .jstree-themeicon,
+.jstree-anchor > .jstree-themeicon-hidden {
+ display: none;
+}
+.jstree-hidden,
+.jstree-node.jstree-hidden {
+ display: none;
+}
+.jstree-rtl .jstree-anchor {
+ padding: 0 1px 0 4px;
+}
+.jstree-rtl .jstree-anchor > .jstree-themeicon {
+ margin-left: 2px;
+ margin-right: 0;
+}
+.jstree-rtl .jstree-node {
+ margin-left: 0;
+}
+.jstree-rtl .jstree-container-ul > .jstree-node {
+ margin-right: 0;
+}
+.jstree-wholerow-ul {
+ position: relative;
+ display: inline-block;
+ min-width: 100%;
+}
+.jstree-wholerow-ul .jstree-leaf > .jstree-ocl {
+ cursor: pointer;
+}
+.jstree-wholerow-ul .jstree-anchor,
+.jstree-wholerow-ul .jstree-icon {
+ position: relative;
+}
+.jstree-wholerow-ul .jstree-wholerow {
+ width: 100%;
+ cursor: pointer;
+ position: absolute;
+ left: 0;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+.vakata-context {
+ display: none;
+}
+.vakata-context,
+.vakata-context ul {
+ margin: 0;
+ padding: 2px;
+ position: absolute;
+ background: #f5f5f5;
+ border: 1px solid #979797;
+ box-shadow: 2px 2px 2px #999999;
+}
+.vakata-context ul {
+ list-style: none;
+ left: 100%;
+ margin-top: -2.7em;
+ margin-left: -4px;
+}
+.vakata-context .vakata-context-right ul {
+ left: auto;
+ right: 100%;
+ margin-left: auto;
+ margin-right: -4px;
+}
+.vakata-context li {
+ list-style: none;
+}
+.vakata-context li > a {
+ display: block;
+ padding: 0 2em 0 2em;
+ text-decoration: none;
+ width: auto;
+ color: black;
+ white-space: nowrap;
+ line-height: 2.4em;
+ text-shadow: 1px 1px 0 white;
+ border-radius: 1px;
+}
+.vakata-context li > a:hover {
+ position: relative;
+ background-color: #e8eff7;
+ box-shadow: 0 0 2px #0a6aa1;
+}
+.vakata-context li > a.vakata-context-parent {
+ background-image: url("data:image/gif;base64,R0lGODlhCwAHAIAAACgoKP///yH5BAEAAAEALAAAAAALAAcAAAIORI4JlrqN1oMSnmmZDQUAOw==");
+ background-position: right center;
+ background-repeat: no-repeat;
+}
+.vakata-context li > a:focus {
+ outline: 0;
+}
+.vakata-context .vakata-context-hover > a {
+ position: relative;
+ background-color: #e8eff7;
+ box-shadow: 0 0 2px #0a6aa1;
+}
+.vakata-context .vakata-context-separator > a,
+.vakata-context .vakata-context-separator > a:hover {
+ background: white;
+ border: 0;
+ border-top: 1px solid #e2e3e3;
+ height: 1px;
+ min-height: 1px;
+ max-height: 1px;
+ padding: 0;
+ margin: 0 0 0 2.4em;
+ border-left: 1px solid #e0e0e0;
+ text-shadow: 0 0 0 transparent;
+ box-shadow: 0 0 0 transparent;
+ border-radius: 0;
+}
+.vakata-context .vakata-contextmenu-disabled a,
+.vakata-context .vakata-contextmenu-disabled a:hover {
+ color: silver;
+ background-color: transparent;
+ border: 0;
+ box-shadow: 0 0 0;
+}
+.vakata-context li > a > i {
+ text-decoration: none;
+ display: inline-block;
+ width: 2.4em;
+ height: 2.4em;
+ background: transparent;
+ margin: 0 0 0 -2em;
+ vertical-align: top;
+ text-align: center;
+ line-height: 2.4em;
+}
+.vakata-context li > a > i:empty {
+ width: 2.4em;
+ line-height: 2.4em;
+}
+.vakata-context li > a .vakata-contextmenu-sep {
+ display: inline-block;
+ width: 1px;
+ height: 2.4em;
+ background: white;
+ margin: 0 0.5em 0 0;
+ border-left: 1px solid #e2e3e3;
+}
+.vakata-context .vakata-contextmenu-shortcut {
+ font-size: 0.8em;
+ color: silver;
+ opacity: 0.5;
+ display: none;
+}
+.vakata-context-rtl ul {
+ left: auto;
+ right: 100%;
+ margin-left: auto;
+ margin-right: -4px;
+}
+.vakata-context-rtl li > a.vakata-context-parent {
+ background-image: url("data:image/gif;base64,R0lGODlhCwAHAIAAACgoKP///yH5BAEAAAEALAAAAAALAAcAAAINjI+AC7rWHIsPtmoxLAA7");
+ background-position: left center;
+ background-repeat: no-repeat;
+}
+.vakata-context-rtl .vakata-context-separator > a {
+ margin: 0 2.4em 0 0;
+ border-left: 0;
+ border-right: 1px solid #e2e3e3;
+}
+.vakata-context-rtl .vakata-context-left ul {
+ right: auto;
+ left: 100%;
+ margin-left: -4px;
+ margin-right: auto;
+}
+.vakata-context-rtl li > a > i {
+ margin: 0 -2em 0 0;
+}
+.vakata-context-rtl li > a .vakata-contextmenu-sep {
+ margin: 0 0 0 0.5em;
+ border-left-color: white;
+ background: #e2e3e3;
+}
+#jstree-marker {
+ position: absolute;
+ top: 0;
+ left: 0;
+ margin: -5px 0 0 0;
+ padding: 0;
+ border-right: 0;
+ border-top: 5px solid transparent;
+ border-bottom: 5px solid transparent;
+ border-left: 5px solid;
+ width: 0;
+ height: 0;
+ font-size: 0;
+ line-height: 0;
+}
+#jstree-dnd {
+ line-height: 16px;
+ margin: 0;
+ padding: 4px;
+}
+#jstree-dnd .jstree-icon,
+#jstree-dnd .jstree-copy {
+ display: inline-block;
+ text-decoration: none;
+ margin: 0 2px 0 0;
+ padding: 0;
+ width: 16px;
+ height: 16px;
+}
+#jstree-dnd .jstree-ok {
+ background: green;
+}
+#jstree-dnd .jstree-er {
+ background: red;
+}
+#jstree-dnd .jstree-copy {
+ margin: 0 2px 0 2px;
+}
+.jstree-default-dark .jstree-node,
+.jstree-default-dark .jstree-icon {
+ background-repeat: no-repeat;
+ background-color: transparent;
+}
+.jstree-default-dark .jstree-anchor,
+.jstree-default-dark .jstree-animated,
+.jstree-default-dark .jstree-wholerow {
+ transition: background-color 0.15s, box-shadow 0.15s;
+}
+.jstree-default-dark .jstree-hovered {
+ background: #555555;
+ border-radius: 2px;
+ box-shadow: inset 0 0 1px #555555;
+}
+.jstree-default-dark .jstree-context {
+ background: #555555;
+ border-radius: 2px;
+ box-shadow: inset 0 0 1px #555555;
+}
+.jstree-default-dark .jstree-clicked {
+ background: #5fa2db;
+ border-radius: 2px;
+ box-shadow: inset 0 0 1px #666666;
+}
+.jstree-default-dark .jstree-no-icons .jstree-anchor > .jstree-themeicon {
+ display: none;
+}
+.jstree-default-dark .jstree-disabled {
+ background: transparent;
+ color: #666666;
+}
+.jstree-default-dark .jstree-disabled.jstree-hovered {
+ background: transparent;
+ box-shadow: none;
+}
+.jstree-default-dark .jstree-disabled.jstree-clicked {
+ background: #333333;
+}
+.jstree-default-dark .jstree-disabled > .jstree-icon {
+ opacity: 0.8;
+ filter: url("data:image/svg+xml;utf8,#jstree-grayscale");
+ /* Firefox 10+ */
+ filter: gray;
+ /* IE6-9 */
+ -webkit-filter: grayscale(100%);
+ /* Chrome 19+ & Safari 6+ */
+}
+.jstree-default-dark .jstree-search {
+ font-style: italic;
+ color: #ffffff;
+ font-weight: bold;
+}
+.jstree-default-dark .jstree-no-checkboxes .jstree-checkbox {
+ display: none !important;
+}
+.jstree-default-dark.jstree-checkbox-no-clicked .jstree-clicked {
+ background: transparent;
+ box-shadow: none;
+}
+.jstree-default-dark.jstree-checkbox-no-clicked .jstree-clicked.jstree-hovered {
+ background: #555555;
+}
+.jstree-default-dark.jstree-checkbox-no-clicked > .jstree-wholerow-ul .jstree-wholerow-clicked {
+ background: transparent;
+}
+.jstree-default-dark.jstree-checkbox-no-clicked > .jstree-wholerow-ul .jstree-wholerow-clicked.jstree-wholerow-hovered {
+ background: #555555;
+}
+.jstree-default-dark > .jstree-striped {
+ min-width: 100%;
+ display: inline-block;
+ background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAkCAMAAAB/qqA+AAAABlBMVEUAAAAAAAClZ7nPAAAAAnRSTlMNAMM9s3UAAAAXSURBVHjajcEBAQAAAIKg/H/aCQZ70AUBjAATb6YPDgAAAABJRU5ErkJggg==") left top repeat;
+}
+.jstree-default-dark > .jstree-wholerow-ul .jstree-hovered,
+.jstree-default-dark > .jstree-wholerow-ul .jstree-clicked {
+ background: transparent;
+ box-shadow: none;
+ border-radius: 0;
+}
+.jstree-default-dark .jstree-wholerow {
+ -moz-box-sizing: border-box;
+ -webkit-box-sizing: border-box;
+ box-sizing: border-box;
+}
+.jstree-default-dark .jstree-wholerow-hovered {
+ background: #555555;
+}
+.jstree-default-dark .jstree-wholerow-clicked {
+ background: #5fa2db;
+ background: -webkit-linear-gradient(top, #5fa2db 0%, #5fa2db 100%);
+ background: linear-gradient(to bottom, #5fa2db 0%, #5fa2db 100%);
+}
+.jstree-default-dark .jstree-node {
+ min-height: 24px;
+ line-height: 24px;
+ margin-left: 24px;
+ min-width: 24px;
+}
+.jstree-default-dark .jstree-anchor {
+ line-height: 24px;
+ height: 24px;
+}
+.jstree-default-dark .jstree-icon {
+ width: 24px;
+ height: 24px;
+ line-height: 24px;
+}
+.jstree-default-dark .jstree-icon:empty {
+ width: 24px;
+ height: 24px;
+ line-height: 24px;
+}
+.jstree-default-dark.jstree-rtl .jstree-node {
+ margin-right: 24px;
+}
+.jstree-default-dark .jstree-wholerow {
+ height: 24px;
+}
+.jstree-default-dark .jstree-node,
+.jstree-default-dark .jstree-icon {
+ background-image: url("32px.png");
+}
+.jstree-default-dark .jstree-node {
+ background-position: -292px -4px;
+ background-repeat: repeat-y;
+}
+.jstree-default-dark .jstree-last {
+ background: transparent;
+}
+.jstree-default-dark .jstree-open > .jstree-ocl {
+ background-position: -132px -4px;
+}
+.jstree-default-dark .jstree-closed > .jstree-ocl {
+ background-position: -100px -4px;
+}
+.jstree-default-dark .jstree-leaf > .jstree-ocl {
+ background-position: -68px -4px;
+}
+.jstree-default-dark .jstree-themeicon {
+ background-position: -260px -4px;
+}
+.jstree-default-dark > .jstree-no-dots .jstree-node,
+.jstree-default-dark > .jstree-no-dots .jstree-leaf > .jstree-ocl {
+ background: transparent;
+}
+.jstree-default-dark > .jstree-no-dots .jstree-open > .jstree-ocl {
+ background-position: -36px -4px;
+}
+.jstree-default-dark > .jstree-no-dots .jstree-closed > .jstree-ocl {
+ background-position: -4px -4px;
+}
+.jstree-default-dark .jstree-disabled {
+ background: transparent;
+}
+.jstree-default-dark .jstree-disabled.jstree-hovered {
+ background: transparent;
+}
+.jstree-default-dark .jstree-disabled.jstree-clicked {
+ background: #efefef;
+}
+.jstree-default-dark .jstree-checkbox {
+ background-position: -164px -4px;
+}
+.jstree-default-dark .jstree-checkbox:hover {
+ background-position: -164px -36px;
+}
+.jstree-default-dark.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox,
+.jstree-default-dark .jstree-checked > .jstree-checkbox {
+ background-position: -228px -4px;
+}
+.jstree-default-dark.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox:hover,
+.jstree-default-dark .jstree-checked > .jstree-checkbox:hover {
+ background-position: -228px -36px;
+}
+.jstree-default-dark .jstree-anchor > .jstree-undetermined {
+ background-position: -196px -4px;
+}
+.jstree-default-dark .jstree-anchor > .jstree-undetermined:hover {
+ background-position: -196px -36px;
+}
+.jstree-default-dark .jstree-checkbox-disabled {
+ opacity: 0.8;
+ filter: url("data:image/svg+xml;utf8,#jstree-grayscale");
+ /* Firefox 10+ */
+ filter: gray;
+ /* IE6-9 */
+ -webkit-filter: grayscale(100%);
+ /* Chrome 19+ & Safari 6+ */
+}
+.jstree-default-dark > .jstree-striped {
+ background-size: auto 48px;
+}
+.jstree-default-dark.jstree-rtl .jstree-node {
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==");
+ background-position: 100% 1px;
+ background-repeat: repeat-y;
+}
+.jstree-default-dark.jstree-rtl .jstree-last {
+ background: transparent;
+}
+.jstree-default-dark.jstree-rtl .jstree-open > .jstree-ocl {
+ background-position: -132px -36px;
+}
+.jstree-default-dark.jstree-rtl .jstree-closed > .jstree-ocl {
+ background-position: -100px -36px;
+}
+.jstree-default-dark.jstree-rtl .jstree-leaf > .jstree-ocl {
+ background-position: -68px -36px;
+}
+.jstree-default-dark.jstree-rtl > .jstree-no-dots .jstree-node,
+.jstree-default-dark.jstree-rtl > .jstree-no-dots .jstree-leaf > .jstree-ocl {
+ background: transparent;
+}
+.jstree-default-dark.jstree-rtl > .jstree-no-dots .jstree-open > .jstree-ocl {
+ background-position: -36px -36px;
+}
+.jstree-default-dark.jstree-rtl > .jstree-no-dots .jstree-closed > .jstree-ocl {
+ background-position: -4px -36px;
+}
+.jstree-default-dark .jstree-themeicon-custom {
+ background-color: transparent;
+ background-image: none;
+ background-position: 0 0;
+}
+.jstree-default-dark > .jstree-container-ul .jstree-loading > .jstree-ocl {
+ background: url("throbber.gif") center center no-repeat;
+}
+.jstree-default-dark .jstree-file {
+ background: url("32px.png") -100px -68px no-repeat;
+}
+.jstree-default-dark .jstree-folder {
+ background: url("32px.png") -260px -4px no-repeat;
+}
+.jstree-default-dark > .jstree-container-ul > .jstree-node {
+ margin-left: 0;
+ margin-right: 0;
+}
+#jstree-dnd.jstree-default-dark {
+ line-height: 24px;
+ padding: 0 4px;
+}
+#jstree-dnd.jstree-default-dark .jstree-ok,
+#jstree-dnd.jstree-default-dark .jstree-er {
+ background-image: url("32px.png");
+ background-repeat: no-repeat;
+ background-color: transparent;
+}
+#jstree-dnd.jstree-default-dark i {
+ background: transparent;
+ width: 24px;
+ height: 24px;
+ line-height: 24px;
+}
+#jstree-dnd.jstree-default-dark .jstree-ok {
+ background-position: -4px -68px;
+}
+#jstree-dnd.jstree-default-dark .jstree-er {
+ background-position: -36px -68px;
+}
+.jstree-default-dark .jstree-ellipsis {
+ overflow: hidden;
+}
+.jstree-default-dark .jstree-ellipsis .jstree-anchor {
+ width: calc(100% - 29px);
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
+.jstree-default-dark .jstree-ellipsis.jstree-no-icons .jstree-anchor {
+ width: calc(100% - 5px);
+}
+.jstree-default-dark.jstree-rtl .jstree-node {
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==");
+}
+.jstree-default-dark.jstree-rtl .jstree-last {
+ background: transparent;
+}
+.jstree-default-dark-small .jstree-node {
+ min-height: 18px;
+ line-height: 18px;
+ margin-left: 18px;
+ min-width: 18px;
+}
+.jstree-default-dark-small .jstree-anchor {
+ line-height: 18px;
+ height: 18px;
+}
+.jstree-default-dark-small .jstree-icon {
+ width: 18px;
+ height: 18px;
+ line-height: 18px;
+}
+.jstree-default-dark-small .jstree-icon:empty {
+ width: 18px;
+ height: 18px;
+ line-height: 18px;
+}
+.jstree-default-dark-small.jstree-rtl .jstree-node {
+ margin-right: 18px;
+}
+.jstree-default-dark-small .jstree-wholerow {
+ height: 18px;
+}
+.jstree-default-dark-small .jstree-node,
+.jstree-default-dark-small .jstree-icon {
+ background-image: url("32px.png");
+}
+.jstree-default-dark-small .jstree-node {
+ background-position: -295px -7px;
+ background-repeat: repeat-y;
+}
+.jstree-default-dark-small .jstree-last {
+ background: transparent;
+}
+.jstree-default-dark-small .jstree-open > .jstree-ocl {
+ background-position: -135px -7px;
+}
+.jstree-default-dark-small .jstree-closed > .jstree-ocl {
+ background-position: -103px -7px;
+}
+.jstree-default-dark-small .jstree-leaf > .jstree-ocl {
+ background-position: -71px -7px;
+}
+.jstree-default-dark-small .jstree-themeicon {
+ background-position: -263px -7px;
+}
+.jstree-default-dark-small > .jstree-no-dots .jstree-node,
+.jstree-default-dark-small > .jstree-no-dots .jstree-leaf > .jstree-ocl {
+ background: transparent;
+}
+.jstree-default-dark-small > .jstree-no-dots .jstree-open > .jstree-ocl {
+ background-position: -39px -7px;
+}
+.jstree-default-dark-small > .jstree-no-dots .jstree-closed > .jstree-ocl {
+ background-position: -7px -7px;
+}
+.jstree-default-dark-small .jstree-disabled {
+ background: transparent;
+}
+.jstree-default-dark-small .jstree-disabled.jstree-hovered {
+ background: transparent;
+}
+.jstree-default-dark-small .jstree-disabled.jstree-clicked {
+ background: #efefef;
+}
+.jstree-default-dark-small .jstree-checkbox {
+ background-position: -167px -7px;
+}
+.jstree-default-dark-small .jstree-checkbox:hover {
+ background-position: -167px -39px;
+}
+.jstree-default-dark-small.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox,
+.jstree-default-dark-small .jstree-checked > .jstree-checkbox {
+ background-position: -231px -7px;
+}
+.jstree-default-dark-small.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox:hover,
+.jstree-default-dark-small .jstree-checked > .jstree-checkbox:hover {
+ background-position: -231px -39px;
+}
+.jstree-default-dark-small .jstree-anchor > .jstree-undetermined {
+ background-position: -199px -7px;
+}
+.jstree-default-dark-small .jstree-anchor > .jstree-undetermined:hover {
+ background-position: -199px -39px;
+}
+.jstree-default-dark-small .jstree-checkbox-disabled {
+ opacity: 0.8;
+ filter: url("data:image/svg+xml;utf8,#jstree-grayscale");
+ /* Firefox 10+ */
+ filter: gray;
+ /* IE6-9 */
+ -webkit-filter: grayscale(100%);
+ /* Chrome 19+ & Safari 6+ */
+}
+.jstree-default-dark-small > .jstree-striped {
+ background-size: auto 36px;
+}
+.jstree-default-dark-small.jstree-rtl .jstree-node {
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==");
+ background-position: 100% 1px;
+ background-repeat: repeat-y;
+}
+.jstree-default-dark-small.jstree-rtl .jstree-last {
+ background: transparent;
+}
+.jstree-default-dark-small.jstree-rtl .jstree-open > .jstree-ocl {
+ background-position: -135px -39px;
+}
+.jstree-default-dark-small.jstree-rtl .jstree-closed > .jstree-ocl {
+ background-position: -103px -39px;
+}
+.jstree-default-dark-small.jstree-rtl .jstree-leaf > .jstree-ocl {
+ background-position: -71px -39px;
+}
+.jstree-default-dark-small.jstree-rtl > .jstree-no-dots .jstree-node,
+.jstree-default-dark-small.jstree-rtl > .jstree-no-dots .jstree-leaf > .jstree-ocl {
+ background: transparent;
+}
+.jstree-default-dark-small.jstree-rtl > .jstree-no-dots .jstree-open > .jstree-ocl {
+ background-position: -39px -39px;
+}
+.jstree-default-dark-small.jstree-rtl > .jstree-no-dots .jstree-closed > .jstree-ocl {
+ background-position: -7px -39px;
+}
+.jstree-default-dark-small .jstree-themeicon-custom {
+ background-color: transparent;
+ background-image: none;
+ background-position: 0 0;
+}
+.jstree-default-dark-small > .jstree-container-ul .jstree-loading > .jstree-ocl {
+ background: url("throbber.gif") center center no-repeat;
+}
+.jstree-default-dark-small .jstree-file {
+ background: url("32px.png") -103px -71px no-repeat;
+}
+.jstree-default-dark-small .jstree-folder {
+ background: url("32px.png") -263px -7px no-repeat;
+}
+.jstree-default-dark-small > .jstree-container-ul > .jstree-node {
+ margin-left: 0;
+ margin-right: 0;
+}
+#jstree-dnd.jstree-default-dark-small {
+ line-height: 18px;
+ padding: 0 4px;
+}
+#jstree-dnd.jstree-default-dark-small .jstree-ok,
+#jstree-dnd.jstree-default-dark-small .jstree-er {
+ background-image: url("32px.png");
+ background-repeat: no-repeat;
+ background-color: transparent;
+}
+#jstree-dnd.jstree-default-dark-small i {
+ background: transparent;
+ width: 18px;
+ height: 18px;
+ line-height: 18px;
+}
+#jstree-dnd.jstree-default-dark-small .jstree-ok {
+ background-position: -7px -71px;
+}
+#jstree-dnd.jstree-default-dark-small .jstree-er {
+ background-position: -39px -71px;
+}
+.jstree-default-dark-small .jstree-ellipsis {
+ overflow: hidden;
+}
+.jstree-default-dark-small .jstree-ellipsis .jstree-anchor {
+ width: calc(100% - 23px);
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
+.jstree-default-dark-small .jstree-ellipsis.jstree-no-icons .jstree-anchor {
+ width: calc(100% - 5px);
+}
+.jstree-default-dark-small.jstree-rtl .jstree-node {
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAACAQMAAABv1h6PAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMHBgAAiABBI4gz9AAAAABJRU5ErkJggg==");
+}
+.jstree-default-dark-small.jstree-rtl .jstree-last {
+ background: transparent;
+}
+.jstree-default-dark-large .jstree-node {
+ min-height: 32px;
+ line-height: 32px;
+ margin-left: 32px;
+ min-width: 32px;
+}
+.jstree-default-dark-large .jstree-anchor {
+ line-height: 32px;
+ height: 32px;
+}
+.jstree-default-dark-large .jstree-icon {
+ width: 32px;
+ height: 32px;
+ line-height: 32px;
+}
+.jstree-default-dark-large .jstree-icon:empty {
+ width: 32px;
+ height: 32px;
+ line-height: 32px;
+}
+.jstree-default-dark-large.jstree-rtl .jstree-node {
+ margin-right: 32px;
+}
+.jstree-default-dark-large .jstree-wholerow {
+ height: 32px;
+}
+.jstree-default-dark-large .jstree-node,
+.jstree-default-dark-large .jstree-icon {
+ background-image: url("32px.png");
+}
+.jstree-default-dark-large .jstree-node {
+ background-position: -288px 0px;
+ background-repeat: repeat-y;
+}
+.jstree-default-dark-large .jstree-last {
+ background: transparent;
+}
+.jstree-default-dark-large .jstree-open > .jstree-ocl {
+ background-position: -128px 0px;
+}
+.jstree-default-dark-large .jstree-closed > .jstree-ocl {
+ background-position: -96px 0px;
+}
+.jstree-default-dark-large .jstree-leaf > .jstree-ocl {
+ background-position: -64px 0px;
+}
+.jstree-default-dark-large .jstree-themeicon {
+ background-position: -256px 0px;
+}
+.jstree-default-dark-large > .jstree-no-dots .jstree-node,
+.jstree-default-dark-large > .jstree-no-dots .jstree-leaf > .jstree-ocl {
+ background: transparent;
+}
+.jstree-default-dark-large > .jstree-no-dots .jstree-open > .jstree-ocl {
+ background-position: -32px 0px;
+}
+.jstree-default-dark-large > .jstree-no-dots .jstree-closed > .jstree-ocl {
+ background-position: 0px 0px;
+}
+.jstree-default-dark-large .jstree-disabled {
+ background: transparent;
+}
+.jstree-default-dark-large .jstree-disabled.jstree-hovered {
+ background: transparent;
+}
+.jstree-default-dark-large .jstree-disabled.jstree-clicked {
+ background: #efefef;
+}
+.jstree-default-dark-large .jstree-checkbox {
+ background-position: -160px 0px;
+}
+.jstree-default-dark-large .jstree-checkbox:hover {
+ background-position: -160px -32px;
+}
+.jstree-default-dark-large.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox,
+.jstree-default-dark-large .jstree-checked > .jstree-checkbox {
+ background-position: -224px 0px;
+}
+.jstree-default-dark-large.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox:hover,
+.jstree-default-dark-large .jstree-checked > .jstree-checkbox:hover {
+ background-position: -224px -32px;
+}
+.jstree-default-dark-large .jstree-anchor > .jstree-undetermined {
+ background-position: -192px 0px;
+}
+.jstree-default-dark-large .jstree-anchor > .jstree-undetermined:hover {
+ background-position: -192px -32px;
+}
+.jstree-default-dark-large .jstree-checkbox-disabled {
+ opacity: 0.8;
+ filter: url("data:image/svg+xml;utf8,#jstree-grayscale");
+ /* Firefox 10+ */
+ filter: gray;
+ /* IE6-9 */
+ -webkit-filter: grayscale(100%);
+ /* Chrome 19+ & Safari 6+ */
+}
+.jstree-default-dark-large > .jstree-striped {
+ background-size: auto 64px;
+}
+.jstree-default-dark-large.jstree-rtl .jstree-node {
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==");
+ background-position: 100% 1px;
+ background-repeat: repeat-y;
+}
+.jstree-default-dark-large.jstree-rtl .jstree-last {
+ background: transparent;
+}
+.jstree-default-dark-large.jstree-rtl .jstree-open > .jstree-ocl {
+ background-position: -128px -32px;
+}
+.jstree-default-dark-large.jstree-rtl .jstree-closed > .jstree-ocl {
+ background-position: -96px -32px;
+}
+.jstree-default-dark-large.jstree-rtl .jstree-leaf > .jstree-ocl {
+ background-position: -64px -32px;
+}
+.jstree-default-dark-large.jstree-rtl > .jstree-no-dots .jstree-node,
+.jstree-default-dark-large.jstree-rtl > .jstree-no-dots .jstree-leaf > .jstree-ocl {
+ background: transparent;
+}
+.jstree-default-dark-large.jstree-rtl > .jstree-no-dots .jstree-open > .jstree-ocl {
+ background-position: -32px -32px;
+}
+.jstree-default-dark-large.jstree-rtl > .jstree-no-dots .jstree-closed > .jstree-ocl {
+ background-position: 0px -32px;
+}
+.jstree-default-dark-large .jstree-themeicon-custom {
+ background-color: transparent;
+ background-image: none;
+ background-position: 0 0;
+}
+.jstree-default-dark-large > .jstree-container-ul .jstree-loading > .jstree-ocl {
+ background: url("throbber.gif") center center no-repeat;
+}
+.jstree-default-dark-large .jstree-file {
+ background: url("32px.png") -96px -64px no-repeat;
+}
+.jstree-default-dark-large .jstree-folder {
+ background: url("32px.png") -256px 0px no-repeat;
+}
+.jstree-default-dark-large > .jstree-container-ul > .jstree-node {
+ margin-left: 0;
+ margin-right: 0;
+}
+#jstree-dnd.jstree-default-dark-large {
+ line-height: 32px;
+ padding: 0 4px;
+}
+#jstree-dnd.jstree-default-dark-large .jstree-ok,
+#jstree-dnd.jstree-default-dark-large .jstree-er {
+ background-image: url("32px.png");
+ background-repeat: no-repeat;
+ background-color: transparent;
+}
+#jstree-dnd.jstree-default-dark-large i {
+ background: transparent;
+ width: 32px;
+ height: 32px;
+ line-height: 32px;
+}
+#jstree-dnd.jstree-default-dark-large .jstree-ok {
+ background-position: 0px -64px;
+}
+#jstree-dnd.jstree-default-dark-large .jstree-er {
+ background-position: -32px -64px;
+}
+.jstree-default-dark-large .jstree-ellipsis {
+ overflow: hidden;
+}
+.jstree-default-dark-large .jstree-ellipsis .jstree-anchor {
+ width: calc(100% - 37px);
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
+.jstree-default-dark-large .jstree-ellipsis.jstree-no-icons .jstree-anchor {
+ width: calc(100% - 5px);
+}
+.jstree-default-dark-large.jstree-rtl .jstree-node {
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAACAQMAAAAD0EyKAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjgIIGBgABCgCBvVLXcAAAAABJRU5ErkJggg==");
+}
+.jstree-default-dark-large.jstree-rtl .jstree-last {
+ background: transparent;
+}
+@media (max-width: 768px) {
+ #jstree-dnd.jstree-dnd-responsive {
+ line-height: 40px;
+ font-weight: bold;
+ font-size: 1.1em;
+ text-shadow: 1px 1px white;
+ }
+ #jstree-dnd.jstree-dnd-responsive > i {
+ background: transparent;
+ width: 40px;
+ height: 40px;
+ }
+ #jstree-dnd.jstree-dnd-responsive > .jstree-ok {
+ background-image: url("40px.png");
+ background-position: 0 -200px;
+ background-size: 120px 240px;
+ }
+ #jstree-dnd.jstree-dnd-responsive > .jstree-er {
+ background-image: url("40px.png");
+ background-position: -40px -200px;
+ background-size: 120px 240px;
+ }
+ #jstree-marker.jstree-dnd-responsive {
+ border-left-width: 10px;
+ border-top-width: 10px;
+ border-bottom-width: 10px;
+ margin-top: -10px;
+ }
+}
+@media (max-width: 768px) {
+ .jstree-default-dark-responsive {
+ /*
+ .jstree-open > .jstree-ocl,
+ .jstree-closed > .jstree-ocl { border-radius:20px; background-color:white; }
+ */
+ }
+ .jstree-default-dark-responsive .jstree-icon {
+ background-image: url("40px.png");
+ }
+ .jstree-default-dark-responsive .jstree-node,
+ .jstree-default-dark-responsive .jstree-leaf > .jstree-ocl {
+ background: transparent;
+ }
+ .jstree-default-dark-responsive .jstree-node {
+ min-height: 40px;
+ line-height: 40px;
+ margin-left: 40px;
+ min-width: 40px;
+ white-space: nowrap;
+ }
+ .jstree-default-dark-responsive .jstree-anchor {
+ line-height: 40px;
+ height: 40px;
+ }
+ .jstree-default-dark-responsive .jstree-icon,
+ .jstree-default-dark-responsive .jstree-icon:empty {
+ width: 40px;
+ height: 40px;
+ line-height: 40px;
+ }
+ .jstree-default-dark-responsive > .jstree-container-ul > .jstree-node {
+ margin-left: 0;
+ }
+ .jstree-default-dark-responsive.jstree-rtl .jstree-node {
+ margin-left: 0;
+ margin-right: 40px;
+ background: transparent;
+ }
+ .jstree-default-dark-responsive.jstree-rtl .jstree-container-ul > .jstree-node {
+ margin-right: 0;
+ }
+ .jstree-default-dark-responsive .jstree-ocl,
+ .jstree-default-dark-responsive .jstree-themeicon,
+ .jstree-default-dark-responsive .jstree-checkbox {
+ background-size: 120px 240px;
+ }
+ .jstree-default-dark-responsive .jstree-leaf > .jstree-ocl,
+ .jstree-default-dark-responsive.jstree-rtl .jstree-leaf > .jstree-ocl {
+ background: transparent;
+ }
+ .jstree-default-dark-responsive .jstree-open > .jstree-ocl {
+ background-position: 0 0px !important;
+ }
+ .jstree-default-dark-responsive .jstree-closed > .jstree-ocl {
+ background-position: 0 -40px !important;
+ }
+ .jstree-default-dark-responsive.jstree-rtl .jstree-closed > .jstree-ocl {
+ background-position: -40px 0px !important;
+ }
+ .jstree-default-dark-responsive .jstree-themeicon {
+ background-position: -40px -40px;
+ }
+ .jstree-default-dark-responsive .jstree-checkbox,
+ .jstree-default-dark-responsive .jstree-checkbox:hover {
+ background-position: -40px -80px;
+ }
+ .jstree-default-dark-responsive.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox,
+ .jstree-default-dark-responsive.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox:hover,
+ .jstree-default-dark-responsive .jstree-checked > .jstree-checkbox,
+ .jstree-default-dark-responsive .jstree-checked > .jstree-checkbox:hover {
+ background-position: 0 -80px;
+ }
+ .jstree-default-dark-responsive .jstree-anchor > .jstree-undetermined,
+ .jstree-default-dark-responsive .jstree-anchor > .jstree-undetermined:hover {
+ background-position: 0 -120px;
+ }
+ .jstree-default-dark-responsive .jstree-anchor {
+ font-weight: bold;
+ font-size: 1.1em;
+ text-shadow: 1px 1px white;
+ }
+ .jstree-default-dark-responsive > .jstree-striped {
+ background: transparent;
+ }
+ .jstree-default-dark-responsive .jstree-wholerow {
+ border-top: 1px solid #666666;
+ border-bottom: 1px solid #000000;
+ background: #333333;
+ height: 40px;
+ }
+ .jstree-default-dark-responsive .jstree-wholerow-hovered {
+ background: #555555;
+ }
+ .jstree-default-dark-responsive .jstree-wholerow-clicked {
+ background: #5fa2db;
+ }
+ .jstree-default-dark-responsive .jstree-children .jstree-last > .jstree-wholerow {
+ box-shadow: inset 0 -6px 3px -5px #111111;
+ }
+ .jstree-default-dark-responsive .jstree-children .jstree-open > .jstree-wholerow {
+ box-shadow: inset 0 6px 3px -5px #111111;
+ border-top: 0;
+ }
+ .jstree-default-dark-responsive .jstree-children .jstree-open + .jstree-open {
+ box-shadow: none;
+ }
+ .jstree-default-dark-responsive .jstree-node,
+ .jstree-default-dark-responsive .jstree-icon,
+ .jstree-default-dark-responsive .jstree-node > .jstree-ocl,
+ .jstree-default-dark-responsive .jstree-themeicon,
+ .jstree-default-dark-responsive .jstree-checkbox {
+ background-image: url("40px.png");
+ background-size: 120px 240px;
+ }
+ .jstree-default-dark-responsive .jstree-node {
+ background-position: -80px 0;
+ background-repeat: repeat-y;
+ }
+ .jstree-default-dark-responsive .jstree-last {
+ background: transparent;
+ }
+ .jstree-default-dark-responsive .jstree-leaf > .jstree-ocl {
+ background-position: -40px -120px;
+ }
+ .jstree-default-dark-responsive .jstree-last > .jstree-ocl {
+ background-position: -40px -160px;
+ }
+ .jstree-default-dark-responsive .jstree-themeicon-custom {
+ background-color: transparent;
+ background-image: none;
+ background-position: 0 0;
+ }
+ .jstree-default-dark-responsive .jstree-file {
+ background: url("40px.png") 0 -160px no-repeat;
+ background-size: 120px 240px;
+ }
+ .jstree-default-dark-responsive .jstree-folder {
+ background: url("40px.png") -40px -40px no-repeat;
+ background-size: 120px 240px;
+ }
+ .jstree-default-dark-responsive > .jstree-container-ul > .jstree-node {
+ margin-left: 0;
+ margin-right: 0;
+ }
+}
+.jstree-default-dark {
+ background: #333;
+}
+.jstree-default-dark .jstree-anchor {
+ color: #999;
+ text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.5);
+}
+.jstree-default-dark .jstree-clicked,
+.jstree-default-dark .jstree-checked {
+ color: white;
+}
+.jstree-default-dark .jstree-hovered {
+ color: white;
+}
+#jstree-marker.jstree-default-dark {
+ border-left-color: #999;
+ background: transparent;
+}
+.jstree-default-dark .jstree-anchor > .jstree-icon {
+ opacity: 0.75;
+}
+.jstree-default-dark .jstree-clicked > .jstree-icon,
+.jstree-default-dark .jstree-hovered > .jstree-icon,
+.jstree-default-dark .jstree-checked > .jstree-icon {
+ opacity: 1;
+}
+.jstree-default-dark.jstree-rtl .jstree-node {
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAACZmZl+9SADAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==");
+}
+.jstree-default-dark.jstree-rtl .jstree-last {
+ background: transparent;
+}
+.jstree-default-dark-small.jstree-rtl .jstree-node {
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAACAQMAAABv1h6PAAAABlBMVEUAAACZmZl+9SADAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMHBgAAiABBI4gz9AAAAABJRU5ErkJggg==");
+}
+.jstree-default-dark-small.jstree-rtl .jstree-last {
+ background: transparent;
+}
+.jstree-default-dark-large.jstree-rtl .jstree-node {
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAACAQMAAAAD0EyKAAAABlBMVEUAAACZmZl+9SADAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjgIIGBgABCgCBvVLXcAAAAABJRU5ErkJggg==");
+}
+.jstree-default-dark-large.jstree-rtl .jstree-last {
+ background: transparent;
+}
diff --git a/public/static/lib/jstree/themes/default-dark/style.min.css b/public/static/lib/jstree/themes/default-dark/style.min.css
new file mode 100755
index 00000000..d9084d40
--- /dev/null
+++ b/public/static/lib/jstree/themes/default-dark/style.min.css
@@ -0,0 +1 @@
+.jstree-node,.jstree-children,.jstree-container-ul{display:block;margin:0;padding:0;list-style-type:none;list-style-image:none}.jstree-node{white-space:nowrap}.jstree-anchor{display:inline-block;color:#000;white-space:nowrap;padding:0 4px 0 1px;margin:0;vertical-align:top}.jstree-anchor:focus{outline:0}.jstree-anchor,.jstree-anchor:link,.jstree-anchor:visited,.jstree-anchor:hover,.jstree-anchor:active{text-decoration:none;color:inherit}.jstree-icon{display:inline-block;text-decoration:none;margin:0;padding:0;vertical-align:top;text-align:center}.jstree-icon:empty{display:inline-block;text-decoration:none;margin:0;padding:0;vertical-align:top;text-align:center}.jstree-ocl{cursor:pointer}.jstree-leaf>.jstree-ocl{cursor:default}.jstree .jstree-open>.jstree-children{display:block}.jstree .jstree-closed>.jstree-children,.jstree .jstree-leaf>.jstree-children{display:none}.jstree-anchor>.jstree-themeicon{margin-right:2px}.jstree-no-icons .jstree-themeicon,.jstree-anchor>.jstree-themeicon-hidden{display:none}.jstree-hidden,.jstree-node.jstree-hidden{display:none}.jstree-rtl .jstree-anchor{padding:0 1px 0 4px}.jstree-rtl .jstree-anchor>.jstree-themeicon{margin-left:2px;margin-right:0}.jstree-rtl .jstree-node{margin-left:0}.jstree-rtl .jstree-container-ul>.jstree-node{margin-right:0}.jstree-wholerow-ul{position:relative;display:inline-block;min-width:100%}.jstree-wholerow-ul .jstree-leaf>.jstree-ocl{cursor:pointer}.jstree-wholerow-ul .jstree-anchor,.jstree-wholerow-ul .jstree-icon{position:relative}.jstree-wholerow-ul .jstree-wholerow{width:100%;cursor:pointer;position:absolute;left:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.vakata-context{display:none}.vakata-context,.vakata-context ul{margin:0;padding:2px;position:absolute;background:#f5f5f5;border:1px solid #979797;box-shadow:2px 2px 2px #999}.vakata-context ul{list-style:none;left:100%;margin-top:-2.7em;margin-left:-4px}.vakata-context .vakata-context-right ul{left:auto;right:100%;margin-left:auto;margin-right:-4px}.vakata-context li{list-style:none}.vakata-context li>a{display:block;padding:0 2em;text-decoration:none;width:auto;color:#000;white-space:nowrap;line-height:2.4em;text-shadow:1px 1px 0 #fff;border-radius:1px}.vakata-context li>a:hover{position:relative;background-color:#e8eff7;box-shadow:0 0 2px #0a6aa1}.vakata-context li>a.vakata-context-parent{background-image:url(data:image/gif;base64,R0lGODlhCwAHAIAAACgoKP///yH5BAEAAAEALAAAAAALAAcAAAIORI4JlrqN1oMSnmmZDQUAOw==);background-position:right center;background-repeat:no-repeat}.vakata-context li>a:focus{outline:0}.vakata-context .vakata-context-hover>a{position:relative;background-color:#e8eff7;box-shadow:0 0 2px #0a6aa1}.vakata-context .vakata-context-separator>a,.vakata-context .vakata-context-separator>a:hover{background:#fff;border:0;border-top:1px solid #e2e3e3;height:1px;min-height:1px;max-height:1px;padding:0;margin:0 0 0 2.4em;border-left:1px solid #e0e0e0;text-shadow:0 0 0 transparent;box-shadow:0 0 0 transparent;border-radius:0}.vakata-context .vakata-contextmenu-disabled a,.vakata-context .vakata-contextmenu-disabled a:hover{color:silver;background-color:transparent;border:0;box-shadow:0 0 0}.vakata-context li>a>i{text-decoration:none;display:inline-block;width:2.4em;height:2.4em;background:0 0;margin:0 0 0 -2em;vertical-align:top;text-align:center;line-height:2.4em}.vakata-context li>a>i:empty{width:2.4em;line-height:2.4em}.vakata-context li>a .vakata-contextmenu-sep{display:inline-block;width:1px;height:2.4em;background:#fff;margin:0 .5em 0 0;border-left:1px solid #e2e3e3}.vakata-context .vakata-contextmenu-shortcut{font-size:.8em;color:silver;opacity:.5;display:none}.vakata-context-rtl ul{left:auto;right:100%;margin-left:auto;margin-right:-4px}.vakata-context-rtl li>a.vakata-context-parent{background-image:url(data:image/gif;base64,R0lGODlhCwAHAIAAACgoKP///yH5BAEAAAEALAAAAAALAAcAAAINjI+AC7rWHIsPtmoxLAA7);background-position:left center;background-repeat:no-repeat}.vakata-context-rtl .vakata-context-separator>a{margin:0 2.4em 0 0;border-left:0;border-right:1px solid #e2e3e3}.vakata-context-rtl .vakata-context-left ul{right:auto;left:100%;margin-left:-4px;margin-right:auto}.vakata-context-rtl li>a>i{margin:0 -2em 0 0}.vakata-context-rtl li>a .vakata-contextmenu-sep{margin:0 0 0 .5em;border-left-color:#fff;background:#e2e3e3}#jstree-marker{position:absolute;top:0;left:0;margin:-5px 0 0 0;padding:0;border-right:0;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid;width:0;height:0;font-size:0;line-height:0}#jstree-dnd{line-height:16px;margin:0;padding:4px}#jstree-dnd .jstree-icon,#jstree-dnd .jstree-copy{display:inline-block;text-decoration:none;margin:0 2px 0 0;padding:0;width:16px;height:16px}#jstree-dnd .jstree-ok{background:green}#jstree-dnd .jstree-er{background:red}#jstree-dnd .jstree-copy{margin:0 2px}.jstree-default-dark .jstree-node,.jstree-default-dark .jstree-icon{background-repeat:no-repeat;background-color:transparent}.jstree-default-dark .jstree-anchor,.jstree-default-dark .jstree-animated,.jstree-default-dark .jstree-wholerow{transition:background-color .15s,box-shadow .15s}.jstree-default-dark .jstree-hovered{background:#555;border-radius:2px;box-shadow:inset 0 0 1px #555}.jstree-default-dark .jstree-context{background:#555;border-radius:2px;box-shadow:inset 0 0 1px #555}.jstree-default-dark .jstree-clicked{background:#5fa2db;border-radius:2px;box-shadow:inset 0 0 1px #666}.jstree-default-dark .jstree-no-icons .jstree-anchor>.jstree-themeicon{display:none}.jstree-default-dark .jstree-disabled{background:0 0;color:#666}.jstree-default-dark .jstree-disabled.jstree-hovered{background:0 0;box-shadow:none}.jstree-default-dark .jstree-disabled.jstree-clicked{background:#333}.jstree-default-dark .jstree-disabled>.jstree-icon{opacity:.8;filter:url("data:image/svg+xml;utf8,#jstree-grayscale");filter:gray;-webkit-filter:grayscale(100%)}.jstree-default-dark .jstree-search{font-style:italic;color:#fff;font-weight:700}.jstree-default-dark .jstree-no-checkboxes .jstree-checkbox{display:none!important}.jstree-default-dark.jstree-checkbox-no-clicked .jstree-clicked{background:0 0;box-shadow:none}.jstree-default-dark.jstree-checkbox-no-clicked .jstree-clicked.jstree-hovered{background:#555}.jstree-default-dark.jstree-checkbox-no-clicked>.jstree-wholerow-ul .jstree-wholerow-clicked{background:0 0}.jstree-default-dark.jstree-checkbox-no-clicked>.jstree-wholerow-ul .jstree-wholerow-clicked.jstree-wholerow-hovered{background:#555}.jstree-default-dark>.jstree-striped{min-width:100%;display:inline-block;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAkCAMAAAB/qqA+AAAABlBMVEUAAAAAAAClZ7nPAAAAAnRSTlMNAMM9s3UAAAAXSURBVHjajcEBAQAAAIKg/H/aCQZ70AUBjAATb6YPDgAAAABJRU5ErkJggg==) left top repeat}.jstree-default-dark>.jstree-wholerow-ul .jstree-hovered,.jstree-default-dark>.jstree-wholerow-ul .jstree-clicked{background:0 0;box-shadow:none;border-radius:0}.jstree-default-dark .jstree-wholerow{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.jstree-default-dark .jstree-wholerow-hovered{background:#555}.jstree-default-dark .jstree-wholerow-clicked{background:#5fa2db;background:-webkit-linear-gradient(top,#5fa2db 0,#5fa2db 100%);background:linear-gradient(to bottom,#5fa2db 0,#5fa2db 100%)}.jstree-default-dark .jstree-node{min-height:24px;line-height:24px;margin-left:24px;min-width:24px}.jstree-default-dark .jstree-anchor{line-height:24px;height:24px}.jstree-default-dark .jstree-icon{width:24px;height:24px;line-height:24px}.jstree-default-dark .jstree-icon:empty{width:24px;height:24px;line-height:24px}.jstree-default-dark.jstree-rtl .jstree-node{margin-right:24px}.jstree-default-dark .jstree-wholerow{height:24px}.jstree-default-dark .jstree-node,.jstree-default-dark .jstree-icon{background-image:url(32px.png)}.jstree-default-dark .jstree-node{background-position:-292px -4px;background-repeat:repeat-y}.jstree-default-dark .jstree-last{background:0 0}.jstree-default-dark .jstree-open>.jstree-ocl{background-position:-132px -4px}.jstree-default-dark .jstree-closed>.jstree-ocl{background-position:-100px -4px}.jstree-default-dark .jstree-leaf>.jstree-ocl{background-position:-68px -4px}.jstree-default-dark .jstree-themeicon{background-position:-260px -4px}.jstree-default-dark>.jstree-no-dots .jstree-node,.jstree-default-dark>.jstree-no-dots .jstree-leaf>.jstree-ocl{background:0 0}.jstree-default-dark>.jstree-no-dots .jstree-open>.jstree-ocl{background-position:-36px -4px}.jstree-default-dark>.jstree-no-dots .jstree-closed>.jstree-ocl{background-position:-4px -4px}.jstree-default-dark .jstree-disabled{background:0 0}.jstree-default-dark .jstree-disabled.jstree-hovered{background:0 0}.jstree-default-dark .jstree-disabled.jstree-clicked{background:#efefef}.jstree-default-dark .jstree-checkbox{background-position:-164px -4px}.jstree-default-dark .jstree-checkbox:hover{background-position:-164px -36px}.jstree-default-dark.jstree-checkbox-selection .jstree-clicked>.jstree-checkbox,.jstree-default-dark .jstree-checked>.jstree-checkbox{background-position:-228px -4px}.jstree-default-dark.jstree-checkbox-selection .jstree-clicked>.jstree-checkbox:hover,.jstree-default-dark .jstree-checked>.jstree-checkbox:hover{background-position:-228px -36px}.jstree-default-dark .jstree-anchor>.jstree-undetermined{background-position:-196px -4px}.jstree-default-dark .jstree-anchor>.jstree-undetermined:hover{background-position:-196px -36px}.jstree-default-dark .jstree-checkbox-disabled{opacity:.8;filter:url("data:image/svg+xml;utf8,#jstree-grayscale");filter:gray;-webkit-filter:grayscale(100%)}.jstree-default-dark>.jstree-striped{background-size:auto 48px}.jstree-default-dark.jstree-rtl .jstree-node{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==);background-position:100% 1px;background-repeat:repeat-y}.jstree-default-dark.jstree-rtl .jstree-last{background:0 0}.jstree-default-dark.jstree-rtl .jstree-open>.jstree-ocl{background-position:-132px -36px}.jstree-default-dark.jstree-rtl .jstree-closed>.jstree-ocl{background-position:-100px -36px}.jstree-default-dark.jstree-rtl .jstree-leaf>.jstree-ocl{background-position:-68px -36px}.jstree-default-dark.jstree-rtl>.jstree-no-dots .jstree-node,.jstree-default-dark.jstree-rtl>.jstree-no-dots .jstree-leaf>.jstree-ocl{background:0 0}.jstree-default-dark.jstree-rtl>.jstree-no-dots .jstree-open>.jstree-ocl{background-position:-36px -36px}.jstree-default-dark.jstree-rtl>.jstree-no-dots .jstree-closed>.jstree-ocl{background-position:-4px -36px}.jstree-default-dark .jstree-themeicon-custom{background-color:transparent;background-image:none;background-position:0 0}.jstree-default-dark>.jstree-container-ul .jstree-loading>.jstree-ocl{background:url(throbber.gif) center center no-repeat}.jstree-default-dark .jstree-file{background:url(32px.png) -100px -68px no-repeat}.jstree-default-dark .jstree-folder{background:url(32px.png) -260px -4px no-repeat}.jstree-default-dark>.jstree-container-ul>.jstree-node{margin-left:0;margin-right:0}#jstree-dnd.jstree-default-dark{line-height:24px;padding:0 4px}#jstree-dnd.jstree-default-dark .jstree-ok,#jstree-dnd.jstree-default-dark .jstree-er{background-image:url(32px.png);background-repeat:no-repeat;background-color:transparent}#jstree-dnd.jstree-default-dark i{background:0 0;width:24px;height:24px;line-height:24px}#jstree-dnd.jstree-default-dark .jstree-ok{background-position:-4px -68px}#jstree-dnd.jstree-default-dark .jstree-er{background-position:-36px -68px}.jstree-default-dark .jstree-ellipsis{overflow:hidden}.jstree-default-dark .jstree-ellipsis .jstree-anchor{width:calc(100% - 29px);text-overflow:ellipsis;overflow:hidden}.jstree-default-dark .jstree-ellipsis.jstree-no-icons .jstree-anchor{width:calc(100% - 5px)}.jstree-default-dark.jstree-rtl .jstree-node{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==)}.jstree-default-dark.jstree-rtl .jstree-last{background:0 0}.jstree-default-dark-small .jstree-node{min-height:18px;line-height:18px;margin-left:18px;min-width:18px}.jstree-default-dark-small .jstree-anchor{line-height:18px;height:18px}.jstree-default-dark-small .jstree-icon{width:18px;height:18px;line-height:18px}.jstree-default-dark-small .jstree-icon:empty{width:18px;height:18px;line-height:18px}.jstree-default-dark-small.jstree-rtl .jstree-node{margin-right:18px}.jstree-default-dark-small .jstree-wholerow{height:18px}.jstree-default-dark-small .jstree-node,.jstree-default-dark-small .jstree-icon{background-image:url(32px.png)}.jstree-default-dark-small .jstree-node{background-position:-295px -7px;background-repeat:repeat-y}.jstree-default-dark-small .jstree-last{background:0 0}.jstree-default-dark-small .jstree-open>.jstree-ocl{background-position:-135px -7px}.jstree-default-dark-small .jstree-closed>.jstree-ocl{background-position:-103px -7px}.jstree-default-dark-small .jstree-leaf>.jstree-ocl{background-position:-71px -7px}.jstree-default-dark-small .jstree-themeicon{background-position:-263px -7px}.jstree-default-dark-small>.jstree-no-dots .jstree-node,.jstree-default-dark-small>.jstree-no-dots .jstree-leaf>.jstree-ocl{background:0 0}.jstree-default-dark-small>.jstree-no-dots .jstree-open>.jstree-ocl{background-position:-39px -7px}.jstree-default-dark-small>.jstree-no-dots .jstree-closed>.jstree-ocl{background-position:-7px -7px}.jstree-default-dark-small .jstree-disabled{background:0 0}.jstree-default-dark-small .jstree-disabled.jstree-hovered{background:0 0}.jstree-default-dark-small .jstree-disabled.jstree-clicked{background:#efefef}.jstree-default-dark-small .jstree-checkbox{background-position:-167px -7px}.jstree-default-dark-small .jstree-checkbox:hover{background-position:-167px -39px}.jstree-default-dark-small.jstree-checkbox-selection .jstree-clicked>.jstree-checkbox,.jstree-default-dark-small .jstree-checked>.jstree-checkbox{background-position:-231px -7px}.jstree-default-dark-small.jstree-checkbox-selection .jstree-clicked>.jstree-checkbox:hover,.jstree-default-dark-small .jstree-checked>.jstree-checkbox:hover{background-position:-231px -39px}.jstree-default-dark-small .jstree-anchor>.jstree-undetermined{background-position:-199px -7px}.jstree-default-dark-small .jstree-anchor>.jstree-undetermined:hover{background-position:-199px -39px}.jstree-default-dark-small .jstree-checkbox-disabled{opacity:.8;filter:url("data:image/svg+xml;utf8,#jstree-grayscale");filter:gray;-webkit-filter:grayscale(100%)}.jstree-default-dark-small>.jstree-striped{background-size:auto 36px}.jstree-default-dark-small.jstree-rtl .jstree-node{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==);background-position:100% 1px;background-repeat:repeat-y}.jstree-default-dark-small.jstree-rtl .jstree-last{background:0 0}.jstree-default-dark-small.jstree-rtl .jstree-open>.jstree-ocl{background-position:-135px -39px}.jstree-default-dark-small.jstree-rtl .jstree-closed>.jstree-ocl{background-position:-103px -39px}.jstree-default-dark-small.jstree-rtl .jstree-leaf>.jstree-ocl{background-position:-71px -39px}.jstree-default-dark-small.jstree-rtl>.jstree-no-dots .jstree-node,.jstree-default-dark-small.jstree-rtl>.jstree-no-dots .jstree-leaf>.jstree-ocl{background:0 0}.jstree-default-dark-small.jstree-rtl>.jstree-no-dots .jstree-open>.jstree-ocl{background-position:-39px -39px}.jstree-default-dark-small.jstree-rtl>.jstree-no-dots .jstree-closed>.jstree-ocl{background-position:-7px -39px}.jstree-default-dark-small .jstree-themeicon-custom{background-color:transparent;background-image:none;background-position:0 0}.jstree-default-dark-small>.jstree-container-ul .jstree-loading>.jstree-ocl{background:url(throbber.gif) center center no-repeat}.jstree-default-dark-small .jstree-file{background:url(32px.png) -103px -71px no-repeat}.jstree-default-dark-small .jstree-folder{background:url(32px.png) -263px -7px no-repeat}.jstree-default-dark-small>.jstree-container-ul>.jstree-node{margin-left:0;margin-right:0}#jstree-dnd.jstree-default-dark-small{line-height:18px;padding:0 4px}#jstree-dnd.jstree-default-dark-small .jstree-ok,#jstree-dnd.jstree-default-dark-small .jstree-er{background-image:url(32px.png);background-repeat:no-repeat;background-color:transparent}#jstree-dnd.jstree-default-dark-small i{background:0 0;width:18px;height:18px;line-height:18px}#jstree-dnd.jstree-default-dark-small .jstree-ok{background-position:-7px -71px}#jstree-dnd.jstree-default-dark-small .jstree-er{background-position:-39px -71px}.jstree-default-dark-small .jstree-ellipsis{overflow:hidden}.jstree-default-dark-small .jstree-ellipsis .jstree-anchor{width:calc(100% - 23px);text-overflow:ellipsis;overflow:hidden}.jstree-default-dark-small .jstree-ellipsis.jstree-no-icons .jstree-anchor{width:calc(100% - 5px)}.jstree-default-dark-small.jstree-rtl .jstree-node{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAACAQMAAABv1h6PAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMHBgAAiABBI4gz9AAAAABJRU5ErkJggg==)}.jstree-default-dark-small.jstree-rtl .jstree-last{background:0 0}.jstree-default-dark-large .jstree-node{min-height:32px;line-height:32px;margin-left:32px;min-width:32px}.jstree-default-dark-large .jstree-anchor{line-height:32px;height:32px}.jstree-default-dark-large .jstree-icon{width:32px;height:32px;line-height:32px}.jstree-default-dark-large .jstree-icon:empty{width:32px;height:32px;line-height:32px}.jstree-default-dark-large.jstree-rtl .jstree-node{margin-right:32px}.jstree-default-dark-large .jstree-wholerow{height:32px}.jstree-default-dark-large .jstree-node,.jstree-default-dark-large .jstree-icon{background-image:url(32px.png)}.jstree-default-dark-large .jstree-node{background-position:-288px 0;background-repeat:repeat-y}.jstree-default-dark-large .jstree-last{background:0 0}.jstree-default-dark-large .jstree-open>.jstree-ocl{background-position:-128px 0}.jstree-default-dark-large .jstree-closed>.jstree-ocl{background-position:-96px 0}.jstree-default-dark-large .jstree-leaf>.jstree-ocl{background-position:-64px 0}.jstree-default-dark-large .jstree-themeicon{background-position:-256px 0}.jstree-default-dark-large>.jstree-no-dots .jstree-node,.jstree-default-dark-large>.jstree-no-dots .jstree-leaf>.jstree-ocl{background:0 0}.jstree-default-dark-large>.jstree-no-dots .jstree-open>.jstree-ocl{background-position:-32px 0}.jstree-default-dark-large>.jstree-no-dots .jstree-closed>.jstree-ocl{background-position:0 0}.jstree-default-dark-large .jstree-disabled{background:0 0}.jstree-default-dark-large .jstree-disabled.jstree-hovered{background:0 0}.jstree-default-dark-large .jstree-disabled.jstree-clicked{background:#efefef}.jstree-default-dark-large .jstree-checkbox{background-position:-160px 0}.jstree-default-dark-large .jstree-checkbox:hover{background-position:-160px -32px}.jstree-default-dark-large.jstree-checkbox-selection .jstree-clicked>.jstree-checkbox,.jstree-default-dark-large .jstree-checked>.jstree-checkbox{background-position:-224px 0}.jstree-default-dark-large.jstree-checkbox-selection .jstree-clicked>.jstree-checkbox:hover,.jstree-default-dark-large .jstree-checked>.jstree-checkbox:hover{background-position:-224px -32px}.jstree-default-dark-large .jstree-anchor>.jstree-undetermined{background-position:-192px 0}.jstree-default-dark-large .jstree-anchor>.jstree-undetermined:hover{background-position:-192px -32px}.jstree-default-dark-large .jstree-checkbox-disabled{opacity:.8;filter:url("data:image/svg+xml;utf8,#jstree-grayscale");filter:gray;-webkit-filter:grayscale(100%)}.jstree-default-dark-large>.jstree-striped{background-size:auto 64px}.jstree-default-dark-large.jstree-rtl .jstree-node{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==);background-position:100% 1px;background-repeat:repeat-y}.jstree-default-dark-large.jstree-rtl .jstree-last{background:0 0}.jstree-default-dark-large.jstree-rtl .jstree-open>.jstree-ocl{background-position:-128px -32px}.jstree-default-dark-large.jstree-rtl .jstree-closed>.jstree-ocl{background-position:-96px -32px}.jstree-default-dark-large.jstree-rtl .jstree-leaf>.jstree-ocl{background-position:-64px -32px}.jstree-default-dark-large.jstree-rtl>.jstree-no-dots .jstree-node,.jstree-default-dark-large.jstree-rtl>.jstree-no-dots .jstree-leaf>.jstree-ocl{background:0 0}.jstree-default-dark-large.jstree-rtl>.jstree-no-dots .jstree-open>.jstree-ocl{background-position:-32px -32px}.jstree-default-dark-large.jstree-rtl>.jstree-no-dots .jstree-closed>.jstree-ocl{background-position:0 -32px}.jstree-default-dark-large .jstree-themeicon-custom{background-color:transparent;background-image:none;background-position:0 0}.jstree-default-dark-large>.jstree-container-ul .jstree-loading>.jstree-ocl{background:url(throbber.gif) center center no-repeat}.jstree-default-dark-large .jstree-file{background:url(32px.png) -96px -64px no-repeat}.jstree-default-dark-large .jstree-folder{background:url(32px.png) -256px 0 no-repeat}.jstree-default-dark-large>.jstree-container-ul>.jstree-node{margin-left:0;margin-right:0}#jstree-dnd.jstree-default-dark-large{line-height:32px;padding:0 4px}#jstree-dnd.jstree-default-dark-large .jstree-ok,#jstree-dnd.jstree-default-dark-large .jstree-er{background-image:url(32px.png);background-repeat:no-repeat;background-color:transparent}#jstree-dnd.jstree-default-dark-large i{background:0 0;width:32px;height:32px;line-height:32px}#jstree-dnd.jstree-default-dark-large .jstree-ok{background-position:0 -64px}#jstree-dnd.jstree-default-dark-large .jstree-er{background-position:-32px -64px}.jstree-default-dark-large .jstree-ellipsis{overflow:hidden}.jstree-default-dark-large .jstree-ellipsis .jstree-anchor{width:calc(100% - 37px);text-overflow:ellipsis;overflow:hidden}.jstree-default-dark-large .jstree-ellipsis.jstree-no-icons .jstree-anchor{width:calc(100% - 5px)}.jstree-default-dark-large.jstree-rtl .jstree-node{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAACAQMAAAAD0EyKAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjgIIGBgABCgCBvVLXcAAAAABJRU5ErkJggg==)}.jstree-default-dark-large.jstree-rtl .jstree-last{background:0 0}@media (max-width:768px){#jstree-dnd.jstree-dnd-responsive{line-height:40px;font-weight:700;font-size:1.1em;text-shadow:1px 1px #fff}#jstree-dnd.jstree-dnd-responsive>i{background:0 0;width:40px;height:40px}#jstree-dnd.jstree-dnd-responsive>.jstree-ok{background-image:url(40px.png);background-position:0 -200px;background-size:120px 240px}#jstree-dnd.jstree-dnd-responsive>.jstree-er{background-image:url(40px.png);background-position:-40px -200px;background-size:120px 240px}#jstree-marker.jstree-dnd-responsive{border-left-width:10px;border-top-width:10px;border-bottom-width:10px;margin-top:-10px}}@media (max-width:768px){.jstree-default-dark-responsive .jstree-icon{background-image:url(40px.png)}.jstree-default-dark-responsive .jstree-node,.jstree-default-dark-responsive .jstree-leaf>.jstree-ocl{background:0 0}.jstree-default-dark-responsive .jstree-node{min-height:40px;line-height:40px;margin-left:40px;min-width:40px;white-space:nowrap}.jstree-default-dark-responsive .jstree-anchor{line-height:40px;height:40px}.jstree-default-dark-responsive .jstree-icon,.jstree-default-dark-responsive .jstree-icon:empty{width:40px;height:40px;line-height:40px}.jstree-default-dark-responsive>.jstree-container-ul>.jstree-node{margin-left:0}.jstree-default-dark-responsive.jstree-rtl .jstree-node{margin-left:0;margin-right:40px;background:0 0}.jstree-default-dark-responsive.jstree-rtl .jstree-container-ul>.jstree-node{margin-right:0}.jstree-default-dark-responsive .jstree-ocl,.jstree-default-dark-responsive .jstree-themeicon,.jstree-default-dark-responsive .jstree-checkbox{background-size:120px 240px}.jstree-default-dark-responsive .jstree-leaf>.jstree-ocl,.jstree-default-dark-responsive.jstree-rtl .jstree-leaf>.jstree-ocl{background:0 0}.jstree-default-dark-responsive .jstree-open>.jstree-ocl{background-position:0 0!important}.jstree-default-dark-responsive .jstree-closed>.jstree-ocl{background-position:0 -40px!important}.jstree-default-dark-responsive.jstree-rtl .jstree-closed>.jstree-ocl{background-position:-40px 0!important}.jstree-default-dark-responsive .jstree-themeicon{background-position:-40px -40px}.jstree-default-dark-responsive .jstree-checkbox,.jstree-default-dark-responsive .jstree-checkbox:hover{background-position:-40px -80px}.jstree-default-dark-responsive.jstree-checkbox-selection .jstree-clicked>.jstree-checkbox,.jstree-default-dark-responsive.jstree-checkbox-selection .jstree-clicked>.jstree-checkbox:hover,.jstree-default-dark-responsive .jstree-checked>.jstree-checkbox,.jstree-default-dark-responsive .jstree-checked>.jstree-checkbox:hover{background-position:0 -80px}.jstree-default-dark-responsive .jstree-anchor>.jstree-undetermined,.jstree-default-dark-responsive .jstree-anchor>.jstree-undetermined:hover{background-position:0 -120px}.jstree-default-dark-responsive .jstree-anchor{font-weight:700;font-size:1.1em;text-shadow:1px 1px #fff}.jstree-default-dark-responsive>.jstree-striped{background:0 0}.jstree-default-dark-responsive .jstree-wholerow{border-top:1px solid #666;border-bottom:1px solid #000;background:#333;height:40px}.jstree-default-dark-responsive .jstree-wholerow-hovered{background:#555}.jstree-default-dark-responsive .jstree-wholerow-clicked{background:#5fa2db}.jstree-default-dark-responsive .jstree-children .jstree-last>.jstree-wholerow{box-shadow:inset 0 -6px 3px -5px #111}.jstree-default-dark-responsive .jstree-children .jstree-open>.jstree-wholerow{box-shadow:inset 0 6px 3px -5px #111;border-top:0}.jstree-default-dark-responsive .jstree-children .jstree-open+.jstree-open{box-shadow:none}.jstree-default-dark-responsive .jstree-node,.jstree-default-dark-responsive .jstree-icon,.jstree-default-dark-responsive .jstree-node>.jstree-ocl,.jstree-default-dark-responsive .jstree-themeicon,.jstree-default-dark-responsive .jstree-checkbox{background-image:url(40px.png);background-size:120px 240px}.jstree-default-dark-responsive .jstree-node{background-position:-80px 0;background-repeat:repeat-y}.jstree-default-dark-responsive .jstree-last{background:0 0}.jstree-default-dark-responsive .jstree-leaf>.jstree-ocl{background-position:-40px -120px}.jstree-default-dark-responsive .jstree-last>.jstree-ocl{background-position:-40px -160px}.jstree-default-dark-responsive .jstree-themeicon-custom{background-color:transparent;background-image:none;background-position:0 0}.jstree-default-dark-responsive .jstree-file{background:url(40px.png) 0 -160px no-repeat;background-size:120px 240px}.jstree-default-dark-responsive .jstree-folder{background:url(40px.png) -40px -40px no-repeat;background-size:120px 240px}.jstree-default-dark-responsive>.jstree-container-ul>.jstree-node{margin-left:0;margin-right:0}}.jstree-default-dark{background:#333}.jstree-default-dark .jstree-anchor{color:#999;text-shadow:1px 1px 0 rgba(0,0,0,.5)}.jstree-default-dark .jstree-clicked,.jstree-default-dark .jstree-checked{color:#fff}.jstree-default-dark .jstree-hovered{color:#fff}#jstree-marker.jstree-default-dark{border-left-color:#999;background:0 0}.jstree-default-dark .jstree-anchor>.jstree-icon{opacity:.75}.jstree-default-dark .jstree-clicked>.jstree-icon,.jstree-default-dark .jstree-hovered>.jstree-icon,.jstree-default-dark .jstree-checked>.jstree-icon{opacity:1}.jstree-default-dark.jstree-rtl .jstree-node{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAACZmZl+9SADAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==)}.jstree-default-dark.jstree-rtl .jstree-last{background:0 0}.jstree-default-dark-small.jstree-rtl .jstree-node{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAACAQMAAABv1h6PAAAABlBMVEUAAACZmZl+9SADAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMHBgAAiABBI4gz9AAAAABJRU5ErkJggg==)}.jstree-default-dark-small.jstree-rtl .jstree-last{background:0 0}.jstree-default-dark-large.jstree-rtl .jstree-node{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAACAQMAAAAD0EyKAAAABlBMVEUAAACZmZl+9SADAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjgIIGBgABCgCBvVLXcAAAAABJRU5ErkJggg==)}.jstree-default-dark-large.jstree-rtl .jstree-last{background:0 0}
\ No newline at end of file
diff --git a/public/static/lib/jstree/themes/default-dark/throbber.gif b/public/static/lib/jstree/themes/default-dark/throbber.gif
new file mode 100755
index 00000000..cd75035c
Binary files /dev/null and b/public/static/lib/jstree/themes/default-dark/throbber.gif differ
diff --git a/public/static/lib/jstree/themes/default/32px.png b/public/static/lib/jstree/themes/default/32px.png
new file mode 100755
index 00000000..15327152
Binary files /dev/null and b/public/static/lib/jstree/themes/default/32px.png differ
diff --git a/public/static/lib/jstree/themes/default/40px.png b/public/static/lib/jstree/themes/default/40px.png
new file mode 100755
index 00000000..1959347a
Binary files /dev/null and b/public/static/lib/jstree/themes/default/40px.png differ
diff --git a/public/static/lib/jstree/themes/default/style.css b/public/static/lib/jstree/themes/default/style.css
new file mode 100755
index 00000000..616be241
--- /dev/null
+++ b/public/static/lib/jstree/themes/default/style.css
@@ -0,0 +1,1102 @@
+/* jsTree default theme */
+.jstree-node,
+.jstree-children,
+.jstree-container-ul {
+ display: block;
+ margin: 0;
+ padding: 0;
+ list-style-type: none;
+ list-style-image: none;
+}
+.jstree-node {
+ white-space: nowrap;
+}
+.jstree-anchor {
+ display: inline-block;
+ color: black;
+ white-space: nowrap;
+ padding: 0 4px 0 1px;
+ margin: 0;
+ vertical-align: top;
+}
+.jstree-anchor:focus {
+ outline: 0;
+}
+.jstree-anchor,
+.jstree-anchor:link,
+.jstree-anchor:visited,
+.jstree-anchor:hover,
+.jstree-anchor:active {
+ text-decoration: none;
+ color: inherit;
+}
+.jstree-icon {
+ display: inline-block;
+ text-decoration: none;
+ margin: 0;
+ padding: 0;
+ vertical-align: top;
+ text-align: center;
+}
+.jstree-icon:empty {
+ display: inline-block;
+ text-decoration: none;
+ margin: 0;
+ padding: 0;
+ vertical-align: top;
+ text-align: center;
+}
+.jstree-ocl {
+ cursor: pointer;
+}
+.jstree-leaf > .jstree-ocl {
+ cursor: default;
+}
+.jstree .jstree-open > .jstree-children {
+ display: block;
+}
+.jstree .jstree-closed > .jstree-children,
+.jstree .jstree-leaf > .jstree-children {
+ display: none;
+}
+.jstree-anchor > .jstree-themeicon {
+ margin-right: 2px;
+}
+.jstree-no-icons .jstree-themeicon,
+.jstree-anchor > .jstree-themeicon-hidden {
+ display: none;
+}
+.jstree-hidden,
+.jstree-node.jstree-hidden {
+ display: none;
+}
+.jstree-rtl .jstree-anchor {
+ padding: 0 1px 0 4px;
+}
+.jstree-rtl .jstree-anchor > .jstree-themeicon {
+ margin-left: 2px;
+ margin-right: 0;
+}
+.jstree-rtl .jstree-node {
+ margin-left: 0;
+}
+.jstree-rtl .jstree-container-ul > .jstree-node {
+ margin-right: 0;
+}
+.jstree-wholerow-ul {
+ position: relative;
+ display: inline-block;
+ min-width: 100%;
+}
+.jstree-wholerow-ul .jstree-leaf > .jstree-ocl {
+ cursor: pointer;
+}
+.jstree-wholerow-ul .jstree-anchor,
+.jstree-wholerow-ul .jstree-icon {
+ position: relative;
+}
+.jstree-wholerow-ul .jstree-wholerow {
+ width: 100%;
+ cursor: pointer;
+ position: absolute;
+ left: 0;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+.vakata-context {
+ display: none;
+}
+.vakata-context,
+.vakata-context ul {
+ margin: 0;
+ padding: 2px;
+ position: absolute;
+ background: #f5f5f5;
+ border: 1px solid #979797;
+ box-shadow: 2px 2px 2px #999999;
+}
+.vakata-context ul {
+ list-style: none;
+ left: 100%;
+ margin-top: -2.7em;
+ margin-left: -4px;
+}
+.vakata-context .vakata-context-right ul {
+ left: auto;
+ right: 100%;
+ margin-left: auto;
+ margin-right: -4px;
+}
+.vakata-context li {
+ list-style: none;
+}
+.vakata-context li > a {
+ display: block;
+ padding: 0 2em 0 2em;
+ text-decoration: none;
+ width: auto;
+ color: black;
+ white-space: nowrap;
+ line-height: 2.4em;
+ text-shadow: 1px 1px 0 white;
+ border-radius: 1px;
+}
+.vakata-context li > a:hover {
+ position: relative;
+ background-color: #e8eff7;
+ box-shadow: 0 0 2px #0a6aa1;
+}
+.vakata-context li > a.vakata-context-parent {
+ background-image: url("data:image/gif;base64,R0lGODlhCwAHAIAAACgoKP///yH5BAEAAAEALAAAAAALAAcAAAIORI4JlrqN1oMSnmmZDQUAOw==");
+ background-position: right center;
+ background-repeat: no-repeat;
+}
+.vakata-context li > a:focus {
+ outline: 0;
+}
+.vakata-context .vakata-context-hover > a {
+ position: relative;
+ background-color: #e8eff7;
+ box-shadow: 0 0 2px #0a6aa1;
+}
+.vakata-context .vakata-context-separator > a,
+.vakata-context .vakata-context-separator > a:hover {
+ background: white;
+ border: 0;
+ border-top: 1px solid #e2e3e3;
+ height: 1px;
+ min-height: 1px;
+ max-height: 1px;
+ padding: 0;
+ margin: 0 0 0 2.4em;
+ border-left: 1px solid #e0e0e0;
+ text-shadow: 0 0 0 transparent;
+ box-shadow: 0 0 0 transparent;
+ border-radius: 0;
+}
+.vakata-context .vakata-contextmenu-disabled a,
+.vakata-context .vakata-contextmenu-disabled a:hover {
+ color: silver;
+ background-color: transparent;
+ border: 0;
+ box-shadow: 0 0 0;
+}
+.vakata-context li > a > i {
+ text-decoration: none;
+ display: inline-block;
+ width: 2.4em;
+ height: 2.4em;
+ background: transparent;
+ margin: 0 0 0 -2em;
+ vertical-align: top;
+ text-align: center;
+ line-height: 2.4em;
+}
+.vakata-context li > a > i:empty {
+ width: 2.4em;
+ line-height: 2.4em;
+}
+.vakata-context li > a .vakata-contextmenu-sep {
+ display: inline-block;
+ width: 1px;
+ height: 2.4em;
+ background: white;
+ margin: 0 0.5em 0 0;
+ border-left: 1px solid #e2e3e3;
+}
+.vakata-context .vakata-contextmenu-shortcut {
+ font-size: 0.8em;
+ color: silver;
+ opacity: 0.5;
+ display: none;
+}
+.vakata-context-rtl ul {
+ left: auto;
+ right: 100%;
+ margin-left: auto;
+ margin-right: -4px;
+}
+.vakata-context-rtl li > a.vakata-context-parent {
+ background-image: url("data:image/gif;base64,R0lGODlhCwAHAIAAACgoKP///yH5BAEAAAEALAAAAAALAAcAAAINjI+AC7rWHIsPtmoxLAA7");
+ background-position: left center;
+ background-repeat: no-repeat;
+}
+.vakata-context-rtl .vakata-context-separator > a {
+ margin: 0 2.4em 0 0;
+ border-left: 0;
+ border-right: 1px solid #e2e3e3;
+}
+.vakata-context-rtl .vakata-context-left ul {
+ right: auto;
+ left: 100%;
+ margin-left: -4px;
+ margin-right: auto;
+}
+.vakata-context-rtl li > a > i {
+ margin: 0 -2em 0 0;
+}
+.vakata-context-rtl li > a .vakata-contextmenu-sep {
+ margin: 0 0 0 0.5em;
+ border-left-color: white;
+ background: #e2e3e3;
+}
+#jstree-marker {
+ position: absolute;
+ top: 0;
+ left: 0;
+ margin: -5px 0 0 0;
+ padding: 0;
+ border-right: 0;
+ border-top: 5px solid transparent;
+ border-bottom: 5px solid transparent;
+ border-left: 5px solid;
+ width: 0;
+ height: 0;
+ font-size: 0;
+ line-height: 0;
+}
+#jstree-dnd {
+ line-height: 16px;
+ margin: 0;
+ padding: 4px;
+}
+#jstree-dnd .jstree-icon,
+#jstree-dnd .jstree-copy {
+ display: inline-block;
+ text-decoration: none;
+ margin: 0 2px 0 0;
+ padding: 0;
+ width: 16px;
+ height: 16px;
+}
+#jstree-dnd .jstree-ok {
+ background: green;
+}
+#jstree-dnd .jstree-er {
+ background: red;
+}
+#jstree-dnd .jstree-copy {
+ margin: 0 2px 0 2px;
+}
+.jstree-default .jstree-node,
+.jstree-default .jstree-icon {
+ background-repeat: no-repeat;
+ background-color: transparent;
+}
+.jstree-default .jstree-anchor,
+.jstree-default .jstree-animated,
+.jstree-default .jstree-wholerow {
+ transition: background-color 0.15s, box-shadow 0.15s;
+}
+.jstree-default .jstree-hovered {
+ background: #e7f4f9;
+ border-radius: 2px;
+ box-shadow: inset 0 0 1px #cccccc;
+}
+.jstree-default .jstree-context {
+ background: #e7f4f9;
+ border-radius: 2px;
+ box-shadow: inset 0 0 1px #cccccc;
+}
+.jstree-default .jstree-clicked {
+ background: #beebff;
+ border-radius: 2px;
+ box-shadow: inset 0 0 1px #999999;
+}
+.jstree-default .jstree-no-icons .jstree-anchor > .jstree-themeicon {
+ display: none;
+}
+.jstree-default .jstree-disabled {
+ background: transparent;
+ color: #666666;
+}
+.jstree-default .jstree-disabled.jstree-hovered {
+ background: transparent;
+ box-shadow: none;
+}
+.jstree-default .jstree-disabled.jstree-clicked {
+ background: #efefef;
+}
+.jstree-default .jstree-disabled > .jstree-icon {
+ opacity: 0.8;
+ filter: url("data:image/svg+xml;utf8,#jstree-grayscale");
+ /* Firefox 10+ */
+ filter: gray;
+ /* IE6-9 */
+ -webkit-filter: grayscale(100%);
+ /* Chrome 19+ & Safari 6+ */
+}
+.jstree-default .jstree-search {
+ font-style: italic;
+ color: #8b0000;
+ font-weight: bold;
+}
+.jstree-default .jstree-no-checkboxes .jstree-checkbox {
+ display: none !important;
+}
+.jstree-default.jstree-checkbox-no-clicked .jstree-clicked {
+ background: transparent;
+ box-shadow: none;
+}
+.jstree-default.jstree-checkbox-no-clicked .jstree-clicked.jstree-hovered {
+ background: #e7f4f9;
+}
+.jstree-default.jstree-checkbox-no-clicked > .jstree-wholerow-ul .jstree-wholerow-clicked {
+ background: transparent;
+}
+.jstree-default.jstree-checkbox-no-clicked > .jstree-wholerow-ul .jstree-wholerow-clicked.jstree-wholerow-hovered {
+ background: #e7f4f9;
+}
+.jstree-default > .jstree-striped {
+ min-width: 100%;
+ display: inline-block;
+ background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAkCAMAAAB/qqA+AAAABlBMVEUAAAAAAAClZ7nPAAAAAnRSTlMNAMM9s3UAAAAXSURBVHjajcEBAQAAAIKg/H/aCQZ70AUBjAATb6YPDgAAAABJRU5ErkJggg==") left top repeat;
+}
+.jstree-default > .jstree-wholerow-ul .jstree-hovered,
+.jstree-default > .jstree-wholerow-ul .jstree-clicked {
+ background: transparent;
+ box-shadow: none;
+ border-radius: 0;
+}
+.jstree-default .jstree-wholerow {
+ -moz-box-sizing: border-box;
+ -webkit-box-sizing: border-box;
+ box-sizing: border-box;
+}
+.jstree-default .jstree-wholerow-hovered {
+ background: #e7f4f9;
+}
+.jstree-default .jstree-wholerow-clicked {
+ background: #beebff;
+ background: -webkit-linear-gradient(top, #beebff 0%, #a8e4ff 100%);
+ background: linear-gradient(to bottom, #beebff 0%, #a8e4ff 100%);
+}
+.jstree-default .jstree-node {
+ min-height: 24px;
+ line-height: 24px;
+ margin-left: 24px;
+ min-width: 24px;
+}
+.jstree-default .jstree-anchor {
+ line-height: 24px;
+ height: 24px;
+}
+.jstree-default .jstree-icon {
+ width: 24px;
+ height: 24px;
+ line-height: 24px;
+}
+.jstree-default .jstree-icon:empty {
+ width: 24px;
+ height: 24px;
+ line-height: 24px;
+}
+.jstree-default.jstree-rtl .jstree-node {
+ margin-right: 24px;
+}
+.jstree-default .jstree-wholerow {
+ height: 24px;
+}
+.jstree-default .jstree-node,
+.jstree-default .jstree-icon {
+ background-image: url("32px.png");
+}
+.jstree-default .jstree-node {
+ background-position: -292px -4px;
+ background-repeat: repeat-y;
+}
+.jstree-default .jstree-last {
+ background: transparent;
+}
+.jstree-default .jstree-open > .jstree-ocl {
+ background-position: -132px -4px;
+}
+.jstree-default .jstree-closed > .jstree-ocl {
+ background-position: -100px -4px;
+}
+.jstree-default .jstree-leaf > .jstree-ocl {
+ background-position: -68px -4px;
+}
+.jstree-default .jstree-themeicon {
+ background-position: -260px -4px;
+}
+.jstree-default > .jstree-no-dots .jstree-node,
+.jstree-default > .jstree-no-dots .jstree-leaf > .jstree-ocl {
+ background: transparent;
+}
+.jstree-default > .jstree-no-dots .jstree-open > .jstree-ocl {
+ background-position: -36px -4px;
+}
+.jstree-default > .jstree-no-dots .jstree-closed > .jstree-ocl {
+ background-position: -4px -4px;
+}
+.jstree-default .jstree-disabled {
+ background: transparent;
+}
+.jstree-default .jstree-disabled.jstree-hovered {
+ background: transparent;
+}
+.jstree-default .jstree-disabled.jstree-clicked {
+ background: #efefef;
+}
+.jstree-default .jstree-checkbox {
+ background-position: -164px -4px;
+}
+.jstree-default .jstree-checkbox:hover {
+ background-position: -164px -36px;
+}
+.jstree-default.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox,
+.jstree-default .jstree-checked > .jstree-checkbox {
+ background-position: -228px -4px;
+}
+.jstree-default.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox:hover,
+.jstree-default .jstree-checked > .jstree-checkbox:hover {
+ background-position: -228px -36px;
+}
+.jstree-default .jstree-anchor > .jstree-undetermined {
+ background-position: -196px -4px;
+}
+.jstree-default .jstree-anchor > .jstree-undetermined:hover {
+ background-position: -196px -36px;
+}
+.jstree-default .jstree-checkbox-disabled {
+ opacity: 0.8;
+ filter: url("data:image/svg+xml;utf8,#jstree-grayscale");
+ /* Firefox 10+ */
+ filter: gray;
+ /* IE6-9 */
+ -webkit-filter: grayscale(100%);
+ /* Chrome 19+ & Safari 6+ */
+}
+.jstree-default > .jstree-striped {
+ background-size: auto 48px;
+}
+.jstree-default.jstree-rtl .jstree-node {
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==");
+ background-position: 100% 1px;
+ background-repeat: repeat-y;
+}
+.jstree-default.jstree-rtl .jstree-last {
+ background: transparent;
+}
+.jstree-default.jstree-rtl .jstree-open > .jstree-ocl {
+ background-position: -132px -36px;
+}
+.jstree-default.jstree-rtl .jstree-closed > .jstree-ocl {
+ background-position: -100px -36px;
+}
+.jstree-default.jstree-rtl .jstree-leaf > .jstree-ocl {
+ background-position: -68px -36px;
+}
+.jstree-default.jstree-rtl > .jstree-no-dots .jstree-node,
+.jstree-default.jstree-rtl > .jstree-no-dots .jstree-leaf > .jstree-ocl {
+ background: transparent;
+}
+.jstree-default.jstree-rtl > .jstree-no-dots .jstree-open > .jstree-ocl {
+ background-position: -36px -36px;
+}
+.jstree-default.jstree-rtl > .jstree-no-dots .jstree-closed > .jstree-ocl {
+ background-position: -4px -36px;
+}
+.jstree-default .jstree-themeicon-custom {
+ background-color: transparent;
+ background-image: none;
+ background-position: 0 0;
+}
+.jstree-default > .jstree-container-ul .jstree-loading > .jstree-ocl {
+ background: url("throbber.gif") center center no-repeat;
+}
+.jstree-default .jstree-file {
+ background: url("32px.png") -100px -68px no-repeat;
+}
+.jstree-default .jstree-folder {
+ background: url("32px.png") -260px -4px no-repeat;
+}
+.jstree-default > .jstree-container-ul > .jstree-node {
+ margin-left: 0;
+ margin-right: 0;
+}
+#jstree-dnd.jstree-default {
+ line-height: 24px;
+ padding: 0 4px;
+}
+#jstree-dnd.jstree-default .jstree-ok,
+#jstree-dnd.jstree-default .jstree-er {
+ background-image: url("32px.png");
+ background-repeat: no-repeat;
+ background-color: transparent;
+}
+#jstree-dnd.jstree-default i {
+ background: transparent;
+ width: 24px;
+ height: 24px;
+ line-height: 24px;
+}
+#jstree-dnd.jstree-default .jstree-ok {
+ background-position: -4px -68px;
+}
+#jstree-dnd.jstree-default .jstree-er {
+ background-position: -36px -68px;
+}
+.jstree-default .jstree-ellipsis {
+ overflow: hidden;
+}
+.jstree-default .jstree-ellipsis .jstree-anchor {
+ width: calc(100% - 29px);
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
+.jstree-default .jstree-ellipsis.jstree-no-icons .jstree-anchor {
+ width: calc(100% - 5px);
+}
+.jstree-default.jstree-rtl .jstree-node {
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==");
+}
+.jstree-default.jstree-rtl .jstree-last {
+ background: transparent;
+}
+.jstree-default-small .jstree-node {
+ min-height: 18px;
+ line-height: 18px;
+ margin-left: 18px;
+ min-width: 18px;
+}
+.jstree-default-small .jstree-anchor {
+ line-height: 18px;
+ height: 18px;
+}
+.jstree-default-small .jstree-icon {
+ width: 18px;
+ height: 18px;
+ line-height: 18px;
+}
+.jstree-default-small .jstree-icon:empty {
+ width: 18px;
+ height: 18px;
+ line-height: 18px;
+}
+.jstree-default-small.jstree-rtl .jstree-node {
+ margin-right: 18px;
+}
+.jstree-default-small .jstree-wholerow {
+ height: 18px;
+}
+.jstree-default-small .jstree-node,
+.jstree-default-small .jstree-icon {
+ background-image: url("32px.png");
+}
+.jstree-default-small .jstree-node {
+ background-position: -295px -7px;
+ background-repeat: repeat-y;
+}
+.jstree-default-small .jstree-last {
+ background: transparent;
+}
+.jstree-default-small .jstree-open > .jstree-ocl {
+ background-position: -135px -7px;
+}
+.jstree-default-small .jstree-closed > .jstree-ocl {
+ background-position: -103px -7px;
+}
+.jstree-default-small .jstree-leaf > .jstree-ocl {
+ background-position: -71px -7px;
+}
+.jstree-default-small .jstree-themeicon {
+ background-position: -263px -7px;
+}
+.jstree-default-small > .jstree-no-dots .jstree-node,
+.jstree-default-small > .jstree-no-dots .jstree-leaf > .jstree-ocl {
+ background: transparent;
+}
+.jstree-default-small > .jstree-no-dots .jstree-open > .jstree-ocl {
+ background-position: -39px -7px;
+}
+.jstree-default-small > .jstree-no-dots .jstree-closed > .jstree-ocl {
+ background-position: -7px -7px;
+}
+.jstree-default-small .jstree-disabled {
+ background: transparent;
+}
+.jstree-default-small .jstree-disabled.jstree-hovered {
+ background: transparent;
+}
+.jstree-default-small .jstree-disabled.jstree-clicked {
+ background: #efefef;
+}
+.jstree-default-small .jstree-checkbox {
+ background-position: -167px -7px;
+}
+.jstree-default-small .jstree-checkbox:hover {
+ background-position: -167px -39px;
+}
+.jstree-default-small.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox,
+.jstree-default-small .jstree-checked > .jstree-checkbox {
+ background-position: -231px -7px;
+}
+.jstree-default-small.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox:hover,
+.jstree-default-small .jstree-checked > .jstree-checkbox:hover {
+ background-position: -231px -39px;
+}
+.jstree-default-small .jstree-anchor > .jstree-undetermined {
+ background-position: -199px -7px;
+}
+.jstree-default-small .jstree-anchor > .jstree-undetermined:hover {
+ background-position: -199px -39px;
+}
+.jstree-default-small .jstree-checkbox-disabled {
+ opacity: 0.8;
+ filter: url("data:image/svg+xml;utf8,#jstree-grayscale");
+ /* Firefox 10+ */
+ filter: gray;
+ /* IE6-9 */
+ -webkit-filter: grayscale(100%);
+ /* Chrome 19+ & Safari 6+ */
+}
+.jstree-default-small > .jstree-striped {
+ background-size: auto 36px;
+}
+.jstree-default-small.jstree-rtl .jstree-node {
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==");
+ background-position: 100% 1px;
+ background-repeat: repeat-y;
+}
+.jstree-default-small.jstree-rtl .jstree-last {
+ background: transparent;
+}
+.jstree-default-small.jstree-rtl .jstree-open > .jstree-ocl {
+ background-position: -135px -39px;
+}
+.jstree-default-small.jstree-rtl .jstree-closed > .jstree-ocl {
+ background-position: -103px -39px;
+}
+.jstree-default-small.jstree-rtl .jstree-leaf > .jstree-ocl {
+ background-position: -71px -39px;
+}
+.jstree-default-small.jstree-rtl > .jstree-no-dots .jstree-node,
+.jstree-default-small.jstree-rtl > .jstree-no-dots .jstree-leaf > .jstree-ocl {
+ background: transparent;
+}
+.jstree-default-small.jstree-rtl > .jstree-no-dots .jstree-open > .jstree-ocl {
+ background-position: -39px -39px;
+}
+.jstree-default-small.jstree-rtl > .jstree-no-dots .jstree-closed > .jstree-ocl {
+ background-position: -7px -39px;
+}
+.jstree-default-small .jstree-themeicon-custom {
+ background-color: transparent;
+ background-image: none;
+ background-position: 0 0;
+}
+.jstree-default-small > .jstree-container-ul .jstree-loading > .jstree-ocl {
+ background: url("throbber.gif") center center no-repeat;
+}
+.jstree-default-small .jstree-file {
+ background: url("32px.png") -103px -71px no-repeat;
+}
+.jstree-default-small .jstree-folder {
+ background: url("32px.png") -263px -7px no-repeat;
+}
+.jstree-default-small > .jstree-container-ul > .jstree-node {
+ margin-left: 0;
+ margin-right: 0;
+}
+#jstree-dnd.jstree-default-small {
+ line-height: 18px;
+ padding: 0 4px;
+}
+#jstree-dnd.jstree-default-small .jstree-ok,
+#jstree-dnd.jstree-default-small .jstree-er {
+ background-image: url("32px.png");
+ background-repeat: no-repeat;
+ background-color: transparent;
+}
+#jstree-dnd.jstree-default-small i {
+ background: transparent;
+ width: 18px;
+ height: 18px;
+ line-height: 18px;
+}
+#jstree-dnd.jstree-default-small .jstree-ok {
+ background-position: -7px -71px;
+}
+#jstree-dnd.jstree-default-small .jstree-er {
+ background-position: -39px -71px;
+}
+.jstree-default-small .jstree-ellipsis {
+ overflow: hidden;
+}
+.jstree-default-small .jstree-ellipsis .jstree-anchor {
+ width: calc(100% - 23px);
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
+.jstree-default-small .jstree-ellipsis.jstree-no-icons .jstree-anchor {
+ width: calc(100% - 5px);
+}
+.jstree-default-small.jstree-rtl .jstree-node {
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAACAQMAAABv1h6PAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMHBgAAiABBI4gz9AAAAABJRU5ErkJggg==");
+}
+.jstree-default-small.jstree-rtl .jstree-last {
+ background: transparent;
+}
+.jstree-default-large .jstree-node {
+ min-height: 32px;
+ line-height: 32px;
+ margin-left: 32px;
+ min-width: 32px;
+}
+.jstree-default-large .jstree-anchor {
+ line-height: 32px;
+ height: 32px;
+}
+.jstree-default-large .jstree-icon {
+ width: 32px;
+ height: 32px;
+ line-height: 32px;
+}
+.jstree-default-large .jstree-icon:empty {
+ width: 32px;
+ height: 32px;
+ line-height: 32px;
+}
+.jstree-default-large.jstree-rtl .jstree-node {
+ margin-right: 32px;
+}
+.jstree-default-large .jstree-wholerow {
+ height: 32px;
+}
+.jstree-default-large .jstree-node,
+.jstree-default-large .jstree-icon {
+ background-image: url("32px.png");
+}
+.jstree-default-large .jstree-node {
+ background-position: -288px 0px;
+ background-repeat: repeat-y;
+}
+.jstree-default-large .jstree-last {
+ background: transparent;
+}
+.jstree-default-large .jstree-open > .jstree-ocl {
+ background-position: -128px 0px;
+}
+.jstree-default-large .jstree-closed > .jstree-ocl {
+ background-position: -96px 0px;
+}
+.jstree-default-large .jstree-leaf > .jstree-ocl {
+ background-position: -64px 0px;
+}
+.jstree-default-large .jstree-themeicon {
+ background-position: -256px 0px;
+}
+.jstree-default-large > .jstree-no-dots .jstree-node,
+.jstree-default-large > .jstree-no-dots .jstree-leaf > .jstree-ocl {
+ background: transparent;
+}
+.jstree-default-large > .jstree-no-dots .jstree-open > .jstree-ocl {
+ background-position: -32px 0px;
+}
+.jstree-default-large > .jstree-no-dots .jstree-closed > .jstree-ocl {
+ background-position: 0px 0px;
+}
+.jstree-default-large .jstree-disabled {
+ background: transparent;
+}
+.jstree-default-large .jstree-disabled.jstree-hovered {
+ background: transparent;
+}
+.jstree-default-large .jstree-disabled.jstree-clicked {
+ background: #efefef;
+}
+.jstree-default-large .jstree-checkbox {
+ background-position: -160px 0px;
+}
+.jstree-default-large .jstree-checkbox:hover {
+ background-position: -160px -32px;
+}
+.jstree-default-large.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox,
+.jstree-default-large .jstree-checked > .jstree-checkbox {
+ background-position: -224px 0px;
+}
+.jstree-default-large.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox:hover,
+.jstree-default-large .jstree-checked > .jstree-checkbox:hover {
+ background-position: -224px -32px;
+}
+.jstree-default-large .jstree-anchor > .jstree-undetermined {
+ background-position: -192px 0px;
+}
+.jstree-default-large .jstree-anchor > .jstree-undetermined:hover {
+ background-position: -192px -32px;
+}
+.jstree-default-large .jstree-checkbox-disabled {
+ opacity: 0.8;
+ filter: url("data:image/svg+xml;utf8,#jstree-grayscale");
+ /* Firefox 10+ */
+ filter: gray;
+ /* IE6-9 */
+ -webkit-filter: grayscale(100%);
+ /* Chrome 19+ & Safari 6+ */
+}
+.jstree-default-large > .jstree-striped {
+ background-size: auto 64px;
+}
+.jstree-default-large.jstree-rtl .jstree-node {
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==");
+ background-position: 100% 1px;
+ background-repeat: repeat-y;
+}
+.jstree-default-large.jstree-rtl .jstree-last {
+ background: transparent;
+}
+.jstree-default-large.jstree-rtl .jstree-open > .jstree-ocl {
+ background-position: -128px -32px;
+}
+.jstree-default-large.jstree-rtl .jstree-closed > .jstree-ocl {
+ background-position: -96px -32px;
+}
+.jstree-default-large.jstree-rtl .jstree-leaf > .jstree-ocl {
+ background-position: -64px -32px;
+}
+.jstree-default-large.jstree-rtl > .jstree-no-dots .jstree-node,
+.jstree-default-large.jstree-rtl > .jstree-no-dots .jstree-leaf > .jstree-ocl {
+ background: transparent;
+}
+.jstree-default-large.jstree-rtl > .jstree-no-dots .jstree-open > .jstree-ocl {
+ background-position: -32px -32px;
+}
+.jstree-default-large.jstree-rtl > .jstree-no-dots .jstree-closed > .jstree-ocl {
+ background-position: 0px -32px;
+}
+.jstree-default-large .jstree-themeicon-custom {
+ background-color: transparent;
+ background-image: none;
+ background-position: 0 0;
+}
+.jstree-default-large > .jstree-container-ul .jstree-loading > .jstree-ocl {
+ background: url("throbber.gif") center center no-repeat;
+}
+.jstree-default-large .jstree-file {
+ background: url("32px.png") -96px -64px no-repeat;
+}
+.jstree-default-large .jstree-folder {
+ background: url("32px.png") -256px 0px no-repeat;
+}
+.jstree-default-large > .jstree-container-ul > .jstree-node {
+ margin-left: 0;
+ margin-right: 0;
+}
+#jstree-dnd.jstree-default-large {
+ line-height: 32px;
+ padding: 0 4px;
+}
+#jstree-dnd.jstree-default-large .jstree-ok,
+#jstree-dnd.jstree-default-large .jstree-er {
+ background-image: url("32px.png");
+ background-repeat: no-repeat;
+ background-color: transparent;
+}
+#jstree-dnd.jstree-default-large i {
+ background: transparent;
+ width: 32px;
+ height: 32px;
+ line-height: 32px;
+}
+#jstree-dnd.jstree-default-large .jstree-ok {
+ background-position: 0px -64px;
+}
+#jstree-dnd.jstree-default-large .jstree-er {
+ background-position: -32px -64px;
+}
+.jstree-default-large .jstree-ellipsis {
+ overflow: hidden;
+}
+.jstree-default-large .jstree-ellipsis .jstree-anchor {
+ width: calc(100% - 37px);
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
+.jstree-default-large .jstree-ellipsis.jstree-no-icons .jstree-anchor {
+ width: calc(100% - 5px);
+}
+.jstree-default-large.jstree-rtl .jstree-node {
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAACAQMAAAAD0EyKAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjgIIGBgABCgCBvVLXcAAAAABJRU5ErkJggg==");
+}
+.jstree-default-large.jstree-rtl .jstree-last {
+ background: transparent;
+}
+@media (max-width: 768px) {
+ #jstree-dnd.jstree-dnd-responsive {
+ line-height: 40px;
+ font-weight: bold;
+ font-size: 1.1em;
+ text-shadow: 1px 1px white;
+ }
+ #jstree-dnd.jstree-dnd-responsive > i {
+ background: transparent;
+ width: 40px;
+ height: 40px;
+ }
+ #jstree-dnd.jstree-dnd-responsive > .jstree-ok {
+ background-image: url("40px.png");
+ background-position: 0 -200px;
+ background-size: 120px 240px;
+ }
+ #jstree-dnd.jstree-dnd-responsive > .jstree-er {
+ background-image: url("40px.png");
+ background-position: -40px -200px;
+ background-size: 120px 240px;
+ }
+ #jstree-marker.jstree-dnd-responsive {
+ border-left-width: 10px;
+ border-top-width: 10px;
+ border-bottom-width: 10px;
+ margin-top: -10px;
+ }
+}
+@media (max-width: 768px) {
+ .jstree-default-responsive {
+ /*
+ .jstree-open > .jstree-ocl,
+ .jstree-closed > .jstree-ocl { border-radius:20px; background-color:white; }
+ */
+ }
+ .jstree-default-responsive .jstree-icon {
+ background-image: url("40px.png");
+ }
+ .jstree-default-responsive .jstree-node,
+ .jstree-default-responsive .jstree-leaf > .jstree-ocl {
+ background: transparent;
+ }
+ .jstree-default-responsive .jstree-node {
+ min-height: 40px;
+ line-height: 40px;
+ margin-left: 40px;
+ min-width: 40px;
+ white-space: nowrap;
+ }
+ .jstree-default-responsive .jstree-anchor {
+ line-height: 40px;
+ height: 40px;
+ }
+ .jstree-default-responsive .jstree-icon,
+ .jstree-default-responsive .jstree-icon:empty {
+ width: 40px;
+ height: 40px;
+ line-height: 40px;
+ }
+ .jstree-default-responsive > .jstree-container-ul > .jstree-node {
+ margin-left: 0;
+ }
+ .jstree-default-responsive.jstree-rtl .jstree-node {
+ margin-left: 0;
+ margin-right: 40px;
+ background: transparent;
+ }
+ .jstree-default-responsive.jstree-rtl .jstree-container-ul > .jstree-node {
+ margin-right: 0;
+ }
+ .jstree-default-responsive .jstree-ocl,
+ .jstree-default-responsive .jstree-themeicon,
+ .jstree-default-responsive .jstree-checkbox {
+ background-size: 120px 240px;
+ }
+ .jstree-default-responsive .jstree-leaf > .jstree-ocl,
+ .jstree-default-responsive.jstree-rtl .jstree-leaf > .jstree-ocl {
+ background: transparent;
+ }
+ .jstree-default-responsive .jstree-open > .jstree-ocl {
+ background-position: 0 0px !important;
+ }
+ .jstree-default-responsive .jstree-closed > .jstree-ocl {
+ background-position: 0 -40px !important;
+ }
+ .jstree-default-responsive.jstree-rtl .jstree-closed > .jstree-ocl {
+ background-position: -40px 0px !important;
+ }
+ .jstree-default-responsive .jstree-themeicon {
+ background-position: -40px -40px;
+ }
+ .jstree-default-responsive .jstree-checkbox,
+ .jstree-default-responsive .jstree-checkbox:hover {
+ background-position: -40px -80px;
+ }
+ .jstree-default-responsive.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox,
+ .jstree-default-responsive.jstree-checkbox-selection .jstree-clicked > .jstree-checkbox:hover,
+ .jstree-default-responsive .jstree-checked > .jstree-checkbox,
+ .jstree-default-responsive .jstree-checked > .jstree-checkbox:hover {
+ background-position: 0 -80px;
+ }
+ .jstree-default-responsive .jstree-anchor > .jstree-undetermined,
+ .jstree-default-responsive .jstree-anchor > .jstree-undetermined:hover {
+ background-position: 0 -120px;
+ }
+ .jstree-default-responsive .jstree-anchor {
+ font-weight: bold;
+ font-size: 1.1em;
+ text-shadow: 1px 1px white;
+ }
+ .jstree-default-responsive > .jstree-striped {
+ background: transparent;
+ }
+ .jstree-default-responsive .jstree-wholerow {
+ border-top: 1px solid rgba(255, 255, 255, 0.7);
+ border-bottom: 1px solid rgba(64, 64, 64, 0.2);
+ background: #ebebeb;
+ height: 40px;
+ }
+ .jstree-default-responsive .jstree-wholerow-hovered {
+ background: #e7f4f9;
+ }
+ .jstree-default-responsive .jstree-wholerow-clicked {
+ background: #beebff;
+ }
+ .jstree-default-responsive .jstree-children .jstree-last > .jstree-wholerow {
+ box-shadow: inset 0 -6px 3px -5px #666666;
+ }
+ .jstree-default-responsive .jstree-children .jstree-open > .jstree-wholerow {
+ box-shadow: inset 0 6px 3px -5px #666666;
+ border-top: 0;
+ }
+ .jstree-default-responsive .jstree-children .jstree-open + .jstree-open {
+ box-shadow: none;
+ }
+ .jstree-default-responsive .jstree-node,
+ .jstree-default-responsive .jstree-icon,
+ .jstree-default-responsive .jstree-node > .jstree-ocl,
+ .jstree-default-responsive .jstree-themeicon,
+ .jstree-default-responsive .jstree-checkbox {
+ background-image: url("40px.png");
+ background-size: 120px 240px;
+ }
+ .jstree-default-responsive .jstree-node {
+ background-position: -80px 0;
+ background-repeat: repeat-y;
+ }
+ .jstree-default-responsive .jstree-last {
+ background: transparent;
+ }
+ .jstree-default-responsive .jstree-leaf > .jstree-ocl {
+ background-position: -40px -120px;
+ }
+ .jstree-default-responsive .jstree-last > .jstree-ocl {
+ background-position: -40px -160px;
+ }
+ .jstree-default-responsive .jstree-themeicon-custom {
+ background-color: transparent;
+ background-image: none;
+ background-position: 0 0;
+ }
+ .jstree-default-responsive .jstree-file {
+ background: url("40px.png") 0 -160px no-repeat;
+ background-size: 120px 240px;
+ }
+ .jstree-default-responsive .jstree-folder {
+ background: url("40px.png") -40px -40px no-repeat;
+ background-size: 120px 240px;
+ }
+ .jstree-default-responsive > .jstree-container-ul > .jstree-node {
+ margin-left: 0;
+ margin-right: 0;
+ }
+}
diff --git a/public/static/lib/jstree/themes/default/style.min.css b/public/static/lib/jstree/themes/default/style.min.css
new file mode 100755
index 00000000..a76f15c0
--- /dev/null
+++ b/public/static/lib/jstree/themes/default/style.min.css
@@ -0,0 +1 @@
+.jstree-node,.jstree-children,.jstree-container-ul{display:block;margin:0;padding:0;list-style-type:none;list-style-image:none}.jstree-node{white-space:nowrap}.jstree-anchor{display:inline-block;color:#000;white-space:nowrap;padding:0 4px 0 1px;margin:0;vertical-align:top}.jstree-anchor:focus{outline:0}.jstree-anchor,.jstree-anchor:link,.jstree-anchor:visited,.jstree-anchor:hover,.jstree-anchor:active{text-decoration:none;color:inherit}.jstree-icon{display:inline-block;text-decoration:none;margin:0;padding:0;vertical-align:top;text-align:center}.jstree-icon:empty{display:inline-block;text-decoration:none;margin:0;padding:0;vertical-align:top;text-align:center}.jstree-ocl{cursor:pointer}.jstree-leaf>.jstree-ocl{cursor:default}.jstree .jstree-open>.jstree-children{display:block}.jstree .jstree-closed>.jstree-children,.jstree .jstree-leaf>.jstree-children{display:none}.jstree-anchor>.jstree-themeicon{margin-right:2px}.jstree-no-icons .jstree-themeicon,.jstree-anchor>.jstree-themeicon-hidden{display:none}.jstree-hidden,.jstree-node.jstree-hidden{display:none}.jstree-rtl .jstree-anchor{padding:0 1px 0 4px}.jstree-rtl .jstree-anchor>.jstree-themeicon{margin-left:2px;margin-right:0}.jstree-rtl .jstree-node{margin-left:0}.jstree-rtl .jstree-container-ul>.jstree-node{margin-right:0}.jstree-wholerow-ul{position:relative;display:inline-block;min-width:100%}.jstree-wholerow-ul .jstree-leaf>.jstree-ocl{cursor:pointer}.jstree-wholerow-ul .jstree-anchor,.jstree-wholerow-ul .jstree-icon{position:relative}.jstree-wholerow-ul .jstree-wholerow{width:100%;cursor:pointer;position:absolute;left:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.vakata-context{display:none}.vakata-context,.vakata-context ul{margin:0;padding:2px;position:absolute;background:#f5f5f5;border:1px solid #979797;box-shadow:2px 2px 2px #999}.vakata-context ul{list-style:none;left:100%;margin-top:-2.7em;margin-left:-4px}.vakata-context .vakata-context-right ul{left:auto;right:100%;margin-left:auto;margin-right:-4px}.vakata-context li{list-style:none}.vakata-context li>a{display:block;padding:0 2em;text-decoration:none;width:auto;color:#000;white-space:nowrap;line-height:2.4em;text-shadow:1px 1px 0 #fff;border-radius:1px}.vakata-context li>a:hover{position:relative;background-color:#e8eff7;box-shadow:0 0 2px #0a6aa1}.vakata-context li>a.vakata-context-parent{background-image:url(data:image/gif;base64,R0lGODlhCwAHAIAAACgoKP///yH5BAEAAAEALAAAAAALAAcAAAIORI4JlrqN1oMSnmmZDQUAOw==);background-position:right center;background-repeat:no-repeat}.vakata-context li>a:focus{outline:0}.vakata-context .vakata-context-hover>a{position:relative;background-color:#e8eff7;box-shadow:0 0 2px #0a6aa1}.vakata-context .vakata-context-separator>a,.vakata-context .vakata-context-separator>a:hover{background:#fff;border:0;border-top:1px solid #e2e3e3;height:1px;min-height:1px;max-height:1px;padding:0;margin:0 0 0 2.4em;border-left:1px solid #e0e0e0;text-shadow:0 0 0 transparent;box-shadow:0 0 0 transparent;border-radius:0}.vakata-context .vakata-contextmenu-disabled a,.vakata-context .vakata-contextmenu-disabled a:hover{color:silver;background-color:transparent;border:0;box-shadow:0 0 0}.vakata-context li>a>i{text-decoration:none;display:inline-block;width:2.4em;height:2.4em;background:0 0;margin:0 0 0 -2em;vertical-align:top;text-align:center;line-height:2.4em}.vakata-context li>a>i:empty{width:2.4em;line-height:2.4em}.vakata-context li>a .vakata-contextmenu-sep{display:inline-block;width:1px;height:2.4em;background:#fff;margin:0 .5em 0 0;border-left:1px solid #e2e3e3}.vakata-context .vakata-contextmenu-shortcut{font-size:.8em;color:silver;opacity:.5;display:none}.vakata-context-rtl ul{left:auto;right:100%;margin-left:auto;margin-right:-4px}.vakata-context-rtl li>a.vakata-context-parent{background-image:url(data:image/gif;base64,R0lGODlhCwAHAIAAACgoKP///yH5BAEAAAEALAAAAAALAAcAAAINjI+AC7rWHIsPtmoxLAA7);background-position:left center;background-repeat:no-repeat}.vakata-context-rtl .vakata-context-separator>a{margin:0 2.4em 0 0;border-left:0;border-right:1px solid #e2e3e3}.vakata-context-rtl .vakata-context-left ul{right:auto;left:100%;margin-left:-4px;margin-right:auto}.vakata-context-rtl li>a>i{margin:0 -2em 0 0}.vakata-context-rtl li>a .vakata-contextmenu-sep{margin:0 0 0 .5em;border-left-color:#fff;background:#e2e3e3}#jstree-marker{position:absolute;top:0;left:0;margin:-5px 0 0 0;padding:0;border-right:0;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid;width:0;height:0;font-size:0;line-height:0}#jstree-dnd{line-height:16px;margin:0;padding:4px}#jstree-dnd .jstree-icon,#jstree-dnd .jstree-copy{display:inline-block;text-decoration:none;margin:0 2px 0 0;padding:0;width:16px;height:16px}#jstree-dnd .jstree-ok{background:green}#jstree-dnd .jstree-er{background:red}#jstree-dnd .jstree-copy{margin:0 2px}.jstree-default .jstree-node,.jstree-default .jstree-icon{background-repeat:no-repeat;background-color:transparent}.jstree-default .jstree-anchor,.jstree-default .jstree-animated,.jstree-default .jstree-wholerow{transition:background-color .15s,box-shadow .15s}.jstree-default .jstree-hovered{background:#e7f4f9;border-radius:2px;box-shadow:inset 0 0 1px #ccc}.jstree-default .jstree-context{background:#e7f4f9;border-radius:2px;box-shadow:inset 0 0 1px #ccc}.jstree-default .jstree-clicked{background:#beebff;border-radius:2px;box-shadow:inset 0 0 1px #999}.jstree-default .jstree-no-icons .jstree-anchor>.jstree-themeicon{display:none}.jstree-default .jstree-disabled{background:0 0;color:#666}.jstree-default .jstree-disabled.jstree-hovered{background:0 0;box-shadow:none}.jstree-default .jstree-disabled.jstree-clicked{background:#efefef}.jstree-default .jstree-disabled>.jstree-icon{opacity:.8;filter:url("data:image/svg+xml;utf8,#jstree-grayscale");filter:gray;-webkit-filter:grayscale(100%)}.jstree-default .jstree-search{font-style:italic;color:#8b0000;font-weight:700}.jstree-default .jstree-no-checkboxes .jstree-checkbox{display:none!important}.jstree-default.jstree-checkbox-no-clicked .jstree-clicked{background:0 0;box-shadow:none}.jstree-default.jstree-checkbox-no-clicked .jstree-clicked.jstree-hovered{background:#e7f4f9}.jstree-default.jstree-checkbox-no-clicked>.jstree-wholerow-ul .jstree-wholerow-clicked{background:0 0}.jstree-default.jstree-checkbox-no-clicked>.jstree-wholerow-ul .jstree-wholerow-clicked.jstree-wholerow-hovered{background:#e7f4f9}.jstree-default>.jstree-striped{min-width:100%;display:inline-block;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAkCAMAAAB/qqA+AAAABlBMVEUAAAAAAAClZ7nPAAAAAnRSTlMNAMM9s3UAAAAXSURBVHjajcEBAQAAAIKg/H/aCQZ70AUBjAATb6YPDgAAAABJRU5ErkJggg==) left top repeat}.jstree-default>.jstree-wholerow-ul .jstree-hovered,.jstree-default>.jstree-wholerow-ul .jstree-clicked{background:0 0;box-shadow:none;border-radius:0}.jstree-default .jstree-wholerow{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.jstree-default .jstree-wholerow-hovered{background:#e7f4f9}.jstree-default .jstree-wholerow-clicked{background:#beebff;background:-webkit-linear-gradient(top,#beebff 0,#a8e4ff 100%);background:linear-gradient(to bottom,#beebff 0,#a8e4ff 100%)}.jstree-default .jstree-node{min-height:24px;line-height:24px;margin-left:24px;min-width:24px}.jstree-default .jstree-anchor{line-height:24px;height:24px}.jstree-default .jstree-icon{width:24px;height:24px;line-height:24px}.jstree-default .jstree-icon:empty{width:24px;height:24px;line-height:24px}.jstree-default.jstree-rtl .jstree-node{margin-right:24px}.jstree-default .jstree-wholerow{height:24px}.jstree-default .jstree-node,.jstree-default .jstree-icon{background-image:url(32px.png)}.jstree-default .jstree-node{background-position:-292px -4px;background-repeat:repeat-y}.jstree-default .jstree-last{background:0 0}.jstree-default .jstree-open>.jstree-ocl{background-position:-132px -4px}.jstree-default .jstree-closed>.jstree-ocl{background-position:-100px -4px}.jstree-default .jstree-leaf>.jstree-ocl{background-position:-68px -4px}.jstree-default .jstree-themeicon{background-position:-260px -4px}.jstree-default>.jstree-no-dots .jstree-node,.jstree-default>.jstree-no-dots .jstree-leaf>.jstree-ocl{background:0 0}.jstree-default>.jstree-no-dots .jstree-open>.jstree-ocl{background-position:-36px -4px}.jstree-default>.jstree-no-dots .jstree-closed>.jstree-ocl{background-position:-4px -4px}.jstree-default .jstree-disabled{background:0 0}.jstree-default .jstree-disabled.jstree-hovered{background:0 0}.jstree-default .jstree-disabled.jstree-clicked{background:#efefef}.jstree-default .jstree-checkbox{background-position:-164px -4px}.jstree-default .jstree-checkbox:hover{background-position:-164px -36px}.jstree-default.jstree-checkbox-selection .jstree-clicked>.jstree-checkbox,.jstree-default .jstree-checked>.jstree-checkbox{background-position:-228px -4px}.jstree-default.jstree-checkbox-selection .jstree-clicked>.jstree-checkbox:hover,.jstree-default .jstree-checked>.jstree-checkbox:hover{background-position:-228px -36px}.jstree-default .jstree-anchor>.jstree-undetermined{background-position:-196px -4px}.jstree-default .jstree-anchor>.jstree-undetermined:hover{background-position:-196px -36px}.jstree-default .jstree-checkbox-disabled{opacity:.8;filter:url("data:image/svg+xml;utf8,#jstree-grayscale");filter:gray;-webkit-filter:grayscale(100%)}.jstree-default>.jstree-striped{background-size:auto 48px}.jstree-default.jstree-rtl .jstree-node{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==);background-position:100% 1px;background-repeat:repeat-y}.jstree-default.jstree-rtl .jstree-last{background:0 0}.jstree-default.jstree-rtl .jstree-open>.jstree-ocl{background-position:-132px -36px}.jstree-default.jstree-rtl .jstree-closed>.jstree-ocl{background-position:-100px -36px}.jstree-default.jstree-rtl .jstree-leaf>.jstree-ocl{background-position:-68px -36px}.jstree-default.jstree-rtl>.jstree-no-dots .jstree-node,.jstree-default.jstree-rtl>.jstree-no-dots .jstree-leaf>.jstree-ocl{background:0 0}.jstree-default.jstree-rtl>.jstree-no-dots .jstree-open>.jstree-ocl{background-position:-36px -36px}.jstree-default.jstree-rtl>.jstree-no-dots .jstree-closed>.jstree-ocl{background-position:-4px -36px}.jstree-default .jstree-themeicon-custom{background-color:transparent;background-image:none;background-position:0 0}.jstree-default>.jstree-container-ul .jstree-loading>.jstree-ocl{background:url(throbber.gif) center center no-repeat}.jstree-default .jstree-file{background:url(32px.png) -100px -68px no-repeat}.jstree-default .jstree-folder{background:url(32px.png) -260px -4px no-repeat}.jstree-default>.jstree-container-ul>.jstree-node{margin-left:0;margin-right:0}#jstree-dnd.jstree-default{line-height:24px;padding:0 4px}#jstree-dnd.jstree-default .jstree-ok,#jstree-dnd.jstree-default .jstree-er{background-image:url(32px.png);background-repeat:no-repeat;background-color:transparent}#jstree-dnd.jstree-default i{background:0 0;width:24px;height:24px;line-height:24px}#jstree-dnd.jstree-default .jstree-ok{background-position:-4px -68px}#jstree-dnd.jstree-default .jstree-er{background-position:-36px -68px}.jstree-default .jstree-ellipsis{overflow:hidden}.jstree-default .jstree-ellipsis .jstree-anchor{width:calc(100% - 29px);text-overflow:ellipsis;overflow:hidden}.jstree-default .jstree-ellipsis.jstree-no-icons .jstree-anchor{width:calc(100% - 5px)}.jstree-default.jstree-rtl .jstree-node{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==)}.jstree-default.jstree-rtl .jstree-last{background:0 0}.jstree-default-small .jstree-node{min-height:18px;line-height:18px;margin-left:18px;min-width:18px}.jstree-default-small .jstree-anchor{line-height:18px;height:18px}.jstree-default-small .jstree-icon{width:18px;height:18px;line-height:18px}.jstree-default-small .jstree-icon:empty{width:18px;height:18px;line-height:18px}.jstree-default-small.jstree-rtl .jstree-node{margin-right:18px}.jstree-default-small .jstree-wholerow{height:18px}.jstree-default-small .jstree-node,.jstree-default-small .jstree-icon{background-image:url(32px.png)}.jstree-default-small .jstree-node{background-position:-295px -7px;background-repeat:repeat-y}.jstree-default-small .jstree-last{background:0 0}.jstree-default-small .jstree-open>.jstree-ocl{background-position:-135px -7px}.jstree-default-small .jstree-closed>.jstree-ocl{background-position:-103px -7px}.jstree-default-small .jstree-leaf>.jstree-ocl{background-position:-71px -7px}.jstree-default-small .jstree-themeicon{background-position:-263px -7px}.jstree-default-small>.jstree-no-dots .jstree-node,.jstree-default-small>.jstree-no-dots .jstree-leaf>.jstree-ocl{background:0 0}.jstree-default-small>.jstree-no-dots .jstree-open>.jstree-ocl{background-position:-39px -7px}.jstree-default-small>.jstree-no-dots .jstree-closed>.jstree-ocl{background-position:-7px -7px}.jstree-default-small .jstree-disabled{background:0 0}.jstree-default-small .jstree-disabled.jstree-hovered{background:0 0}.jstree-default-small .jstree-disabled.jstree-clicked{background:#efefef}.jstree-default-small .jstree-checkbox{background-position:-167px -7px}.jstree-default-small .jstree-checkbox:hover{background-position:-167px -39px}.jstree-default-small.jstree-checkbox-selection .jstree-clicked>.jstree-checkbox,.jstree-default-small .jstree-checked>.jstree-checkbox{background-position:-231px -7px}.jstree-default-small.jstree-checkbox-selection .jstree-clicked>.jstree-checkbox:hover,.jstree-default-small .jstree-checked>.jstree-checkbox:hover{background-position:-231px -39px}.jstree-default-small .jstree-anchor>.jstree-undetermined{background-position:-199px -7px}.jstree-default-small .jstree-anchor>.jstree-undetermined:hover{background-position:-199px -39px}.jstree-default-small .jstree-checkbox-disabled{opacity:.8;filter:url("data:image/svg+xml;utf8,#jstree-grayscale");filter:gray;-webkit-filter:grayscale(100%)}.jstree-default-small>.jstree-striped{background-size:auto 36px}.jstree-default-small.jstree-rtl .jstree-node{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==);background-position:100% 1px;background-repeat:repeat-y}.jstree-default-small.jstree-rtl .jstree-last{background:0 0}.jstree-default-small.jstree-rtl .jstree-open>.jstree-ocl{background-position:-135px -39px}.jstree-default-small.jstree-rtl .jstree-closed>.jstree-ocl{background-position:-103px -39px}.jstree-default-small.jstree-rtl .jstree-leaf>.jstree-ocl{background-position:-71px -39px}.jstree-default-small.jstree-rtl>.jstree-no-dots .jstree-node,.jstree-default-small.jstree-rtl>.jstree-no-dots .jstree-leaf>.jstree-ocl{background:0 0}.jstree-default-small.jstree-rtl>.jstree-no-dots .jstree-open>.jstree-ocl{background-position:-39px -39px}.jstree-default-small.jstree-rtl>.jstree-no-dots .jstree-closed>.jstree-ocl{background-position:-7px -39px}.jstree-default-small .jstree-themeicon-custom{background-color:transparent;background-image:none;background-position:0 0}.jstree-default-small>.jstree-container-ul .jstree-loading>.jstree-ocl{background:url(throbber.gif) center center no-repeat}.jstree-default-small .jstree-file{background:url(32px.png) -103px -71px no-repeat}.jstree-default-small .jstree-folder{background:url(32px.png) -263px -7px no-repeat}.jstree-default-small>.jstree-container-ul>.jstree-node{margin-left:0;margin-right:0}#jstree-dnd.jstree-default-small{line-height:18px;padding:0 4px}#jstree-dnd.jstree-default-small .jstree-ok,#jstree-dnd.jstree-default-small .jstree-er{background-image:url(32px.png);background-repeat:no-repeat;background-color:transparent}#jstree-dnd.jstree-default-small i{background:0 0;width:18px;height:18px;line-height:18px}#jstree-dnd.jstree-default-small .jstree-ok{background-position:-7px -71px}#jstree-dnd.jstree-default-small .jstree-er{background-position:-39px -71px}.jstree-default-small .jstree-ellipsis{overflow:hidden}.jstree-default-small .jstree-ellipsis .jstree-anchor{width:calc(100% - 23px);text-overflow:ellipsis;overflow:hidden}.jstree-default-small .jstree-ellipsis.jstree-no-icons .jstree-anchor{width:calc(100% - 5px)}.jstree-default-small.jstree-rtl .jstree-node{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAACAQMAAABv1h6PAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMHBgAAiABBI4gz9AAAAABJRU5ErkJggg==)}.jstree-default-small.jstree-rtl .jstree-last{background:0 0}.jstree-default-large .jstree-node{min-height:32px;line-height:32px;margin-left:32px;min-width:32px}.jstree-default-large .jstree-anchor{line-height:32px;height:32px}.jstree-default-large .jstree-icon{width:32px;height:32px;line-height:32px}.jstree-default-large .jstree-icon:empty{width:32px;height:32px;line-height:32px}.jstree-default-large.jstree-rtl .jstree-node{margin-right:32px}.jstree-default-large .jstree-wholerow{height:32px}.jstree-default-large .jstree-node,.jstree-default-large .jstree-icon{background-image:url(32px.png)}.jstree-default-large .jstree-node{background-position:-288px 0;background-repeat:repeat-y}.jstree-default-large .jstree-last{background:0 0}.jstree-default-large .jstree-open>.jstree-ocl{background-position:-128px 0}.jstree-default-large .jstree-closed>.jstree-ocl{background-position:-96px 0}.jstree-default-large .jstree-leaf>.jstree-ocl{background-position:-64px 0}.jstree-default-large .jstree-themeicon{background-position:-256px 0}.jstree-default-large>.jstree-no-dots .jstree-node,.jstree-default-large>.jstree-no-dots .jstree-leaf>.jstree-ocl{background:0 0}.jstree-default-large>.jstree-no-dots .jstree-open>.jstree-ocl{background-position:-32px 0}.jstree-default-large>.jstree-no-dots .jstree-closed>.jstree-ocl{background-position:0 0}.jstree-default-large .jstree-disabled{background:0 0}.jstree-default-large .jstree-disabled.jstree-hovered{background:0 0}.jstree-default-large .jstree-disabled.jstree-clicked{background:#efefef}.jstree-default-large .jstree-checkbox{background-position:-160px 0}.jstree-default-large .jstree-checkbox:hover{background-position:-160px -32px}.jstree-default-large.jstree-checkbox-selection .jstree-clicked>.jstree-checkbox,.jstree-default-large .jstree-checked>.jstree-checkbox{background-position:-224px 0}.jstree-default-large.jstree-checkbox-selection .jstree-clicked>.jstree-checkbox:hover,.jstree-default-large .jstree-checked>.jstree-checkbox:hover{background-position:-224px -32px}.jstree-default-large .jstree-anchor>.jstree-undetermined{background-position:-192px 0}.jstree-default-large .jstree-anchor>.jstree-undetermined:hover{background-position:-192px -32px}.jstree-default-large .jstree-checkbox-disabled{opacity:.8;filter:url("data:image/svg+xml;utf8,#jstree-grayscale");filter:gray;-webkit-filter:grayscale(100%)}.jstree-default-large>.jstree-striped{background-size:auto 64px}.jstree-default-large.jstree-rtl .jstree-node{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAACAQMAAAB49I5GAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjAAMOBgAAGAAJMwQHdQAAAABJRU5ErkJggg==);background-position:100% 1px;background-repeat:repeat-y}.jstree-default-large.jstree-rtl .jstree-last{background:0 0}.jstree-default-large.jstree-rtl .jstree-open>.jstree-ocl{background-position:-128px -32px}.jstree-default-large.jstree-rtl .jstree-closed>.jstree-ocl{background-position:-96px -32px}.jstree-default-large.jstree-rtl .jstree-leaf>.jstree-ocl{background-position:-64px -32px}.jstree-default-large.jstree-rtl>.jstree-no-dots .jstree-node,.jstree-default-large.jstree-rtl>.jstree-no-dots .jstree-leaf>.jstree-ocl{background:0 0}.jstree-default-large.jstree-rtl>.jstree-no-dots .jstree-open>.jstree-ocl{background-position:-32px -32px}.jstree-default-large.jstree-rtl>.jstree-no-dots .jstree-closed>.jstree-ocl{background-position:0 -32px}.jstree-default-large .jstree-themeicon-custom{background-color:transparent;background-image:none;background-position:0 0}.jstree-default-large>.jstree-container-ul .jstree-loading>.jstree-ocl{background:url(throbber.gif) center center no-repeat}.jstree-default-large .jstree-file{background:url(32px.png) -96px -64px no-repeat}.jstree-default-large .jstree-folder{background:url(32px.png) -256px 0 no-repeat}.jstree-default-large>.jstree-container-ul>.jstree-node{margin-left:0;margin-right:0}#jstree-dnd.jstree-default-large{line-height:32px;padding:0 4px}#jstree-dnd.jstree-default-large .jstree-ok,#jstree-dnd.jstree-default-large .jstree-er{background-image:url(32px.png);background-repeat:no-repeat;background-color:transparent}#jstree-dnd.jstree-default-large i{background:0 0;width:32px;height:32px;line-height:32px}#jstree-dnd.jstree-default-large .jstree-ok{background-position:0 -64px}#jstree-dnd.jstree-default-large .jstree-er{background-position:-32px -64px}.jstree-default-large .jstree-ellipsis{overflow:hidden}.jstree-default-large .jstree-ellipsis .jstree-anchor{width:calc(100% - 37px);text-overflow:ellipsis;overflow:hidden}.jstree-default-large .jstree-ellipsis.jstree-no-icons .jstree-anchor{width:calc(100% - 5px)}.jstree-default-large.jstree-rtl .jstree-node{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAACAQMAAAAD0EyKAAAABlBMVEUAAAAdHRvEkCwcAAAAAXRSTlMAQObYZgAAAAxJREFUCNdjgIIGBgABCgCBvVLXcAAAAABJRU5ErkJggg==)}.jstree-default-large.jstree-rtl .jstree-last{background:0 0}@media (max-width:768px){#jstree-dnd.jstree-dnd-responsive{line-height:40px;font-weight:700;font-size:1.1em;text-shadow:1px 1px #fff}#jstree-dnd.jstree-dnd-responsive>i{background:0 0;width:40px;height:40px}#jstree-dnd.jstree-dnd-responsive>.jstree-ok{background-image:url(40px.png);background-position:0 -200px;background-size:120px 240px}#jstree-dnd.jstree-dnd-responsive>.jstree-er{background-image:url(40px.png);background-position:-40px -200px;background-size:120px 240px}#jstree-marker.jstree-dnd-responsive{border-left-width:10px;border-top-width:10px;border-bottom-width:10px;margin-top:-10px}}@media (max-width:768px){.jstree-default-responsive .jstree-icon{background-image:url(40px.png)}.jstree-default-responsive .jstree-node,.jstree-default-responsive .jstree-leaf>.jstree-ocl{background:0 0}.jstree-default-responsive .jstree-node{min-height:40px;line-height:40px;margin-left:40px;min-width:40px;white-space:nowrap}.jstree-default-responsive .jstree-anchor{line-height:40px;height:40px}.jstree-default-responsive .jstree-icon,.jstree-default-responsive .jstree-icon:empty{width:40px;height:40px;line-height:40px}.jstree-default-responsive>.jstree-container-ul>.jstree-node{margin-left:0}.jstree-default-responsive.jstree-rtl .jstree-node{margin-left:0;margin-right:40px;background:0 0}.jstree-default-responsive.jstree-rtl .jstree-container-ul>.jstree-node{margin-right:0}.jstree-default-responsive .jstree-ocl,.jstree-default-responsive .jstree-themeicon,.jstree-default-responsive .jstree-checkbox{background-size:120px 240px}.jstree-default-responsive .jstree-leaf>.jstree-ocl,.jstree-default-responsive.jstree-rtl .jstree-leaf>.jstree-ocl{background:0 0}.jstree-default-responsive .jstree-open>.jstree-ocl{background-position:0 0!important}.jstree-default-responsive .jstree-closed>.jstree-ocl{background-position:0 -40px!important}.jstree-default-responsive.jstree-rtl .jstree-closed>.jstree-ocl{background-position:-40px 0!important}.jstree-default-responsive .jstree-themeicon{background-position:-40px -40px}.jstree-default-responsive .jstree-checkbox,.jstree-default-responsive .jstree-checkbox:hover{background-position:-40px -80px}.jstree-default-responsive.jstree-checkbox-selection .jstree-clicked>.jstree-checkbox,.jstree-default-responsive.jstree-checkbox-selection .jstree-clicked>.jstree-checkbox:hover,.jstree-default-responsive .jstree-checked>.jstree-checkbox,.jstree-default-responsive .jstree-checked>.jstree-checkbox:hover{background-position:0 -80px}.jstree-default-responsive .jstree-anchor>.jstree-undetermined,.jstree-default-responsive .jstree-anchor>.jstree-undetermined:hover{background-position:0 -120px}.jstree-default-responsive .jstree-anchor{font-weight:700;font-size:1.1em;text-shadow:1px 1px #fff}.jstree-default-responsive>.jstree-striped{background:0 0}.jstree-default-responsive .jstree-wholerow{border-top:1px solid rgba(255,255,255,.7);border-bottom:1px solid rgba(64,64,64,.2);background:#ebebeb;height:40px}.jstree-default-responsive .jstree-wholerow-hovered{background:#e7f4f9}.jstree-default-responsive .jstree-wholerow-clicked{background:#beebff}.jstree-default-responsive .jstree-children .jstree-last>.jstree-wholerow{box-shadow:inset 0 -6px 3px -5px #666}.jstree-default-responsive .jstree-children .jstree-open>.jstree-wholerow{box-shadow:inset 0 6px 3px -5px #666;border-top:0}.jstree-default-responsive .jstree-children .jstree-open+.jstree-open{box-shadow:none}.jstree-default-responsive .jstree-node,.jstree-default-responsive .jstree-icon,.jstree-default-responsive .jstree-node>.jstree-ocl,.jstree-default-responsive .jstree-themeicon,.jstree-default-responsive .jstree-checkbox{background-image:url(40px.png);background-size:120px 240px}.jstree-default-responsive .jstree-node{background-position:-80px 0;background-repeat:repeat-y}.jstree-default-responsive .jstree-last{background:0 0}.jstree-default-responsive .jstree-leaf>.jstree-ocl{background-position:-40px -120px}.jstree-default-responsive .jstree-last>.jstree-ocl{background-position:-40px -160px}.jstree-default-responsive .jstree-themeicon-custom{background-color:transparent;background-image:none;background-position:0 0}.jstree-default-responsive .jstree-file{background:url(40px.png) 0 -160px no-repeat;background-size:120px 240px}.jstree-default-responsive .jstree-folder{background:url(40px.png) -40px -40px no-repeat;background-size:120px 240px}.jstree-default-responsive>.jstree-container-ul>.jstree-node{margin-left:0;margin-right:0}}
\ No newline at end of file
diff --git a/public/static/lib/jstree/themes/default/throbber.gif b/public/static/lib/jstree/themes/default/throbber.gif
new file mode 100755
index 00000000..1b5b2fde
Binary files /dev/null and b/public/static/lib/jstree/themes/default/throbber.gif differ
diff --git a/public/static/lib/opengraph/OpenGraph-0.1.3-SNAPSHOT.js b/public/static/lib/opengraph/OpenGraph-0.1.3-SNAPSHOT.js
new file mode 100644
index 00000000..a38d97bc
--- /dev/null
+++ b/public/static/lib/opengraph/OpenGraph-0.1.3-SNAPSHOT.js
@@ -0,0 +1,38234 @@
+// ┌────────────────────────────────────────────────────────────────────┐ \\
+// │ Raphaël 2.1.0 - JavaScript Vector Library │ \\
+// ├────────────────────────────────────────────────────────────────────┤ \\
+// │ Copyright © 2008-2012 Dmitry Baranovskiy (http://raphaeljs.com) │ \\
+// │ Copyright © 2008-2012 Sencha Labs (http://sencha.com) │ \\
+// ├────────────────────────────────────────────────────────────────────┤ \\
+// │ Licensed under the MIT (http://raphaeljs.com/license.html) license.│ \\
+// └────────────────────────────────────────────────────────────────────┘ \\
+
+/* group 기능 추가 (by 이승백, 2012-04-17)
+ @example
+ var paper = new Raphael('canvas', 800, 600);
+ var ele1 = paper.circle(50, 50, 50);
+ var group = paper.group();
+ var ele2 = paper.rect(0, 0, 50, 50, 5);
+
+ group.appendChild(ele1);
+ group.appendChild(ele2);
+
+ group.rotate(45, 50, 50);
+ */
+
+
+/* foreignObject 기능 추가 (by 이승백 2012-09-18)
+ @example
+ var paper = new Raphael('canvas', 800, 600);
+ paper.foreignObject('test
', 100, 100, 50, 50);
+ */
+
+// ┌──────────────────────────────────────────────────────────────────────────────────────┐ \\
+// │ Eve 0.3.4 - JavaScript Events Library │ \\
+// ├──────────────────────────────────────────────────────────────────────────────────────┤ \\
+// │ Copyright (c) 2008-2011 Dmitry Baranovskiy (http://dmitry.baranovskiy.com/) │ \\
+// │ Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license. │ \\
+// └──────────────────────────────────────────────────────────────────────────────────────┘ \\
+
+(function (glob) {
+ var version = "0.3.4",
+ has = "hasOwnProperty",
+ separator = /[\.\/]/,
+ wildcard = "*",
+ fun = function () {
+ },
+ numsort = function (a, b) {
+ return a - b;
+ },
+ current_event,
+ stop,
+ events = {n: {}},
+
+ eve = function (name, scope) {
+ var e = events,
+ oldstop = stop,
+ args = Array.prototype.slice.call(arguments, 2),
+ listeners = eve.listeners(name),
+ z = 0,
+ f = false,
+ l,
+ indexed = [],
+ queue = {},
+ out = [],
+ ce = current_event,
+ errors = [];
+ current_event = name;
+ stop = 0;
+ for (var i = 0, ii = listeners.length; i < ii; i++) if ("zIndex" in listeners[i]) {
+ indexed.push(listeners[i].zIndex);
+ if (listeners[i].zIndex < 0) {
+ queue[listeners[i].zIndex] = listeners[i];
+ }
+ }
+ indexed.sort(numsort);
+ while (indexed[z] < 0) {
+ l = queue[indexed[z++]];
+ out.push(l.apply(scope, args));
+ if (stop) {
+ stop = oldstop;
+ return out;
+ }
+ }
+ for (i = 0; i < ii; i++) {
+ l = listeners[i];
+ if ("zIndex" in l) {
+ if (l.zIndex == indexed[z]) {
+ out.push(l.apply(scope, args));
+ if (stop) {
+ break;
+ }
+ do {
+ z++;
+ l = queue[indexed[z]];
+ l && out.push(l.apply(scope, args));
+ if (stop) {
+ break;
+ }
+ } while (l)
+ } else {
+ queue[l.zIndex] = l;
+ }
+ } else {
+ out.push(l.apply(scope, args));
+ if (stop) {
+ break;
+ }
+ }
+ }
+ stop = oldstop;
+ current_event = ce;
+ return out.length ? out : null;
+ };
+
+ eve.listeners = function (name) {
+ var names = name.split(separator),
+ e = events,
+ item,
+ items,
+ k,
+ i,
+ ii,
+ j,
+ jj,
+ nes,
+ es = [e],
+ out = [];
+ for (i = 0, ii = names.length; i < ii; i++) {
+ nes = [];
+ for (j = 0, jj = es.length; j < jj; j++) {
+ e = es[j].n;
+ items = [e[names[i]], e[wildcard]];
+ k = 2;
+ while (k--) {
+ item = items[k];
+ if (item) {
+ nes.push(item);
+ out = out.concat(item.f || []);
+ }
+ }
+ }
+ es = nes;
+ }
+ return out;
+ };
+
+
+ eve.on = function (name, f) {
+ var names = name.split(separator),
+ e = events;
+ for (var i = 0, ii = names.length; i < ii; i++) {
+ e = e.n;
+ !e[names[i]] && (e[names[i]] = {n: {}});
+ e = e[names[i]];
+ }
+ e.f = e.f || [];
+ for (i = 0, ii = e.f.length; i < ii; i++) if (e.f[i] == f) {
+ return fun;
+ }
+ e.f.push(f);
+ return function (zIndex) {
+ if (+zIndex == +zIndex) {
+ f.zIndex = +zIndex;
+ }
+ };
+ };
+
+ eve.stop = function () {
+ stop = 1;
+ };
+
+ eve.nt = function (subname) {
+ if (subname) {
+ return new RegExp("(?:\\.|\\/|^)" + subname + "(?:\\.|\\/|$)").test(current_event);
+ }
+ return current_event;
+ };
+
+
+ eve.off = eve.unbind = function (name, f) {
+ var names = name.split(separator),
+ e,
+ key,
+ splice,
+ i, ii, j, jj,
+ cur = [events];
+ for (i = 0, ii = names.length; i < ii; i++) {
+ for (j = 0; j < cur.length; j += splice.length - 2) {
+ splice = [j, 1];
+ e = cur[j].n;
+ if (names[i] != wildcard) {
+ if (e[names[i]]) {
+ splice.push(e[names[i]]);
+ }
+ } else {
+ for (key in e) if (e[has](key)) {
+ splice.push(e[key]);
+ }
+ }
+ cur.splice.apply(cur, splice);
+ }
+ }
+ for (i = 0, ii = cur.length; i < ii; i++) {
+ e = cur[i];
+ while (e.n) {
+ if (f) {
+ if (e.f) {
+ for (j = 0, jj = e.f.length; j < jj; j++) if (e.f[j] == f) {
+ e.f.splice(j, 1);
+ break;
+ }
+ !e.f.length && delete e.f;
+ }
+ for (key in e.n) if (e.n[has](key) && e.n[key].f) {
+ var funcs = e.n[key].f;
+ for (j = 0, jj = funcs.length; j < jj; j++) if (funcs[j] == f) {
+ funcs.splice(j, 1);
+ break;
+ }
+ !funcs.length && delete e.n[key].f;
+ }
+ } else {
+ delete e.f;
+ for (key in e.n) if (e.n[has](key) && e.n[key].f) {
+ delete e.n[key].f;
+ }
+ }
+ e = e.n;
+ }
+ }
+ };
+
+ eve.once = function (name, f) {
+ var f2 = function () {
+ var res = f.apply(this, arguments);
+ eve.unbind(name, f2);
+ return res;
+ };
+ return eve.on(name, f2);
+ };
+
+ eve.version = version;
+ eve.toString = function () {
+ return "You are running Eve " + version;
+ };
+ (typeof module != "undefined" && module.exports) ? (module.exports = eve) : (typeof define != "undefined" ? (define("eve", [], function () {
+ return eve;
+ })) : (glob.eve = eve));
+})(this);
+
+
+// ┌─────────────────────────────────────────────────────────────────────┐ \\
+// │ "Raphaël 2.1.0" - JavaScript Vector Library │ \\
+// ├─────────────────────────────────────────────────────────────────────┤ \\
+// │ Copyright (c) 2008-2011 Dmitry Baranovskiy (http://raphaeljs.com) │ \\
+// │ Copyright (c) 2008-2011 Sencha Labs (http://sencha.com) │ \\
+// │ Licensed under the MIT (http://raphaeljs.com/license.html) license. │ \\
+// └─────────────────────────────────────────────────────────────────────┘ \\
+(function () {
+
+ function R(first) {
+ if (R.is(first, "function")) {
+ return loaded ? first() : eve.on("raphael.DOMload", first);
+ } else if (R.is(first, array)) {
+ return R._engine.create[apply](R, first.splice(0, 3 + R.is(first[0], nu))).add(first);
+ } else {
+ var args = Array.prototype.slice.call(arguments, 0);
+ if (R.is(args[args.length - 1], "function")) {
+ var f = args.pop();
+ return loaded ? f.call(R._engine.create[apply](R, args)) : eve.on("raphael.DOMload", function () {
+ f.call(R._engine.create[apply](R, args));
+ });
+ } else {
+ return R._engine.create[apply](R, arguments);
+ }
+ }
+ }
+
+ R.version = "2.1.0";
+ R.eve = eve;
+ var loaded,
+ separator = /[, ]+/,
+ elements = {circle: 1, rect: 1, path: 1, ellipse: 1, text: 1, image: 1},
+ formatrg = /\{(\d+)\}/g,
+ proto = "prototype",
+ has = "hasOwnProperty",
+ g = {
+ doc: document,
+ win: window
+ },
+ oldRaphael = {
+ was: Object.prototype[has].call(g.win, "Raphael"),
+ is: g.win.Raphael
+ },
+ Paper = function () {
+
+
+ this.ca = this.customAttributes = {};
+ },
+ paperproto,
+ appendChild = "appendChild",
+ apply = "apply",
+ concat = "concat",
+ supportsTouch = "createTouch" in g.doc,
+ E = "",
+ S = " ",
+ Str = String,
+ split = "split",
+ events = "click dblclick mousedown mousemove mouseout mouseover mouseup touchstart touchmove touchend touchcancel"[split](S),
+ touchMap = {
+ mousedown: "touchstart",
+ mousemove: "touchmove",
+ mouseup: "touchend"
+ },
+ lowerCase = Str.prototype.toLowerCase,
+ math = Math,
+ mmax = math.max,
+ mmin = math.min,
+ abs = math.abs,
+ pow = math.pow,
+ PI = math.PI,
+ nu = "number",
+ string = "string",
+ array = "array",
+ toString = "toString",
+ fillString = "fill",
+ objectToString = Object.prototype.toString,
+ paper = {},
+ push = "push",
+ ISURL = R._ISURL = /^url\(['"]?([^\)]+?)['"]?\)$/i,
+ colourRegExp = /^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgba?\(\s*([\d\.]+%?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+%?(?:\s*,\s*[\d\.]+%?)?)\s*\)|hsba?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\)|hsla?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\))\s*$/i,
+ isnan = {"NaN": 1, "Infinity": 1, "-Infinity": 1},
+ bezierrg = /^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,
+ round = math.round,
+ setAttribute = "setAttribute",
+ toFloat = parseFloat,
+ toInt = parseInt,
+ upperCase = Str.prototype.toUpperCase,
+ availableAttrs = R._availableAttrs = {
+ "arrow-end": "none",
+ "arrow-start": "none",
+ blur: 0,
+ "clip-rect": "0 0 1e9 1e9",
+ cursor: "default",
+ cx: 0,
+ cy: 0,
+ "fill-r": .5,
+ "fill-cx": .5,
+ "fill-cy": .5,
+ fill: "#fff",
+ "fill-opacity": 1,
+ font: "10px 'serif'",
+ "font-family": "'serif'",
+ "font-size": "10",
+ "font-style": "normal",
+ "font-weight": 400,
+ gradient: 0,
+ height: 0,
+ href: "http://raphaeljs.com/",
+ "letter-spacing": 0,
+ opacity: 1,
+ path: "M0,0",
+ r: 0,
+ rx: 0,
+ ry: 0,
+ src: "",
+ stroke: "#000",
+ "stroke-dasharray": "",
+ "stroke-linecap": "butt",
+ "stroke-linejoin": "butt",
+ "stroke-miterlimit": 0,
+ "stroke-opacity": 1,
+ "stroke-width": 1,
+ target: "_blank",
+ "text-anchor": "middle",
+ title: "Raphael",
+ transform: "",
+ width: 0,
+ x: 0,
+ y: 0,
+ // 추가 ("shape-rendering": "crispEdges")
+ "shape-rendering": "crispEdges",
+ "text-decoration": "none",
+ "word-wrap": "none"
+ },
+ availableAnimAttrs = R._availableAnimAttrs = {
+ blur: nu,
+ "clip-rect": "csv",
+ cx: nu,
+ cy: nu,
+ fill: "colour",
+ "fill-opacity": nu,
+ "font-size": nu,
+ height: nu,
+ opacity: nu,
+ path: "path",
+ r: nu,
+ rx: nu,
+ ry: nu,
+ stroke: "colour",
+ "stroke-opacity": nu,
+ "stroke-width": nu,
+ transform: "transform",
+ width: nu,
+ x: nu,
+ y: nu
+ },
+ whitespace = /[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]/g,
+ commaSpaces = /[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*/,
+ hsrg = {hs: 1, rg: 1},
+ p2s = /,?([achlmqrstvxz]),?/gi,
+ pathCommand = /([achlmrqstvz])[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*)+)/ig,
+ tCommand = /([rstm])[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*)+)/ig,
+ pathValues = /(-?\d*\.?\d*(?:e[\-+]?\d+)?)[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*/ig,
+ radial_gradient = R._radial_gradient = /^r(?:\(([^,]+?)[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*([^\)]+?)\))?/,
+ eldata = {},
+ sortByKey = function (a, b) {
+ return a.key - b.key;
+ },
+ sortByNumber = function (a, b) {
+ return toFloat(a) - toFloat(b);
+ },
+ fun = function () {
+ },
+ pipe = function (x) {
+ return x;
+ },
+ rectPath = R._rectPath = function (x, y, w, h, r) {
+ if (r) {
+ return [["M", x + r, y], ["l", w - r * 2, 0], ["a", r, r, 0, 0, 1, r, r], ["l", 0, h - r * 2], ["a", r, r, 0, 0, 1, -r, r], ["l", r * 2 - w, 0], ["a", r, r, 0, 0, 1, -r, -r], ["l", 0, r * 2 - h], ["a", r, r, 0, 0, 1, r, -r], ["z"]];
+ }
+ return [["M", x, y], ["l", w, 0], ["l", 0, h], ["l", -w, 0], ["z"]];
+ },
+ ellipsePath = function (x, y, rx, ry) {
+ if (ry == null) {
+ ry = rx;
+ }
+ return [["M", x, y], ["m", 0, -ry], ["a", rx, ry, 0, 1, 1, 0, 2 * ry], ["a", rx, ry, 0, 1, 1, 0, -2 * ry], ["z"]];
+ },
+ getPath = R._getPath = {
+ // 추가(for group 기능)
+ group: function (el) {
+ throw new TypeError("Not support for group element!");
+ },
+ path: function (el) {
+ return el.attr("path");
+ },
+ circle: function (el) {
+ var a = el.attrs;
+ return ellipsePath(a.cx, a.cy, a.r);
+ },
+ ellipse: function (el) {
+ var a = el.attrs;
+ return ellipsePath(a.cx, a.cy, a.rx, a.ry);
+ },
+ rect: function (el) {
+ var a = el.attrs;
+ return rectPath(a.x, a.y, a.width, a.height, a.r);
+ },
+ image: function (el) {
+ var a = el.attrs;
+ return rectPath(a.x, a.y, a.width, a.height);
+ },
+ text: function (el) {
+ var bbox = el._getBBox();
+ return rectPath(bbox.x, bbox.y, bbox.width, bbox.height);
+ },
+ foreignObject: function (el) {
+ var a = el.attrs;
+ return rectPath(a.x, a.y, a.width, a.height);
+ }
+ },
+
+ mapPath = R.mapPath = function (path, matrix) {
+ if (!matrix) {
+ return path;
+ }
+ var x, y, i, j, ii, jj, pathi;
+ path = path2curve(path);
+ for (i = 0, ii = path.length; i < ii; i++) {
+ pathi = path[i];
+ for (j = 1, jj = pathi.length; j < jj; j += 2) {
+ x = matrix.x(pathi[j], pathi[j + 1]);
+ y = matrix.y(pathi[j], pathi[j + 1]);
+ pathi[j] = x;
+ pathi[j + 1] = y;
+ }
+ }
+ return path;
+ };
+
+ R._g = g;
+
+ R.type = (g.win.SVGAngle || g.doc.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1") ? "SVG" : "VML");
+ if (R.type == "VML") {
+ var d = g.doc.createElement("div"),
+ b;
+ d.innerHTML = '';
+ b = d.firstChild;
+ b.style.behavior = "url(#default#VML)";
+ if (!(b && typeof b.adj == "object")) {
+ return (R.type = E);
+ }
+ d = null;
+ }
+
+ //IE 일 때는 vml 로 그리게 처리
+ R.svg = !(R.vml = R.type == "VML");
+ //R.vml = navigator.appName.charAt(0) == "M";
+ //R.svg = !R.vml;
+
+ R._Paper = Paper;
+
+ R.fn = paperproto = Paper.prototype = R.prototype;
+ R._id = 0;
+ R._oid = 0;
+
+ R.is = function (o, type) {
+ type = lowerCase.call(type);
+ if (type == "finite") {
+ return !isnan[has](+o);
+ }
+ if (type == "array") {
+ return o instanceof Array;
+ }
+ return (type == "null" && o === null) ||
+ (type == typeof o && o !== null) ||
+ (type == "object" && o === Object(o)) ||
+ (type == "array" && Array.isArray && Array.isArray(o)) ||
+ objectToString.call(o).slice(8, -1).toLowerCase() == type;
+ };
+
+ function clone(obj) {
+ if (Object(obj) !== obj) {
+ return obj;
+ }
+ var res = new obj.constructor;
+ for (var key in obj) if (obj[has](key)) {
+ res[key] = clone(obj[key]);
+ }
+ return res;
+ }
+
+
+ R.angle = function (x1, y1, x2, y2, x3, y3) {
+ if (x3 == null) {
+ var x = x1 - x2,
+ y = y1 - y2;
+ if (!x && !y) {
+ return 0;
+ }
+ return (180 + math.atan2(-y, -x) * 180 / PI + 360) % 360;
+ } else {
+ return R.angle(x1, y1, x3, y3) - R.angle(x2, y2, x3, y3);
+ }
+ };
+
+ R.rad = function (deg) {
+ return deg % 360 * PI / 180;
+ };
+
+ R.deg = function (rad) {
+ return rad * 180 / PI % 360;
+ };
+
+ R.snapTo = function (values, value, tolerance) {
+ tolerance = R.is(tolerance, "finite") ? tolerance : 10;
+ if (R.is(values, array)) {
+ var i = values.length;
+ while (i--) if (abs(values[i] - value) <= tolerance) {
+ return values[i];
+ }
+ } else {
+ values = +values;
+ var rem = value % values;
+ if (rem < tolerance) {
+ return value - rem;
+ }
+ if (rem > values - tolerance) {
+ return value - rem + values;
+ }
+ }
+ return value;
+ };
+
+
+ var createUUID = R.createUUID = (function (uuidRegEx, uuidReplacer) {
+ return function () {
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(uuidRegEx, uuidReplacer).toUpperCase();
+ };
+ })(/[xy]/g, function (c) {
+ var r = math.random() * 16 | 0,
+ v = c == "x" ? r : (r & 3 | 8);
+ return v.toString(16);
+ });
+
+
+ R.setWindow = function (newwin) {
+ eve("raphael.setWindow", R, g.win, newwin);
+ g.win = newwin;
+ g.doc = g.win.document;
+ if (R._engine.initWin) {
+ R._engine.initWin(g.win);
+ }
+ };
+ var toHex = function (color) {
+ if (R.vml) {
+ // http://dean.edwards.name/weblog/2009/10/convert-any-colour-value-to-hex-in-msie/
+ var trim = /^\s+|\s+$/g;
+ var bod;
+ try {
+ var docum = new ActiveXObject("htmlfile");
+ docum.write("");
+ docum.close();
+ bod = docum.body;
+ } catch (e) {
+ bod = createPopup().document.body;
+ }
+ var range = bod.createTextRange();
+ toHex = cacher(function (color) {
+ try {
+ bod.style.color = Str(color).replace(trim, E);
+ var value = range.queryCommandValue("ForeColor");
+ value = ((value & 255) << 16) | (value & 65280) | ((value & 16711680) >>> 16);
+ return "#" + ("000000" + value.toString(16)).slice(-6);
+ } catch (e) {
+ return "none";
+ }
+ });
+ } else {
+ var i = g.doc.createElement("i");
+ i.title = "Rapha\xebl Colour Picker";
+ i.style.display = "none";
+ g.doc.body.appendChild(i);
+ toHex = cacher(function (color) {
+ i.style.color = color;
+ return g.doc.defaultView.getComputedStyle(i, E).getPropertyValue("color");
+ });
+ }
+ return toHex(color);
+ },
+ hsbtoString = function () {
+ return "hsb(" + [this.h, this.s, this.b] + ")";
+ },
+ hsltoString = function () {
+ return "hsl(" + [this.h, this.s, this.l] + ")";
+ },
+ rgbtoString = function () {
+ return this.hex;
+ },
+ prepareRGB = function (r, g, b) {
+ if (g == null && R.is(r, "object") && "r" in r && "g" in r && "b" in r) {
+ b = r.b;
+ g = r.g;
+ r = r.r;
+ }
+ if (g == null && R.is(r, string)) {
+ var clr = R.getRGB(r);
+ r = clr.r;
+ g = clr.g;
+ b = clr.b;
+ }
+ if (r > 1 || g > 1 || b > 1) {
+ r /= 255;
+ g /= 255;
+ b /= 255;
+ }
+
+ return [r, g, b];
+ },
+ packageRGB = function (r, g, b, o) {
+ r *= 255;
+ g *= 255;
+ b *= 255;
+ var rgb = {
+ r: r,
+ g: g,
+ b: b,
+ hex: R.rgb(r, g, b),
+ toString: rgbtoString
+
+
+ };
+ R.is(o, "finite") && (rgb.opacity = o);
+ return rgb;
+ };
+
+
+ R.color = function (clr) {
+ var rgb;
+ if (R.is(clr, "object") && "h" in clr && "s" in clr && "b" in clr) {
+ rgb = R.hsb2rgb(clr);
+ clr.r = rgb.r;
+ clr.g = rgb.g;
+ clr.b = rgb.b;
+ clr.hex = rgb.hex;
+ } else if (R.is(clr, "object") && "h" in clr && "s" in clr && "l" in clr) {
+ rgb = R.hsl2rgb(clr);
+ clr.r = rgb.r;
+ clr.g = rgb.g;
+ clr.b = rgb.b;
+ clr.hex = rgb.hex;
+ } else {
+ if (R.is(clr, "string")) {
+ clr = R.getRGB(clr);
+ }
+ if (R.is(clr, "object") && "r" in clr && "g" in clr && "b" in clr) {
+ rgb = R.rgb2hsl(clr);
+ clr.h = rgb.h;
+ clr.s = rgb.s;
+ clr.l = rgb.l;
+ rgb = R.rgb2hsb(clr);
+ clr.v = rgb.b;
+ } else {
+ clr = {hex: "none"};
+ clr.r = clr.g = clr.b = clr.h = clr.s = clr.v = clr.l = -1;
+ }
+ }
+ clr.toString = rgbtoString;
+ return clr;
+ };
+
+ R.hsb2rgb = function (h, s, v, o) {
+ if (this.is(h, "object") && "h" in h && "s" in h && "b" in h) {
+ v = h.b;
+ s = h.s;
+ h = h.h;
+ o = h.o;
+ }
+ h *= 360;
+ var R, G, B, X, C;
+ h = (h % 360) / 60;
+ C = v * s;
+ X = C * (1 - abs(h % 2 - 1));
+ R = G = B = v - C;
+
+ h = ~~h;
+ R += [C, X, 0, 0, X, C][h];
+ G += [X, C, C, X, 0, 0][h];
+ B += [0, 0, X, C, C, X][h];
+ return packageRGB(R, G, B, o);
+ };
+
+ R.hsl2rgb = function (h, s, l, o) {
+ if (this.is(h, "object") && "h" in h && "s" in h && "l" in h) {
+ l = h.l;
+ s = h.s;
+ h = h.h;
+ }
+ if (h > 1 || s > 1 || l > 1) {
+ h /= 360;
+ s /= 100;
+ l /= 100;
+ }
+ h *= 360;
+ var R, G, B, X, C;
+ h = (h % 360) / 60;
+ C = 2 * s * (l < .5 ? l : 1 - l);
+ X = C * (1 - abs(h % 2 - 1));
+ R = G = B = l - C / 2;
+
+ h = ~~h;
+ R += [C, X, 0, 0, X, C][h];
+ G += [X, C, C, X, 0, 0][h];
+ B += [0, 0, X, C, C, X][h];
+ return packageRGB(R, G, B, o);
+ };
+
+ R.rgb2hsb = function (r, g, b) {
+ b = prepareRGB(r, g, b);
+ r = b[0];
+ g = b[1];
+ b = b[2];
+
+ var H, S, V, C;
+ V = mmax(r, g, b);
+ C = V - mmin(r, g, b);
+ H = (C == 0 ? null :
+ V == r ? (g - b) / C :
+ V == g ? (b - r) / C + 2 :
+ (r - g) / C + 4
+ );
+ H = ((H + 360) % 6) * 60 / 360;
+ S = C == 0 ? 0 : C / V;
+ return {h: H, s: S, b: V, toString: hsbtoString};
+ };
+
+ R.rgb2hsl = function (r, g, b) {
+ b = prepareRGB(r, g, b);
+ r = b[0];
+ g = b[1];
+ b = b[2];
+
+ var H, S, L, M, m, C;
+ M = mmax(r, g, b);
+ m = mmin(r, g, b);
+ C = M - m;
+ H = (C == 0 ? null :
+ M == r ? (g - b) / C :
+ M == g ? (b - r) / C + 2 :
+ (r - g) / C + 4);
+ H = ((H + 360) % 6) * 60 / 360;
+ L = (M + m) / 2;
+ S = (C == 0 ? 0 :
+ L < .5 ? C / (2 * L) :
+ C / (2 - 2 * L));
+ return {h: H, s: S, l: L, toString: hsltoString};
+ };
+ R._path2string = function () {
+ return this.join(",").replace(p2s, "$1");
+ };
+
+ function repush(array, item) {
+ for (var i = 0, ii = array.length; i < ii; i++) if (array[i] === item) {
+ return array.push(array.splice(i, 1)[0]);
+ }
+ }
+
+ function cacher(f, scope, postprocessor) {
+ function newf() {
+ var arg = Array.prototype.slice.call(arguments, 0),
+ args = arg.join("\u2400"),
+ cache = newf.cache = newf.cache || {},
+ count = newf.count = newf.count || [];
+ if (cache[has](args)) {
+ repush(count, args);
+ return postprocessor ? postprocessor(cache[args]) : cache[args];
+ }
+ count.length >= 1e3 && delete cache[count.shift()];
+ count.push(args);
+ cache[args] = f[apply](scope, arg);
+ return postprocessor ? postprocessor(cache[args]) : cache[args];
+ }
+
+ return newf;
+ }
+
+ var preload = R._preload = function (src, f) {
+ var img = g.doc.createElement("img");
+ img.style.cssText = "position:absolute;left:-9999em;top:-9999em";
+ img.onload = function () {
+ f.call(this);
+ this.onload = null;
+ g.doc.body.removeChild(this);
+ };
+ img.onerror = function () {
+ g.doc.body.removeChild(this);
+ };
+ g.doc.body.appendChild(img);
+ img.src = src;
+ };
+
+ function clrToString() {
+ return this.hex;
+ }
+
+
+ R.getRGB = cacher(function (colour) {
+ if (!colour || !!((colour = Str(colour)).indexOf("-") + 1)) {
+ return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: clrToString};
+ }
+ if (colour == "none") {
+ return {r: -1, g: -1, b: -1, hex: "none", toString: clrToString};
+ }
+ !(hsrg[has](colour.toLowerCase().substring(0, 2)) || colour.charAt() == "#") && (colour = toHex(colour));
+ var res,
+ red,
+ green,
+ blue,
+ opacity,
+ t,
+ values,
+ rgb = colour.match(colourRegExp);
+ if (rgb) {
+ if (rgb[2]) {
+ blue = toInt(rgb[2].substring(5), 16);
+ green = toInt(rgb[2].substring(3, 5), 16);
+ red = toInt(rgb[2].substring(1, 3), 16);
+ }
+ if (rgb[3]) {
+ blue = toInt((t = rgb[3].charAt(3)) + t, 16);
+ green = toInt((t = rgb[3].charAt(2)) + t, 16);
+ red = toInt((t = rgb[3].charAt(1)) + t, 16);
+ }
+ if (rgb[4]) {
+ values = rgb[4][split](commaSpaces);
+ red = toFloat(values[0]);
+ values[0].slice(-1) == "%" && (red *= 2.55);
+ green = toFloat(values[1]);
+ values[1].slice(-1) == "%" && (green *= 2.55);
+ blue = toFloat(values[2]);
+ values[2].slice(-1) == "%" && (blue *= 2.55);
+ rgb[1].toLowerCase().slice(0, 4) == "rgba" && (opacity = toFloat(values[3]));
+ values[3] && values[3].slice(-1) == "%" && (opacity /= 100);
+ }
+ if (rgb[5]) {
+ values = rgb[5][split](commaSpaces);
+ red = toFloat(values[0]);
+ values[0].slice(-1) == "%" && (red *= 2.55);
+ green = toFloat(values[1]);
+ values[1].slice(-1) == "%" && (green *= 2.55);
+ blue = toFloat(values[2]);
+ values[2].slice(-1) == "%" && (blue *= 2.55);
+ (values[0].slice(-3) == "deg" || values[0].slice(-1) == "\xb0") && (red /= 360);
+ rgb[1].toLowerCase().slice(0, 4) == "hsba" && (opacity = toFloat(values[3]));
+ values[3] && values[3].slice(-1) == "%" && (opacity /= 100);
+ return R.hsb2rgb(red, green, blue, opacity);
+ }
+ if (rgb[6]) {
+ values = rgb[6][split](commaSpaces);
+ red = toFloat(values[0]);
+ values[0].slice(-1) == "%" && (red *= 2.55);
+ green = toFloat(values[1]);
+ values[1].slice(-1) == "%" && (green *= 2.55);
+ blue = toFloat(values[2]);
+ values[2].slice(-1) == "%" && (blue *= 2.55);
+ (values[0].slice(-3) == "deg" || values[0].slice(-1) == "\xb0") && (red /= 360);
+ rgb[1].toLowerCase().slice(0, 4) == "hsla" && (opacity = toFloat(values[3]));
+ values[3] && values[3].slice(-1) == "%" && (opacity /= 100);
+ return R.hsl2rgb(red, green, blue, opacity);
+ }
+ rgb = {r: red, g: green, b: blue, toString: clrToString};
+ rgb.hex = "#" + (16777216 | blue | (green << 8) | (red << 16)).toString(16).slice(1);
+ R.is(opacity, "finite") && (rgb.opacity = opacity);
+ return rgb;
+ }
+ return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: clrToString};
+ }, R);
+
+ R.hsb = cacher(function (h, s, b) {
+ return R.hsb2rgb(h, s, b).hex;
+ });
+
+ R.hsl = cacher(function (h, s, l) {
+ return R.hsl2rgb(h, s, l).hex;
+ });
+
+ R.rgb = cacher(function (r, g, b) {
+ return "#" + (16777216 | b | (g << 8) | (r << 16)).toString(16).slice(1);
+ });
+
+ R.getColor = function (value) {
+ var start = this.getColor.start = this.getColor.start || {h: 0, s: 1, b: value || .75},
+ rgb = this.hsb2rgb(start.h, start.s, start.b);
+ start.h += .075;
+ if (start.h > 1) {
+ start.h = 0;
+ start.s -= .2;
+ start.s <= 0 && (this.getColor.start = {h: 0, s: 1, b: start.b});
+ }
+ return rgb.hex;
+ };
+
+ R.getColor.reset = function () {
+ delete this.start;
+ };
+
+ // http://schepers.cc/getting-to-the-point
+ function catmullRom2bezier(crp, z) {
+ var d = [];
+ for (var i = 0, iLen = crp.length; iLen - 2 * !z > i; i += 2) {
+ var p = [
+ {x: +crp[i - 2], y: +crp[i - 1]},
+ {x: +crp[i], y: +crp[i + 1]},
+ {x: +crp[i + 2], y: +crp[i + 3]},
+ {x: +crp[i + 4], y: +crp[i + 5]}
+ ];
+ if (z) {
+ if (!i) {
+ p[0] = {x: +crp[iLen - 2], y: +crp[iLen - 1]};
+ } else if (iLen - 4 == i) {
+ p[3] = {x: +crp[0], y: +crp[1]};
+ } else if (iLen - 2 == i) {
+ p[2] = {x: +crp[0], y: +crp[1]};
+ p[3] = {x: +crp[2], y: +crp[3]};
+ }
+ } else {
+ if (iLen - 4 == i) {
+ p[3] = p[2];
+ } else if (!i) {
+ p[0] = {x: +crp[i], y: +crp[i + 1]};
+ }
+ }
+ d.push(["C",
+ (-p[0].x + 6 * p[1].x + p[2].x) / 6,
+ (-p[0].y + 6 * p[1].y + p[2].y) / 6,
+ (p[1].x + 6 * p[2].x - p[3].x) / 6,
+ (p[1].y + 6 * p[2].y - p[3].y) / 6,
+ p[2].x,
+ p[2].y
+ ]);
+ }
+
+ return d;
+ }
+
+ R.parsePathString = function (pathString) {
+ if (!pathString) {
+ return null;
+ }
+ var pth = paths(pathString);
+ if (pth.arr) {
+ return pathClone(pth.arr);
+ }
+
+ var paramCounts = {a: 7, c: 6, h: 1, l: 2, m: 2, r: 4, q: 4, s: 4, t: 2, v: 1, z: 0},
+ data = [];
+ if (R.is(pathString, array) && R.is(pathString[0], array)) { // rough assumption
+ data = pathClone(pathString);
+ }
+ if (!data.length) {
+ Str(pathString).replace(pathCommand, function (a, b, c) {
+ var params = [],
+ name = b.toLowerCase();
+ c.replace(pathValues, function (a, b) {
+ b && params.push(+b);
+ });
+ if (name == "m" && params.length > 2) {
+ data.push([b][concat](params.splice(0, 2)));
+ name = "l";
+ b = b == "m" ? "l" : "L";
+ }
+ if (name == "r") {
+ data.push([b][concat](params));
+ } else while (params.length >= paramCounts[name]) {
+ data.push([b][concat](params.splice(0, paramCounts[name])));
+ if (!paramCounts[name]) {
+ break;
+ }
+ }
+ });
+ }
+ data.toString = R._path2string;
+ pth.arr = pathClone(data);
+ return data;
+ };
+
+ R.parseTransformString = cacher(function (TString) {
+ if (!TString) {
+ return null;
+ }
+ var paramCounts = {r: 3, s: 4, t: 2, m: 6},
+ data = [];
+ if (R.is(TString, array) && R.is(TString[0], array)) { // rough assumption
+ data = pathClone(TString);
+ }
+ if (!data.length) {
+ Str(TString).replace(tCommand, function (a, b, c) {
+ var params = [],
+ name = lowerCase.call(b);
+ c.replace(pathValues, function (a, b) {
+ b && params.push(+b);
+ });
+ data.push([b][concat](params));
+ });
+ }
+ data.toString = R._path2string;
+ return data;
+ });
+ // PATHS
+ var paths = function (ps) {
+ var p = paths.ps = paths.ps || {};
+ if (p[ps]) {
+ p[ps].sleep = 100;
+ } else {
+ p[ps] = {
+ sleep: 100
+ };
+ }
+ setTimeout(function () {
+ for (var key in p) if (p[has](key) && key != ps) {
+ p[key].sleep--;
+ !p[key].sleep && delete p[key];
+ }
+ });
+ return p[ps];
+ };
+
+ R.findDotsAtSegment = function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
+ var t1 = 1 - t,
+ t13 = pow(t1, 3),
+ t12 = pow(t1, 2),
+ t2 = t * t,
+ t3 = t2 * t,
+ x = t13 * p1x + t12 * 3 * t * c1x + t1 * 3 * t * t * c2x + t3 * p2x,
+ y = t13 * p1y + t12 * 3 * t * c1y + t1 * 3 * t * t * c2y + t3 * p2y,
+ mx = p1x + 2 * t * (c1x - p1x) + t2 * (c2x - 2 * c1x + p1x),
+ my = p1y + 2 * t * (c1y - p1y) + t2 * (c2y - 2 * c1y + p1y),
+ nx = c1x + 2 * t * (c2x - c1x) + t2 * (p2x - 2 * c2x + c1x),
+ ny = c1y + 2 * t * (c2y - c1y) + t2 * (p2y - 2 * c2y + c1y),
+ ax = t1 * p1x + t * c1x,
+ ay = t1 * p1y + t * c1y,
+ cx = t1 * c2x + t * p2x,
+ cy = t1 * c2y + t * p2y,
+ alpha = (90 - math.atan2(mx - nx, my - ny) * 180 / PI);
+ (mx > nx || my < ny) && (alpha += 180);
+ return {
+ x: x,
+ y: y,
+ m: {x: mx, y: my},
+ n: {x: nx, y: ny},
+ start: {x: ax, y: ay},
+ end: {x: cx, y: cy},
+ alpha: alpha
+ };
+ };
+
+ R.bezierBBox = function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
+ if (!R.is(p1x, "array")) {
+ p1x = [p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y];
+ }
+ var bbox = curveDim.apply(null, p1x);
+ return {
+ x: bbox.min.x,
+ y: bbox.min.y,
+ x2: bbox.max.x,
+ y2: bbox.max.y,
+ width: bbox.max.x - bbox.min.x,
+ height: bbox.max.y - bbox.min.y
+ };
+ };
+
+ R.isPointInsideBBox = function (bbox, x, y) {
+ return x >= bbox.x && x <= bbox.x2 && y >= bbox.y && y <= bbox.y2;
+ };
+
+ R.isBBoxIntersect = function (bbox1, bbox2) {
+ var i = R.isPointInsideBBox;
+ return i(bbox2, bbox1.x, bbox1.y)
+ || i(bbox2, bbox1.x2, bbox1.y)
+ || i(bbox2, bbox1.x, bbox1.y2)
+ || i(bbox2, bbox1.x2, bbox1.y2)
+ || i(bbox1, bbox2.x, bbox2.y)
+ || i(bbox1, bbox2.x2, bbox2.y)
+ || i(bbox1, bbox2.x, bbox2.y2)
+ || i(bbox1, bbox2.x2, bbox2.y2)
+ || (bbox1.x < bbox2.x2 && bbox1.x > bbox2.x || bbox2.x < bbox1.x2 && bbox2.x > bbox1.x)
+ && (bbox1.y < bbox2.y2 && bbox1.y > bbox2.y || bbox2.y < bbox1.y2 && bbox2.y > bbox1.y);
+ };
+
+ function base3(t, p1, p2, p3, p4) {
+ var t1 = -3 * p1 + 9 * p2 - 9 * p3 + 3 * p4,
+ t2 = t * t1 + 6 * p1 - 12 * p2 + 6 * p3;
+ return t * t2 - 3 * p1 + 3 * p2;
+ }
+
+ function bezlen(x1, y1, x2, y2, x3, y3, x4, y4, z) {
+ if (z == null) {
+ z = 1;
+ }
+ z = z > 1 ? 1 : z < 0 ? 0 : z;
+ var z2 = z / 2,
+ n = 12,
+ Tvalues = [-0.1252, 0.1252, -0.3678, 0.3678, -0.5873, 0.5873, -0.7699, 0.7699, -0.9041, 0.9041, -0.9816, 0.9816],
+ Cvalues = [0.2491, 0.2491, 0.2335, 0.2335, 0.2032, 0.2032, 0.1601, 0.1601, 0.1069, 0.1069, 0.0472, 0.0472],
+ sum = 0;
+ for (var i = 0; i < n; i++) {
+ var ct = z2 * Tvalues[i] + z2,
+ xbase = base3(ct, x1, x2, x3, x4),
+ ybase = base3(ct, y1, y2, y3, y4),
+ comb = xbase * xbase + ybase * ybase;
+ sum += Cvalues[i] * math.sqrt(comb);
+ }
+ return z2 * sum;
+ }
+
+ function getTatLen(x1, y1, x2, y2, x3, y3, x4, y4, ll) {
+ if (ll < 0 || bezlen(x1, y1, x2, y2, x3, y3, x4, y4) < ll) {
+ return;
+ }
+ var t = 1,
+ step = t / 2,
+ t2 = t - step,
+ l,
+ e = .01;
+ l = bezlen(x1, y1, x2, y2, x3, y3, x4, y4, t2);
+ while (abs(l - ll) > e) {
+ step /= 2;
+ t2 += (l < ll ? 1 : -1) * step;
+ l = bezlen(x1, y1, x2, y2, x3, y3, x4, y4, t2);
+ }
+ return t2;
+ }
+
+ function intersect(x1, y1, x2, y2, x3, y3, x4, y4) {
+ if (
+ mmax(x1, x2) < mmin(x3, x4) ||
+ mmin(x1, x2) > mmax(x3, x4) ||
+ mmax(y1, y2) < mmin(y3, y4) ||
+ mmin(y1, y2) > mmax(y3, y4)
+ ) {
+ return;
+ }
+ var nx = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4),
+ ny = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4),
+ denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
+
+ if (!denominator) {
+ return;
+ }
+ var px = nx / denominator,
+ py = ny / denominator,
+ px2 = +px.toFixed(2),
+ py2 = +py.toFixed(2);
+ if (
+ px2 < +mmin(x1, x2).toFixed(2) ||
+ px2 > +mmax(x1, x2).toFixed(2) ||
+ px2 < +mmin(x3, x4).toFixed(2) ||
+ px2 > +mmax(x3, x4).toFixed(2) ||
+ py2 < +mmin(y1, y2).toFixed(2) ||
+ py2 > +mmax(y1, y2).toFixed(2) ||
+ py2 < +mmin(y3, y4).toFixed(2) ||
+ py2 > +mmax(y3, y4).toFixed(2)
+ ) {
+ return;
+ }
+ return {x: px, y: py};
+ }
+
+ function inter(bez1, bez2) {
+ return interHelper(bez1, bez2);
+ }
+
+ function interCount(bez1, bez2) {
+ return interHelper(bez1, bez2, 1);
+ }
+
+ function interHelper(bez1, bez2, justCount) {
+ var bbox1 = R.bezierBBox(bez1),
+ bbox2 = R.bezierBBox(bez2);
+ if (!R.isBBoxIntersect(bbox1, bbox2)) {
+ return justCount ? 0 : [];
+ }
+ var l1 = bezlen.apply(0, bez1),
+ l2 = bezlen.apply(0, bez2),
+ n1 = ~~(l1 / 5),
+ n2 = ~~(l2 / 5),
+ dots1 = [],
+ dots2 = [],
+ xy = {},
+ res = justCount ? 0 : [];
+ for (var i = 0; i < n1 + 1; i++) {
+ var p = R.findDotsAtSegment.apply(R, bez1.concat(i / n1));
+ dots1.push({x: p.x, y: p.y, t: i / n1});
+ }
+ for (i = 0; i < n2 + 1; i++) {
+ p = R.findDotsAtSegment.apply(R, bez2.concat(i / n2));
+ dots2.push({x: p.x, y: p.y, t: i / n2});
+ }
+ for (i = 0; i < n1; i++) {
+ for (var j = 0; j < n2; j++) {
+ var di = dots1[i],
+ di1 = dots1[i + 1],
+ dj = dots2[j],
+ dj1 = dots2[j + 1],
+ ci = abs(di1.x - di.x) < .001 ? "y" : "x",
+ cj = abs(dj1.x - dj.x) < .001 ? "y" : "x",
+ is = intersect(di.x, di.y, di1.x, di1.y, dj.x, dj.y, dj1.x, dj1.y);
+ if (is) {
+ if (xy[is.x.toFixed(4)] == is.y.toFixed(4)) {
+ continue;
+ }
+ xy[is.x.toFixed(4)] = is.y.toFixed(4);
+ var t1 = di.t + abs((is[ci] - di[ci]) / (di1[ci] - di[ci])) * (di1.t - di.t),
+ t2 = dj.t + abs((is[cj] - dj[cj]) / (dj1[cj] - dj[cj])) * (dj1.t - dj.t);
+ if (t1 >= 0 && t1 <= 1 && t2 >= 0 && t2 <= 1) {
+ if (justCount) {
+ res++;
+ } else {
+ res.push({
+ x: is.x,
+ y: is.y,
+ t1: t1,
+ t2: t2
+ });
+ }
+ }
+ }
+ }
+ }
+ return res;
+ }
+
+ R.pathIntersection = function (path1, path2) {
+ return interPathHelper(path1, path2);
+ };
+ R.pathIntersectionNumber = function (path1, path2) {
+ return interPathHelper(path1, path2, 1);
+ };
+
+ function interPathHelper(path1, path2, justCount) {
+ path1 = R._path2curve(path1);
+ path2 = R._path2curve(path2);
+ var x1, y1, x2, y2, x1m, y1m, x2m, y2m, bez1, bez2,
+ res = justCount ? 0 : [];
+ for (var i = 0, ii = path1.length; i < ii; i++) {
+ var pi = path1[i];
+ if (pi[0] == "M") {
+ x1 = x1m = pi[1];
+ y1 = y1m = pi[2];
+ } else {
+ if (pi[0] == "C") {
+ bez1 = [x1, y1].concat(pi.slice(1));
+ x1 = bez1[6];
+ y1 = bez1[7];
+ } else {
+ bez1 = [x1, y1, x1, y1, x1m, y1m, x1m, y1m];
+ x1 = x1m;
+ y1 = y1m;
+ }
+ for (var j = 0, jj = path2.length; j < jj; j++) {
+ var pj = path2[j];
+ if (pj[0] == "M") {
+ x2 = x2m = pj[1];
+ y2 = y2m = pj[2];
+ } else {
+ if (pj[0] == "C") {
+ bez2 = [x2, y2].concat(pj.slice(1));
+ x2 = bez2[6];
+ y2 = bez2[7];
+ } else {
+ bez2 = [x2, y2, x2, y2, x2m, y2m, x2m, y2m];
+ x2 = x2m;
+ y2 = y2m;
+ }
+ var intr = interHelper(bez1, bez2, justCount);
+ if (justCount) {
+ res += intr;
+ } else {
+ for (var k = 0, kk = intr.length; k < kk; k++) {
+ intr[k].segment1 = i;
+ intr[k].segment2 = j;
+ intr[k].bez1 = bez1;
+ intr[k].bez2 = bez2;
+ }
+ res = res.concat(intr);
+ }
+ }
+ }
+ }
+ }
+ return res;
+ }
+
+ R.isPointInsidePath = function (path, x, y) {
+ var bbox = R.pathBBox(path);
+ return R.isPointInsideBBox(bbox, x, y) &&
+ interPathHelper(path, [["M", x, y], ["H", bbox.x2 + 10]], 1) % 2 == 1;
+ };
+ R._removedFactory = function (methodname) {
+ return function () {
+ eve("raphael.log", null, "Rapha\xebl: you are calling to method \u201c" + methodname + "\u201d of removed object", methodname);
+ };
+ };
+
+ var pathDimensions = R.pathBBox = function (path) {
+ var pth = paths(path);
+ if (pth.bbox) {
+ return pth.bbox;
+ }
+ if (!path) {
+ return {x: 0, y: 0, width: 0, height: 0, x2: 0, y2: 0};
+ }
+ path = path2curve(path);
+ var x = 0,
+ y = 0,
+ X = [],
+ Y = [],
+ p;
+ for (var i = 0, ii = path.length; i < ii; i++) {
+ p = path[i];
+ if (p[0] == "M") {
+ x = p[1];
+ y = p[2];
+ X.push(x);
+ Y.push(y);
+ } else {
+ var dim = curveDim(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
+ X = X[concat](dim.min.x, dim.max.x);
+ Y = Y[concat](dim.min.y, dim.max.y);
+ x = p[5];
+ y = p[6];
+ }
+ }
+ var xmin = mmin[apply](0, X),
+ ymin = mmin[apply](0, Y),
+ xmax = mmax[apply](0, X),
+ ymax = mmax[apply](0, Y),
+ bb = {
+ x: xmin,
+ y: ymin,
+ x2: xmax,
+ y2: ymax,
+ width: xmax - xmin,
+ height: ymax - ymin
+ };
+ pth.bbox = clone(bb);
+ return bb;
+ },
+ pathClone = function (pathArray) {
+ var res = clone(pathArray);
+ res.toString = R._path2string;
+ return res;
+ },
+ pathToRelative = R._pathToRelative = function (pathArray) {
+ var pth = paths(pathArray);
+ if (pth.rel) {
+ return pathClone(pth.rel);
+ }
+ if (!R.is(pathArray, array) || !R.is(pathArray && pathArray[0], array)) { // rough assumption
+ pathArray = R.parsePathString(pathArray);
+ }
+ var res = [],
+ x = 0,
+ y = 0,
+ mx = 0,
+ my = 0,
+ start = 0;
+ if (pathArray[0][0] == "M") {
+ x = pathArray[0][1];
+ y = pathArray[0][2];
+ mx = x;
+ my = y;
+ start++;
+ res.push(["M", x, y]);
+ }
+ for (var i = start, ii = pathArray.length; i < ii; i++) {
+ var r = res[i] = [],
+ pa = pathArray[i];
+ if (pa[0] != lowerCase.call(pa[0])) {
+ r[0] = lowerCase.call(pa[0]);
+ switch (r[0]) {
+ case "a":
+ r[1] = pa[1];
+ r[2] = pa[2];
+ r[3] = pa[3];
+ r[4] = pa[4];
+ r[5] = pa[5];
+ r[6] = +(pa[6] - x).toFixed(3);
+ r[7] = +(pa[7] - y).toFixed(3);
+ break;
+ case "v":
+ r[1] = +(pa[1] - y).toFixed(3);
+ break;
+ case "m":
+ mx = pa[1];
+ my = pa[2];
+ default:
+ for (var j = 1, jj = pa.length; j < jj; j++) {
+ r[j] = +(pa[j] - ((j % 2) ? x : y)).toFixed(3);
+ }
+ }
+ } else {
+ r = res[i] = [];
+ if (pa[0] == "m") {
+ mx = pa[1] + x;
+ my = pa[2] + y;
+ }
+ for (var k = 0, kk = pa.length; k < kk; k++) {
+ res[i][k] = pa[k];
+ }
+ }
+ var len = res[i].length;
+ switch (res[i][0]) {
+ case "z":
+ x = mx;
+ y = my;
+ break;
+ case "h":
+ x += +res[i][len - 1];
+ break;
+ case "v":
+ y += +res[i][len - 1];
+ break;
+ default:
+ x += +res[i][len - 2];
+ y += +res[i][len - 1];
+ }
+ }
+ res.toString = R._path2string;
+ pth.rel = pathClone(res);
+ return res;
+ },
+ pathToAbsolute = R._pathToAbsolute = function (pathArray) {
+ var pth = paths(pathArray);
+ if (pth.abs) {
+ return pathClone(pth.abs);
+ }
+ if (!R.is(pathArray, array) || !R.is(pathArray && pathArray[0], array)) { // rough assumption
+ pathArray = R.parsePathString(pathArray);
+ }
+ if (!pathArray || !pathArray.length) {
+ return [["M", 0, 0]];
+ }
+ var res = [],
+ x = 0,
+ y = 0,
+ mx = 0,
+ my = 0,
+ start = 0;
+ if (pathArray[0][0] == "M") {
+ x = +pathArray[0][1];
+ y = +pathArray[0][2];
+ mx = x;
+ my = y;
+ start++;
+ res[0] = ["M", x, y];
+ }
+ var crz = pathArray.length == 3 && pathArray[0][0] == "M" && pathArray[1][0].toUpperCase() == "R" && pathArray[2][0].toUpperCase() == "Z";
+ for (var r, pa, i = start, ii = pathArray.length; i < ii; i++) {
+ res.push(r = []);
+ pa = pathArray[i];
+ if (pa[0] != upperCase.call(pa[0])) {
+ r[0] = upperCase.call(pa[0]);
+ switch (r[0]) {
+ case "A":
+ r[1] = pa[1];
+ r[2] = pa[2];
+ r[3] = pa[3];
+ r[4] = pa[4];
+ r[5] = pa[5];
+ r[6] = +(pa[6] + x);
+ r[7] = +(pa[7] + y);
+ break;
+ case "V":
+ r[1] = +pa[1] + y;
+ break;
+ case "H":
+ r[1] = +pa[1] + x;
+ break;
+ case "R":
+ var dots = [x, y][concat](pa.slice(1));
+ for (var j = 2, jj = dots.length; j < jj; j++) {
+ dots[j] = +dots[j] + x;
+ dots[++j] = +dots[j] + y;
+ }
+ res.pop();
+ res = res[concat](catmullRom2bezier(dots, crz));
+ break;
+ case "M":
+ mx = +pa[1] + x;
+ my = +pa[2] + y;
+ default:
+ for (j = 1, jj = pa.length; j < jj; j++) {
+ r[j] = +pa[j] + ((j % 2) ? x : y);
+ }
+ }
+ } else if (pa[0] == "R") {
+ dots = [x, y][concat](pa.slice(1));
+ res.pop();
+ res = res[concat](catmullRom2bezier(dots, crz));
+ r = ["R"][concat](pa.slice(-2));
+ } else {
+ for (var k = 0, kk = pa.length; k < kk; k++) {
+ r[k] = pa[k];
+ }
+ }
+ switch (r[0]) {
+ case "Z":
+ x = mx;
+ y = my;
+ break;
+ case "H":
+ x = r[1];
+ break;
+ case "V":
+ y = r[1];
+ break;
+ case "M":
+ mx = r[r.length - 2];
+ my = r[r.length - 1];
+ default:
+ x = r[r.length - 2];
+ y = r[r.length - 1];
+ }
+ }
+ res.toString = R._path2string;
+ pth.abs = pathClone(res);
+ return res;
+ },
+ l2c = function (x1, y1, x2, y2) {
+ return [x1, y1, x2, y2, x2, y2];
+ },
+ q2c = function (x1, y1, ax, ay, x2, y2) {
+ var _13 = 1 / 3,
+ _23 = 2 / 3;
+ return [
+ _13 * x1 + _23 * ax,
+ _13 * y1 + _23 * ay,
+ _13 * x2 + _23 * ax,
+ _13 * y2 + _23 * ay,
+ x2,
+ y2
+ ];
+ },
+ a2c = function (x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
+ // for more information of where this math came from visit:
+ // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
+ var _120 = PI * 120 / 180,
+ rad = PI / 180 * (+angle || 0),
+ res = [],
+ xy,
+ rotate = cacher(function (x, y, rad) {
+ var X = x * math.cos(rad) - y * math.sin(rad),
+ Y = x * math.sin(rad) + y * math.cos(rad);
+ return {x: X, y: Y};
+ });
+ if (!recursive) {
+ xy = rotate(x1, y1, -rad);
+ x1 = xy.x;
+ y1 = xy.y;
+ xy = rotate(x2, y2, -rad);
+ x2 = xy.x;
+ y2 = xy.y;
+ var cos = math.cos(PI / 180 * angle),
+ sin = math.sin(PI / 180 * angle),
+ x = (x1 - x2) / 2,
+ y = (y1 - y2) / 2;
+ var h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
+ if (h > 1) {
+ h = math.sqrt(h);
+ rx = h * rx;
+ ry = h * ry;
+ }
+ var rx2 = rx * rx,
+ ry2 = ry * ry,
+ k = (large_arc_flag == sweep_flag ? -1 : 1) *
+ math.sqrt(abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x))),
+ cx = k * rx * y / ry + (x1 + x2) / 2,
+ cy = k * -ry * x / rx + (y1 + y2) / 2,
+ f1 = math.asin(((y1 - cy) / ry).toFixed(9)),
+ f2 = math.asin(((y2 - cy) / ry).toFixed(9));
+
+ f1 = x1 < cx ? PI - f1 : f1;
+ f2 = x2 < cx ? PI - f2 : f2;
+ f1 < 0 && (f1 = PI * 2 + f1);
+ f2 < 0 && (f2 = PI * 2 + f2);
+ if (sweep_flag && f1 > f2) {
+ f1 = f1 - PI * 2;
+ }
+ if (!sweep_flag && f2 > f1) {
+ f2 = f2 - PI * 2;
+ }
+ } else {
+ f1 = recursive[0];
+ f2 = recursive[1];
+ cx = recursive[2];
+ cy = recursive[3];
+ }
+ var df = f2 - f1;
+ if (abs(df) > _120) {
+ var f2old = f2,
+ x2old = x2,
+ y2old = y2;
+ f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
+ x2 = cx + rx * math.cos(f2);
+ y2 = cy + ry * math.sin(f2);
+ res = a2c(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
+ }
+ df = f2 - f1;
+ var c1 = math.cos(f1),
+ s1 = math.sin(f1),
+ c2 = math.cos(f2),
+ s2 = math.sin(f2),
+ t = math.tan(df / 4),
+ hx = 4 / 3 * rx * t,
+ hy = 4 / 3 * ry * t,
+ m1 = [x1, y1],
+ m2 = [x1 + hx * s1, y1 - hy * c1],
+ m3 = [x2 + hx * s2, y2 - hy * c2],
+ m4 = [x2, y2];
+ m2[0] = 2 * m1[0] - m2[0];
+ m2[1] = 2 * m1[1] - m2[1];
+ if (recursive) {
+ return [m2, m3, m4][concat](res);
+ } else {
+ res = [m2, m3, m4][concat](res).join()[split](",");
+ var newres = [];
+ for (var i = 0, ii = res.length; i < ii; i++) {
+ newres[i] = i % 2 ? rotate(res[i - 1], res[i], rad).y : rotate(res[i], res[i + 1], rad).x;
+ }
+ return newres;
+ }
+ },
+ findDotAtSegment = function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
+ var t1 = 1 - t;
+ return {
+ x: pow(t1, 3) * p1x + pow(t1, 2) * 3 * t * c1x + t1 * 3 * t * t * c2x + pow(t, 3) * p2x,
+ y: pow(t1, 3) * p1y + pow(t1, 2) * 3 * t * c1y + t1 * 3 * t * t * c2y + pow(t, 3) * p2y
+ };
+ },
+ curveDim = cacher(function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
+ var a = (c2x - 2 * c1x + p1x) - (p2x - 2 * c2x + c1x),
+ b = 2 * (c1x - p1x) - 2 * (c2x - c1x),
+ c = p1x - c1x,
+ t1 = (-b + math.sqrt(b * b - 4 * a * c)) / 2 / a,
+ t2 = (-b - math.sqrt(b * b - 4 * a * c)) / 2 / a,
+ y = [p1y, p2y],
+ x = [p1x, p2x],
+ dot;
+ abs(t1) > "1e12" && (t1 = .5);
+ abs(t2) > "1e12" && (t2 = .5);
+ if (t1 > 0 && t1 < 1) {
+ dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
+ x.push(dot.x);
+ y.push(dot.y);
+ }
+ if (t2 > 0 && t2 < 1) {
+ dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
+ x.push(dot.x);
+ y.push(dot.y);
+ }
+ a = (c2y - 2 * c1y + p1y) - (p2y - 2 * c2y + c1y);
+ b = 2 * (c1y - p1y) - 2 * (c2y - c1y);
+ c = p1y - c1y;
+ t1 = (-b + math.sqrt(b * b - 4 * a * c)) / 2 / a;
+ t2 = (-b - math.sqrt(b * b - 4 * a * c)) / 2 / a;
+ abs(t1) > "1e12" && (t1 = .5);
+ abs(t2) > "1e12" && (t2 = .5);
+ if (t1 > 0 && t1 < 1) {
+ dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
+ x.push(dot.x);
+ y.push(dot.y);
+ }
+ if (t2 > 0 && t2 < 1) {
+ dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
+ x.push(dot.x);
+ y.push(dot.y);
+ }
+ return {
+ min: {x: mmin[apply](0, x), y: mmin[apply](0, y)},
+ max: {x: mmax[apply](0, x), y: mmax[apply](0, y)}
+ };
+ }),
+ path2curve = R._path2curve = cacher(function (path, path2) {
+ var pth = !path2 && paths(path);
+ if (!path2 && pth.curve) {
+ return pathClone(pth.curve);
+ }
+ var p = pathToAbsolute(path),
+ p2 = path2 && pathToAbsolute(path2),
+ attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
+ attrs2 = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
+ processPath = function (path, d) {
+ var nx, ny;
+ if (!path) {
+ return ["C", d.x, d.y, d.x, d.y, d.x, d.y];
+ }
+ !(path[0] in {T: 1, Q: 1}) && (d.qx = d.qy = null);
+ switch (path[0]) {
+ case "M":
+ d.X = path[1];
+ d.Y = path[2];
+ break;
+ case "A":
+ path = ["C"][concat](a2c[apply](0, [d.x, d.y][concat](path.slice(1))));
+ break;
+ case "S":
+ nx = d.x + (d.x - (d.bx || d.x));
+ ny = d.y + (d.y - (d.by || d.y));
+ path = ["C", nx, ny][concat](path.slice(1));
+ break;
+ case "T":
+ d.qx = d.x + (d.x - (d.qx || d.x));
+ d.qy = d.y + (d.y - (d.qy || d.y));
+ path = ["C"][concat](q2c(d.x, d.y, d.qx, d.qy, path[1], path[2]));
+ break;
+ case "Q":
+ d.qx = path[1];
+ d.qy = path[2];
+ path = ["C"][concat](q2c(d.x, d.y, path[1], path[2], path[3], path[4]));
+ break;
+ case "L":
+ path = ["C"][concat](l2c(d.x, d.y, path[1], path[2]));
+ break;
+ case "H":
+ path = ["C"][concat](l2c(d.x, d.y, path[1], d.y));
+ break;
+ case "V":
+ path = ["C"][concat](l2c(d.x, d.y, d.x, path[1]));
+ break;
+ case "Z":
+ path = ["C"][concat](l2c(d.x, d.y, d.X, d.Y));
+ break;
+ }
+ return path;
+ },
+ fixArc = function (pp, i) {
+ if (pp[i].length > 7) {
+ pp[i].shift();
+ var pi = pp[i];
+ while (pi.length) {
+ pp.splice(i++, 0, ["C"][concat](pi.splice(0, 6)));
+ }
+ pp.splice(i, 1);
+ ii = mmax(p.length, p2 && p2.length || 0);
+ }
+ },
+ fixM = function (path1, path2, a1, a2, i) {
+ if (path1 && path2 && path1[i][0] == "M" && path2[i][0] != "M") {
+ path2.splice(i, 0, ["M", a2.x, a2.y]);
+ a1.bx = 0;
+ a1.by = 0;
+ a1.x = path1[i][1];
+ a1.y = path1[i][2];
+ ii = mmax(p.length, p2 && p2.length || 0);
+ }
+ };
+ for (var i = 0, ii = mmax(p.length, p2 && p2.length || 0); i < ii; i++) {
+ p[i] = processPath(p[i], attrs);
+ fixArc(p, i);
+ p2 && (p2[i] = processPath(p2[i], attrs2));
+ p2 && fixArc(p2, i);
+ fixM(p, p2, attrs, attrs2, i);
+ fixM(p2, p, attrs2, attrs, i);
+ var seg = p[i],
+ seg2 = p2 && p2[i],
+ seglen = seg.length,
+ seg2len = p2 && seg2.length;
+ attrs.x = seg[seglen - 2];
+ attrs.y = seg[seglen - 1];
+ attrs.bx = toFloat(seg[seglen - 4]) || attrs.x;
+ attrs.by = toFloat(seg[seglen - 3]) || attrs.y;
+ attrs2.bx = p2 && (toFloat(seg2[seg2len - 4]) || attrs2.x);
+ attrs2.by = p2 && (toFloat(seg2[seg2len - 3]) || attrs2.y);
+ attrs2.x = p2 && seg2[seg2len - 2];
+ attrs2.y = p2 && seg2[seg2len - 1];
+ }
+ if (!p2) {
+ pth.curve = pathClone(p);
+ }
+ return p2 ? [p, p2] : p;
+ }, null, pathClone),
+ parseDots = R._parseDots = cacher(function (gradient) {
+ var dots = [];
+ for (var i = 0, ii = gradient.length; i < ii; i++) {
+ var dot = {},
+ par = gradient[i].match(/^([^:]*):?([\d\.]*)/);
+ dot.color = R.getRGB(par[1]);
+ if (dot.color.error) {
+ return null;
+ }
+ dot.color = dot.color.hex;
+ par[2] && (dot.offset = par[2] + "%");
+ dots.push(dot);
+ }
+ for (i = 1, ii = dots.length - 1; i < ii; i++) {
+ if (!dots[i].offset) {
+ var start = toFloat(dots[i - 1].offset || 0),
+ end = 0;
+ for (var j = i + 1; j < ii; j++) {
+ if (dots[j].offset) {
+ end = dots[j].offset;
+ break;
+ }
+ }
+ if (!end) {
+ end = 100;
+ j = ii;
+ }
+ end = toFloat(end);
+ var d = (end - start) / (j - i + 1);
+ for (; i < j; i++) {
+ start += d;
+ dots[i].offset = start + "%";
+ }
+ }
+ }
+ return dots;
+ }),
+ tear = R._tear = function (el, paper) {
+ el == paper.top && (paper.top = el.prev);
+ el == paper.bottom && (paper.bottom = el.next);
+ el.next && (el.next.prev = el.prev);
+ el.prev && (el.prev.next = el.next);
+ },
+ tofront = R._tofront = function (el, paper) {
+ if (paper.top === el) {
+ return;
+ }
+ tear(el, paper);
+ el.next = null;
+ el.prev = paper.top;
+ paper.top.next = el;
+ paper.top = el;
+ },
+ toback = R._toback = function (el, paper) {
+ if (paper.bottom === el) {
+ return;
+ }
+ tear(el, paper);
+ el.next = paper.bottom;
+ el.prev = null;
+ paper.bottom.prev = el;
+ paper.bottom = el;
+ },
+ insertafter = R._insertafter = function (el, el2, paper) {
+ tear(el, paper);
+ el2 == paper.top && (paper.top = el);
+ el2.next && (el2.next.prev = el);
+ el.next = el2.next;
+ el.prev = el2;
+ el2.next = el;
+ },
+ insertbefore = R._insertbefore = function (el, el2, paper) {
+ tear(el, paper);
+ el2 == paper.bottom && (paper.bottom = el);
+ el2.prev && (el2.prev.next = el);
+ el.prev = el2.prev;
+ el2.prev = el;
+ el.next = el2;
+ },
+
+ toMatrix = R.toMatrix = function (path, transform) {
+ var bb = pathDimensions(path),
+ el = {
+ _: {
+ transform: E
+ },
+ getBBox: function () {
+ return bb;
+ }
+ };
+ extractTransform(el, transform);
+ return el.matrix;
+ },
+
+ transformPath = R.transformPath = function (path, transform) {
+ return mapPath(path, toMatrix(path, transform));
+ },
+ extractTransform = R._extractTransform = function (el, tstr) {
+ if (tstr == null) {
+ return el._.transform;
+ }
+ tstr = Str(tstr).replace(/\.{3}|\u2026/g, el._.transform || E);
+ var tdata = R.parseTransformString(tstr),
+ deg = 0,
+ dx = 0,
+ dy = 0,
+ sx = 1,
+ sy = 1,
+ _ = el._,
+ m = new Matrix;
+ _.transform = tdata || [];
+ if (tdata) {
+ for (var i = 0, ii = tdata.length; i < ii; i++) {
+ var t = tdata[i],
+ tlen = t.length,
+ command = Str(t[0]).toLowerCase(),
+ absolute = t[0] != command,
+ inver = absolute ? m.invert() : 0,
+ x1,
+ y1,
+ x2,
+ y2,
+ bb;
+ if (command == "t" && tlen == 3) {
+ if (absolute) {
+ x1 = inver.x(0, 0);
+ y1 = inver.y(0, 0);
+ x2 = inver.x(t[1], t[2]);
+ y2 = inver.y(t[1], t[2]);
+ m.translate(x2 - x1, y2 - y1);
+ } else {
+ m.translate(t[1], t[2]);
+ }
+ } else if (command == "r") {
+ if (tlen == 2) {
+ bb = bb || el.getBBox(1);
+ m.rotate(t[1], bb.x + bb.width / 2, bb.y + bb.height / 2);
+ deg += t[1];
+ } else if (tlen == 4) {
+ if (absolute) {
+ x2 = inver.x(t[2], t[3]);
+ y2 = inver.y(t[2], t[3]);
+ m.rotate(t[1], x2, y2);
+ } else {
+ m.rotate(t[1], t[2], t[3]);
+ }
+ deg += t[1];
+ }
+ } else if (command == "s") {
+ if (tlen == 2 || tlen == 3) {
+ bb = bb || el.getBBox(1);
+ m.scale(t[1], t[tlen - 1], bb.x + bb.width / 2, bb.y + bb.height / 2);
+ sx *= t[1];
+ sy *= t[tlen - 1];
+ } else if (tlen == 5) {
+ if (absolute) {
+ x2 = inver.x(t[3], t[4]);
+ y2 = inver.y(t[3], t[4]);
+ m.scale(t[1], t[2], x2, y2);
+ } else {
+ m.scale(t[1], t[2], t[3], t[4]);
+ }
+ sx *= t[1];
+ sy *= t[2];
+ }
+ } else if (command == "m" && tlen == 7) {
+ m.add(t[1], t[2], t[3], t[4], t[5], t[6]);
+ }
+ _.dirtyT = 1;
+ el.matrix = m;
+ }
+ }
+
+
+ el.matrix = m;
+
+ _.sx = sx;
+ _.sy = sy;
+ _.deg = deg;
+ _.dx = dx = m.e;
+ _.dy = dy = m.f;
+
+ if (sx == 1 && sy == 1 && !deg && _.bbox) {
+ _.bbox.x += +dx;
+ _.bbox.y += +dy;
+ } else {
+ _.dirtyT = 1;
+ }
+ },
+ getEmpty = function (item) {
+ var l = item[0];
+ switch (l.toLowerCase()) {
+ case "t":
+ return [l, 0, 0];
+ case "m":
+ return [l, 1, 0, 0, 1, 0, 0];
+ case "r":
+ if (item.length == 4) {
+ return [l, 0, item[2], item[3]];
+ } else {
+ return [l, 0];
+ }
+ case "s":
+ if (item.length == 5) {
+ return [l, 1, 1, item[3], item[4]];
+ } else if (item.length == 3) {
+ return [l, 1, 1];
+ } else {
+ return [l, 1];
+ }
+ }
+ },
+ equaliseTransform = R._equaliseTransform = function (t1, t2) {
+ t2 = Str(t2).replace(/\.{3}|\u2026/g, t1);
+ t1 = R.parseTransformString(t1) || [];
+ t2 = R.parseTransformString(t2) || [];
+ var maxlength = mmax(t1.length, t2.length),
+ from = [],
+ to = [],
+ i = 0, j, jj,
+ tt1, tt2;
+ for (; i < maxlength; i++) {
+ tt1 = t1[i] || getEmpty(t2[i]);
+ tt2 = t2[i] || getEmpty(tt1);
+ if ((tt1[0] != tt2[0]) ||
+ (tt1[0].toLowerCase() == "r" && (tt1[2] != tt2[2] || tt1[3] != tt2[3])) ||
+ (tt1[0].toLowerCase() == "s" && (tt1[3] != tt2[3] || tt1[4] != tt2[4]))
+ ) {
+ return;
+ }
+ from[i] = [];
+ to[i] = [];
+ for (j = 0, jj = mmax(tt1.length, tt2.length); j < jj; j++) {
+ j in tt1 && (from[i][j] = tt1[j]);
+ j in tt2 && (to[i][j] = tt2[j]);
+ }
+ }
+ return {
+ from: from,
+ to: to
+ };
+ };
+ R._getContainer = function (x, y, w, h) {
+ var container;
+ container = h == null && !R.is(x, "object") ? g.doc.getElementById(x) : x;
+ if (container == null) {
+ return;
+ }
+ if (container.tagName) {
+ if (y == null) {
+ return {
+ container: container,
+ width: container.style.pixelWidth || container.offsetWidth,
+ height: container.style.pixelHeight || container.offsetHeight
+ };
+ } else {
+ return {
+ container: container,
+ width: y,
+ height: w
+ };
+ }
+ }
+ return {
+ container: 1,
+ x: x,
+ y: y,
+ width: w,
+ height: h
+ };
+ };
+
+ R.pathToRelative = pathToRelative;
+ R._engine = {};
+
+ R.path2curve = path2curve;
+
+ R.matrix = function (a, b, c, d, e, f) {
+ return new Matrix(a, b, c, d, e, f);
+ };
+
+ function Matrix(a, b, c, d, e, f) {
+ if (a != null) {
+ this.a = +a;
+ this.b = +b;
+ this.c = +c;
+ this.d = +d;
+ this.e = +e;
+ this.f = +f;
+ } else {
+ this.a = 1;
+ this.b = 0;
+ this.c = 0;
+ this.d = 1;
+ this.e = 0;
+ this.f = 0;
+ }
+ }
+
+ (function (matrixproto) {
+
+ matrixproto.add = function (a, b, c, d, e, f) {
+ var out = [[], [], []],
+ m = [[this.a, this.c, this.e], [this.b, this.d, this.f], [0, 0, 1]],
+ matrix = [[a, c, e], [b, d, f], [0, 0, 1]],
+ x, y, z, res;
+
+ if (a && a instanceof Matrix) {
+ matrix = [[a.a, a.c, a.e], [a.b, a.d, a.f], [0, 0, 1]];
+ }
+
+ for (x = 0; x < 3; x++) {
+ for (y = 0; y < 3; y++) {
+ res = 0;
+ for (z = 0; z < 3; z++) {
+ res += m[x][z] * matrix[z][y];
+ }
+ out[x][y] = res;
+ }
+ }
+ this.a = out[0][0];
+ this.b = out[1][0];
+ this.c = out[0][1];
+ this.d = out[1][1];
+ this.e = out[0][2];
+ this.f = out[1][2];
+ };
+
+ matrixproto.invert = function () {
+ var me = this,
+ x = me.a * me.d - me.b * me.c;
+ return new Matrix(me.d / x, -me.b / x, -me.c / x, me.a / x, (me.c * me.f - me.d * me.e) / x, (me.b * me.e - me.a * me.f) / x);
+ };
+
+ matrixproto.clone = function () {
+ return new Matrix(this.a, this.b, this.c, this.d, this.e, this.f);
+ };
+
+ matrixproto.translate = function (x, y) {
+ this.add(1, 0, 0, 1, x, y);
+ };
+
+ matrixproto.scale = function (x, y, cx, cy) {
+ y == null && (y = x);
+ (cx || cy) && this.add(1, 0, 0, 1, cx, cy);
+ this.add(x, 0, 0, y, 0, 0);
+ (cx || cy) && this.add(1, 0, 0, 1, -cx, -cy);
+ };
+
+ matrixproto.rotate = function (a, x, y) {
+ a = R.rad(a);
+ x = x || 0;
+ y = y || 0;
+ var cos = +math.cos(a).toFixed(9),
+ sin = +math.sin(a).toFixed(9);
+ this.add(cos, sin, -sin, cos, x, y);
+ this.add(1, 0, 0, 1, -x, -y);
+ };
+
+ matrixproto.x = function (x, y) {
+ return x * this.a + y * this.c + this.e;
+ };
+
+ matrixproto.y = function (x, y) {
+ return x * this.b + y * this.d + this.f;
+ };
+ matrixproto.get = function (i) {
+ return +this[Str.fromCharCode(97 + i)].toFixed(4);
+ };
+ matrixproto.toString = function () {
+ return R.svg ?
+ "matrix(" + [this.get(0), this.get(1), this.get(2), this.get(3), this.get(4), this.get(5)].join() + ")" :
+ [this.get(0), this.get(2), this.get(1), this.get(3), 0, 0].join();
+ };
+ matrixproto.toFilter = function () {
+ return "progid:DXImageTransform.Microsoft.Matrix(M11=" + this.get(0) +
+ ", M12=" + this.get(2) + ", M21=" + this.get(1) + ", M22=" + this.get(3) +
+ ", Dx=" + this.get(4) + ", Dy=" + this.get(5) + ", sizingmethod='auto expand')";
+ };
+ matrixproto.offset = function () {
+ return [this.e.toFixed(4), this.f.toFixed(4)];
+ };
+
+ function norm(a) {
+ return a[0] * a[0] + a[1] * a[1];
+ }
+
+ function normalize(a) {
+ var mag = math.sqrt(norm(a));
+ a[0] && (a[0] /= mag);
+ a[1] && (a[1] /= mag);
+ }
+
+ matrixproto.split = function () {
+ var out = {};
+ // translation
+ out.dx = this.e;
+ out.dy = this.f;
+
+ // scale and shear
+ var row = [[this.a, this.c], [this.b, this.d]];
+ out.scalex = math.sqrt(norm(row[0]));
+ normalize(row[0]);
+
+ out.shear = row[0][0] * row[1][0] + row[0][1] * row[1][1];
+ row[1] = [row[1][0] - row[0][0] * out.shear, row[1][1] - row[0][1] * out.shear];
+
+ out.scaley = math.sqrt(norm(row[1]));
+ normalize(row[1]);
+ out.shear /= out.scaley;
+
+ // rotation
+ var sin = -row[0][1],
+ cos = row[1][1];
+ if (cos < 0) {
+ out.rotate = R.deg(math.acos(cos));
+ if (sin < 0) {
+ out.rotate = 360 - out.rotate;
+ }
+ } else {
+ out.rotate = R.deg(math.asin(sin));
+ }
+
+ out.isSimple = !+out.shear.toFixed(9) && (out.scalex.toFixed(9) == out.scaley.toFixed(9) || !out.rotate);
+ out.isSuperSimple = !+out.shear.toFixed(9) && out.scalex.toFixed(9) == out.scaley.toFixed(9) && !out.rotate;
+ out.noRotation = !+out.shear.toFixed(9) && !out.rotate;
+ return out;
+ };
+
+ matrixproto.toTransformString = function (shorter) {
+ var s = shorter || this[split]();
+ if (s.isSimple) {
+ s.scalex = +s.scalex.toFixed(4);
+ s.scaley = +s.scaley.toFixed(4);
+ s.rotate = +s.rotate.toFixed(4);
+ return (s.dx || s.dy ? "t" + [s.dx, s.dy] : E) +
+ (s.scalex != 1 || s.scaley != 1 ? "s" + [s.scalex, s.scaley, 0, 0] : E) +
+ (s.rotate ? "r" + [s.rotate, 0, 0] : E);
+ } else {
+ return "m" + [this.get(0), this.get(1), this.get(2), this.get(3), this.get(4), this.get(5)];
+ }
+ };
+ })(Matrix.prototype);
+
+ // WebKit rendering bug workaround method
+ var version = navigator.userAgent.match(/Version\/(.*?)\s/) || navigator.userAgent.match(/Chrome\/(\d+)/);
+ if ((navigator.vendor == "Apple Computer, Inc.") && (version && version[1] < 4 || navigator.platform.slice(0, 2) == "iP") ||
+ (navigator.vendor == "Google Inc." && version && version[1] < 8)) {
+
+ paperproto.safari = function () {
+ var rect = this.rect(-99, -99, this.width + 99, this.height + 99).attr({stroke: "none"});
+ setTimeout(function () {
+ rect.remove();
+ });
+ };
+ } else {
+ paperproto.safari = fun;
+ }
+
+ var preventDefault = function () {
+ this.returnValue = false;
+ },
+ preventTouch = function () {
+ return this.originalEvent.preventDefault();
+ },
+ stopPropagation = function () {
+ this.cancelBubble = true;
+ },
+ stopTouch = function () {
+ return this.originalEvent.stopPropagation();
+ },
+ addEvent = (function () {
+ if (g.doc.addEventListener) {
+ return function (obj, type, fn, element) {
+ var realName = supportsTouch && touchMap[type] ? touchMap[type] : type,
+ f = function (e) {
+
+
+ var scrollY = g.doc.documentElement.scrollTop || g.doc.body.scrollTop,
+ scrollX = g.doc.documentElement.scrollLeft || g.doc.body.scrollLeft,
+ x = e.clientX + scrollX,
+ y = e.clientY + scrollY;
+ if (supportsTouch && touchMap[has](type)) {
+ for (var i = 0, ii = e.targetTouches && e.targetTouches.length; i < ii; i++) {
+ if (e.targetTouches[i].target == obj) {
+ var olde = e;
+ e = e.targetTouches[i];
+ e.originalEvent = olde;
+ e.preventDefault = preventTouch;
+ e.stopPropagation = stopTouch;
+ break;
+ }
+
+
+ }
+ }
+ return fn.call(element, e, x, y);
+ };
+ obj.addEventListener(realName, f, false);
+ return function () {
+ obj.removeEventListener(realName, f, false);
+ return true;
+ };
+ };
+ } else if (g.doc.attachEvent) {
+ return function (obj, type, fn, element) {
+ var f = function (e) {
+ e = e || g.win.event;
+ var scrollY = g.doc.documentElement.scrollTop || g.doc.body.scrollTop,
+ scrollX = g.doc.documentElement.scrollLeft || g.doc.body.scrollLeft,
+ x = e.clientX + scrollX,
+ y = e.clientY + scrollY;
+ e.preventDefault = e.preventDefault || preventDefault;
+ e.stopPropagation = e.stopPropagation || stopPropagation;
+ return fn.call(element, e, x, y);
+ };
+ obj.attachEvent("on" + type, f);
+ var detacher = function () {
+ obj.detachEvent("on" + type, f);
+ return true;
+ };
+ return detacher;
+ };
+ }
+ })(),
+ drag = [],
+ dragMove = function (e) {
+ var x = e.clientX,
+ y = e.clientY,
+ scrollY = g.doc.documentElement.scrollTop || g.doc.body.scrollTop,
+ scrollX = g.doc.documentElement.scrollLeft || g.doc.body.scrollLeft,
+ dragi,
+ j = drag.length;
+ while (j--) {
+ dragi = drag[j];
+ if (supportsTouch) {
+ var i = e.touches.length,
+ touch;
+ while (i--) {
+ touch = e.touches[i];
+ if (touch.identifier == dragi.el._drag.id) {
+ x = touch.clientX;
+ y = touch.clientY;
+ (e.originalEvent ? e.originalEvent : e).preventDefault();
+ break;
+ }
+ }
+ } else {
+ e.preventDefault();
+ }
+ var node = dragi.el.node,
+ o,
+ next = node.nextSibling,
+ parent = node.parentNode,
+ display = node.style.display;
+ g.win.opera && parent.removeChild(node);
+ node.style.display = "none";
+ o = dragi.el.paper.getElementByPoint(x, y);
+ node.style.display = display;
+ g.win.opera && (next ? parent.insertBefore(node, next) : parent.appendChild(node));
+ o && eve("raphael.drag.over." + dragi.el.id, dragi.el, o);
+ x += scrollX;
+ y += scrollY;
+ eve("raphael.drag.move." + dragi.el.id, dragi.move_scope || dragi.el, x - dragi.el._drag.x, y - dragi.el._drag.y, x, y, e);
+ }
+ },
+ dragUp = function (e) {
+ R.unmousemove(dragMove).unmouseup(dragUp);
+ var i = drag.length,
+ dragi;
+ while (i--) {
+ dragi = drag[i];
+ dragi.el._drag = {};
+ eve("raphael.drag.end." + dragi.el.id, dragi.end_scope || dragi.start_scope || dragi.move_scope || dragi.el, e);
+ }
+ drag = [];
+ },
+
+ elproto = R.el = {};
+
+
+ for (var i = events.length; i--;) {
+ (function (eventName) {
+ R[eventName] = elproto[eventName] = function (fn, scope) {
+ if (R.is(fn, "function")) {
+ this.events = this.events || [];
+ this.events.push({
+ name: eventName,
+ f: fn,
+ unbind: addEvent(this.shape || this.node || g.doc, eventName, fn, scope || this)
+ });
+ }
+ return this;
+ };
+ R["un" + eventName] = elproto["un" + eventName] = function (fn) {
+ var events = this.events || [],
+ l = events.length;
+ while (l--) if (events[l].name == eventName && events[l].f == fn) {
+ events[l].unbind();
+ events.splice(l, 1);
+ !events.length && delete this.events;
+ return this;
+ }
+ return this;
+ };
+ })(events[i]);
+ }
+
+
+ elproto.data = function (key, value) {
+ var data = eldata[this.id] = eldata[this.id] || {};
+ if (arguments.length == 1) {
+ if (R.is(key, "object")) {
+ for (var i in key) if (key[has](i)) {
+ this.data(i, key[i]);
+ }
+ return this;
+ }
+ eve("raphael.data.get." + this.id, this, data[key], key);
+ return data[key];
+ }
+ data[key] = value;
+ eve("raphael.data.set." + this.id, this, value, key);
+ return this;
+ };
+
+ elproto.removeData = function (key) {
+ if (key == null) {
+ eldata[this.id] = {};
+ } else {
+ eldata[this.id] && delete eldata[this.id][key];
+ }
+ return this;
+ };
+
+ elproto.hover = function (f_in, f_out, scope_in, scope_out) {
+ return this.mouseover(f_in, scope_in).mouseout(f_out, scope_out || scope_in);
+ };
+
+ elproto.unhover = function (f_in, f_out) {
+ return this.unmouseover(f_in).unmouseout(f_out);
+ };
+ var draggable = [];
+
+ elproto.drag = function (onmove, onstart, onend, move_scope, start_scope, end_scope) {
+ function start(e) {
+ (e.originalEvent || e).preventDefault();
+ var scrollY = g.doc.documentElement.scrollTop || g.doc.body.scrollTop,
+ scrollX = g.doc.documentElement.scrollLeft || g.doc.body.scrollLeft;
+ this._drag.x = e.clientX + scrollX;
+ this._drag.y = e.clientY + scrollY;
+ this._drag.id = e.identifier;
+ !drag.length && R.mousemove(dragMove).mouseup(dragUp);
+ drag.push({el: this, move_scope: move_scope, start_scope: start_scope, end_scope: end_scope});
+ onstart && eve.on("raphael.drag.start." + this.id, onstart);
+ onmove && eve.on("raphael.drag.move." + this.id, onmove);
+ onend && eve.on("raphael.drag.end." + this.id, onend);
+ eve("raphael.drag.start." + this.id, start_scope || move_scope || this, e.clientX + scrollX, e.clientY + scrollY, e);
+ }
+
+ this._drag = {};
+ draggable.push({el: this, start: start});
+ this.mousedown(start);
+ return this;
+ };
+
+ elproto.onDragOver = function (f) {
+ f ? eve.on("raphael.drag.over." + this.id, f) : eve.unbind("raphael.drag.over." + this.id);
+ };
+
+ elproto.undrag = function () {
+ var i = draggable.length;
+ while (i--) if (draggable[i].el == this) {
+ this.unmousedown(draggable[i].start);
+ draggable.splice(i, 1);
+ eve.unbind("raphael.drag.*." + this.id);
+ }
+ !draggable.length && R.unmousemove(dragMove).unmouseup(dragUp);
+ };
+
+ // 추가(for group 기능)
+ paperproto.group = function (x, y) {
+ var out = R._engine.group(this, x || 0, y || 0);
+ this.__set__ && this.__set__.push(out);
+ return out;
+ };
+
+ paperproto.circle = function (x, y, r) {
+ var out = R._engine.circle(this, x || 0, y || 0, r || 0);
+ this.__set__ && this.__set__.push(out);
+ return out;
+ };
+
+ paperproto.rect = function (x, y, w, h, r) {
+ var out = R._engine.rect(this, x || 0, y || 0, w || 0, h || 0, r || 0);
+ this.__set__ && this.__set__.push(out);
+ return out;
+ };
+
+ paperproto.ellipse = function (x, y, rx, ry) {
+ var out = R._engine.ellipse(this, x || 0, y || 0, rx || 0, ry || 0);
+ this.__set__ && this.__set__.push(out);
+ return out;
+ };
+
+ paperproto.path = function (pathString) {
+ pathString && !R.is(pathString, string) && !R.is(pathString[0], array) && (pathString += E);
+ var out = R._engine.path(R.format[apply](R, arguments), this);
+ this.__set__ && this.__set__.push(out);
+ return out;
+ };
+
+ paperproto.image = function (src, x, y, w, h, title) {
+ var out = R._engine.image(this, src || "about:blank", x || 0, y || 0, w || 0, h || 0, title);
+ this.__set__ && this.__set__.push(out);
+ return out;
+ };
+
+ paperproto.text = function (x, y, text, size) {
+ var out = R._engine.text(this, x || 0, y || 0, Str(text), size);
+ this.__set__ && this.__set__.push(out);
+ return out;
+ };
+
+ // 추가(for foreignObject 기능)
+ paperproto.foreignObject = function (obj, x, y, w, h) {
+ var out = R._engine.foreignObject(this, x || 0, y || 0, w || 0, h || 0, obj);
+ this.__set__ && this.__set__.push(out);
+ return out;
+ };
+
+ paperproto.set = function (itemsArray) {
+ !R.is(itemsArray, "array") && (itemsArray = Array.prototype.splice.call(arguments, 0, arguments.length));
+ var out = new Set(itemsArray);
+ this.__set__ && this.__set__.push(out);
+ return out;
+ };
+
+ paperproto.setStart = function (set) {
+ this.__set__ = set || this.set();
+ };
+
+ paperproto.setFinish = function (set) {
+ var out = this.__set__;
+ delete this.__set__;
+ return out;
+ };
+
+ paperproto.setSize = function (width, height) {
+ return R._engine.setSize.call(this, width, height);
+ };
+
+ paperproto.setViewBox = function (x, y, w, h, fit) {
+ return R._engine.setViewBox.call(this, x, y, w, h, fit);
+ };
+
+
+ paperproto.top = paperproto.bottom = null;
+
+ paperproto.raphael = R;
+ var getOffset = function (elem) {
+ var box = elem.getBoundingClientRect(),
+ doc = elem.ownerDocument,
+ body = doc.body,
+ docElem = doc.documentElement,
+ clientTop = docElem.clientTop || body.clientTop || 0,
+ clientLeft = docElem.clientLeft || body.clientLeft || 0,
+ top = box.top + (g.win.pageYOffset || docElem.scrollTop || body.scrollTop) - clientTop,
+ left = box.left + (g.win.pageXOffset || docElem.scrollLeft || body.scrollLeft) - clientLeft;
+ return {
+ y: top,
+ x: left
+ };
+ };
+
+ paperproto.getElementByPoint = function (x, y) {
+ var paper = this,
+ svg = paper.canvas,
+ target = g.doc.elementFromPoint(x, y);
+ if (g.win.opera && target.tagName == "svg") {
+ var so = getOffset(svg),
+ sr = svg.createSVGRect();
+ sr.x = x - so.x;
+ sr.y = y - so.y;
+ sr.width = sr.height = 1;
+ var hits = svg.getIntersectionList(sr, null);
+ if (hits.length) {
+ target = hits[hits.length - 1];
+ }
+ }
+ if (!target) {
+ return null;
+ }
+ while (target.parentNode && target != svg.parentNode && !target.raphael) {
+ target = target.parentNode;
+ }
+ target == paper.canvas.parentNode && (target = svg);
+ target = target && target.raphael ? paper.getById(target.raphaelid) : null;
+ return target;
+ };
+
+ paperproto.getById = function (id) {
+ var bot = this.bottom;
+ while (bot) {
+ if (bot.id == id) {
+ return bot;
+ }
+ bot = bot.next;
+ }
+ return null;
+ };
+
+ paperproto.forEach = function (callback, thisArg) {
+ var bot = this.bottom;
+ while (bot) {
+ if (callback.call(thisArg, bot) === false) {
+ return this;
+ }
+ bot = bot.next;
+ }
+ return this;
+ };
+
+ paperproto.getElementsByPoint = function (x, y) {
+ var set = this.set();
+ this.forEach(function (el) {
+ if (el.isPointInside(x, y)) {
+ set.push(el);
+ }
+ });
+ return set;
+ };
+
+ function x_y() {
+ return this.x + S + this.y;
+ }
+
+ function x_y_w_h() {
+ return this.x + S + this.y + S + this.width + " \xd7 " + this.height;
+ }
+
+ elproto.isPointInside = function (x, y) {
+ var rp = this.realPath = this.realPath || getPath[this.type](this);
+ return R.isPointInsidePath(rp, x, y);
+ };
+
+ elproto.getBBox = function (isWithoutTransform) {
+ if (this.removed) {
+ return {};
+ }
+ var _ = this._;
+ if (isWithoutTransform) {
+ if (_.dirty || !_.bboxwt) {
+ this.realPath = getPath[this.type](this);
+ _.bboxwt = pathDimensions(this.realPath);
+ _.bboxwt.toString = x_y_w_h;
+ _.dirty = 0;
+ }
+ return _.bboxwt;
+ }
+ if (_.dirty || _.dirtyT || !_.bbox) {
+ if (_.dirty || !this.realPath) {
+ _.bboxwt = 0;
+ this.realPath = getPath[this.type](this);
+ }
+ _.bbox = pathDimensions(mapPath(this.realPath, this.matrix));
+ _.bbox.toString = x_y_w_h;
+ _.dirty = _.dirtyT = 0;
+ }
+ return _.bbox;
+ };
+
+ elproto.clone = function () {
+ if (this.removed) {
+ return null;
+ }
+ var out = this.paper[this.type]().attr(this.attr());
+ this.__set__ && this.__set__.push(out);
+ return out;
+ };
+
+ elproto.glow = function (glow) {
+ if (this.type == "text") {
+ return null;
+ }
+ glow = glow || {};
+ var s = {
+ width: (glow.width || 10) + (+this.attr("stroke-width") || 1),
+ fill: glow.fill || false,
+ opacity: glow.opacity || .5,
+ offsetx: glow.offsetx || 0,
+ offsety: glow.offsety || 0,
+ color: glow.color || "#000"
+ },
+ c = s.width / 2,
+ r = this.paper,
+ out = r.set(),
+ path = this.realPath || getPath[this.type](this);
+ path = this.matrix ? mapPath(path, this.matrix) : path;
+ for (var i = 1; i < c + 1; i++) {
+ out.push(r.path(path).attr({
+ stroke: s.color,
+ fill: s.fill ? s.color : "none",
+ "stroke-linejoin": "round",
+ "stroke-linecap": "round",
+ "stroke-width": +(s.width / c * i).toFixed(3),
+ opacity: +(s.opacity / c).toFixed(3)
+ }));
+ }
+ return out.insertBefore(this).translate(s.offsetx, s.offsety);
+ };
+ var curveslengths = {},
+ getPointAtSegmentLength = function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, length) {
+ if (length == null) {
+ return bezlen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y);
+ } else {
+ return R.findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, getTatLen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, length));
+ }
+ },
+ getLengthFactory = function (istotal, subpath) {
+ return function (path, length, onlystart) {
+ path = path2curve(path);
+ var x, y, p, l, sp = "", subpaths = {}, point,
+ len = 0;
+ for (var i = 0, ii = path.length; i < ii; i++) {
+ p = path[i];
+ if (p[0] == "M") {
+ x = +p[1];
+ y = +p[2];
+ } else {
+ l = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
+ if (len + l > length) {
+ if (subpath && !subpaths.start) {
+ point = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6], length - len);
+ sp += ["C" + point.start.x, point.start.y, point.m.x, point.m.y, point.x, point.y];
+ if (onlystart) {
+ return sp;
+ }
+ subpaths.start = sp;
+ sp = ["M" + point.x, point.y + "C" + point.n.x, point.n.y, point.end.x, point.end.y, p[5], p[6]].join();
+ len += l;
+ x = +p[5];
+ y = +p[6];
+ continue;
+
+
+ }
+ if (!istotal && !subpath) {
+ point = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6], length - len);
+ return {x: point.x, y: point.y, alpha: point.alpha};
+ }
+ }
+ len += l;
+ x = +p[5];
+ y = +p[6];
+ }
+ sp += p.shift() + p;
+ }
+ subpaths.end = sp;
+ point = istotal ? len : subpath ? subpaths : R.findDotsAtSegment(x, y, p[0], p[1], p[2], p[3], p[4], p[5], 1);
+ point.alpha && (point = {x: point.x, y: point.y, alpha: point.alpha});
+ return point;
+ };
+ };
+ var getTotalLength = getLengthFactory(1),
+ getPointAtLength = getLengthFactory(),
+ getSubpathsAtLength = getLengthFactory(0, 1);
+
+ R.getTotalLength = getTotalLength;
+
+ R.getPointAtLength = getPointAtLength;
+
+ R.getSubpath = function (path, from, to) {
+ if (this.getTotalLength(path) - to < 1e-6) {
+ return getSubpathsAtLength(path, from).end;
+ }
+ var a = getSubpathsAtLength(path, to, 1);
+ return from ? getSubpathsAtLength(a, from).end : a;
+ };
+
+ elproto.getTotalLength = function () {
+ if (this.type != "path") {
+ return;
+ }
+ if (this.node.getTotalLength) {
+ return this.node.getTotalLength();
+ }
+ return getTotalLength(this.attrs.path);
+ };
+
+ elproto.getPointAtLength = function (length) {
+ if (this.type != "path") {
+ return;
+ }
+ return getPointAtLength(this.attrs.path, length);
+ };
+
+ elproto.getSubpath = function (from, to) {
+ if (this.type != "path") {
+ return;
+ }
+ return R.getSubpath(this.attrs.path, from, to);
+ };
+
+ var ef = R.easing_formulas = {
+ linear: function (n) {
+ return n;
+ },
+ "<": function (n) {
+ return pow(n, 1.7);
+ },
+ ">": function (n) {
+ return pow(n, .48);
+ },
+ "<>": function (n) {
+ var q = .48 - n / 1.04,
+ Q = math.sqrt(.1734 + q * q),
+ x = Q - q,
+ X = pow(abs(x), 1 / 3) * (x < 0 ? -1 : 1),
+ y = -Q - q,
+ Y = pow(abs(y), 1 / 3) * (y < 0 ? -1 : 1),
+ t = X + Y + .5;
+ return (1 - t) * 3 * t * t + t * t * t;
+ },
+ backIn: function (n) {
+ var s = 1.70158;
+ return n * n * ((s + 1) * n - s);
+ },
+ backOut: function (n) {
+ n = n - 1;
+ var s = 1.70158;
+ return n * n * ((s + 1) * n + s) + 1;
+ },
+ elastic: function (n) {
+ if (n == !!n) {
+ return n;
+ }
+ return pow(2, -10 * n) * math.sin((n - .075) * (2 * PI) / .3) + 1;
+ },
+ bounce: function (n) {
+ var s = 7.5625,
+ p = 2.75,
+ l;
+ if (n < (1 / p)) {
+ l = s * n * n;
+ } else {
+ if (n < (2 / p)) {
+ n -= (1.5 / p);
+ l = s * n * n + .75;
+ } else {
+ if (n < (2.5 / p)) {
+ n -= (2.25 / p);
+ l = s * n * n + .9375;
+ } else {
+ n -= (2.625 / p);
+ l = s * n * n + .984375;
+ }
+ }
+ }
+ return l;
+ }
+ };
+ ef.easeIn = ef["ease-in"] = ef["<"];
+ ef.easeOut = ef["ease-out"] = ef[">"];
+ ef.easeInOut = ef["ease-in-out"] = ef["<>"];
+ ef["back-in"] = ef.backIn;
+ ef["back-out"] = ef.backOut;
+
+ var animationElements = [],
+ requestAnimFrame = window.requestAnimationFrame ||
+ window.webkitRequestAnimationFrame ||
+ window.mozRequestAnimationFrame ||
+ window.oRequestAnimationFrame ||
+ window.msRequestAnimationFrame ||
+ function (callback) {
+ setTimeout(callback, 16);
+ },
+ animation = function () {
+ var Now = +new Date,
+ l = 0;
+ for (; l < animationElements.length; l++) {
+ var e = animationElements[l];
+ if (e.el.removed || e.paused) {
+ continue;
+ }
+ var time = Now - e.start,
+ ms = e.ms,
+ easing = e.easing,
+ from = e.from,
+ diff = e.diff,
+ to = e.to,
+ t = e.t,
+ that = e.el,
+ set = {},
+ now,
+ init = {},
+ key;
+ if (e.initstatus) {
+ time = (e.initstatus * e.anim.top - e.prev) / (e.percent - e.prev) * ms;
+ e.status = e.initstatus;
+ delete e.initstatus;
+ e.stop && animationElements.splice(l--, 1);
+ } else {
+ e.status = (e.prev + (e.percent - e.prev) * (time / ms)) / e.anim.top;
+ }
+ if (time < 0) {
+ continue;
+ }
+ if (time < ms) {
+ var pos = easing(time / ms);
+ for (var attr in from) if (from[has](attr)) {
+ switch (availableAnimAttrs[attr]) {
+ case nu:
+ now = +from[attr] + pos * ms * diff[attr];
+ break;
+ case "colour":
+ now = "rgb(" + [
+ upto255(round(from[attr].r + pos * ms * diff[attr].r)),
+ upto255(round(from[attr].g + pos * ms * diff[attr].g)),
+ upto255(round(from[attr].b + pos * ms * diff[attr].b))
+ ].join(",") + ")";
+ break;
+ case "path":
+ now = [];
+ for (var i = 0, ii = from[attr].length; i < ii; i++) {
+ now[i] = [from[attr][i][0]];
+ for (var j = 1, jj = from[attr][i].length; j < jj; j++) {
+ now[i][j] = +from[attr][i][j] + pos * ms * diff[attr][i][j];
+ }
+ now[i] = now[i].join(S);
+ }
+ now = now.join(S);
+ break;
+ case "transform":
+ if (diff[attr].real) {
+ now = [];
+ for (i = 0, ii = from[attr].length; i < ii; i++) {
+ now[i] = [from[attr][i][0]];
+ for (j = 1, jj = from[attr][i].length; j < jj; j++) {
+ now[i][j] = from[attr][i][j] + pos * ms * diff[attr][i][j];
+ }
+ }
+ } else {
+ var get = function (i) {
+ return +from[attr][i] + pos * ms * diff[attr][i];
+ };
+ // now = [["r", get(2), 0, 0], ["t", get(3), get(4)], ["s", get(0), get(1), 0, 0]];
+ now = [["m", get(0), get(1), get(2), get(3), get(4), get(5)]];
+ }
+ break;
+ case "csv":
+ if (attr == "clip-rect") {
+ now = [];
+ i = 4;
+ while (i--) {
+ now[i] = +from[attr][i] + pos * ms * diff[attr][i];
+ }
+ }
+ break;
+ default:
+ var from2 = [][concat](from[attr]);
+ now = [];
+ i = that.paper.customAttributes[attr].length;
+ while (i--) {
+ now[i] = +from2[i] + pos * ms * diff[attr][i];
+ }
+ break;
+ }
+ set[attr] = now;
+ }
+ that.attr(set);
+ (function (id, that, anim) {
+ setTimeout(function () {
+ eve("raphael.anim.frame." + id, that, anim);
+ });
+ })(that.id, that, e.anim);
+ } else {
+ (function (f, el, a) {
+ setTimeout(function () {
+ eve("raphael.anim.frame." + el.id, el, a);
+ eve("raphael.anim.finish." + el.id, el, a);
+ R.is(f, "function") && f.call(el);
+ });
+ })(e.callback, that, e.anim);
+ that.attr(to);
+ animationElements.splice(l--, 1);
+ if (e.repeat > 1 && !e.next) {
+ for (key in to) if (to[has](key)) {
+ init[key] = e.totalOrigin[key];
+ }
+ e.el.attr(init);
+ runAnimation(e.anim, e.el, e.anim.percents[0], null, e.totalOrigin, e.repeat - 1);
+ }
+ if (e.next && !e.stop) {
+ runAnimation(e.anim, e.el, e.next, null, e.totalOrigin, e.repeat);
+ }
+ }
+ }
+ R.svg && that && that.paper && that.paper.safari();
+ animationElements.length && requestAnimFrame(animation);
+ },
+ upto255 = function (color) {
+ return color > 255 ? 255 : color < 0 ? 0 : color;
+ };
+
+ elproto.animateWith = function (el, anim, params, ms, easing, callback) {
+ var element = this;
+ if (element.removed) {
+ callback && callback.call(element);
+ return element;
+ }
+ var a = params instanceof Animation ? params : R.animation(params, ms, easing, callback),
+ x, y;
+ runAnimation(a, element, a.percents[0], null, element.attr());
+ for (var i = 0, ii = animationElements.length; i < ii; i++) {
+ if (animationElements[i].anim == anim && animationElements[i].el == el) {
+ animationElements[ii - 1].start = animationElements[i].start;
+ break;
+ }
+ }
+ return element;
+ //
+ //
+ // var a = params ? R.animation(params, ms, easing, callback) : anim,
+ // status = element.status(anim);
+ // return this.animate(a).status(a, status * anim.ms / a.ms);
+ };
+
+ function CubicBezierAtTime(t, p1x, p1y, p2x, p2y, duration) {
+ var cx = 3 * p1x,
+ bx = 3 * (p2x - p1x) - cx,
+ ax = 1 - cx - bx,
+ cy = 3 * p1y,
+ by = 3 * (p2y - p1y) - cy,
+ ay = 1 - cy - by;
+
+ function sampleCurveX(t) {
+ return ((ax * t + bx) * t + cx) * t;
+ }
+
+ function solve(x, epsilon) {
+ var t = solveCurveX(x, epsilon);
+ return ((ay * t + by) * t + cy) * t;
+ }
+
+ function solveCurveX(x, epsilon) {
+ var t0, t1, t2, x2, d2, i;
+ for (t2 = x, i = 0; i < 8; i++) {
+ x2 = sampleCurveX(t2) - x;
+ if (abs(x2) < epsilon) {
+ return t2;
+ }
+ d2 = (3 * ax * t2 + 2 * bx) * t2 + cx;
+ if (abs(d2) < 1e-6) {
+ break;
+ }
+ t2 = t2 - x2 / d2;
+ }
+ t0 = 0;
+ t1 = 1;
+ t2 = x;
+ if (t2 < t0) {
+ return t0;
+ }
+ if (t2 > t1) {
+ return t1;
+ }
+ while (t0 < t1) {
+ x2 = sampleCurveX(t2);
+ if (abs(x2 - x) < epsilon) {
+ return t2;
+ }
+ if (x > x2) {
+ t0 = t2;
+ } else {
+ t1 = t2;
+ }
+ t2 = (t1 - t0) / 2 + t0;
+ }
+ return t2;
+ }
+
+ return solve(t, 1 / (200 * duration));
+ }
+
+ elproto.onAnimation = function (f) {
+ f ? eve.on("raphael.anim.frame." + this.id, f) : eve.unbind("raphael.anim.frame." + this.id);
+ return this;
+ };
+
+ function Animation(anim, ms) {
+ var percents = [],
+ newAnim = {};
+ this.ms = ms;
+ this.times = 1;
+ if (anim) {
+ for (var attr in anim) if (anim[has](attr)) {
+ newAnim[toFloat(attr)] = anim[attr];
+ percents.push(toFloat(attr));
+ }
+ percents.sort(sortByNumber);
+ }
+ this.anim = newAnim;
+ this.top = percents[percents.length - 1];
+ this.percents = percents;
+ }
+
+ Animation.prototype.delay = function (delay) {
+ var a = new Animation(this.anim, this.ms);
+ a.times = this.times;
+ a.del = +delay || 0;
+ return a;
+ };
+
+ Animation.prototype.repeat = function (times) {
+ var a = new Animation(this.anim, this.ms);
+ a.del = this.del;
+ a.times = math.floor(mmax(times, 0)) || 1;
+ return a;
+ };
+
+ function runAnimation(anim, element, percent, status, totalOrigin, times) {
+ percent = toFloat(percent);
+ var params,
+ isInAnim,
+ isInAnimSet,
+ percents = [],
+ next,
+ prev,
+ timestamp,
+ ms = anim.ms,
+ from = {},
+ to = {},
+ diff = {};
+ if (status) {
+ for (i = 0, ii = animationElements.length; i < ii; i++) {
+ var e = animationElements[i];
+ if (e.el.id == element.id && e.anim == anim) {
+ if (e.percent != percent) {
+ animationElements.splice(i, 1);
+ isInAnimSet = 1;
+ } else {
+ isInAnim = e;
+ }
+ element.attr(e.totalOrigin);
+ break;
+ }
+ }
+ } else {
+ status = +to; // NaN
+ }
+ for (var i = 0, ii = anim.percents.length; i < ii; i++) {
+ if (anim.percents[i] == percent || anim.percents[i] > status * anim.top) {
+ percent = anim.percents[i];
+ prev = anim.percents[i - 1] || 0;
+ ms = ms / anim.top * (percent - prev);
+ next = anim.percents[i + 1];
+ params = anim.anim[percent];
+ break;
+ } else if (status) {
+ element.attr(anim.anim[anim.percents[i]]);
+ }
+ }
+ if (!params) {
+ return;
+ }
+ if (!isInAnim) {
+ for (var attr in params) if (params[has](attr)) {
+ if (availableAnimAttrs[has](attr) || element.paper.customAttributes[has](attr)) {
+ from[attr] = element.attr(attr);
+ (from[attr] == null) && (from[attr] = availableAttrs[attr]);
+
+ to[attr] = params[attr];
+ switch (availableAnimAttrs[attr]) {
+ case nu:
+ diff[attr] = (to[attr] - from[attr]) / ms;
+ break;
+ case "colour":
+ from[attr] = R.getRGB(from[attr]);
+ var toColour = R.getRGB(to[attr]);
+ diff[attr] = {
+ r: (toColour.r - from[attr].r) / ms,
+ g: (toColour.g - from[attr].g) / ms,
+ b: (toColour.b - from[attr].b) / ms
+ };
+ break;
+ case "path":
+ var pathes = path2curve(from[attr], to[attr]),
+ toPath = pathes[1];
+ from[attr] = pathes[0];
+ diff[attr] = [];
+ for (i = 0, ii = from[attr].length; i < ii; i++) {
+ diff[attr][i] = [0];
+ for (var j = 1, jj = from[attr][i].length; j < jj; j++) {
+ diff[attr][i][j] = (toPath[i][j] - from[attr][i][j]) / ms;
+ }
+ }
+ break;
+ case "transform":
+ var _ = element._,
+ eq = equaliseTransform(_[attr], to[attr]);
+ if (eq) {
+ from[attr] = eq.from;
+ to[attr] = eq.to;
+ diff[attr] = [];
+ diff[attr].real = true;
+ for (i = 0, ii = from[attr].length; i < ii; i++) {
+ diff[attr][i] = [from[attr][i][0]];
+ for (j = 1, jj = from[attr][i].length; j < jj; j++) {
+ diff[attr][i][j] = (to[attr][i][j] - from[attr][i][j]) / ms;
+ }
+ }
+ } else {
+ var m = (element.matrix || new Matrix),
+ to2 = {
+ _: {transform: _.transform},
+ getBBox: function () {
+ return element.getBBox(1);
+ }
+ };
+ from[attr] = [
+ m.a,
+ m.b,
+ m.c,
+ m.d,
+ m.e,
+ m.f
+ ];
+ extractTransform(to2, to[attr]);
+ to[attr] = to2._.transform;
+ diff[attr] = [
+ (to2.matrix.a - m.a) / ms,
+ (to2.matrix.b - m.b) / ms,
+ (to2.matrix.c - m.c) / ms,
+ (to2.matrix.d - m.d) / ms,
+ (to2.matrix.e - m.e) / ms,
+ (to2.matrix.f - m.f) / ms
+ ];
+ // from[attr] = [_.sx, _.sy, _.deg, _.dx, _.dy];
+ // var to2 = {_:{}, getBBox: function () { return element.getBBox(); }};
+ // extractTransform(to2, to[attr]);
+ // diff[attr] = [
+ // (to2._.sx - _.sx) / ms,
+ // (to2._.sy - _.sy) / ms,
+ // (to2._.deg - _.deg) / ms,
+ // (to2._.dx - _.dx) / ms,
+ // (to2._.dy - _.dy) / ms
+ // ];
+ }
+ break;
+ case "csv":
+ var values = Str(params[attr])[split](separator),
+ from2 = Str(from[attr])[split](separator);
+ if (attr == "clip-rect") {
+ from[attr] = from2;
+ diff[attr] = [];
+ i = from2.length;
+ while (i--) {
+ diff[attr][i] = (values[i] - from[attr][i]) / ms;
+ }
+ }
+ to[attr] = values;
+ break;
+ default:
+ values = [][concat](params[attr]);
+ from2 = [][concat](from[attr]);
+ diff[attr] = [];
+ i = element.paper.customAttributes[attr].length;
+ while (i--) {
+ diff[attr][i] = ((values[i] || 0) - (from2[i] || 0)) / ms;
+ }
+ break;
+ }
+ }
+ }
+ var easing = params.easing,
+ easyeasy = R.easing_formulas[easing];
+ if (!easyeasy) {
+ easyeasy = Str(easing).match(bezierrg);
+ if (easyeasy && easyeasy.length == 5) {
+ var curve = easyeasy;
+ easyeasy = function (t) {
+ return CubicBezierAtTime(t, +curve[1], +curve[2], +curve[3], +curve[4], ms);
+ };
+ } else {
+ easyeasy = pipe;
+ }
+ }
+ timestamp = params.start || anim.start || +new Date;
+ e = {
+ anim: anim,
+ percent: percent,
+ timestamp: timestamp,
+ start: timestamp + (anim.del || 0),
+ status: 0,
+ initstatus: status || 0,
+ stop: false,
+ ms: ms,
+ easing: easyeasy,
+ from: from,
+ diff: diff,
+ to: to,
+ el: element,
+ callback: params.callback,
+ prev: prev,
+ next: next,
+ repeat: times || anim.times,
+ origin: element.attr(),
+ totalOrigin: totalOrigin
+ };
+ animationElements.push(e);
+ if (status && !isInAnim && !isInAnimSet) {
+ e.stop = true;
+ e.start = new Date - ms * status;
+ if (animationElements.length == 1) {
+ return animation();
+ }
+ }
+ if (isInAnimSet) {
+ e.start = new Date - e.ms * status;
+ }
+ animationElements.length == 1 && requestAnimFrame(animation);
+ } else {
+ isInAnim.initstatus = status;
+ isInAnim.start = new Date - isInAnim.ms * status;
+ }
+ eve("raphael.anim.start." + element.id, element, anim);
+ }
+
+ R.animation = function (params, ms, easing, callback) {
+ if (params instanceof Animation) {
+ return params;
+ }
+ if (R.is(easing, "function") || !easing) {
+ callback = callback || easing || null;
+ easing = null;
+ }
+ params = Object(params);
+ ms = +ms || 0;
+ var p = {},
+ json,
+ attr;
+ for (attr in params) if (params[has](attr) && toFloat(attr) != attr && toFloat(attr) + "%" != attr) {
+ json = true;
+ p[attr] = params[attr];
+ }
+ if (!json) {
+ return new Animation(params, ms);
+ } else {
+ easing && (p.easing = easing);
+ callback && (p.callback = callback);
+ return new Animation({100: p}, ms);
+ }
+ };
+
+ elproto.animate = function (params, ms, easing, callback) {
+ var element = this;
+ if (element.removed) {
+ callback && callback.call(element);
+ return element;
+ }
+ var anim = params instanceof Animation ? params : R.animation(params, ms, easing, callback);
+ runAnimation(anim, element, anim.percents[0], null, element.attr());
+ return element;
+ };
+
+ elproto.setTime = function (anim, value) {
+ if (anim && value != null) {
+ this.status(anim, mmin(value, anim.ms) / anim.ms);
+ }
+ return this;
+ };
+
+ elproto.status = function (anim, value) {
+ var out = [],
+ i = 0,
+ len,
+ e;
+ if (value != null) {
+ runAnimation(anim, this, -1, mmin(value, 1));
+ return this;
+ } else {
+ len = animationElements.length;
+ for (; i < len; i++) {
+ e = animationElements[i];
+ if (e.el.id == this.id && (!anim || e.anim == anim)) {
+ if (anim) {
+ return e.status;
+ }
+ out.push({
+ anim: e.anim,
+ status: e.status
+ });
+ }
+ }
+ if (anim) {
+ return 0;
+ }
+ return out;
+ }
+ };
+
+ elproto.pause = function (anim) {
+ for (var i = 0; i < animationElements.length; i++) if (animationElements[i].el.id == this.id && (!anim || animationElements[i].anim == anim)) {
+ if (eve("raphael.anim.pause." + this.id, this, animationElements[i].anim) !== false) {
+ animationElements[i].paused = true;
+ }
+ }
+ return this;
+ };
+
+ elproto.resume = function (anim) {
+ for (var i = 0; i < animationElements.length; i++) if (animationElements[i].el.id == this.id && (!anim || animationElements[i].anim == anim)) {
+ var e = animationElements[i];
+ if (eve("raphael.anim.resume." + this.id, this, e.anim) !== false) {
+ delete e.paused;
+ this.status(e.anim, e.status);
+ }
+ }
+ return this;
+ };
+
+ elproto.stop = function (anim) {
+ for (var i = 0; i < animationElements.length; i++) if (animationElements[i].el.id == this.id && (!anim || animationElements[i].anim == anim)) {
+ if (eve("raphael.anim.stop." + this.id, this, animationElements[i].anim) !== false) {
+ animationElements.splice(i--, 1);
+ }
+ }
+ return this;
+ };
+
+ function stopAnimation(paper) {
+ for (var i = 0; i < animationElements.length; i++) if (animationElements[i].el.paper == paper) {
+ animationElements.splice(i--, 1);
+ }
+ }
+
+ eve.on("raphael.remove", stopAnimation);
+ eve.on("raphael.clear", stopAnimation);
+ elproto.toString = function () {
+ return "Rapha\xebl\u2019s object";
+ };
+
+ // Set
+ var Set = function (items) {
+ this.items = [];
+ this.length = 0;
+ this.type = "set";
+ if (items) {
+ for (var i = 0, ii = items.length; i < ii; i++) {
+ if (items[i] && (items[i].constructor == elproto.constructor || items[i].constructor == Set)) {
+ this[this.items.length] = this.items[this.items.length] = items[i];
+ this.length++;
+
+ }
+ }
+ }
+ },
+ setproto = Set.prototype;
+
+ setproto.push = function () {
+ var item,
+ len;
+ for (var i = 0, ii = arguments.length; i < ii; i++) {
+ item = arguments[i];
+ if (item && (item.constructor == elproto.constructor || item.constructor == Set)) {
+ len = this.items.length;
+ this[len] = this.items[len] = item;
+ this.length++;
+ }
+ }
+ return this;
+ };
+
+ setproto.pop = function () {
+ this.length && delete this[this.length--];
+ return this.items.pop();
+ };
+
+ setproto.forEach = function (callback, thisArg) {
+ for (var i = 0, ii = this.items.length; i < ii; i++) {
+ if (callback.call(thisArg, this.items[i], i) === false) {
+ return this;
+ }
+ }
+ return this;
+ };
+ for (var method in elproto) if (elproto[has](method)) {
+ setproto[method] = (function (methodname) {
+ return function () {
+ var arg = arguments;
+ return this.forEach(function (el) {
+ el[methodname][apply](el, arg);
+ });
+ };
+ })(method);
+ }
+ setproto.attr = function (name, value) {
+ if (name && R.is(name, array) && R.is(name[0], "object")) {
+ for (var j = 0, jj = name.length; j < jj; j++) {
+ this.items[j].attr(name[j]);
+ }
+ } else {
+ for (var i = 0, ii = this.items.length; i < ii; i++) {
+ this.items[i].attr(name, value);
+ }
+ }
+ return this;
+ };
+
+ setproto.clear = function () {
+ while (this.length) {
+ this.pop();
+ }
+ };
+
+ setproto.splice = function (index, count, insertion) {
+ index = index < 0 ? mmax(this.length + index, 0) : index;
+ count = mmax(0, mmin(this.length - index, count));
+ var tail = [],
+ todel = [],
+ args = [],
+ i;
+ for (i = 2; i < arguments.length; i++) {
+ args.push(arguments[i]);
+ }
+ for (i = 0; i < count; i++) {
+ todel.push(this[index + i]);
+ }
+ for (; i < this.length - index; i++) {
+ tail.push(this[index + i]);
+ }
+ var arglen = args.length;
+ for (i = 0; i < arglen + tail.length; i++) {
+ this.items[index + i] = this[index + i] = i < arglen ? args[i] : tail[i - arglen];
+ }
+ i = this.items.length = this.length -= count - arglen;
+ while (this[i]) {
+ delete this[i++];
+ }
+ return new Set(todel);
+ };
+
+ setproto.exclude = function (el) {
+ for (var i = 0, ii = this.length; i < ii; i++) if (this[i] == el) {
+ this.splice(i, 1);
+ return true;
+ }
+ };
+ setproto.animate = function (params, ms, easing, callback) {
+ (R.is(easing, "function") || !easing) && (callback = easing || null);
+ var len = this.items.length,
+ i = len,
+ item,
+ set = this,
+ collector;
+ if (!len) {
+ return this;
+ }
+ callback && (collector = function () {
+ !--len && callback.call(set);
+ });
+ easing = R.is(easing, string) ? easing : collector;
+ var anim = R.animation(params, ms, easing, collector);
+ item = this.items[--i].animate(anim);
+ while (i--) {
+ this.items[i] && !this.items[i].removed && this.items[i].animateWith(item, anim, anim);
+ }
+ return this;
+ };
+ setproto.insertAfter = function (el) {
+ var i = this.items.length;
+ while (i--) {
+ this.items[i].insertAfter(el);
+ }
+ return this;
+ };
+ setproto.getBBox = function () {
+ var x = [],
+ y = [],
+ x2 = [],
+ y2 = [];
+ for (var i = this.items.length; i--;) if (!this.items[i].removed) {
+ var box = this.items[i].getBBox();
+ x.push(box.x);
+ y.push(box.y);
+ x2.push(box.x + box.width);
+ y2.push(box.y + box.height);
+ }
+ x = mmin[apply](0, x);
+ y = mmin[apply](0, y);
+ x2 = mmax[apply](0, x2);
+ y2 = mmax[apply](0, y2);
+ return {
+ x: x,
+ y: y,
+ x2: x2,
+ y2: y2,
+ width: x2 - x,
+ height: y2 - y
+ };
+ };
+ setproto.clone = function (s) {
+ s = new Set;
+ for (var i = 0, ii = this.items.length; i < ii; i++) {
+ s.push(this.items[i].clone());
+ }
+ return s;
+ };
+ setproto.toString = function () {
+ return "Rapha\xebl\u2018s set";
+ };
+
+
+ R.registerFont = function (font) {
+ if (!font.face) {
+ return font;
+ }
+ this.fonts = this.fonts || {};
+ var fontcopy = {
+ w: font.w,
+ face: {},
+ glyphs: {}
+ },
+ family = font.face["font-family"];
+ for (var prop in font.face) if (font.face[has](prop)) {
+ fontcopy.face[prop] = font.face[prop];
+ }
+ if (this.fonts[family]) {
+ this.fonts[family].push(fontcopy);
+ } else {
+ this.fonts[family] = [fontcopy];
+ }
+ if (!font.svg) {
+ fontcopy.face["units-per-em"] = toInt(font.face["units-per-em"], 10);
+ for (var glyph in font.glyphs) if (font.glyphs[has](glyph)) {
+ var path = font.glyphs[glyph];
+ fontcopy.glyphs[glyph] = {
+ w: path.w,
+ k: {},
+ d: path.d && "M" + path.d.replace(/[mlcxtrv]/g, function (command) {
+ return {l: "L", c: "C", x: "z", t: "m", r: "l", v: "c"}[command] || "M";
+ }) + "z"
+ };
+ if (path.k) {
+ for (var k in path.k) if (path[has](k)) {
+ fontcopy.glyphs[glyph].k[k] = path.k[k];
+ }
+ }
+ }
+ }
+ return font;
+ };
+
+ paperproto.getFont = function (family, weight, style, stretch) {
+ stretch = stretch || "normal";
+ style = style || "normal";
+ weight = +weight || {normal: 400, bold: 700, lighter: 300, bolder: 800}[weight] || 400;
+ if (!R.fonts) {
+ return;
+ }
+ var font = R.fonts[family];
+ if (!font) {
+ var name = new RegExp("(^|\\s)" + family.replace(/[^\w\d\s+!~.:_-]/g, E) + "(\\s|$)", "i");
+ for (var fontName in R.fonts) if (R.fonts[has](fontName)) {
+ if (name.test(fontName)) {
+ font = R.fonts[fontName];
+ break;
+ }
+ }
+ }
+ var thefont;
+ if (font) {
+ for (var i = 0, ii = font.length; i < ii; i++) {
+ thefont = font[i];
+ if (thefont.face["font-weight"] == weight && (thefont.face["font-style"] == style || !thefont.face["font-style"]) && thefont.face["font-stretch"] == stretch) {
+ break;
+ }
+ }
+ }
+ return thefont;
+ };
+
+ paperproto.print = function (x, y, string, font, size, origin, letter_spacing) {
+ origin = origin || "middle"; // baseline|middle
+ letter_spacing = mmax(mmin(letter_spacing || 0, 1), -1);
+ var letters = Str(string)[split](E),
+ shift = 0,
+ notfirst = 0,
+ path = E,
+ scale;
+ R.is(font, string) && (font = this.getFont(font));
+ if (font) {
+ scale = (size || 16) / font.face["units-per-em"];
+ var bb = font.face.bbox[split](separator),
+ top = +bb[0],
+ lineHeight = bb[3] - bb[1],
+ shifty = 0,
+ height = +bb[1] + (origin == "baseline" ? lineHeight + (+font.face.descent) : lineHeight / 2);
+ for (var i = 0, ii = letters.length; i < ii; i++) {
+ if (letters[i] == "\n") {
+ shift = 0;
+ curr = 0;
+ notfirst = 0;
+ shifty += lineHeight;
+ } else {
+ var prev = notfirst && font.glyphs[letters[i - 1]] || {},
+ curr = font.glyphs[letters[i]];
+ shift += notfirst ? (prev.w || font.w) + (prev.k && prev.k[letters[i]] || 0) + (font.w * letter_spacing) : 0;
+ notfirst = 1;
+ }
+ if (curr && curr.d) {
+ path += R.transformPath(curr.d, ["t", shift * scale, shifty * scale, "s", scale, scale, top, height, "t", (x - top) / scale, (y - height) / scale]);
+ }
+ }
+ }
+ return this.path(path).attr({
+ fill: "#000",
+ stroke: "none"
+ });
+ };
+
+
+ paperproto.add = function (json) {
+ if (R.is(json, "array")) {
+ var res = this.set(),
+ i = 0,
+ ii = json.length,
+ j;
+ for (; i < ii; i++) {
+ j = json[i] || {};
+ elements[has](j.type) && res.push(this[j.type]().attr(j));
+ }
+ }
+ return res;
+ };
+
+
+ R.format = function (token, params) {
+ var args = R.is(params, array) ? [0][concat](params) : arguments;
+ token && R.is(token, string) && args.length - 1 && (token = token.replace(formatrg, function (str, i) {
+ return args[++i] == null ? E : args[i];
+ }));
+ return token || E;
+ };
+
+ R.fullfill = (function () {
+ var tokenRegex = /\{([^\}]+)\}/g,
+ objNotationRegex = /(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g, // matches .xxxxx or ["xxxxx"] to run over object properties
+ replacer = function (all, key, obj) {
+ var res = obj;
+ key.replace(objNotationRegex, function (all, name, quote, quotedName, isFunc) {
+ name = name || quotedName;
+ if (res) {
+ if (name in res) {
+ res = res[name];
+ }
+ typeof res == "function" && isFunc && (res = res());
+ }
+ });
+ res = (res == null || res == obj ? all : res) + "";
+ return res;
+ };
+ return function (str, obj) {
+ return String(str).replace(tokenRegex, function (all, key) {
+ return replacer(all, key, obj);
+ });
+ };
+ })();
+
+ R.ninja = function () {
+ oldRaphael.was ? (g.win.Raphael = oldRaphael.is) : delete Raphael;
+ return R;
+ };
+
+ R.st = setproto;
+ // Firefox <3.6 fix: http://webreflection.blogspot.com/2009/11/195-chars-to-help-lazy-loading.html
+ (function (doc, loaded, f) {
+ if (doc.readyState == null && doc.addEventListener) {
+ doc.addEventListener(loaded, f = function () {
+ doc.removeEventListener(loaded, f, false);
+ doc.readyState = "complete";
+ }, false);
+ doc.readyState = "loading";
+ }
+
+ function isLoaded() {
+ (/in/).test(doc.readyState) ? setTimeout(isLoaded, 9) : R.eve("raphael.DOMload");
+ }
+
+ isLoaded();
+ })(document, "DOMContentLoaded");
+
+ oldRaphael.was ? (g.win.Raphael = R) : (Raphael = R);
+
+ eve.on("raphael.DOMload", function () {
+ loaded = true;
+ });
+})();
+
+
+// ┌─────────────────────────────────────────────────────────────────────┐ \\
+// │ Raphaël - JavaScript Vector Library │ \\
+// ├─────────────────────────────────────────────────────────────────────┤ \\
+// │ SVG Module │ \\
+// ├─────────────────────────────────────────────────────────────────────┤ \\
+// │ Copyright (c) 2008-2011 Dmitry Baranovskiy (http://raphaeljs.com) │ \\
+// │ Copyright (c) 2008-2011 Sencha Labs (http://sencha.com) │ \\
+// │ Licensed under the MIT (http://raphaeljs.com/license.html) license. │ \\
+// └─────────────────────────────────────────────────────────────────────┘ \\
+window.Raphael.svg && function (R) {
+ var has = "hasOwnProperty",
+ Str = String,
+ toFloat = parseFloat,
+ toInt = parseInt,
+ math = Math,
+ mmax = math.max,
+ abs = math.abs,
+ pow = math.pow,
+ separator = /[, ]+/,
+ eve = R.eve,
+ E = "",
+ S = " ";
+ var xlink = "http://www.w3.org/1999/xlink",
+ markers = {
+ block: "M5,0 0,2.5 5,5z",
+ open_block: "M5,0 0,2.5 5,5z",
+ classic: "M5,0 0,2.5 5,5",
+ diamond: "M2.5,0 5,2.5 2.5,5 0,2.5z",
+ open_diamond: "M2.5,0 5,2.5 2.5,5 0,2.5z",
+ open: "M6,1 1,3.5 6,6",
+ oval: "M2.5,0A2.5,2.5,0,0,1,2.5,5 2.5,2.5,0,0,1,2.5,0z",
+ open_oval: "M2.5,0A2.5,2.5,0,0,1,2.5,5 2.5,2.5,0,0,1,2.5,0z"
+ },
+ markerCounter = {};
+ R.toString = function () {
+ return "Your browser supports SVG.\nYou are running Rapha\xebl " + this.version;
+ };
+ var $ = function (el, attr) {
+ if (attr) {
+ if (typeof el == "string") {
+ el = $(el);
+ }
+ for (var key in attr) if (attr[has](key)) {
+ if (key.substring(0, 6) == "xlink:") {
+ el.setAttributeNS(xlink, key.substring(6), Str(attr[key]));
+ } else {
+ el.setAttribute(key, Str(attr[key]));
+ }
+ }
+ } else {
+ el = R._g.doc.createElementNS("http://www.w3.org/2000/svg", el);
+ el.style && (el.style.webkitTapHighlightColor = "rgba(0,0,0,0)");
+ }
+ return el;
+ },
+ addGradientFill = function (element, gradient) {
+ var type = "linear",
+ id = element.id + gradient,
+ fx = .5, fy = .5,
+ o = element.node,
+ SVG = element.paper,
+ s = o.style,
+ el = R._g.doc.getElementById(id);
+
+ if (!el) {
+ gradient = Str(gradient).replace(R._radial_gradient, function (all, _fx, _fy) {
+ type = "radial";
+
+ /*
+ fx = toFloat(_fx);
+ fy = toFloat(_fy);
+ var dir = ((fy > .5) * 2 - 1);
+ pow(fx - .5, 2) + pow(fy - .5, 2) > .25 &&
+ (fy = math.sqrt(.25 - pow(fx - .5, 2)) * dir + .5) &&
+ fy != .5 &&
+ (fy = fy.toFixed(5) - 1e-5 * dir);
+ }
+ */
+ fx = _fx;
+ fy = _fy;
+
+ return E;
+ });
+
+ gradient = gradient.split(/\s*\-\s*/);
+ if (type == "linear") {
+ var angle = 0;
+
+ angle = -toFloat(angle);
+ if (isNaN(angle)) {
+ return null;
+ }
+ var vector = [0, 0, math.cos(R.rad(angle)), math.sin(R.rad(angle))],
+ max = 1 / (mmax(abs(vector[2]), abs(vector[3])) || 1);
+ vector[2] *= max;
+ vector[3] *= max;
+ if (vector[2] < 0) {
+ vector[0] = -vector[2];
+ vector[2] = 0;
+ }
+ if (vector[3] < 0) {
+ vector[1] = -vector[3];
+ vector[3] = 0;
+ }
+ }
+ var dots = R._parseDots(gradient);
+
+ if (!dots) {
+ return null;
+ }
+ id = id.replace(/[\(\)\s,\xb0#]/g, "_");
+ if (element.gradient && id != element.gradient.id) {
+ SVG.defs.removeChild(element.gradient);
+ delete element.gradient;
+ }
+
+ if (!element.gradient) {
+ el = $(type + "Gradient", {id: id});
+
+ element.gradient = el;
+ $(el, type == "radial" ? {
+ fx: fx,
+ fy: fy
+ } : {
+ x1: vector[0],
+ y1: vector[1],
+ x2: vector[2],
+ y2: vector[3],
+ gradientTransform: element.matrix.invert()
+ });
+ SVG.defs.appendChild(el);
+ for (var i = 0, ii = dots.length; i < ii; i++) {
+ el.appendChild($("stop", {
+ offset: dots[i].offset ? dots[i].offset : i ? "100%" : "0%",
+ "stop-color": dots[i].color || "#fff"
+ }));
+ }
+ }
+
+ if (element.attrs['fill-r']) {
+ el.setAttribute('r', element.attrs['fill-r']);
+ }
+ if (element.attrs['fill-cx']) {
+ el.setAttribute('cx', element.attrs['fill-cx']);
+ }
+
+ if (element.attrs['fill-cy']) {
+ el.setAttribute('cy', element.attrs['fill-cy']);
+ }
+ }
+ $(o, {
+ fill: "url(#" + id + ")",
+ opacity: 1,
+ "fill-opacity": 1
+ });
+ s.fill = E;
+ s.opacity = 1;
+ s.fillOpacity = 1;
+ return 1;
+ },
+ updatePosition = function (o) {
+ var bbox = o.getBBox(1);
+ $(o.pattern, {patternTransform: o.matrix.invert() + " translate(" + bbox.x + "," + bbox.y + ")"});
+ },
+ addArrow = function (o, value, isEnd) {
+ if (o.type == "path") {
+ var values = Str(value).toLowerCase().split("-"),
+ p = o.paper,
+ se = isEnd ? "end" : "start",
+ node = o.node,
+ attrs = o.attrs,
+ stroke = attrs["stroke-width"],
+ i = values.length,
+ type = "classic",
+ from,
+ to,
+ dx,
+ refX,
+ attr,
+ marker_stroke_width = stroke / 2,
+ w = 7,
+ h = 7,
+ t = 5;
+ while (i--) {
+ switch (values[i]) {
+ case "block":
+ case "open_block":
+ case "classic":
+ case "oval":
+ case "open_oval":
+ case "diamond":
+ case "open_diamond":
+ case "open":
+ case "none":
+ type = values[i];
+ break;
+ case "wide":
+ h = 10;
+ break;
+ case "narrow":
+ h = 5;
+ break;
+ case "long":
+ w = 10;
+ break;
+ case "short":
+ w = 5;
+ break;
+ }
+ }
+ if (type == "open") {
+ w += 2;
+ h += 2;
+ t += 2;
+ dx = 1;
+ refX = isEnd ? w - 2 : 1;
+ attr = {
+ fill: "none",
+ stroke: attrs.stroke,
+ 'stroke-dasharray': 0
+ };
+ } else if (type == 'classic') {
+ refX = dx = w / 2;
+ attr = {
+ fill: "none",
+ 'fill-opacity': 1,
+ stroke: attrs.stroke,
+ 'stroke-dasharray': 0
+ };
+ } else if (type == 'open_block' || type == 'open_diamond' || type == 'open_oval') {
+ refX = dx = w / 2;
+ attr = {
+ fill: 'white',
+ 'fill-opacity': 1,
+ stroke: attrs.stroke,
+ 'stroke-dasharray': 0
+ };
+ } else {
+ refX = dx = w / 2;
+ attr = {
+ fill: attrs.stroke,
+ 'fill-opacity': 1,
+ stroke: "none"
+ };
+ }
+ if (o._.arrows) {
+ if (isEnd) {
+ o._.arrows.endPath && markerCounter[o._.arrows.endPath]--;
+ o._.arrows.endMarker && markerCounter[o._.arrows.endMarker]--;
+ } else {
+ o._.arrows.startPath && markerCounter[o._.arrows.startPath]--;
+ o._.arrows.startMarker && markerCounter[o._.arrows.startMarker]--;
+ }
+ } else {
+ o._.arrows = {};
+ }
+ if (type != "none") {
+ var pathId = "raphael-marker-" + type,
+ markerId = "raphael-marker-" + se + type + w + h + p.id + "_" + (Math.floor(Math.random() * 100000) + 1);
+ if (!p.canvas.getElementById(pathId)) {
+ p.defs.appendChild($($("path"), {
+ "stroke-linecap": "round",
+ d: markers[type],
+ id: pathId
+ }));
+ markerCounter[pathId] = 1;
+ } else {
+ markerCounter[pathId]++;
+ }
+ var marker = R._g.doc.getElementById(markerId),
+ use;
+ if (!marker) {
+ marker = $($("marker"), {
+ id: markerId,
+ markerHeight: h,
+ markerWidth: w,
+ orient: "auto",
+ refX: refX,
+ refY: h / 2
+ });
+ use = $($("use"), {
+ "xlink:href": "#" + pathId,
+ transform: (isEnd ? "rotate(180 " + w / 2 + " " + h / 2 + ") " : E) + "scale(" + w / t + "," + h / t + ")",
+ "stroke-width": (1 / ((w / t + h / t) / 2)).toFixed(4)
+ });
+ marker.appendChild(use);
+ p.defs.appendChild(marker);
+ markerCounter[markerId] = 1;
+ } else {
+ markerCounter[markerId]++;
+ use = marker.getElementsByTagName("use")[0];
+ }
+ $(use, attr);
+ var delta = dx;
+ if (isEnd) {
+ from = o._.arrows.startdx * stroke || 0;
+ to = R.getTotalLength(attrs.path) - delta * stroke;
+ } else {
+ from = delta * stroke;
+ to = R.getTotalLength(attrs.path) - (o._.arrows.enddx * stroke || 0);
+ }
+ attr = {};
+ attr["marker-" + se] = "url(#" + markerId + ")";
+ if (to || from) {
+ attr.d = Raphael.getSubpath(attrs.path, from, to);
+ }
+ $(node, attr);
+ o._.arrows[se + "Path"] = pathId;
+ o._.arrows[se + "Marker"] = markerId;
+ o._.arrows[se + "dx"] = delta;
+ o._.arrows[se + "Type"] = type;
+ o._.arrows[se + "String"] = value;
+ } else {
+ if (isEnd) {
+ from = o._.arrows.startdx * stroke || 0;
+ to = R.getTotalLength(attrs.path) - from;
+ } else {
+ from = 0;
+ to = R.getTotalLength(attrs.path) - (o._.arrows.enddx * stroke || 0);
+ }
+ o._.arrows[se + "Path"] && $(node, {d: Raphael.getSubpath(attrs.path, from, to)});
+ delete o._.arrows[se + "Path"];
+ delete o._.arrows[se + "Marker"];
+ delete o._.arrows[se + "dx"];
+ delete o._.arrows[se + "Type"];
+ delete o._.arrows[se + "String"];
+ }
+
+ for (attr in markerCounter) if (markerCounter[has](attr) && !markerCounter[attr]) {
+ var item = R._g.doc.getElementById(attr);
+ item && item.parentNode.removeChild(item);
+ }
+ }
+ },
+ dasharray = {
+ "": [0],
+ "none": [0],
+ "-": [3, 1],
+ ".": [1, 1],
+ "-.": [3, 1, 1, 1],
+ "-..": [3, 1, 1, 1, 1, 1],
+ ". ": [1, 3],
+ "- ": [4, 3],
+ "--": [8, 3],
+ "- .": [4, 3, 1, 3],
+ "--.": [8, 3, 1, 3],
+ "--..": [8, 3, 1, 3, 1, 3]
+ },
+ addDashes = function (o, value, params) {
+ value = dasharray[Str(value).toLowerCase()];
+ if (value) {
+ var width = o.attrs["stroke-width"] || "1",
+ butt = {
+ round: width,
+ square: width,
+ butt: 0
+ }[o.attrs["stroke-linecap"] || params["stroke-linecap"]] || 0,
+ dashes = [],
+ i = value.length;
+ while (i--) {
+ dashes[i] = value[i] * width + ((i % 2) ? 1 : -1) * butt;
+ }
+ $(o.node, {"stroke-dasharray": dashes.join(",")});
+ }
+ },
+ setFillAndStroke = function (o, params, size) {
+ if (!o.node.style) {
+ o.node.style = {};
+ }
+ var node = o.node,
+ attrs = o.attrs,
+ vis = node.style.visibility;
+ node.style.visibility = "hidden";
+
+ for (var att in params) {
+ if (params[has](att)) {
+ if (!R._availableAttrs[has](att)) {
+ continue;
+ }
+ var value = params[att];
+ attrs[att] = value;
+ switch (att) {
+ case "blur":
+ o.blur(value);
+ break;
+ case "href":
+ case "title":
+ case "target":
+ var pn = node.parentNode;
+ if (pn.tagName.toLowerCase() != "a") {
+ var hl = $("a");
+ pn.insertBefore(hl, node);
+ hl.appendChild(node);
+ pn = hl;
+ }
+ if (att == "target") {
+ pn.setAttributeNS(xlink, "show", value == "blank" ? "new" : value);
+ } else {
+ pn.setAttributeNS(xlink, att, value);
+ }
+ break;
+ case "cursor":
+ node.style.cursor = value;
+ break;
+ case "transform":
+ o.transform(value);
+ break;
+ case "arrow-start":
+ addArrow(o, value);
+ break;
+ case "arrow-end":
+ addArrow(o, value, 1);
+ break;
+ case "clip-rect":
+ var rect = Str(value).split(separator);
+ if (rect.length == 4) {
+ o.clip && o.clip.parentNode.parentNode.removeChild(o.clip.parentNode);
+ var el = $("clipPath"),
+ rc = $("rect");
+ el.id = R.createUUID();
+ $(rc, {
+ x: rect[0],
+ y: rect[1],
+ width: rect[2],
+ height: rect[3]
+ });
+ el.appendChild(rc);
+ o.paper.defs.appendChild(el);
+ $(node, {"clip-path": "url(#" + el.id + ")"});
+ o.clip = rc;
+ }
+ if (!value) {
+ var path = node.getAttribute("clip-path");
+ if (path) {
+ var clip = R._g.doc.getElementById(path.replace(/(^url\(#|\)$)/g, E));
+ clip && clip.parentNode.removeChild(clip);
+ $(node, {"clip-path": E});
+ delete o.clip;
+ }
+ }
+ break;
+ case "path":
+ if (o.type == "path") {
+ $(node, {d: value ? attrs.path = R._pathToAbsolute(value) : "M0,0"});
+ o._.dirty = 1;
+ if (o._.arrows) {
+ "startString" in o._.arrows && addArrow(o, o._.arrows.startString);
+ "endString" in o._.arrows && addArrow(o, o._.arrows.endString, 1);
+ }
+ }
+ break;
+ case "width":
+ node.setAttribute(att, value);
+ o._.dirty = 1;
+ if (attrs.fx) {
+ att = "x";
+ value = attrs.x;
+ } else {
+ break;
+ }
+ case "x":
+ if (attrs.fx) {
+ value = -attrs.x - (attrs.width || 0);
+ }
+ case "rx":
+ if (att == "rx" && o.type == "rect") {
+ break;
+ }
+ case "cx":
+ node.setAttribute(att, value);
+ o.pattern && updatePosition(o);
+ o._.dirty = 1;
+ break;
+ case "height":
+ node.setAttribute(att, value);
+ o._.dirty = 1;
+ if (attrs.fy) {
+ att = "y";
+ value = attrs.y;
+ } else {
+ break;
+ }
+ case "y":
+ if (attrs.fy) {
+ value = -attrs.y - (attrs.height || 0);
+ }
+ case "ry":
+ if (att == "ry" && o.type == "rect") {
+ break;
+ }
+ case "cy":
+ node.setAttribute(att, value);
+ o.pattern && updatePosition(o);
+ o._.dirty = 1;
+ break;
+ case "r":
+ if (o.type == "rect") {
+ $(node, {rx: value, ry: value});
+ } else {
+ node.setAttribute(att, value);
+ }
+ o._.dirty = 1;
+ break;
+ case "src":
+ if (o.type == "image") {
+ node.setAttributeNS(xlink, "href", value);
+ }
+ break;
+ case "stroke-width":
+ if (o._.sx != 1 || o._.sy != 1) {
+ value /= mmax(abs(o._.sx), abs(o._.sy)) || 1;
+ }
+ if (o.paper._vbSize) {
+ value *= o.paper._vbSize;
+ }
+ node.setAttribute(att, value);
+ if (attrs["stroke-dasharray"]) {
+ addDashes(o, attrs["stroke-dasharray"], params);
+ }
+ if (o._.arrows) {
+ "startString" in o._.arrows && addArrow(o, o._.arrows.startString);
+ "endString" in o._.arrows && addArrow(o, o._.arrows.endString, 1);
+ }
+ break;
+ case "stroke-dasharray":
+ addDashes(o, value, params);
+ break;
+ case "fill":
+ var isURL = Str(value).match(R._ISURL);
+ if (isURL) {
+ el = $("pattern");
+ var ig = $("image");
+ el.id = R.createUUID();
+ $(el, {x: 0, y: 0, patternUnits: "userSpaceOnUse", height: 1, width: 1});
+ $(ig, {x: 0, y: 0, "xlink:href": isURL[1]});
+ el.appendChild(ig);
+
+ (function (el) {
+ R._preload(isURL[1], function () {
+ var w = this.offsetWidth,
+ h = this.offsetHeight;
+ $(el, {width: w, height: h});
+ $(ig, {width: w, height: h});
+ o.paper.safari();
+ });
+ })(el);
+ o.paper.defs.appendChild(el);
+ $(node, {fill: "url(#" + el.id + ")"});
+ o.pattern = el;
+ o.pattern && updatePosition(o);
+ break;
+ }
+ var clr = R.getRGB(value);
+ if (!clr.error) {
+ delete params.gradient;
+ delete attrs.gradient;
+ !R.is(attrs.opacity, "undefined") &&
+ R.is(params.opacity, "undefined") &&
+ $(node, {opacity: attrs.opacity});
+ !R.is(attrs["fill-opacity"], "undefined") &&
+ R.is(params["fill-opacity"], "undefined") &&
+ $(node, {"fill-opacity": attrs["fill-opacity"]});
+ } else if ((o.type == "circle" || o.type == "ellipse" || o.type == "path" || Str(value).charAt() != "r") && addGradientFill(o, value)) {
+ if ("opacity" in attrs || "fill-opacity" in attrs) {
+ var gradient = R._g.doc.getElementById(node.getAttribute("fill").replace(/^url\(#|\)$/g, E));
+ if (gradient) {
+ var stops = gradient.getElementsByTagName("stop");
+ $(stops[stops.length - 1], {"stop-opacity": ("opacity" in attrs ? attrs.opacity : 1) * ("fill-opacity" in attrs ? attrs["fill-opacity"] : 1)});
+ }
+ }
+ attrs.gradient = value;
+ attrs.fill = "none";
+ break;
+ }
+ clr[has]("opacity") && $(node, {"fill-opacity": clr.opacity > 1 ? clr.opacity / 100 : clr.opacity});
+ case "stroke":
+ clr = R.getRGB(value);
+ node.setAttribute(att, clr.hex);
+ att == "stroke" && clr[has]("opacity") && $(node, {"stroke-opacity": clr.opacity > 1 ? clr.opacity / 100 : clr.opacity});
+ if (att == "stroke" && o._.arrows) {
+ "startString" in o._.arrows && addArrow(o, o._.arrows.startString);
+ "endString" in o._.arrows && addArrow(o, o._.arrows.endString, 1);
+ }
+ break;
+ case "gradient":
+ (o.type == "circle" || o.type == "ellipse" || Str(value).charAt() != "r") && addGradientFill(o, value);
+ break;
+ case "opacity":
+ if (attrs.gradient && !attrs[has]("stroke-opacity")) {
+ $(node, {"stroke-opacity": value > 1 ? value / 100 : value});
+ }
+ case "fill-opacity":
+ if (attrs.gradient) {
+ gradient = R._g.doc.getElementById(node.getAttribute("fill").replace(/^url\(#|\)$/g, E));
+ if (gradient) {
+ stops = gradient.getElementsByTagName("stop");
+ $(stops[stops.length - 1], {"stop-opacity": value});
+ }
+ break;
+ }
+ // 추가 ("shape-rendering": "crispEdges")
+ case "shape-rendering":
+ node.setAttribute(att, value);
+ break;
+
+ case "text-decoration":
+ node.setAttribute(att, value);
+ break;
+
+ case "word-wrap":
+ node.setAttribute(att, value);
+ break;
+
+ default:
+ att == "font-size" && (value = toInt(value, 10) + "px");
+ var cssrule = att.replace(/(\-.)/g, function (w) {
+ return w.substring(1).toUpperCase();
+ });
+ node.style[cssrule] = value;
+ o._.dirty = 1;
+ node.setAttribute(att, value);
+ break;
+ }
+ }
+ }
+
+ tuneText(o, params, size);
+ node.style.visibility = vis;
+ },
+ leading = 1.2,
+ tuneText = function (el, params, size) {
+ if (el.type != "text" || !(params[has]("text") || params[has]("font") || params[has]("font-size") || params[has]("x") || params[has]("y"))) {
+ return;
+ }
+ var a = el.attrs,
+ node = el.node,
+ fontSize = node.firstChild ? toInt(R._g.doc.defaultView.getComputedStyle(node.firstChild, E).getPropertyValue("font-size"), 10) : 10;
+
+ if (params[has]("text")) {
+ a.text = params.text;
+ while (node.firstChild) {
+ node.removeChild(node.firstChild);
+ }
+ var tspans = [], finaltspans = [],
+ tspan;
+
+ //TODO 한 라인의 최대 글자 수. font size 를 얻어와야 하는데 param의 font 프로퍼티는 정확하지 않음.
+ var ogFontSize = params['font-size'];
+ ogFontSize = ogFontSize ? ogFontSize : 12;
+ var maxNum = parseInt(size[0] / (ogFontSize / 2));
+
+ function wordWrap(str, maxWidth) {
+ var texts = str.split("\n"), text;
+ var lines = [];
+ var done = false;
+ var testWhite = function (x) {
+ var white = new RegExp(/^\s$/);
+ return white.test(x.charAt(0));
+ };
+ for (var t = 0; t < texts.length; t++) {
+ text = texts[t];
+ do {
+ var found = false;
+ var res = '';
+ if (text.length <= maxWidth) {
+ lines.push(text);
+ done = true;
+ } else {
+ // maxWidth 1인경우 처리
+ if (maxWidth == 1) {
+ lines.push(text);
+ done = true;
+ break;
+ }
+ // Inserts new line at first whitespace of the line
+ for (i = maxWidth - 1; i >= 0; i--) {
+ if (testWhite(text.charAt(i))) {
+ res = res + text.slice(0, i);
+ text = text.slice(i + 1);
+ found = true;
+ lines.push(res);
+ break;
+ }
+ }
+
+ // Inserts new line at maxWidth position, the word is too long to wrap
+ if (!found) {
+ res = res + text.slice(0, maxWidth);
+ text = text.slice(maxWidth);
+ lines.push(res);
+ }
+ }
+ } while (!done);
+ }
+
+ var result = [];
+ for (var r = 0, lenr = lines.length; r < lenr; r++) {
+ if (lines[r] && lines[r].length > 0) {
+ result.push(lines[r]);
+ }
+ }
+ if (!lines.length) {
+ lines.push('');
+ }
+ return lines;
+ }
+
+ if (!params.text) {
+ finaltspans = [];
+ finaltspans.push('');
+ }
+ if (!maxNum || isNaN(maxNum)) {
+ finaltspans = [];
+ finaltspans.push(params.text);
+ } else if (params['word-wrap'] == 'none') {
+ finaltspans = params.text.split("\n");
+ }
+ else {
+ finaltspans = wordWrap(params.text, maxNum);
+ }
+ for (var i = 0, ii = finaltspans.length; i < ii; i++) {
+ tspan = $("tspan");
+ i && $(tspan, {dy: fontSize * leading, x: a.x});
+ tspan.appendChild(R._g.doc.createTextNode(finaltspans[i]));
+ node.appendChild(tspan);
+ tspans[i] = tspan;
+ }
+ } else {
+ tspans = node.getElementsByTagName("tspan");
+ for (i = 0, ii = tspans.length; i < ii; i++) if (i) {
+ $(tspans[i], {dy: fontSize * leading, x: a.x});
+ } else {
+ $(tspans[0], {dy: 0});
+ }
+ }
+ $(node, {x: a.x, y: a.y});
+ el._.dirty = 1;
+ var bb = el._getBBox(),
+ dif = a.y - (bb.y + bb.height / 2);
+ dif && R.is(dif, "finite") && $(tspans[0], {dy: dif});
+ },
+ Element = function (node, svg) {
+ var X = 0,
+ Y = 0;
+
+ this[0] = this.node = node;
+
+ node.raphael = true;
+
+ this.id = R._oid++;
+ node.raphaelid = this.id;
+ this.matrix = R.matrix();
+ this.realPath = null;
+
+ this.paper = svg;
+ this.attrs = this.attrs || {};
+ this._ = {
+ transform: [],
+ sx: 1,
+ sy: 1,
+ deg: 0,
+ dx: 0,
+ dy: 0,
+ dirty: 1
+ };
+ !svg.bottom && (svg.bottom = this);
+
+ this.prev = svg.top;
+ svg.top && (svg.top.next = this);
+ svg.top = this;
+
+ this.next = null;
+ },
+ elproto = R.el;
+ Element.prototype = elproto;
+ elproto.constructor = Element;
+
+ R._engine.path = function (pathString, SVG) {
+ var el = $("path");
+ SVG.canvas && SVG.canvas.appendChild(el);
+ var p = new Element(el, SVG);
+ p.type = "path";
+ setFillAndStroke(p, {
+ fill: "none",
+ stroke: "#000",
+ path: pathString
+ });
+ return p;
+ };
+
+ elproto.setTooltip = function (title) {
+ if (this.removed) {
+ return this;
+ }
+
+ this.node.setAttribute('tooltip', 'enable');
+ this.node.setAttribute('title', title);
+ };
+
+ elproto.rotate = function (deg, cx, cy) {
+
+ if (this.removed) {
+ return this;
+ }
+ deg = Str(deg).split(separator);
+ if (deg.length - 1) {
+ cx = toFloat(deg[1]);
+ cy = toFloat(deg[2]);
+ }
+ deg = toFloat(deg[0]);
+ (cy == null) && (cx = cy);
+ if (cx == null || cy == null) {
+ var bbox = this.getBBox(1);
+ cx = bbox.x + bbox.width / 2;
+ cy = bbox.y + bbox.height / 2;
+ }
+ this.transform(this._.transform.concat([["r", deg, cx, cy]]));
+ return this;
+ };
+
+ elproto.scale = function (sx, sy, cx, cy) {
+ if (this.removed) {
+ return this;
+ }
+ sx = Str(sx).split(separator);
+ if (sx.length - 1) {
+ sy = toFloat(sx[1]);
+ cx = toFloat(sx[2]);
+ cy = toFloat(sx[3]);
+ }
+ sx = toFloat(sx[0]);
+ (sy == null) && (sy = sx);
+ (cy == null) && (cx = cy);
+ if (cx == null || cy == null) {
+ var bbox = this.getBBox(1);
+ }
+ cx = cx == null ? bbox.x + bbox.width / 2 : cx;
+ cy = cy == null ? bbox.y + bbox.height / 2 : cy;
+ this.transform(this._.transform.concat([["s", sx, sy, cx, cy]]));
+ return this;
+ };
+
+ elproto.translate = function (dx, dy) {
+ if (this.removed) {
+ return this;
+ }
+ dx = Str(dx).split(separator);
+ if (dx.length - 1) {
+ dy = toFloat(dx[1]);
+ }
+ dx = toFloat(dx[0]) || 0;
+ dy = +dy || 0;
+ this.transform(this._.transform.concat([["t", dx, dy]]));
+ return this;
+ };
+
+ elproto.transform = function (tstr) {
+ var _ = this._;
+ if (tstr == null) {
+ return _.transform;
+ }
+ R._extractTransform(this, tstr);
+
+ this.clip && $(this.clip, {transform: this.matrix.invert()});
+ this.pattern && updatePosition(this);
+ this.node && $(this.node, {transform: this.matrix});
+
+ if (_.sx != 1 || _.sy != 1) {
+ var sw = this.attrs[has]("stroke-width") ? this.attrs["stroke-width"] : 1;
+ this.attr({"stroke-width": sw});
+ }
+
+ return this;
+ };
+
+ elproto.hide = function () {
+ !this.removed && this.paper.safari(this.node.style.display = "none");
+ return this;
+ };
+
+ elproto.show = function () {
+ !this.removed && this.paper.safari(this.node.style.display = "");
+ return this;
+ };
+
+ elproto.remove = function () {
+ if (this.removed || !this.node.parentNode) {
+ return;
+ }
+ var paper = this.paper;
+ paper.__set__ && paper.__set__.exclude(this);
+ eve.unbind("raphael.*.*." + this.id);
+ if (this.gradient) {
+ paper.defs.removeChild(this.gradient);
+ }
+ R._tear(this, paper);
+ if (this.node.parentNode.tagName.toLowerCase() == "a") {
+ this.node.parentNode.parentNode.removeChild(this.node.parentNode);
+ } else {
+ this.node.parentNode.removeChild(this.node);
+ }
+ for (var i in this) {
+ this[i] = typeof this[i] == "function" ? R._removedFactory(i) : null;
+ }
+ this.removed = true;
+ };
+ elproto._getBBox = function () {
+ if (this.node.style.display == "none") {
+ this.show();
+ var hide = true;
+ }
+ var bbox = {};
+ try {
+ bbox = this.node.getBBox();
+ } catch (e) {
+ // Firefox 3.0.x plays badly here
+ } finally {
+ bbox = bbox || {};
+ }
+ hide && this.hide();
+ return bbox;
+ };
+
+ elproto.attr = function (name, value) {
+ if (this.removed) {
+ return this;
+ }
+ if (name == null) {
+ var res = {};
+ for (var a in this.attrs) if (this.attrs[has](a)) {
+ res[a] = this.attrs[a];
+ }
+ res.gradient && res.fill == "none" && (res.fill = res.gradient) && delete res.gradient;
+ res.transform = this._.transform;
+ return res;
+ }
+ if (value == null && R.is(name, "string")) {
+ if (name == "fill" && this.attrs.fill == "none" && this.attrs.gradient) {
+ return this.attrs.gradient;
+ }
+ if (name == "transform") {
+ return this._.transform;
+ }
+ var names = name.split(separator),
+ out = {};
+ for (var i = 0, ii = names.length; i < ii; i++) {
+ name = names[i];
+ if (name in this.attrs) {
+ out[name] = this.attrs[name];
+ } else if (R.is(this.paper.customAttributes[name], "function")) {
+ out[name] = this.paper.customAttributes[name].def;
+ } else {
+ out[name] = R._availableAttrs[name];
+ }
+ }
+ return ii - 1 ? out : out[names[0]];
+ }
+ if (value == null && R.is(name, "array")) {
+ out = {};
+ for (i = 0, ii = name.length; i < ii; i++) {
+ out[name[i]] = this.attr(name[i]);
+ }
+ return out;
+ }
+ if (value != null) {
+ var params = {};
+ params[name] = value;
+ } else if (name != null && R.is(name, "object")) {
+ params = name;
+ }
+ for (var key in params) {
+ eve("raphael.attr." + key + "." + this.id, this, params[key]);
+ }
+ for (key in this.paper.customAttributes) if (this.paper.customAttributes[has](key) && params[has](key) && R.is(this.paper.customAttributes[key], "function")) {
+ var par = this.paper.customAttributes[key].apply(this, [].concat(params[key]));
+ this.attrs[key] = params[key];
+ for (var subkey in par) if (par[has](subkey)) {
+ params[subkey] = par[subkey];
+ }
+ }
+ setFillAndStroke(this, params);
+ return this;
+ };
+
+ elproto.toFront = function () {
+ if (this.removed) {
+ return this;
+ }
+ if (this.node.parentNode.tagName.toLowerCase() == "a") {
+ this.node.parentNode.parentNode.appendChild(this.node.parentNode);
+ } else {
+ this.node.parentNode.appendChild(this.node);
+ }
+ var svg = this.paper;
+ svg.top != this && R._tofront(this, svg);
+ return this;
+ };
+
+ elproto.toBack = function () {
+ if (this.removed) {
+ return this;
+ }
+ var parent = this.node.parentNode;
+ if (parent.tagName.toLowerCase() == "a") {
+ parent.parentNode.insertBefore(this.node.parentNode, this.node.parentNode.parentNode.firstChild);
+ } else if (parent.firstChild != this.node) {
+ parent.insertBefore(this.node, this.node.parentNode.firstChild);
+ }
+ R._toback(this, this.paper);
+ var svg = this.paper;
+ return this;
+ };
+
+ // 추가(for group 기능)
+ elproto.appendChild = function (element) {
+ if (this.removed) {
+ return this;
+ }
+ if (this.type !== 'group') {
+ throw new TypeError('appendChild function supports only the group type!');
+ }
+ var node = element.node || element[element.length - 1].node;
+ this.node.appendChild(node);
+ return this;
+ };
+
+ elproto.prependChild = function (element) {
+ if (this.removed) {
+ return this;
+ }
+ if (this.type !== 'group') {
+ throw new TypeError('appendChild function supports only the group type!');
+ }
+
+ var node = element.node || element[element.length - 1].node;
+ this.node.insertBefore(node, this.node.firstChild);
+
+ return this;
+ };
+
+ elproto.insertAfter = function (element) {
+ if (this.removed) {
+ return this;
+ }
+ var node = element.node || element[element.length - 1].node;
+ if (node.nextSibling) {
+ node.parentNode.insertBefore(this.node, node.nextSibling);
+ } else {
+ node.parentNode.appendChild(this.node);
+ }
+ R._insertafter(this, element, this.paper);
+ return this;
+ };
+
+ elproto.insertBefore = function (element) {
+ if (this.removed) {
+ return this;
+ }
+ var node = element.node || element[0].node;
+ node.parentNode.insertBefore(this.node, node);
+ R._insertbefore(this, element, this.paper);
+ return this;
+ };
+ elproto.blur = function (size) {
+ // Experimental. No Safari support. Use it on your own risk.
+ var t = this;
+ if (+size !== 0) {
+ var fltr = $("filter"),
+ blur = $("feGaussianBlur");
+ t.attrs.blur = size;
+ fltr.id = R.createUUID();
+ $(blur, {stdDeviation: +size || 1.5});
+ fltr.appendChild(blur);
+ t.paper.defs.appendChild(fltr);
+ t._blur = fltr;
+ $(t.node, {filter: "url(#" + fltr.id + ")"});
+ } else {
+ if (t._blur) {
+ t._blur.parentNode.removeChild(t._blur);
+ delete t._blur;
+ delete t.attrs.blur;
+ }
+ t.node.removeAttribute("filter");
+ }
+ };
+ // 추가(for group 기능)
+ R._engine.group = function (svg, x, y) {
+ var el = $("g");
+ if (x && y) {
+ el.setAttributeNS(null, "transform", "translate(" + x + ", " + y + ")");
+ }
+ svg.canvas && svg.canvas.appendChild(el);
+ var res = new Element(el, svg);
+ res.attrs = {x: x, y: y, fill: "none", stroke: "#000"};
+ res.type = "group";
+ $(el, res.attrs);
+ return res;
+ };
+ R._engine.circle = function (svg, x, y, r) {
+ var el = $("circle");
+ svg.canvas && svg.canvas.appendChild(el);
+ var res = new Element(el, svg);
+ res.attrs = {cx: x, cy: y, r: r, fill: "none", stroke: "#000"};
+ res.type = "circle";
+ $(el, res.attrs);
+ return res;
+ };
+ R._engine.rect = function (svg, x, y, w, h, r) {
+ var el = $("rect");
+ svg.canvas && svg.canvas.appendChild(el);
+ var res = new Element(el, svg);
+ res.attrs = {x: x, y: y, width: w, height: h, r: r || 0, rx: r || 0, ry: r || 0, fill: "none", stroke: "#000"};
+ res.type = "rect";
+ $(el, res.attrs);
+ return res;
+ };
+ R._engine.ellipse = function (svg, x, y, rx, ry) {
+ var el = $("ellipse");
+ svg.canvas && svg.canvas.appendChild(el);
+ var res = new Element(el, svg);
+ res.attrs = {cx: x, cy: y, rx: rx, ry: ry, fill: "none", stroke: "#000"};
+ res.type = "ellipse";
+ $(el, res.attrs);
+ return res;
+ };
+ R._engine.image = function (svg, src, x, y, w, h, title) {
+ var el = $("image");
+ $(el, {x: x, y: y, width: w, height: h, preserveAspectRatio: "none", title: title});
+ el.setAttributeNS(xlink, "href", src);
+ svg.canvas && svg.canvas.appendChild(el);
+ var res = new Element(el, svg);
+ res.attrs = {x: x, y: y, width: w, height: h, src: src};
+ res.type = "image";
+ return res;
+ };
+ R._engine.text = function (svg, x, y, text, size) {
+ var el = $("text");
+ svg.canvas && svg.canvas.appendChild(el);
+ var res = new Element(el, svg);
+ res.attrs = {
+ x: x,
+ y: y,
+ "text-anchor": "middle",
+ text: text,
+ font: R._availableAttrs.font,
+ stroke: "none",
+ fill: "#000"
+ };
+ res.type = "text";
+ setFillAndStroke(res, res.attrs, size);
+ return res;
+ };
+ // 추가(for foreignObject 기능)
+ R._engine.foreignObject = function (svg, x, y, w, h, obj) {
+ if ((typeof w) !== 'number') {
+ obj = w;
+ w = obj.offsetWidth;
+ h = obj.offsetHeight;
+ }
+ var res;
+ if ((/msie 9/).test(navigator.userAgent.toLowerCase()) || document.documentMode === 9) {
+ // TODO : 개선필요
+ var el = $("div");
+ el.style.cssText = [
+ "position:absolute",
+ "left:" + (x - w / 2) + "px",
+ "top:" + (y - h / 2) + "px",
+ "width:" + w + "px",
+ "height:" + h + "px"
+ ].join(";") + ";";
+
+ svg.canvas && svg.canvas.appendChild(el);
+ res = new Element(el, svg);
+ res.attrs = {x: x, y: y, width: w, height: h};
+ res.type = "foreignObject";
+ $(el, res.attrs);
+ if (obj) {
+ var div = document.createElement('div');
+ div.innerHTML = obj;
+ res.node.appendChild(div);
+ }
+ } else {
+ var el = $("foreignObject");
+ svg.canvas && svg.canvas.appendChild(el);
+ res = new Element(el, svg);
+ res.attrs = {x: x, y: y, width: w, height: h};
+ res.type = "foreignObject";
+ $(el, res.attrs);
+ if (obj) {
+ var div = document.createElement('tspan');
+ div.innerHTML = obj;
+ /*div.style.cssText = [
+ "word-wrap: break-word"
+ ].join(";") + ";";
+ */
+ res.node.appendChild(div);
+ res.attrs = {x: x, y: y, width: div.offsetWidth, height: div.offsetHeight};
+ $(el, res.attrs);
+ }
+ }
+
+ return res;
+ };
+ R._engine.setSize = function (width, height) {
+ this.width = width || this.width;
+ this.height = height || this.height;
+ this.canvas.setAttribute("width", this.width);
+ this.canvas.setAttribute("height", this.height);
+ if (this._viewBox) {
+ this.setViewBox.apply(this, this._viewBox);
+ }
+ return this;
+ };
+ R._engine.create = function () {
+ var con = R._getContainer.apply(0, arguments),
+ container = con && con.container,
+ x = con.x,
+ y = con.y,
+ width = con.width,
+ height = con.height;
+ if (!container) {
+ throw new Error("SVG container not found.");
+ }
+ var cnvs = $("svg"),
+ css = "overflow:hidden;",
+ isFloating;
+ x = x || 0;
+ y = y || 0;
+ width = width || 512;
+ height = height || 342;
+ $(cnvs, {
+ height: height,
+ version: 1.1,
+ width: width,
+ xmlns: "http://www.w3.org/2000/svg",
+ "xmlns:xlink": "http://www.w3.org/1999/xlink"
+ });
+ if (container == 1) {
+ cnvs.style.cssText = css + "position:absolute;left:" + x + "px;top:" + y + "px";
+ R._g.doc.body.appendChild(cnvs);
+ isFloating = 1;
+ } else {
+ cnvs.style.cssText = css + "position:relative";
+ if (container.firstChild) {
+ container.insertBefore(cnvs, container.firstChild);
+ } else {
+ container.appendChild(cnvs);
+ }
+ }
+ container = new R._Paper;
+ container.width = width;
+ container.height = height;
+ container.canvas = cnvs;
+ container.clear();
+ container._left = container._top = 0;
+ isFloating && (container.renderfix = function () {
+ });
+ container.renderfix();
+ return container;
+ };
+ R._engine.setViewBox = function (x, y, w, h, fit) {
+ eve("raphael.setViewBox", this, this._viewBox, [x, y, w, h, fit]);
+ var size = mmax(w / this.width, h / this.height),
+ top = this.top,
+ aspectRatio = fit ? "meet" : "xMinYMin",
+ vb,
+ sw;
+ if (x == null) {
+ if (this._vbSize) {
+ size = 1;
+ }
+ delete this._vbSize;
+ vb = "0 0 " + this.width + S + this.height;
+ } else {
+ this._vbSize = size;
+ vb = x + S + y + S + w + S + h;
+ }
+ $(this.canvas, {
+ viewBox: vb,
+ preserveAspectRatio: aspectRatio
+ });
+ while (size && top) {
+ sw = "stroke-width" in top.attrs ? top.attrs["stroke-width"] : 1;
+ top.attr({"stroke-width": sw});
+ top._.dirty = 1;
+ top._.dirtyT = 1;
+ top = top.prev;
+ }
+ this._viewBox = [x, y, w, h, !!fit];
+ return this;
+ };
+
+ R.prototype.renderfix = function () {
+ var cnvs = this.canvas,
+ s = cnvs.style,
+ pos;
+ try {
+ pos = cnvs.getScreenCTM() || cnvs.createSVGMatrix();
+ } catch (e) {
+ pos = cnvs.createSVGMatrix();
+ }
+ var left = -pos.e % 1,
+ top = -pos.f % 1;
+ if (left || top) {
+ if (left) {
+ this._left = (this._left + left) % 1;
+ s.left = this._left + "px";
+ }
+ if (top) {
+ this._top = (this._top + top) % 1;
+ s.top = this._top + "px";
+ }
+ }
+ };
+
+ R.prototype.clear = function () {
+ R.eve("raphael.clear", this);
+ var c = this.canvas;
+ while (c.firstChild) {
+ c.removeChild(c.firstChild);
+ }
+ this.bottom = this.top = null;
+ (this.desc = $("desc")).appendChild(R._g.doc.createTextNode("Created with Rapha\xebl " + R.version));
+ c.appendChild(this.desc);
+ c.appendChild(this.defs = $("defs"));
+ };
+
+ R.prototype.remove = function () {
+ eve("raphael.remove", this);
+ this.canvas.parentNode && this.canvas.parentNode.removeChild(this.canvas);
+ for (var i in this) {
+ this[i] = typeof this[i] == "function" ? R._removedFactory(i) : null;
+ }
+ };
+ var setproto = R.st;
+ for (var method in elproto) if (elproto[has](method) && !setproto[has](method)) {
+ setproto[method] = (function (methodname) {
+ return function () {
+ var arg = arguments;
+ return this.forEach(function (el) {
+ el[methodname].apply(el, arg);
+ });
+ };
+ })(method);
+ }
+}(window.Raphael);
+
+// ┌─────────────────────────────────────────────────────────────────────┐ \\
+// │ Raphaël - JavaScript Vector Library │ \\
+// ├─────────────────────────────────────────────────────────────────────┤ \\
+// │ VML Module │ \\
+// ├─────────────────────────────────────────────────────────────────────┤ \\
+// │ Copyright (c) 2008-2011 Dmitry Baranovskiy (http://raphaeljs.com) │ \\
+// │ Copyright (c) 2008-2011 Sencha Labs (http://sencha.com) │ \\
+// │ Licensed under the MIT (http://raphaeljs.com/license.html) license. │ \\
+// └─────────────────────────────────────────────────────────────────────┘ \\
+window.Raphael.vml && function (R) {
+ var has = "hasOwnProperty",
+ Str = String,
+ toFloat = parseFloat,
+ math = Math,
+ round = math.round,
+ mmax = math.max,
+ mmin = math.min,
+ abs = math.abs,
+ fillString = "fill",
+ separator = /[, ]+/,
+ eve = R.eve,
+ ms = " progid:DXImageTransform.Microsoft",
+ S = " ",
+ E = "",
+ map = {M: "m", L: "l", C: "c", Z: "x", m: "t", l: "r", c: "v", z: "x"},
+ bites = /([clmz]),?([^clmz]*)/gi,
+ blurregexp = / progid:\S+Blur\([^\)]+\)/g,
+ val = /-?[^,\s-]+/g,
+ cssDot = "position:absolute;left:0;top:0;width:1px;height:1px",
+ zoom = 21600,
+ pathTypes = {path: 1, rect: 1, image: 1},
+ ovalTypes = {circle: 1, ellipse: 1},
+ path2vml = function (path) {
+ var total = /[ahqstv]/ig,
+ command = R._pathToAbsolute;
+ Str(path).match(total) && (command = R._path2curve);
+ total = /[clmz]/g;
+ if (command == R._pathToAbsolute && !Str(path).match(total)) {
+ var res = Str(path).replace(bites, function (all, command, args) {
+ var vals = [],
+ isMove = command.toLowerCase() == "m",
+ res = map[command];
+ args.replace(val, function (value) {
+ if (isMove && vals.length == 2) {
+ res += vals + map[command == "m" ? "l" : "L"];
+ vals = [];
+ }
+ vals.push(round(value * zoom));
+ });
+ return res + vals;
+ });
+ return res;
+ }
+ var pa = command(path), p, r;
+ res = [];
+ for (var i = 0, ii = pa.length; i < ii; i++) {
+ p = pa[i];
+ r = pa[i][0].toLowerCase();
+ r == "z" && (r = "x");
+ for (var j = 1, jj = p.length; j < jj; j++) {
+ r += round(p[j] * zoom) + (j != jj - 1 ? "," : E);
+ }
+ res.push(r);
+ }
+ return res.join(S);
+ },
+ compensation = function (deg, dx, dy) {
+ var m = R.matrix();
+ m.rotate(-deg, .5, .5);
+ return {
+ dx: m.x(dx, dy),
+ dy: m.y(dx, dy)
+ };
+ },
+ setCoords = function (p, sx, sy, dx, dy, deg) {
+ var _ = p._,
+ m = p.matrix,
+ fillpos = _.fillpos,
+ o = p.node,
+ s = o.style,
+ y = 1,
+ flip = "",
+ dxdy,
+ kx = zoom / sx,
+ ky = zoom / sy;
+ s.visibility = "hidden";
+ if (!sx || !sy) {
+ return;
+ }
+ o.coordsize = abs(kx) + S + abs(ky);
+ s.rotation = deg * (sx * sy < 0 ? -1 : 1);
+ if (deg) {
+ var c = compensation(deg, dx, dy);
+ dx = c.dx;
+ dy = c.dy;
+ }
+ sx < 0 && (flip += "x");
+ sy < 0 && (flip += " y") && (y = -1);
+ s.flip = flip;
+ o.coordorigin = (dx * -kx) + S + (dy * -ky);
+ if (fillpos || _.fillsize) {
+ var fill = o.getElementsByTagName(fillString);
+ fill = fill && fill[0];
+ o.removeChild(fill);
+ if (fillpos) {
+ c = compensation(deg, m.x(fillpos[0], fillpos[1]), m.y(fillpos[0], fillpos[1]));
+ fill.position = c.dx * y + S + c.dy * y;
+ }
+ if (_.fillsize) {
+ fill.size = _.fillsize[0] * abs(sx) + S + _.fillsize[1] * abs(sy);
+ }
+ o.appendChild(fill);
+ }
+ s.visibility = "visible";
+ };
+ R.toString = function () {
+ return "Your browser doesn\u2019t support SVG. Falling down to VML.\nYou are running Rapha\xebl " + this.version;
+ };
+ var addArrow = function (o, value, isEnd) {
+ var values = Str(value).toLowerCase().split("-"),
+ se = isEnd ? "end" : "start",
+ i = values.length,
+ type = "classic",
+ w = "medium",
+ h = "medium";
+ while (i--) {
+ switch (values[i]) {
+ case "block":
+ case "classic":
+ case "oval":
+ case "diamond":
+ case "open":
+ case "none":
+ type = values[i];
+ break;
+ case "open_block":
+ type = 'block';
+ break;
+ case "open_oval":
+ type = 'oval';
+ break;
+ case "open_diamond":
+ type = 'diamond';
+ break;
+ case "wide":
+ case "narrow":
+ h = values[i];
+ break;
+ case "long":
+ case "short":
+ w = values[i];
+ break;
+ }
+ }
+ var stroke = o.node.getElementsByTagName("stroke")[0];
+ stroke[se + "arrow"] = type;
+ stroke[se + "arrowlength"] = w;
+ stroke[se + "arrowwidth"] = h;
+ },
+ setFillAndStroke = function (o, params) {
+ // o.paper.canvas.style.display = "none";
+ o.attrs = o.attrs || {};
+ var node = o.node,
+ a = o.attrs,
+ s = node.style,
+ xy,
+ newpath = pathTypes[o.type] && (params.x != a.x || params.y != a.y || params.width != a.width || params.height != a.height || params.cx != a.cx || params.cy != a.cy || params.rx != a.rx || params.ry != a.ry || params.r != a.r),
+ isOval = ovalTypes[o.type] && (a.cx != params.cx || a.cy != params.cy || a.r != params.r || a.rx != params.rx || a.ry != params.ry),
+ res = o;
+
+
+ for (var par in params) if (params[has](par)) {
+ a[par] = params[par];
+ }
+ if (newpath) {
+ a.path = R._getPath[o.type](o);
+ o._.dirty = 1;
+ }
+ params.href && (node.href = params.href);
+ params.title && (node.title = params.title);
+ params.target && (node.target = params.target);
+ params.cursor && (s.cursor = params.cursor);
+ "blur" in params && o.blur(params.blur);
+ if (params.path && o.type == "path" || newpath) {
+ node.path = path2vml(~Str(a.path).toLowerCase().indexOf("r") ? R._pathToAbsolute(a.path) : a.path);
+ if (o.type == "image") {
+ o._.fillpos = [a.x, a.y];
+ o._.fillsize = [a.width, a.height];
+ setCoords(o, 1, 1, 0, 0, 0);
+ }
+ }
+ "transform" in params && o.transform(params.transform);
+ if (isOval) {
+ var cx = +a.cx,
+ cy = +a.cy,
+ rx = +a.rx || +a.r || 0,
+ ry = +a.ry || +a.r || 0;
+ node.path = R.format("ar{0},{1},{2},{3},{4},{1},{4},{1}x", round((cx - rx) * zoom), round((cy - ry) * zoom), round((cx + rx) * zoom), round((cy + ry) * zoom), round(cx * zoom));
+ }
+ if ("clip-rect" in params) {
+ var rect = Str(params["clip-rect"]).split(separator);
+ if (rect.length == 4) {
+ rect[2] = +rect[2] + (+rect[0]);
+ rect[3] = +rect[3] + (+rect[1]);
+ var div = node.clipRect || R._g.doc.createElement("div"),
+ dstyle = div.style;
+ dstyle.clip = R.format("rect({1}px {2}px {3}px {0}px)", rect);
+ if (!node.clipRect) {
+ dstyle.position = "absolute";
+ dstyle.top = 0;
+ dstyle.left = 0;
+ dstyle.width = o.paper.width + "px";
+ dstyle.height = o.paper.height + "px";
+ node.parentNode.insertBefore(div, node);
+ div.appendChild(node);
+ node.clipRect = div;
+ }
+ }
+ if (!params["clip-rect"]) {
+ node.clipRect && (node.clipRect.style.clip = "auto");
+ }
+ }
+ if (o.textpath) {
+ var textpathStyle = o.textpath.style;
+ params.font && (textpathStyle.font = params.font);
+ params["font-family"] && (textpathStyle.fontFamily = '"' + params["font-family"].split(",")[0].replace(/^['"]+|['"]+$/g, E) + '"');
+ params["font-size"] && (textpathStyle.fontSize = params["font-size"]);
+ params["font-weight"] && (textpathStyle.fontWeight = params["font-weight"]);
+ params["font-style"] && (textpathStyle.fontStyle = params["font-style"]);
+ }
+ if ("arrow-start" in params) {
+ addArrow(res, params["arrow-start"]);
+ }
+ if ("arrow-end" in params) {
+ addArrow(res, params["arrow-end"], 1);
+ }
+ if (params.opacity != null ||
+ params["stroke-width"] != null ||
+ params.fill != null ||
+ params.src != null ||
+ params.stroke != null ||
+ params["stroke-width"] != null ||
+ params["stroke-opacity"] != null ||
+ params["fill-opacity"] != null ||
+ params["stroke-dasharray"] != null ||
+ params["stroke-miterlimit"] != null ||
+ params["stroke-linejoin"] != null ||
+ params["stroke-linecap"] != null) {
+
+ var fill = node.getElementsByTagName(fillString),
+ newfill = false;
+ fill = fill && fill[0];
+ !fill && (newfill = fill = createNode(fillString));
+ if (o.type == "image" && params.src) {
+ fill.src = params.src;
+ }
+ params.fill && (fill.on = true);
+ if (fill.on == null || params.fill == "none" || params.fill === null) {
+ fill.on = false;
+ }
+ if (fill.on && params.fill) {
+ var isURL = Str(params.fill).match(R._ISURL);
+ if (isURL) {
+ fill.parentNode == node && node.removeChild(fill);
+ fill.rotate = true;
+ fill.src = isURL[1];
+ fill.type = "tile";
+ var bbox = o.getBBox(1);
+ fill.position = bbox.x + S + bbox.y;
+ o._.fillpos = [bbox.x, bbox.y];
+
+ R._preload(isURL[1], function () {
+ o._.fillsize = [this.offsetWidth, this.offsetHeight];
+ });
+ } else {
+ fill.color = R.getRGB(params.fill).hex;
+ fill.src = E;
+ fill.type = "solid";
+ if (R.getRGB(params.fill).error && (res.type in {
+ circle: 1,
+ ellipse: 1
+ } || Str(params.fill).charAt() != "r") && addGradientFill(res, params.fill, fill)) {
+ a.fill = "none";
+ a.gradient = params.fill;
+ fill.rotate = false;
+ }
+ }
+ }
+ if ("fill-opacity" in params || "opacity" in params) {
+ var opacity = ((+a["fill-opacity"] + 1 || 2) - 1) * ((+a.opacity + 1 || 2) - 1) * ((+R.getRGB(params.fill).o + 1 || 2) - 1);
+ opacity = mmin(mmax(opacity, 0), 1);
+ fill.opacity = opacity;
+ if (fill.src) {
+ fill.color = "none";
+ }
+ }
+ node.appendChild(fill);
+ var stroke = (node.getElementsByTagName("stroke") && node.getElementsByTagName("stroke")[0]),
+ newstroke = false;
+ !stroke && (newstroke = stroke = createNode("stroke"));
+ if ((params.stroke && params.stroke != "none") ||
+ params["stroke-width"] ||
+ params["stroke-opacity"] != null ||
+ params["stroke-dasharray"] ||
+ params["stroke-miterlimit"] ||
+ params["stroke-linejoin"] ||
+ params["stroke-linecap"]) {
+ stroke.on = true;
+ }
+ (params.stroke == "none" || params.stroke === null || stroke.on == null || params.stroke == 0 || params["stroke-width"] == 0) && (stroke.on = false);
+ var strokeColor = R.getRGB(params.stroke);
+ stroke.on && params.stroke && (stroke.color = strokeColor.hex);
+ opacity = ((+a["stroke-opacity"] + 1 || 2) - 1) * ((+a.opacity + 1 || 2) - 1) * ((+strokeColor.o + 1 || 2) - 1);
+ var width = (toFloat(params["stroke-width"]) || 1) * .75;
+ opacity = mmin(mmax(opacity, 0), 1);
+ params["stroke-width"] == null && (width = a["stroke-width"]);
+ params["stroke-width"] && (stroke.weight = width);
+ width && width < 1 && (opacity *= width) && (stroke.weight = 1);
+ stroke.opacity = opacity;
+
+ params["stroke-linejoin"] && (stroke.joinstyle = params["stroke-linejoin"] || "miter");
+ stroke.miterlimit = params["stroke-miterlimit"] || 8;
+ params["stroke-linecap"] && (stroke.endcap = params["stroke-linecap"] == "butt" ? "flat" : params["stroke-linecap"] == "square" ? "square" : "round");
+ if (params["stroke-dasharray"]) {
+ var dasharray = {
+ "-": "shortdash",
+ ".": "shortdot",
+ "-.": "shortdashdot",
+ "-..": "shortdashdotdot",
+ ". ": "dot",
+ "- ": "dash",
+ "--": "longdash",
+ "- .": "dashdot",
+ "--.": "longdashdot",
+ "--..": "longdashdotdot"
+ };
+ stroke.dashstyle = dasharray[has](params["stroke-dasharray"]) ? dasharray[params["stroke-dasharray"]] : E;
+ }
+ newstroke && node.appendChild(stroke);
+ }
+ if (res.type == "text") {
+ res.paper.canvas.style.display = E;
+ var span = res.paper.span,
+ m = 100,
+ fontSize = a.font && a.font.match(/\d+(?:\.\d*)?(?=px)/);
+ s = span.style;
+ a.font && (s.font = a.font);
+ a["font-family"] && (s.fontFamily = a["font-family"]);
+ a["font-weight"] && (s.fontWeight = a["font-weight"]);
+ a["font-style"] && (s.fontStyle = a["font-style"]);
+ fontSize = toFloat(a["font-size"] || fontSize && fontSize[0]) || 10;
+ s.fontSize = fontSize * m + "px";
+ res.textpath.string && (span.innerHTML = Str(res.textpath.string).replace(/"));
+ var brect = span.getBoundingClientRect();
+ res.W = a.w = (brect.right - brect.left) / m;
+ res.H = a.h = (brect.bottom - brect.top) / m;
+ // res.paper.canvas.style.display = "none";
+ res.X = a.x;
+ res.Y = a.y + res.H / 2;
+
+ ("x" in params || "y" in params) && (res.path.v = R.format("m{0},{1}l{2},{1}", round(a.x * zoom), round(a.y * zoom), round(a.x * zoom) + 1));
+ var dirtyattrs = ["x", "y", "text", "font", "font-family", "font-weight", "font-style", "font-size"];
+ for (var d = 0, dd = dirtyattrs.length; d < dd; d++) if (dirtyattrs[d] in params) {
+ res._.dirty = 1;
+ break;
+ }
+
+ // text-anchor emulation
+ switch (a["text-anchor"]) {
+ case "start":
+ res.textpath.style["v-text-align"] = "left";
+ res.bbx = res.W / 2;
+ break;
+ case "end":
+ res.textpath.style["v-text-align"] = "right";
+ res.bbx = -res.W / 2;
+ break;
+ default:
+ res.textpath.style["v-text-align"] = "center";
+ res.bbx = 0;
+ break;
+ }
+ res.textpath.style["v-text-kern"] = true;
+ }
+ // 추가 ("shape-rendering": "crispEdges")
+ if ("shape-rendering" in params) {
+ // TODO : VML 에 대해 처리 필요
+ }
+ // res.paper.canvas.style.display = E;
+ },
+ addGradientFill = function (o, gradient, fill) {
+ o.attrs = o.attrs || {};
+ var attrs = o.attrs,
+ pow = Math.pow,
+ opacity,
+ oindex,
+ type = "linear",
+ fxfy = ".5 .5";
+ o.attrs.gradient = gradient;
+ gradient = Str(gradient).replace(R._radial_gradient, function (all, fx, fy) {
+ type = "radial";
+ if (fx && fy) {
+ fx = toFloat(fx);
+ fy = toFloat(fy);
+ pow(fx - .5, 2) + pow(fy - .5, 2) > .25 && (fy = math.sqrt(.25 - pow(fx - .5, 2)) * ((fy > .5) * 2 - 1) + .5);
+ fxfy = fx + S + fy;
+ }
+ return E;
+ });
+ gradient = gradient.split(/\s*\-\s*/);
+ if (type == "linear") {
+ var angle = gradient.shift();
+ angle = -toFloat(angle);
+ if (isNaN(angle)) {
+ return null;
+ }
+ }
+ var dots = R._parseDots(gradient);
+ if (!dots) {
+ return null;
+ }
+ o = o.shape || o.node;
+ if (dots.length) {
+ o.removeChild(fill);
+ fill.on = true;
+ fill.method = "none";
+ fill.color = dots[0].color;
+ fill.color2 = dots[dots.length - 1].color;
+ var clrs = [];
+ for (var i = 0, ii = dots.length; i < ii; i++) {
+ dots[i].offset && clrs.push(dots[i].offset + S + dots[i].color);
+ }
+ fill.colors = clrs.length ? clrs.join() : "0% " + fill.color;
+ if (type == "radial") {
+ fill.type = "gradientTitle";
+ fill.focus = "100%";
+ fill.focussize = "0 0";
+ fill.focusposition = fxfy;
+ fill.angle = 0;
+ } else {
+ // fill.rotate= true;
+ fill.type = "gradient";
+ fill.angle = (270 - angle) % 360;
+ }
+ o.appendChild(fill);
+ }
+ return 1;
+ },
+ Element = function (node, vml) {
+ this[0] = this.node = node;
+ node.raphael = true;
+ this.id = R._oid++;
+ node.raphaelid = this.id;
+ this.X = 0;
+ this.Y = 0;
+ this.attrs = {};
+ this.paper = vml;
+ this.matrix = R.matrix();
+ this._ = {
+ transform: [],
+ sx: 1,
+ sy: 1,
+ dx: 0,
+ dy: 0,
+ deg: 0,
+ dirty: 1,
+ dirtyT: 1
+ };
+ !vml.bottom && (vml.bottom = this);
+ this.prev = vml.top;
+ vml.top && (vml.top.next = this);
+ vml.top = this;
+ this.next = null;
+ };
+ var elproto = R.el;
+
+ Element.prototype = elproto;
+ elproto.constructor = Element;
+ elproto.transform = function (tstr) {
+ if (tstr == null) {
+ return this._.transform;
+ }
+ var vbs = this.paper._viewBoxShift,
+ vbt = vbs ? "s" + [vbs.scale, vbs.scale] + "-1-1t" + [vbs.dx, vbs.dy] : E,
+ oldt;
+ if (vbs) {
+ oldt = tstr = Str(tstr).replace(/\.{3}|\u2026/g, this._.transform || E);
+ }
+ R._extractTransform(this, vbt + tstr);
+ var matrix = this.matrix.clone(),
+ skew = this.skew,
+ o = this.node,
+ split,
+ isGrad = ~Str(this.attrs.fill).indexOf("-"),
+ isPatt = !Str(this.attrs.fill).indexOf("url(");
+ matrix.translate(-.5, -.5);
+ if (isPatt || isGrad || this.type == "image") {
+ skew.matrix = "1 0 0 1";
+ skew.offset = "0 0";
+ split = matrix.split();
+ if ((isGrad && split.noRotation) || !split.isSimple) {
+ o.style.filter = matrix.toFilter();
+ var bb = this.getBBox(),
+ bbt = this.getBBox(1),
+ dx = bb.x - bbt.x,
+ dy = bb.y - bbt.y;
+ o.coordorigin = (dx * -zoom) + S + (dy * -zoom);
+ setCoords(this, 1, 1, dx, dy, 0);
+ } else {
+ o.style.filter = E;
+ setCoords(this, split.scalex, split.scaley, split.dx, split.dy, split.rotate);
+ }
+ } else {
+ o.style.filter = E;
+ skew.matrix = Str(matrix);
+ skew.offset = matrix.offset();
+ }
+ oldt && (this._.transform = oldt);
+ return this;
+ };
+ elproto.rotate = function (deg, cx, cy) {
+ if (this.removed) {
+ return this;
+ }
+ if (deg == null) {
+ return;
+ }
+ deg = Str(deg).split(separator);
+ if (deg.length - 1) {
+ cx = toFloat(deg[1]);
+ cy = toFloat(deg[2]);
+ }
+ deg = toFloat(deg[0]);
+ (cy == null) && (cx = cy);
+ if (cx == null || cy == null) {
+ var bbox = this.getBBox(1);
+ cx = bbox.x + bbox.width / 2;
+ cy = bbox.y + bbox.height / 2;
+ }
+ this._.dirtyT = 1;
+ this.transform(this._.transform.concat([["r", deg, cx, cy]]));
+ return this;
+ };
+ elproto.translate = function (dx, dy) {
+ if (this.removed) {
+ return this;
+ }
+ dx = Str(dx).split(separator);
+ if (dx.length - 1) {
+ dy = toFloat(dx[1]);
+ }
+ dx = toFloat(dx[0]) || 0;
+ dy = +dy || 0;
+ if (this._.bbox) {
+ this._.bbox.x += dx;
+ this._.bbox.y += dy;
+ }
+ this.transform(this._.transform.concat([["t", dx, dy]]));
+ return this;
+ };
+ elproto.scale = function (sx, sy, cx, cy) {
+ if (this.removed) {
+ return this;
+ }
+ sx = Str(sx).split(separator);
+ if (sx.length - 1) {
+ sy = toFloat(sx[1]);
+ cx = toFloat(sx[2]);
+ cy = toFloat(sx[3]);
+ isNaN(cx) && (cx = null);
+ isNaN(cy) && (cy = null);
+ }
+ sx = toFloat(sx[0]);
+ (sy == null) && (sy = sx);
+ (cy == null) && (cx = cy);
+ if (cx == null || cy == null) {
+ var bbox = this.getBBox(1);
+ }
+ cx = cx == null ? bbox.x + bbox.width / 2 : cx;
+ cy = cy == null ? bbox.y + bbox.height / 2 : cy;
+
+ this.transform(this._.transform.concat([["s", sx, sy, cx, cy]]));
+ this._.dirtyT = 1;
+ return this;
+ };
+ elproto.hide = function () {
+ !this.removed && (this.node.style.display = "none");
+ return this;
+ };
+ elproto.show = function () {
+ !this.removed && (this.node.style.display = E);
+ return this;
+ };
+ elproto._getBBox = function () {
+ if (this.removed) {
+ return {};
+ }
+ return {
+ x: this.X + (this.bbx || 0) - this.W / 2,
+ y: this.Y - this.H,
+ width: this.W,
+ height: this.H
+ };
+ };
+ elproto.remove = function () {
+ if (this.removed || !this.node.parentNode) {
+ return;
+ }
+ this.paper.__set__ && this.paper.__set__.exclude(this);
+ R.eve.unbind("raphael.*.*." + this.id);
+ R._tear(this, this.paper);
+ this.node.parentNode.removeChild(this.node);
+ this.shape && this.shape.parentNode.removeChild(this.shape);
+ for (var i in this) {
+ this[i] = typeof this[i] == "function" ? R._removedFactory(i) : null;
+ }
+ this.removed = true;
+ };
+ elproto.attr = function (name, value) {
+ if (this.removed) {
+ return this;
+ }
+ if (name == null) {
+ var res = {};
+ for (var a in this.attrs) if (this.attrs[has](a)) {
+ res[a] = this.attrs[a];
+ }
+ res.gradient && res.fill == "none" && (res.fill = res.gradient) && delete res.gradient;
+ res.transform = this._.transform;
+ return res;
+ }
+ if (value == null && R.is(name, "string")) {
+ if (name == fillString && this.attrs.fill == "none" && this.attrs.gradient) {
+ return this.attrs.gradient;
+ }
+ var names = name.split(separator),
+ out = {};
+ for (var i = 0, ii = names.length; i < ii; i++) {
+ name = names[i];
+ if (name in this.attrs) {
+ out[name] = this.attrs[name];
+ } else if (R.is(this.paper.customAttributes[name], "function")) {
+ out[name] = this.paper.customAttributes[name].def;
+ } else {
+ out[name] = R._availableAttrs[name];
+ }
+ }
+ return ii - 1 ? out : out[names[0]];
+ }
+ if (this.attrs && value == null && R.is(name, "array")) {
+ out = {};
+ for (i = 0, ii = name.length; i < ii; i++) {
+ out[name[i]] = this.attr(name[i]);
+ }
+ return out;
+ }
+ var params;
+ if (value != null) {
+ params = {};
+ params[name] = value;
+ }
+ value == null && R.is(name, "object") && (params = name);
+ for (var key in params) {
+ eve("raphael.attr." + key + "." + this.id, this, params[key]);
+ }
+ if (params) {
+ for (key in this.paper.customAttributes) if (this.paper.customAttributes[has](key) && params[has](key) && R.is(this.paper.customAttributes[key], "function")) {
+ var par = this.paper.customAttributes[key].apply(this, [].concat(params[key]));
+ this.attrs[key] = params[key];
+ for (var subkey in par) if (par[has](subkey)) {
+ params[subkey] = par[subkey];
+ }
+ }
+ // this.paper.canvas.style.display = "none";
+ if (params.text && this.type == "text") {
+ this.textpath.string = params.text;
+ }
+ setFillAndStroke(this, params);
+ // this.paper.canvas.style.display = E;
+ }
+ return this;
+ };
+ elproto.toFront = function () {
+ !this.removed && this.node.parentNode.appendChild(this.node);
+ this.paper && this.paper.top != this && R._tofront(this, this.paper);
+ return this;
+ };
+ elproto.toBack = function () {
+ if (this.removed) {
+ return this;
+ }
+ if (this.node.parentNode.firstChild != this.node) {
+ this.node.parentNode.insertBefore(this.node, this.node.parentNode.firstChild);
+ R._toback(this, this.paper);
+ }
+ return this;
+ };
+ // 추가(for group 기능)
+ elproto.appendChild = function (element) {
+ if (this.removed) {
+ return this;
+ }
+ if (this.type !== 'group') {
+ throw new TypeError('appendChild function supports only the group type!');
+ }
+ var node = element.node || element[element.length - 1].node;
+ this.node.appendChild(node);
+ return this;
+ };
+ elproto.insertAfter = function (element) {
+ if (this.removed) {
+ return this;
+ }
+ if (element.constructor == R.st.constructor) {
+ element = element[element.length - 1];
+ }
+ if (element.node.nextSibling) {
+ element.node.parentNode.insertBefore(this.node, element.node.nextSibling);
+ } else {
+ element.node.parentNode.appendChild(this.node);
+ }
+ R._insertafter(this, element, this.paper);
+ return this;
+ };
+ elproto.insertBefore = function (element) {
+ if (this.removed) {
+ return this;
+ }
+ if (element.constructor == R.st.constructor) {
+ element = element[0];
+ }
+ element.node.parentNode.insertBefore(this.node, element.node);
+ R._insertbefore(this, element, this.paper);
+ return this;
+ };
+ elproto.blur = function (size) {
+ var s = this.node.runtimeStyle,
+ f = s.filter;
+ f = f.replace(blurregexp, E);
+ if (+size !== 0) {
+ this.attrs.blur = size;
+ s.filter = f + S + ms + ".Blur(pixelradius=" + (+size || 1.5) + ")";
+ s.margin = R.format("-{0}px 0 0 -{0}px", round(+size || 1.5));
+ } else {
+ s.filter = f;
+ s.margin = 0;
+ delete this.attrs.blur;
+ }
+ };
+
+ // 추가(for group 기능)
+ R._engine.group = function (vml, x, y) {
+ var el = createNode("group");
+ el.coordsize = zoom + S + zoom;
+ if (x && y) {
+ el.coordorigin = x + " " + y;
+ }
+ var p = new Element(el, vml),
+ attr = {x: x, y: y, fill: "none", stroke: "#000"};
+ setFillAndStroke(p, attr);
+ vml.canvas.appendChild(el);
+ p.type = "group";
+
+ return p;
+ };
+ R._engine.path = function (pathString, vml) {
+ var el = createNode("shape");
+ el.style.cssText = cssDot;
+ el.coordsize = zoom + S + zoom;
+ el.coordorigin = vml.coordorigin;
+ var p = new Element(el, vml),
+ attr = {fill: "none", stroke: "#000"};
+ pathString && (attr.path = pathString);
+ p.type = "path";
+ p.path = [];
+ p.Path = E;
+ setFillAndStroke(p, attr);
+ vml.canvas.appendChild(el);
+ var skew = createNode("skew");
+ skew.on = true;
+ el.appendChild(skew);
+ p.skew = skew;
+ p.transform(E);
+ return p;
+ };
+ R._engine.rect = function (vml, x, y, w, h, r) {
+ var path = R._rectPath(x, y, w, h, r),
+ res = vml.path(path),
+ a = res.attrs;
+ res.X = a.x = x;
+ res.Y = a.y = y;
+ res.W = a.width = w;
+ res.H = a.height = h;
+ a.r = r;
+ a.path = path;
+ res.type = "rect";
+ return res;
+ };
+ R._engine.ellipse = function (vml, x, y, rx, ry) {
+ var res = vml.path(),
+ a = res.attrs;
+ res.X = x - rx;
+ res.Y = y - ry;
+ res.W = rx * 2;
+ res.H = ry * 2;
+ res.type = "ellipse";
+ setFillAndStroke(res, {
+ cx: x,
+ cy: y,
+ rx: rx,
+ ry: ry
+ });
+ return res;
+ };
+ R._engine.circle = function (vml, x, y, r) {
+ var res = vml.path(),
+ a = res.attrs;
+ res.X = x - r;
+ res.Y = y - r;
+ res.W = res.H = r * 2;
+ res.type = "circle";
+ setFillAndStroke(res, {
+ cx: x,
+ cy: y,
+ r: r
+ });
+ return res;
+ };
+ R._engine.image = function (vml, src, x, y, w, h) {
+ var path = R._rectPath(x, y, w, h),
+ res = vml.path(path).attr({stroke: "none"}),
+ a = res.attrs,
+ node = res.node,
+ fill = node.getElementsByTagName(fillString)[0];
+ a.src = src;
+ res.X = a.x = x;
+ res.Y = a.y = y;
+ res.W = a.width = w;
+ res.H = a.height = h;
+ a.path = path;
+ res.type = "image";
+ fill.parentNode == node && node.removeChild(fill);
+ fill.rotate = true;
+ fill.src = src;
+ fill.type = "tile";
+ res._.fillpos = [x, y];
+ res._.fillsize = [w, h];
+ node.appendChild(fill);
+ setCoords(res, 1, 1, 0, 0, 0);
+ return res;
+ };
+ R._engine.text = function (vml, x, y, text) {
+ var el = createNode("shape"),
+ path = createNode("path"),
+ o = createNode("textpath");
+ x = x || 0;
+ y = y || 0;
+ text = text || "";
+ path.v = R.format("m{0},{1}l{2},{1}", round(x * zoom), round(y * zoom), round(x * zoom) + 1);
+ path.textpathok = true;
+ o.string = Str(text);
+ o.on = true;
+ el.style.cssText = cssDot;
+ el.coordsize = zoom + S + zoom;
+ el.coordorigin = "0 0";
+ var p = new Element(el, vml),
+ attr = {
+ fill: "#000",
+ stroke: "none",
+ font: R._availableAttrs.font,
+ text: text
+ };
+ p.shape = el;
+ p.path = path;
+ p.textpath = o;
+ p.type = "text";
+ p.attrs.text = Str(text);
+ p.attrs.x = x;
+ p.attrs.y = y;
+ p.attrs.w = 1;
+ p.attrs.h = 1;
+ setFillAndStroke(p, attr);
+ el.appendChild(o);
+ el.appendChild(path);
+ vml.canvas.appendChild(el);
+ var skew = createNode("skew");
+ skew.on = true;
+ el.appendChild(skew);
+ p.skew = skew;
+ p.transform(E);
+ return p;
+ };
+ // 추가(for foreignObject 기능)
+ R._engine.foreignObject = function (vml, x, y, w, h, obj) {
+ if ((typeof w) !== 'number') {
+ obj = w;
+ w = obj.offsetWidth;
+ h = obj.offsetHeight;
+ }
+ var g = createNode("group");
+ g.style.cssText = [
+ "position:absolute",
+ "left:" + (x - w / 2) + "px",
+ "top:" + (y - h / 2) + "px",
+ "width:" + w + "px",
+ "height:" + h + "px"
+ ].join(";") + ";";
+ g.coordsize = zoom + S + zoom;
+ g.coordorigin = "0 0";
+
+ var div = document.createElement('div');
+ div.innerHTML = obj;
+
+ g.appendChild(div);
+
+ var res = new Element(g, vml);
+ res.type = "foreignObject";
+ res.X = res.attrs.x = x;
+ res.Y = res.attrs.y = y;
+ res.W = res.attrs.width = w;
+ res.H = res.attrs.height = h;
+
+ vml.canvas.appendChild(g);
+
+ return res;
+ };
+ R._engine.setSize = function (width, height) {
+ var cs = this.canvas.style;
+ this.width = width;
+ this.height = height;
+ width == +width && (width += "px");
+ height == +height && (height += "px");
+ cs.width = width;
+ cs.height = height;
+ cs.clip = "rect(0 " + width + " " + height + " 0)";
+ if (this._viewBox) {
+ R._engine.setViewBox.apply(this, this._viewBox);
+ }
+ return this;
+ };
+ R._engine.setViewBox = function (x, y, w, h, fit) {
+ R.eve("raphael.setViewBox", this, this._viewBox, [x, y, w, h, fit]);
+ var width = this.width,
+ height = this.height,
+ size = 1 / mmax(w / width, h / height),
+ H, W;
+ if (fit) {
+ H = height / h;
+ W = width / w;
+ if (w * H < width) {
+ x -= (width - w * H) / 2 / H;
+ }
+ if (h * W < height) {
+ y -= (height - h * W) / 2 / W;
+ }
+ }
+ this._viewBox = [x, y, w, h, !!fit];
+ this._viewBoxShift = {
+ dx: -x,
+ dy: -y,
+ scale: size
+ };
+ this.forEach(function (el) {
+ el.transform("...");
+ });
+ return this;
+ };
+ var createNode;
+ R._engine.initWin = function (win) {
+ var doc = win.document;
+ doc.createStyleSheet().addRule(".rvml", "behavior:url(#default#VML)");
+ try {
+ createNode = function (tagName) {
+ return doc.createElement('');
+ };
+ } catch (e) {
+ createNode = function (tagName) {
+ return doc.createElement('<' + tagName + ' xmlns="urn:schemas-microsoft.com:vml" class="rvml">');
+ };
+ }
+ };
+ R._engine.initWin(R._g.win);
+ R._engine.create = function () {
+ var con = R._getContainer.apply(0, arguments),
+ container = con.container,
+ height = con.height,
+ s,
+ width = con.width,
+ x = con.x,
+ y = con.y;
+ if (!container) {
+ throw new Error("VML container not found.");
+ }
+ var res = new R._Paper,
+ c = res.canvas = R._g.doc.createElement("div"),
+ cs = c.style;
+ x = x || 0;
+ y = y || 0;
+ width = width || 512;
+ height = height || 342;
+ res.width = width;
+ res.height = height;
+ width == +width && (width += "px");
+ height == +height && (height += "px");
+ res.coordsize = zoom * 1e3 + S + zoom * 1e3;
+ res.coordorigin = "0 0";
+ res.span = R._g.doc.createElement("span");
+ res.span.style.cssText = "position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;";
+ c.appendChild(res.span);
+ cs.cssText = R.format("top:0;left:0;width:{0};height:{1};display:inline-block;position:relative;clip:rect(0 {0} {1} 0);overflow:hidden", width, height);
+ if (container == 1) {
+ R._g.doc.body.appendChild(c);
+ cs.left = x + "px";
+ cs.top = y + "px";
+ cs.position = "absolute";
+ } else {
+ if (container.firstChild) {
+ container.insertBefore(c, container.firstChild);
+ } else {
+ container.appendChild(c);
+ }
+ }
+ res.renderfix = function () {
+ };
+ return res;
+ };
+ R.prototype.clear = function () {
+ R.eve("raphael.clear", this);
+ this.canvas.innerHTML = E;
+ this.span = R._g.doc.createElement("span");
+ this.span.style.cssText = "position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;";
+ this.canvas.appendChild(this.span);
+ this.bottom = this.top = null;
+ };
+ R.prototype.remove = function () {
+ R.eve("raphael.remove", this);
+ this.canvas.parentNode.removeChild(this.canvas);
+ for (var i in this) {
+ this[i] = typeof this[i] == "function" ? R._removedFactory(i) : null;
+ }
+ return true;
+ };
+
+ var setproto = R.st;
+ for (var method in elproto) if (elproto[has](method) && !setproto[has](method)) {
+ setproto[method] = (function (methodname) {
+ return function () {
+ var arg = arguments;
+ return this.forEach(function (el) {
+ el[methodname].apply(el, arg);
+ });
+ };
+ })(method);
+ }
+}(window.Raphael);
+/** @namespace */
+var OG = window.OG || {};
+
+/** @namespace */
+OG.common = {};
+
+/** @namespace */
+OG.geometry = {};
+
+/** @namespace */
+OG.graph = {};
+
+/** @namespace */
+OG.handler = {};
+
+/** @namespace */
+OG.layout = {};
+
+/** @namespace */
+OG.renderer = {};
+
+/** @namespace */
+OG.marker = {};
+
+/** @namespace */
+OG.pattern = {};
+
+/** @namespace */
+OG.shape = {};
+
+/** @namespace */
+OG.shape.bpmn = {};
+
+/** @namespace */
+OG.shape.elec = {};
+
+/** @namespace */
+OG.shape.dids = {};
+
+/** @namespace */
+OG.shape.component = {};
+
+/**
+ * @namespace
+ * @private
+ */
+OG.shape.essencia = {};
+
+/**
+ * 공통 상수 정의 Javascript 클래스
+ *
+ * @class
+ *
+ * @author Seungpil Park
+ */
+OG.common.Constants = {
+
+ /**
+ * 캔버스 검색용 클래스 SUFFIX
+ */
+ CANVAS_SUFFIX: 'OG-CANVAS',
+
+ /**
+ * 공간 기하 객체 타입 정의
+ */
+ GEOM_TYPE: {
+ NULL: 0,
+ POINT: 1,
+ LINE: 2,
+ POLYLINE: 3,
+ POLYGON: 4,
+ RECTANGLE: 5,
+ CIRCLE: 6,
+ ELLIPSE: 7,
+ CURVE: 8,
+ BEZIER_CURVE: 9,
+ COLLECTION: 10
+ },
+
+ /**
+ * 공간 기하 객체 타입-이름 매핑
+ */
+ GEOM_NAME: ["", "Point", "Line", "PolyLine", "Polygon", "Rectangle", "Circle", "Ellipse", "Curve", "BezierCurve", "Collection"],
+
+ /**
+ * 숫자 반올림 소숫점 자리수
+ */
+ NUM_PRECISION: 0,
+
+ /**
+ * 캔버스 노드 타입 정의
+ */
+ NODE_TYPE: {
+ ROOT: "ROOT",
+ SHAPE: "SHAPE",
+ ETC: "ETC"
+ },
+
+ /**
+ * Shape 타입 정의
+ */
+ SHAPE_TYPE: {
+ GEOM: "GEOM",
+ TEXT: "TEXT",
+ HTML: "HTML",
+ IMAGE: "IMAGE",
+ SVG: "SVG",
+ EDGE: "EDGE",
+ GROUP: "GROUP"
+ },
+
+ /**
+ * Edge 타입 정의
+ */
+ EDGE_TYPE: {
+ STRAIGHT: "straight",
+ PLAIN: "plain",
+ BEZIER: "bezier"
+ },
+
+ /**
+ * 라벨 ID의 suffix 정의
+ */
+ LABEL_SUFFIX: "_LABEL",
+
+ /**
+ * 라벨 에디터 ID의 suffix 정의
+ */
+ LABEL_EDITOR_SUFFIX: "_LABEL_EDITOR",
+
+ /**
+ * 시작점 라벨 ID의 suffix 정의
+ */
+ FROM_LABEL_SUFFIX: '_FROMLABEL',
+
+ /**
+ * 끝점 라벨 ID의 suffix 정의
+ */
+ TO_LABEL_SUFFIX: '_TOLABEL',
+
+ /**
+ * Rectangle 모양의 마우스 드래그 선택 박스 영역
+ */
+ RUBBER_BAND_ID: "OG_R_BAND",
+
+ /**
+ * Rubber Band 허용 오차
+ */
+ RUBBER_BAND_TOLERANCE: 3,
+
+ /**
+ * Move & Resize 용 가이드 ID 의 suffix 정의
+ */
+ GUIDE_SUFFIX: {
+ ONRESIZE: "ONRESIZE",
+ GUIDE: "_GUIDE",
+ BBOX: "_GUIDE_BBOX",
+ UL: "_GUIDE_UL",
+ UR: "_GUIDE_UR",
+ LWL: "_GUIDE_LWL",
+ LWR: "_GUIDE_LWR",
+ LC: "_GUIDE_LC",
+ UC: "_GUIDE_UC",
+ RC: "_GUIDE_RC",
+ LWC: "_GUIDE_LWC",
+ FROM: "_GUIDE_FROM",
+ TO: "_GUIDE_TO",
+ CTL: "_GUIDE_CTL_",
+ CTL_H: "_GUIDE_CTL_H_",
+ CTL_V: "_GUIDE_CTL_V_",
+ LINE: "_GUIDE_LINE",
+ LINE_TEXT: "_GUIDE_LINE_TEXT",
+ LINE_CONNECT_MODE: "LINE_CONNECT_MODE",
+ LINE_CONNECT_TEXT: "LINE_CONNECT_TEXT",
+ LINE_CONNECT_SHAPE: "LINE_CONNECT_SHAPE",
+ LINE_CONNECT_LABEL: "LINE_CONNECT_LABEL",
+ LINE_VIRTUAL_EDGE: "LINE_VIRTUAL_EDGE",
+ RECT_CONNECT_MODE: "RECT_CONNECT_MODE",
+ RECT_CONNECT_TO_DRAW: "RECT_CONNECT_TO_DRAW",
+ TRASH: "_GUIDE_TRASH",
+ ROTATE: "_GUIDE_ROTATE",
+ CONTROLLER: "_GUIDE_CONT",
+ RECT: "_GUIDE_RECT",
+ QUARTER_UPPER: "QUARTER_UPPER",
+ QUARTER_LOW: "QUARTER_LOW",
+ QUARTER_BISECTOR: "QUARTER_BISECTOR",
+ QUARTER_THIRDS: "QUARTER_THIRDS"
+ },
+
+ /**
+ * Collapse & Expand 용 가이드 ID의 suffix 정의
+ */
+ COLLAPSE_SUFFIX: "_COLLAPSE",
+ COLLAPSE_BBOX_SUFFIX: "_COLLAPSE_BBOX",
+
+ /**
+ * LoopType 용 가이드 ID의 suffix 정의
+ */
+ LOOPTYPE_SUFFIX: "_LOOPTYPE",
+ LOOPTYPE_BBOX_SUFFIX: "_LOOPTYPE_BBOX",
+
+ /**
+ * TaskType 용 가이드 ID의 suffix 정의
+ */
+ TASKTYPE_SUFFIX: "_TASKTYPE",
+ TASKTYPE_BBOX_SUFFIX: "_TASKTYPE_BBOX",
+
+ /**
+ * TaskType 용 가이드 ID의 suffix 정의
+ */
+ INCLUSION_SUFFIX: "_INCLUSION",
+ INCLUSION_BBOX_SUFFIX: "_INCLUSION_BBOX",
+
+ /**
+ * STATUS 용 가이드 ID의 suffix 정의
+ */
+ STATUS_SUFFIX: "_STATUS",
+ STATUS_BBOX_SUFFIX: "_STATUS_BBOX",
+
+ /**
+ * EXCEPTIONTYPE 용 가이드 ID의 suffix 정의
+ */
+ EXCEPTIONTYPE_SUFFIX: "_EXCEPTIONTYPE",
+ EXCEPTIONTYPE_BBOX_SUFFIX: "_EXCEPTIONTYPE_BBOX",
+
+ /**
+ * Shape Move & Resize 시 이동 간격
+ */
+ MOVE_SNAP_SIZE: 5,
+
+ /**
+ * Edge 연결할때 Drop Over 가이드 ID의 suffix 정의
+ */
+ DROP_OVER_BBOX_SUFFIX: "_DROP_OVER",
+
+ /**
+ * Shape - Edge 와의 연결 포인트 터미널 ID의 suffix 정의
+ */
+ TERMINAL_SUFFIX: {
+ GROUP: "_TERMINAL",
+ BOX: "_TERMINAL_BOX"
+ },
+
+ /**
+ * Shape - Edge 와의 연결 포인트
+ */
+ TERMINAL: "_TERMINAL",
+
+ /**
+ * 마커 등록을 위한 임시 노드 아이디
+ */
+ MARKER_TEMP_NODE: "MARKER_TEMP_NODE",
+
+ /**
+ * 패턴 등록을 위한 임시 노드 아이디
+ */
+ PATTERN_TEMP_NODE: "PATTERN_TEMP_NODE",
+
+ /**
+ * 캔버스의 마커 데피니션 suffix 정의
+ */
+ MARKER_DEFS_SUFFIX: "_MARKER",
+ /**
+ * Shape 에서 마커가 그려질 경우 원본 노드 suffix 정의
+ */
+ ORIGINAL_NODE: "ORIGINAL_NODE",
+ /**
+ * Element 의 커넥트 가이드 이벤트 보정영역의 정의
+ */
+ CONNECT_GUIDE_EVENT_AREA: {
+ NAME: "CONNECT_GUIDE"
+ },
+ /**
+ * Element 의 커넥트 가이드 suffix 정의
+ */
+ CONNECT_GUIDE_SUFFIX: {
+ SPOT: "_CONNECT_SPOT",
+ VIRTUAL_SPOT: "_CONNECT_VIRTUAL_SPOT",
+ BBOX: "_CONNECT_GUIDE_BBOX",
+ SPOT_CIRCLE: "CIRCLE",
+ SPOT_RECT: "RECT",
+ SPOT_EVENT_DRAG: "SPOT_EVENT_DRAG",
+ SPOT_EVENT_MOUSEROVER: "SPOT_EVENT_MOUSEROVER",
+ CONNECTABLE_SPOT_DRAG: "CONNECTABLE_SPOT_DRAG",
+ CONNECT_FOCUS_SHAPE: "CONNECT_FOCUS_SHAPE"
+ },
+ /**
+ * 백도어 서픽스
+ */
+ BACKDOOR_SUFFIX: "backdoor"
+};
+OG.Constants = OG.common.Constants;
+
+// keyCode Definition
+if (typeof KeyEvent === "undefined") {
+ var KeyEvent = {
+ DOM_VK_CANCEL: 3,
+ DOM_VK_HELP: 6,
+ DOM_VK_BACK_SPACE: 8,
+ DOM_VK_TAB: 9,
+ DOM_VK_CLEAR: 12,
+ DOM_VK_RETURN: 13,
+ DOM_VK_ENTER: 14,
+ DOM_VK_SHIFT: 16,
+ DOM_VK_CONTROL: 17,
+ DOM_VK_ALT: 18,
+ DOM_VK_PAUSE: 19,
+ DOM_VK_CAPS_LOCK: 20,
+ DOM_VK_ESCAPE: 27,
+ DOM_VK_SPACE: 32,
+ DOM_VK_PAGE_UP: 33,
+ DOM_VK_PAGE_DOWN: 34,
+ DOM_VK_END: 35,
+ DOM_VK_HOME: 36,
+ DOM_VK_LEFT: 37,
+ DOM_VK_UP: 38,
+ DOM_VK_RIGHT: 39,
+ DOM_VK_DOWN: 40,
+ DOM_VK_PRINTSCREEN: 44,
+ DOM_VK_INSERT: 45,
+ DOM_VK_DELETE: 46,
+ DOM_VK_0: 48,
+ DOM_VK_1: 49,
+ DOM_VK_2: 50,
+ DOM_VK_3: 51,
+ DOM_VK_4: 52,
+ DOM_VK_5: 53,
+ DOM_VK_6: 54,
+ DOM_VK_7: 55,
+ DOM_VK_8: 56,
+ DOM_VK_9: 57,
+ DOM_VK_SEMICOLON: 59,
+ DOM_VK_EQUALS: 61,
+ DOM_VK_A: 65,
+ DOM_VK_B: 66,
+ DOM_VK_C: 67,
+ DOM_VK_D: 68,
+ DOM_VK_E: 69,
+ DOM_VK_F: 70,
+ DOM_VK_G: 71,
+ DOM_VK_H: 72,
+ DOM_VK_I: 73,
+ DOM_VK_J: 74,
+ DOM_VK_K: 75,
+ DOM_VK_L: 76,
+ DOM_VK_M: 77,
+ DOM_VK_N: 78,
+ DOM_VK_O: 79,
+ DOM_VK_P: 80,
+ DOM_VK_Q: 81,
+ DOM_VK_R: 82,
+ DOM_VK_S: 83,
+ DOM_VK_T: 84,
+ DOM_VK_U: 85,
+ DOM_VK_V: 86,
+ DOM_VK_W: 87,
+ DOM_VK_X: 88,
+ DOM_VK_Y: 89,
+ DOM_VK_Z: 90,
+ DOM_VK_COMMAND: 91,
+ DOM_VK_CONTEXT_MENU: 93,
+ DOM_VK_NUMPAD0: 96,
+ DOM_VK_NUMPAD1: 97,
+ DOM_VK_NUMPAD2: 98,
+ DOM_VK_NUMPAD3: 99,
+ DOM_VK_NUMPAD4: 100,
+ DOM_VK_NUMPAD5: 101,
+ DOM_VK_NUMPAD6: 102,
+ DOM_VK_NUMPAD7: 103,
+ DOM_VK_NUMPAD8: 104,
+ DOM_VK_NUMPAD9: 105,
+ DOM_VK_MULTIPLY: 106,
+ DOM_VK_ADD: 107,
+ DOM_VK_SEPARATOR: 108,
+ DOM_VK_SUBTRACT: 109,
+ DOM_VK_DECIMAL: 110,
+ DOM_VK_DIVIDE: 111,
+ DOM_VK_F1: 112,
+ DOM_VK_F2: 113,
+ DOM_VK_F3: 114,
+ DOM_VK_F4: 115,
+ DOM_VK_F5: 116,
+ DOM_VK_F6: 117,
+ DOM_VK_F7: 118,
+ DOM_VK_F8: 119,
+ DOM_VK_F9: 120,
+ DOM_VK_F10: 121,
+ DOM_VK_F11: 122,
+ DOM_VK_F12: 123,
+ DOM_VK_F13: 124,
+ DOM_VK_F14: 125,
+ DOM_VK_F15: 126,
+ DOM_VK_F16: 127,
+ DOM_VK_F17: 128,
+ DOM_VK_F18: 129,
+ DOM_VK_F19: 130,
+ DOM_VK_F20: 131,
+ DOM_VK_F21: 132,
+ DOM_VK_F22: 133,
+ DOM_VK_F23: 134,
+ DOM_VK_F24: 135,
+ DOM_VK_NUM_LOCK: 144,
+ DOM_VK_SCROLL_LOCK: 145,
+ DOM_VK_COMMA: 188,
+ DOM_VK_PERIOD: 190,
+ DOM_VK_SLASH: 191,
+ DOM_VK_BACK_QUOTE: 192,
+ DOM_VK_OPEN_BRACKET: 219,
+ DOM_VK_BACK_SLASH: 220,
+ DOM_VK_CLOSE_BRACKET: 221,
+ DOM_VK_QUOTE: 222,
+ DOM_VK_META: 224
+ };
+}
+/**
+ * 공통 유틸리티 Javascript 클래스
+ *
+ * @class
+ *
+ * @author Seungpil Park
+ */
+OG.common.Util = {
+
+ isEmpty: function (v, allowBlank) {
+ return v === null || v === undefined || ((OG.Util.isArray(v) && !v.length)) || (!allowBlank ? v === '' : false);
+ },
+ isArray: function (v) {
+ return Object.prototype.toString.apply(v) === '[object Array]';
+ },
+ isDate: function (v) {
+ return Object.prototype.toString.apply(v) === '[object Date]';
+ },
+ isObject: function (v) {
+ return !!v && Object.prototype.toString.call(v) === '[object Object]';
+ },
+ isPrimitive: function (v) {
+ return OG.Util.isString(v) || OG.Util.isNumber(v) || OG.Util.isBoolean(v);
+ },
+ isFunction: function (v) {
+ return Object.prototype.toString.apply(v) === '[object Function]';
+ },
+ isNumber: function (v) {
+ return typeof v === 'number' && isFinite(v);
+ },
+ isString: function (v) {
+ return typeof v === 'string';
+ },
+ isBoolean: function (v) {
+ return typeof v === 'boolean';
+ },
+ isElement: function (v) {
+ return !!v && v.tagName ? true : false;
+ },
+ isDefined: function (v) {
+ return typeof v !== 'undefined';
+ },
+
+ isWebKit: function () {
+ return (/webkit/).test(navigator.userAgent.toLowerCase());
+ },
+ isGecko: function () {
+ return !OG.Util.isWebKit() && (/gecko/).test(navigator.userAgent.toLowerCase());
+ },
+ isOpera: function () {
+ return (/opera/).test(navigator.userAgent.toLowerCase());
+ },
+ isChrome: function () {
+ return (/\bchrome\b/).test(navigator.userAgent.toLowerCase());
+ },
+ isSafari: function () {
+ return !OG.Util.isChrome() && (/safari/).test(navigator.userAgent.toLowerCase());
+ },
+ isFirefox: function () {
+ return (/firefox/).test(navigator.userAgent.toLowerCase());
+ },
+ isIE: function () {
+ if (navigator.appName == 'Microsoft Internet Explorer' || !!(navigator.userAgent.match(/Trident/) || navigator.userAgent.match(/rv 11/))) {
+ return true;
+
+ } else {
+ return false;
+ }
+ },
+ isIE6: function () {
+ return OG.Util.isIE() && (/msie 6/).test(navigator.userAgent.toLowerCase());
+ },
+ isIE7: function () {
+ return OG.Util.isIE() && ((/msie 7/).test(navigator.userAgent.toLowerCase()) || document.documentMode === 7);
+ },
+ isIE8: function () {
+ return OG.Util.isIE() && ((/msie 8/).test(navigator.userAgent.toLowerCase()) || document.documentMode === 8);
+ },
+ isIE9: function () {
+ return OG.Util.isIE() && ((/msie 9/).test(navigator.userAgent.toLowerCase()) || document.documentMode === 9);
+ },
+ isWindows: function () {
+ return (/windows|win32/).test(navigator.userAgent.toLowerCase());
+ },
+ isMac: function () {
+ return (/macintosh|mac os x/).test(navigator.userAgent.toLowerCase());
+ },
+ isLinux: function () {
+ return (/linux/).test(navigator.userAgent.toLowerCase());
+ },
+
+ trim: function (string) {
+ return string === null || string === undefined ?
+ string :
+ string.replace(/^[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000]+|[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000]+$/g, "");
+ },
+
+ /**
+ * Object 를 복사한다.
+ *
+ * @param {Object} obj 복사할 Object
+ * @return {Object} 복사된 Object
+ * @static
+ */
+ clone: function (obj) {
+ if (obj === null || obj === undefined) {
+ return obj;
+ }
+
+ // DOM nodes
+ if (obj.nodeType && obj.cloneNode) {
+ return obj.cloneNode(true);
+ }
+
+ var i, j, k, clone, key,
+ type = Object.prototype.toString.call(obj),
+ enumerables = ["hasOwnProperty", "valueOf", "isPrototypeOf", "propertyIsEnumerable",
+ "toLocaleString", "toString", "constructor"];
+
+ // Date
+ if (type === "[object Date]") {
+ return new Date(obj.getTime());
+ }
+
+ // Array, Object
+ if (type === "[object Array]") {
+ i = obj.length;
+
+ clone = [];
+
+ while (i--) {
+ clone[i] = this.clone(obj[i]);
+ }
+ } else if (type === "[object Object]" && obj.constructor === Object) {
+ // TODO : 보완필요
+ clone = {};
+
+ for (key in obj) {
+ clone[key] = this.clone(obj[key]);
+ }
+
+ if (enumerables) {
+ for (j = enumerables.length; j--;) {
+ k = enumerables[j];
+ clone[k] = obj[k];
+ }
+ }
+ }
+
+ return clone || obj;
+ },
+
+ /**
+ * 디폴트로 지정된 소숫점 자리수로 Round 한 값을 반환한다.
+ *
+ * @param {Number} val 반올림할 값
+ * @return {Number} 지정한 소숫점 자리수에 따른 반올림 값
+ */
+ round: function (val) {
+ return this.roundPrecision(val, OG.Constants.NUM_PRECISION);
+ },
+
+ /**
+ * 입력된 숫자값을 지정된 소숫점 자릿수로 Round해서 값을 리턴한다.
+ * @example
+ * OG.Util.roundPrecision(300.12345678, 3);
+ * Result ) 300.123
+ *
+ * @param {Number} val 반올림할 값
+ * @param {Number} precision 소숫점 자리수
+ * @return {Number} 지정한 소숫점 자리수에 따른 반올림 값
+ */
+ roundPrecision: function (val, precision) {
+ var p = Math.pow(10, precision);
+ return Math.round(val * p) / p;
+ },
+
+ /**
+ * Shape Move & Resize 이동 간격으로 Round 한 값을 반환한다.
+ *
+ * @param {Number} val 반올림할 값
+ * @param {Number} snapSize 이동간격
+ * @return {Number} 지정한 간격으로 반올림 값
+ */
+ roundGrid: function (val, snapSize) {
+ snapSize = snapSize || OG.Constants.MOVE_SNAP_SIZE;
+ return OG.Util.round(val / snapSize) * snapSize;
+ },
+
+ /**
+ * Copies all the properties of config to obj.
+ *
+ * @param {Object} obj The receiver of the properties
+ * @param {Object} config The source of the properties
+ * @param {Object} defaults A different object that will also be applied for default values
+ * @return {Object} returns obj
+ */
+ apply: function (obj, config, defaults) {
+ // no "this" reference for friendly out of scope calls
+ var p;
+ if (defaults) {
+ this.apply(obj, defaults);
+ }
+ if (obj && config && typeof config === 'object') {
+ for (p in config) {
+ obj[p] = config[p];
+ }
+ }
+ return obj;
+ },
+
+ /**
+ * Extends one class to create a subclass and optionally overrides members with the passed literal. This method
+ * also adds the function "override()" to the subclass that can be used to override members of the class.
+ * For example, to create a subclass of Ext GridPanel:
+ *
+ MyGridPanel = Ext.extend(Ext.grid.GridPanel, {
+ constructor: function(config) {
+
+ // Create configuration for this Grid.
+ var store = new Ext.data.Store({...});
+ var colModel = new Ext.grid.ColumnModel({...});
+
+ // Create a new config object containing our computed properties
+ // *plus* whatever was in the config parameter.
+ config = Ext.apply({
+ store: store,
+ colModel: colModel
+ }, config);
+
+ MyGridPanel.superclass.constructor.call(this, config);
+
+ // Your postprocessing here
+ },
+
+ yourMethod: function() {
+ // etc.
+ }
+ });
+
+ *
+ * This function also supports a 3-argument call in which the subclass's constructor is
+ * passed as an argument. In this form, the parameters are as follows:
+ *
+ * subclass
: Function The subclass constructor.
+ * superclass
: Function The constructor of class being extended
+ * overrides
: Object A literal with members which are copied into the subclass's
+ * prototype, and are therefore shared among all instances of the new class.
+ *
+ *
+ * @param {Function} superclass The constructor of class being extended.
+ * @param {Object} overrides A literal with members which are copied into the subclass's
+ * prototype, and are therefore shared between all instances of the new class.
+ * This may contain a special member named constructor. This is used
+ * to define the constructor of the new class, and is returned. If this property is
+ * not specified, a constructor is generated and returned which just calls the
+ * superclass's constructor passing on its parameters.
+ * It is essential that you call the superclass constructor in any provided constructor. See example code.
+ * @return {Function} The subclass constructor from the overrides
parameter, or a generated one if not provided.
+ */
+ extend: (function () {
+ // inline overrides
+ var io = function (o) {
+ var m;
+ for (m in o) {
+ this[m] = o[m];
+ }
+ },
+ oc = Object.prototype.constructor;
+
+ return function (sb, sp, overrides) {
+ if (OG.Util.isObject(sp)) {
+ overrides = sp;
+ sp = sb;
+ sb = overrides.constructor !== oc ? overrides.constructor : function () {
+ sp.apply(this, arguments);
+ };
+ }
+ var F = function () {
+ },
+ sbp,
+ spp = sp.prototype;
+
+ F.prototype = spp;
+ sbp = sb.prototype = new F();
+ sbp.constructor = sb;
+ sb.superclass = spp;
+ if (spp.constructor === oc) {
+ spp.constructor = sp;
+ }
+ sb.override = function (o) {
+ OG.Util.override(sb, o);
+ };
+ sbp.superclass = sbp.supr = (function () {
+ return spp;
+ }());
+ sbp.override = io;
+ OG.Util.override(sb, overrides);
+ sb.extend = function (o) {
+ return OG.Util.extend(sb, o);
+ };
+ return sb;
+ };
+ }()),
+
+ /**
+ * Adds a list of functions to the prototype of an existing class, overwriting any existing methods with the same name.
+ * Usage:
+ Ext.override(MyClass, {
+ newMethod1: function(){
+ // etc.
+ },
+ newMethod2: function(foo){
+ // etc.
+ }
+ });
+
+ * @param {Object} origclass The class to override
+ * @param {Object} overrides The list of functions to add to origClass. This should be specified as an object literal
+ * containing one or more methods.
+ * @method override
+ */
+ override: function (origclass, overrides) {
+ if (overrides) {
+ var p = origclass.prototype;
+ OG.Util.apply(p, overrides);
+ if ((/msie/).test(navigator.userAgent.toLowerCase()) && overrides.hasOwnProperty('toString')) {
+ p.toString = overrides.toString;
+ }
+ }
+ },
+
+ xmlToJson: function (node) {
+ var json = {},
+ cloneNS = function (ns) {
+ var nns = {};
+ for (var n in ns) {
+ if (ns.hasOwnProperty(n)) {
+ nns[n] = ns[n];
+ }
+ }
+ return nns;
+ },
+ process = function (node, obj, ns) {
+ if (node.nodeType === 3) {
+ if (!node.nodeValue.match(/[\S]+/)) return;
+ if (obj["$"] instanceof Array) {
+ obj["$"].push(node.nodeValue);
+ } else if (obj["$"] instanceof Object) {
+ obj["$"] = [obj["$"], node.nodeValue];
+ } else {
+ obj["$"] = node.nodeValue;
+ }
+ } else if (node.nodeType === 1) {
+ var p = {};
+ var nodeName = node.nodeName;
+ for (var i = 0; node.attributes && i < node.attributes.length; i++) {
+ var attr = node.attributes[i];
+ var name = attr.nodeName;
+ var value = attr.nodeValue;
+ if (name === "xmlns") {
+ ns["$"] = value;
+ } else if (name.indexOf("xmlns:") === 0) {
+ ns[name.substr(name.indexOf(":") + 1)] = value;
+ } else {
+ p["@" + name] = value;
+ }
+ }
+ for (var prefix in ns) {
+ if (ns.hasOwnProperty(prefix)) {
+ p["@xmlns"] = p["@xmlns"] || {};
+ p["@xmlns"][prefix] = ns[prefix];
+ }
+ }
+ if (obj[nodeName] instanceof Array) {
+ obj[nodeName].push(p);
+ } else if (obj[nodeName] instanceof Object) {
+ obj[nodeName] = [obj[nodeName], p];
+ } else {
+ obj[nodeName] = p;
+ }
+ for (var j = 0, lenj = node.childNodes.length; j < lenj; j++) {
+ process(node.childNodes[j], p, cloneNS(ns));
+ }
+ } else if (node.nodeType === 9) {
+ for (var k = 0, lenk = node.childNodes.length; k < lenk; k++) {
+ process(node.childNodes[k], obj, cloneNS(ns));
+ }
+ }
+ };
+ process(node, json, {});
+ return json;
+ },
+
+ jsonToXml: function (json) {
+ if (typeof json !== "object") return null;
+ var cloneNS = function (ns) {
+ var nns = {};
+ for (var n in ns) {
+ if (ns.hasOwnProperty(n)) {
+ nns[n] = ns[n];
+ }
+ }
+ return nns;
+ };
+
+ var processLeaf = function (lname, child, ns) {
+ var body = "";
+ if (child instanceof Array) {
+ for (var i = 0, leni = child.length; i < leni; i++) {
+ body += processLeaf(lname, child[i], cloneNS(ns));
+ }
+ return body;
+ } else if (typeof child === "object") {
+ var el = "<" + lname;
+ var attributes = "";
+ var text = "";
+ if (child["@xmlns"]) {
+ var xmlns = child["@xmlns"];
+ for (var prefix in xmlns) {
+ if (xmlns.hasOwnProperty(prefix)) {
+ if (prefix === "$") {
+ if (ns[prefix] !== xmlns[prefix]) {
+ attributes += " " + "xmlns=\"" + xmlns[prefix] + "\"";
+ ns[prefix] = xmlns[prefix];
+ }
+ } else if (!ns[prefix] || (ns[prefix] !== xmlns[prefix])) {
+ attributes += " xmlns:" + prefix + "=\"" + xmlns[prefix] + "\"";
+ ns[prefix] = xmlns[prefix];
+ }
+ }
+ }
+ }
+ for (var key in child) {
+ if (child.hasOwnProperty(key) && key !== "@xmlns") {
+ var obj = child[key];
+ if (key === "$") {
+ text += obj;
+ } else if (key.indexOf("@") === 0) {
+ attributes += " " + key.substring(1) + "=\"" + obj + "\"";
+ } else {
+ body += processLeaf(key, obj, cloneNS(ns));
+ }
+ }
+ }
+ body = text + body;
+ return (body !== "") ? el + attributes + ">" + body + "" + lname + ">" : el + attributes + "/>"
+ }
+ };
+ for (var lname in json) {
+ if (json.hasOwnProperty(lname) && lname.indexOf("@") == -1) {
+ return '' + processLeaf(lname, json[lname], {});
+ }
+ }
+ return null;
+ },
+
+ parseXML: function (xmlString) {
+ var doc, parser;
+ if (window.ActiveXObject) {
+ doc = new ActiveXObject('Microsoft.XMLDOM');
+ doc.async = 'false';
+ doc.loadXML(xmlString);
+ } else {
+ parser = new DOMParser();
+ doc = parser.parseFromString(xmlString, 'text/xml');
+ }
+
+ return doc;
+ }
+};
+OG.Util = OG.common.Util;
+/**
+ * 곡선(Curve) 알고리즘을 구현한 Javascript 클래스
+ *
+ * @class
+ *
+ * @author Seungpil Park
+ */
+OG.common.CurveUtil = {
+ /**
+ * 주어진 좌표 Array 에 대해 Cubic Catmull-Rom spline Curve 좌표를 계산하는 함수를 반환한다.
+ * 모든 좌표를 지나는 커브를 계산한다.
+ *
+ * @example
+ * var points = [[2, 2], [2, -2], [-2, 2], [-2, -2]],
+ * cmRomSpline = OG.CurveUtil.CatmullRomSpline(points), t, curve = [];
+ *
+ * // t 는 0 ~ maxT 의 값으로, t 값의 증분값이 작을수록 세밀한 Curve 를 그린다.
+ * for(t = 0; t <= cmRomSpline.maxT; t += 0.1) {
+ * curve.push([cmRomSpline.getX(t), cmRomSpline.getY(t)]);
+ * }
+ *
+ * @param {Array} points 좌표 Array (예, [[x1,y1], [x2,y2], [x3,y3], [x4,y4]])
+ * @return {Object} t 값에 의해 X, Y 좌표를 구하는 함수와 maxT 값을 반환
+ * @static
+ */
+ CatmullRomSpline: function (points) {
+ var coeffs = [], p,
+ first = {},
+ last = {}, // control point at the beginning and at the end
+
+ makeFct = function (which) {
+
+ return function (t, suspendedUpdate) {
+
+ var len = points.length, s, c;
+
+ if (len < 2) {
+ return NaN;
+ }
+
+ t = t - 1;
+
+ if (!suspendedUpdate && coeffs[which]) {
+ suspendedUpdate = true;
+ }
+
+ if (!suspendedUpdate) {
+ first[which] = 2 * points[0][which] - points[1][which];
+ last[which] = 2 * points[len - 1][which] - points[len - 2][which];
+ p = [first].concat(points, [last]);
+
+ coeffs[which] = [];
+ for (s = 0; s < len - 1; s++) {
+ coeffs[which][s] = [
+ 2 * p[s + 1][which],
+ -p[s][which] + p[s + 2][which],
+ 2 * p[s][which] - 5 * p[s + 1][which] + 4 * p[s + 2][which] - p[s + 3][which],
+ -p[s][which] + 3 * p[s + 1][which] - 3 * p[s + 2][which] + p[s + 3][which]
+ ];
+ }
+ }
+ len += 2; // add the two control points
+ if (isNaN(t)) {
+ return NaN;
+ }
+ // This is necessay for our advanced plotting algorithm:
+ if (t < 0) {
+ return p[1][which];
+ } else if (t >= len - 3) {
+ return p[len - 2][which];
+ }
+
+ s = Math.floor(t);
+ if (s === t) {
+ return p[s][which];
+ }
+ t -= s;
+ c = coeffs[which][s];
+ return 0.5 * (((c[3] * t + c[2]) * t + c[1]) * t + c[0]);
+ };
+ };
+
+ return {
+ getX: makeFct(0),
+ getY: makeFct(1),
+ maxT: points.length + 1
+ };
+ },
+
+ /**
+ * 주어진 좌표 Array (좌표1, 콘트롤포인트1, 콘트롤포인트2, 좌표2 ...) 에 대해 Cubic Bezier Curve 좌표를 계산하는 함수를 반환한다.
+ * Array 갯수는 3 * K + 1 이어야 한다.
+ * 예) 좌표1, 콘트롤포인트1, 콘트롤포인트2, 좌표2, 콘트롤포인트1, 콘트롤포인트2, 좌표3 ...
+ *
+ * @example
+ * var points = [[2, 1], [1, 3], [-1, -1], [-2, 1]],
+ * bezier = OG.CurveUtil.Bezier(points), t, curve = [];
+ *
+ * // t 는 0 ~ maxT 의 값으로, t 값의 증분값이 작을수록 세밀한 Curve 를 그린다.
+ * for(t = 0; t <= bezier.maxT; t += 0.1) {
+ * curve.push([bezier.getX(t), bezier.getY(t)]);
+ * }
+ *
+ * @param {Array} points 좌표 Array (예, [[x1,y1], [cp_x1,cp_y1], [cp_x2,cp_y2], [x2,y4]])
+ * @return {Object} t 값에 의해 X, Y 좌표를 구하는 함수와 maxT 값을 반환
+ * @static
+ */
+ Bezier: function (points) {
+ var len,
+ makeFct = function (which) {
+ return function (t, suspendedUpdate) {
+ var z = Math.floor(t) * 3,
+ t0 = t,
+ t1 = 1 - t0;
+
+ if (!suspendedUpdate && len) {
+ suspendedUpdate = true;
+ }
+
+ if (!suspendedUpdate) {
+ len = Math.floor(points.length / 3);
+ }
+
+ if (t < 0) {
+ return points[0][which];
+ }
+ if (t >= len) {
+ return points[points.length - 1][which];
+ }
+ if (isNaN(t)) {
+ return NaN;
+ }
+ return t1 * t1 * (t1 * points[z][which] + 3 * t0 * points[z + 1][which]) +
+ (3 * t1 * points[z + 2][which] + t0 * points[z + 3][which]) * t0 * t0;
+ };
+ };
+
+ return {
+ getX: makeFct(0),
+ getY: makeFct(1),
+ maxT: Math.floor(points.length / 3) + 0.01
+ };
+ },
+
+ /**
+ * 주어진 좌표 Array (시작좌표, 콘트롤포인트1, 콘트롤포인트2, ..., 끝좌표) 에 대해 B-Spline Curve 좌표를 계산하는 함수를 반환한다.
+ *
+ * @example
+ * var points = [[2, 1], [1, 3], [-1, -1], [-2, 1]],
+ * bspline = OG.CurveUtil.BSpline(points), t, curve = [];
+ *
+ * // t 는 0 ~ maxT 의 값으로, t 값의 증분값이 작을수록 세밀한 Curve 를 그린다.
+ * for(t = 0; t <= bspline.maxT; t += 0.1) {
+ * curve.push([bspline.getX(t), bspline.getY(t)]);
+ * }
+ *
+ * @param {Array} points 좌표 Array (예, [[x1,y1], [x2,y2], [x3,y3], [x4,y4]])
+ * @param {Number} order Order of the B-spline curve.
+ * @return {Object} t 값에 의해 X, Y 좌표를 구하는 함수와 maxT 값을 반환
+ * @static
+ */
+ BSpline: function (points, order) {
+ var knots, N = [],
+ _knotVector = function (n, k) {
+ var j, kn = [];
+ for (j = 0; j < n + k + 1; j++) {
+ if (j < k) {
+ kn[j] = 0.0;
+ } else if (j <= n) {
+ kn[j] = j - k + 1;
+ } else {
+ kn[j] = n - k + 2;
+ }
+ }
+ return kn;
+ },
+
+ _evalBasisFuncs = function (t, kn, n, k, s) {
+ var i, j, a, b, den,
+ N = [];
+
+ if (kn[s] <= t && t < kn[s + 1]) {
+ N[s] = 1;
+ } else {
+ N[s] = 0;
+ }
+ for (i = 2; i <= k; i++) {
+ for (j = s - i + 1; j <= s; j++) {
+ if (j <= s - i + 1 || j < 0) {
+ a = 0.0;
+ } else {
+ a = N[j];
+ }
+ if (j >= s) {
+ b = 0.0;
+ } else {
+ b = N[j + 1];
+ }
+ den = kn[j + i - 1] - kn[j];
+ if (den === 0) {
+ N[j] = 0;
+ } else {
+ N[j] = (t - kn[j]) / den * a;
+ }
+ den = kn[j + i] - kn[j + 1];
+ if (den !== 0) {
+ N[j] += (kn[j + i] - t) / den * b;
+ }
+ }
+ }
+ return N;
+ },
+ makeFct = function (which) {
+ return function (t, suspendedUpdate) {
+ var len = points.length, y, j, s,
+ n = len - 1,
+ k = order;
+
+ if (n <= 0) {
+ return NaN;
+ }
+ if (n + 2 <= k) {
+ k = n + 1;
+ }
+ if (t <= 0) {
+ return points[0][which];
+ }
+ if (t >= n - k + 2) {
+ return points[n][which];
+ }
+
+ knots = _knotVector(n, k);
+ s = Math.floor(t) + k - 1;
+ N = _evalBasisFuncs(t, knots, n, k, s);
+
+ y = 0.0;
+ for (j = s - k + 1; j <= s; j++) {
+ if (j < len && j >= 0) {
+ y += points[j][which] * N[j];
+ }
+ }
+ return y;
+ };
+ };
+
+ return {
+ getX: makeFct(0),
+ getY: makeFct(1),
+ maxT: points.length - 2
+ };
+ }
+};
+OG.CurveUtil = OG.common.CurveUtil;
+/**
+ * 사용자 정의 예외 클래스 NotSupportedException
+ *
+ * @class
+ *
+ * @param {String} message 메시지
+ * @author Seungpil Park
+ * @private
+ */
+OG.common.NotSupportedException = function (message) {
+ /**
+ * 예외명
+ * @type String
+ */
+ this.name = "OG.NotSupportedException";
+
+ /**
+ * 메시지
+ * @type String
+ */
+ this.message = message || "Not Supported!";
+};
+OG.NotSupportedException = OG.common.NotSupportedException;
+
+/**
+ * 사용자 정의 예외 클래스 NotImplementedException
+ *
+ * @class
+ *
+ * @param {String} message 메시지
+ * @author Seungpil Park
+ * @private
+ */
+OG.common.NotImplementedException = function (message) {
+ /**
+ * 예외명
+ * @type String
+ */
+ this.name = "OG.NotImplementedException";
+
+ /**
+ * 메시지
+ * @type String
+ */
+ this.message = message || "Not Implemented!";
+};
+OG.NotImplementedException = OG.common.NotImplementedException;
+
+/**
+ * 사용자 정의 예외 클래스 ParamError
+ *
+ * @class
+ *
+ * @param {String} message 메시지
+ * @author Seungpil Park
+ * @private
+ */
+OG.common.ParamError = function (message) {
+ /**
+ * 예외명
+ * @type String
+ */
+ this.name = "OG.ParamError";
+
+ /**
+ * 메시지
+ * @type String
+ */
+ this.message = message || "Invalid Parameter Error!";
+};
+OG.ParamError = OG.common.ParamError;
+/**
+ * HashMap 구현 Javascript 클래스
+ *
+ * @class
+ *
+ * @example
+ * var map1 = new OG.common.HashMap({
+ * 'key1': 'value1',
+ * 'key2': 'value2'
+ * });
+ *
+ * console.log(map1.get('key1'));
+ *
+ * var map2 = new OG.common.HashMap();
+ * map2.put('key1', 'value1');
+ * map2.put('key2', 'value2');
+ *
+ * console.log(map2.get('key1'));
+ *
+ * @param {Object} jsonObject key:value 매핑 JSON 오브젝트
+ * @author Seungpil Park
+ */
+OG.common.HashMap = function (jsonObject) {
+ /**
+ * key:value 매핑 JSON 오브젝트
+ * @type Object
+ */
+ this.map = jsonObject || {};
+};
+OG.common.HashMap.prototype = {
+ /**
+ * key : value 를 매핑한다.
+ *
+ * @param {String} key 키
+ * @param {Object} value 값
+ */
+ put: function (key, value) {
+ this.map[key] = value;
+ },
+
+ /**
+ * key 에 대한 value 를 반환한다.
+ *
+ * @param {String} key 키
+ * @return {Object} 값
+ */
+ get: function (key) {
+ return this.map[key];
+ },
+
+ /**
+ * 주어진 key 를 포함하는지 여부를 반환한다.
+ *
+ * @param {String} key 키
+ * @return {Boolean}
+ */
+ containsKey: function (key) {
+ return this.map.hasOwnProperty(key);
+ },
+
+ /**
+ * 주어진 value 를 포함하는지 여부를 반환한다.
+ *
+ * @param {Object} value 값
+ * @return {Boolean}
+ */
+ containsValue: function (value) {
+ var prop;
+ for (prop in this.map) {
+ if (this.map[prop] === value) {
+ return true;
+ }
+ }
+ return false;
+ },
+
+ /**
+ * Empty 여부를 반환한다.
+ *
+ * @return {Boolean}
+ */
+ isEmpty: function () {
+ return this.size() === 0;
+ },
+
+ /**
+ * 매핑정보를 클리어한다.
+ */
+ clear: function () {
+ var prop;
+ for (prop in this.map) {
+ delete this.map[prop];
+ }
+ },
+
+ /**
+ * 주어진 key 의 매핑정보를 삭제한다.
+ *
+ * @param {String} key 키
+ */
+ remove: function (key) {
+ if (this.map[key]) {
+ delete this.map[key];
+ }
+ },
+
+ /**
+ * key 목록을 반환한다.
+ *
+ * @return {String[]} 키목록
+ */
+ keys: function () {
+ var keys = [], prop;
+ for (prop in this.map) {
+ keys.push(prop);
+ }
+ return keys;
+ },
+
+ /**
+ * value 목록을 반환한다.
+ *
+ * @return {Object[]} 값목록
+ */
+ values: function () {
+ var values = [], prop;
+ for (prop in this.map) {
+ values.push(this.map[prop]);
+ }
+ return values;
+ },
+
+ /**
+ * 매핑된 key:value 갯수를 반환한다.
+ *
+ * @return {Number}
+ */
+ size: function () {
+ var count = 0, prop;
+ for (prop in this.map) {
+ count++;
+ }
+ return count;
+ },
+
+ /**
+ * 객체 프라퍼티 정보를 JSON 스트링으로 반환한다.
+ *
+ * @return {String} 프라퍼티 정보
+ * @override
+ */
+ toString: function () {
+ var s = [], prop;
+ for (prop in this.map) {
+ s.push("'" + prop + "':'" + this.map[prop] + "'");
+ }
+
+ return "{" + s.join() + "}";
+ }
+};
+OG.common.HashMap.prototype.constructor = OG.common.HashMap;
+OG.HashMap = OG.common.HashMap;
+/**
+ * Modified version of Douglas Crockford"s json.js that doesn"t
+ * mess with the Object prototype
+ * http://www.json.org/js.html
+ *
+ * @class
+ *
+ * @author Seungpil Park
+ */
+OG.common.JSON = new (function () {
+ var useHasOwn = !!{}.hasOwnProperty,
+ USE_NATIVE_JSON = false,
+ isNative = (function () {
+ var useNative = null;
+
+ return function () {
+ if (useNative === null) {
+ useNative = USE_NATIVE_JSON && window.JSON && JSON.toString() === '[object JSON]';
+ }
+
+ return useNative;
+ };
+ }()),
+ m = {
+ "\b": '\\b',
+ "\t": '\\t',
+ "\n": '\\n',
+ "\f": '\\f',
+ "\r": '\\r',
+ '"': '\\"',
+ "\\": '\\\\'
+ },
+ pad = function (n) {
+ return n < 10 ? "0" + n : n;
+ },
+ doDecode = function (json) {
+ return eval("(" + json + ')');
+ },
+ encodeString = function (s) {
+ if (/["\\\x00-\x1f]/.test(s)) {
+ return '"' + s.replace(/([\x00-\x1f\\"])/g, function (a, b) {
+ var c = m[b];
+ if (c) {
+ return c;
+ }
+ c = b.charCodeAt();
+ return "\\u00" +
+ Math.floor(c / 16).toString(16) +
+ (c % 16).toString(16);
+ }) + '"';
+ }
+ return '"' + s + '"';
+ },
+ encodeArray = function (o) {
+ var a = ["["], b, i, l = o.length, v;
+ for (i = 0; i < l; i += 1) {
+ v = o[i];
+ switch (typeof v) {
+ case "undefined":
+ case "function":
+ case "unknown":
+ break;
+ default:
+ if (b) {
+ a.push(',');
+ }
+ a.push(v === null ? "null" : OG.common.JSON.encode(v));
+ b = true;
+ }
+ }
+ a.push("]");
+ return a.join("");
+ },
+ doEncode = function (o) {
+ if (!OG.Util.isDefined(o) || o === null) {
+ return "null";
+ } else if (OG.Util.isArray(o)) {
+ return encodeArray(o);
+ } else if (OG.Util.isDate(o)) {
+ return OG.common.JSON.encodeDate(o);
+ } else if (OG.Util.isString(o)) {
+ return encodeString(o);
+ } else if (typeof o === "number") {
+ //don't use isNumber here, since finite checks happen inside isNumber
+ return isFinite(o) ? String(o) : "null";
+ } else if (OG.Util.isBoolean(o)) {
+ return String(o);
+ } else {
+ var a = ["{"], b, i, v;
+ for (i in o) {
+ // don't encode DOM objects
+ if (!o.getElementsByTagName) {
+ if (!useHasOwn || o.hasOwnProperty(i)) {
+ v = o[i];
+ switch (typeof v) {
+ case "undefined":
+ case "function":
+ case "unknown":
+ break;
+ default:
+ if (b) {
+ a.push(',');
+ }
+ a.push(doEncode(i), ":",
+ v === null ? "null" : doEncode(v));
+ b = true;
+ }
+ }
+ }
+ }
+ a.push("}");
+ return a.join("");
+ }
+ };
+
+ /**
+ * Encodes a Date. This returns the actual string which is inserted into the JSON string as the literal expression.
+ * The returned value includes enclosing double quotation marks.
+ * The default return format is "yyyy-mm-ddThh:mm:ss".
+ * To override this:
+ OG.common.JSON.encodeDate = function(d) {
+ return d.format('"Y-m-d"');
+ };
+
+ * @param {Date} d The Date to encode
+ * @return {String} The string literal to use in a JSON string.
+ */
+ this.encodeDate = function (o) {
+ return '"' + o.getFullYear() + "-" +
+ pad(o.getMonth() + 1) + "-" +
+ pad(o.getDate()) + "T" +
+ pad(o.getHours()) + ":" +
+ pad(o.getMinutes()) + ":" +
+ pad(o.getSeconds()) + '"';
+ };
+
+ /**
+ * Encodes an Object, Array or other value
+ * @param {Mixed} o The variable to encode
+ * @return {String} The JSON string
+ */
+ this.encode = (function () {
+ var ec;
+ return function (o) {
+ if (!ec) {
+ // setup encoding function on first access
+ ec = isNative() ? JSON.stringify : doEncode;
+ }
+ return ec(o);
+ };
+ }());
+
+
+ /**
+ * Decodes (parses) a JSON string to an object. If the JSON is invalid, this function throws a SyntaxError unless the safe option is set.
+ * @param {String} json The JSON string
+ * @return {Object} The resulting object
+ */
+ this.decode = (function () {
+ var dc;
+ return function (json) {
+ if (!dc) {
+ // setup decoding function on first access
+ dc = isNative() ? JSON.parse : doDecode;
+ }
+ return dc(json);
+ };
+ }());
+
+})();
+OG.JSON = OG.common.JSON;
+
+/**
+ * 스타일(StyleSheet) Property 정보 클래스
+ *
+ * @class
+ * @extends OG.common.HashMap
+ *
+ * @example
+ * var style = new OG.geometry.Style({
+ * 'cursor': 'default',
+ * 'stroke': 'black'
+ * });
+ *
+ * @param {Object} style 키:값 매핑된 스타일 프라퍼티 정보
+ * @author Seungpil Park
+ */
+OG.geometry.Style = function (style) {
+ var DEFAULT_STYLE = {}, _style = {};
+
+ OG.Util.apply(_style, style, DEFAULT_STYLE);
+
+ OG.geometry.Style.superclass.call(this, _style);
+};
+OG.geometry.Style.prototype = new OG.common.HashMap();
+OG.geometry.Style.superclass = OG.common.HashMap;
+OG.geometry.Style.prototype.constructor = OG.geometry.Style;
+OG.Style = OG.geometry.Style;
+/**
+ * 2차원 좌표계에서의 좌표값
+ *
+ * @example
+ * var coordinate1 = new OG.geometry.Coordinate(10, 10);
+ * or
+ * var coordinate2 = new OG.geometry.Coordinate([20, 20]);
+ *
+ * @class
+ *
+ * @param {Number} x x좌표
+ * @param {Number} y y좌표
+ * @author Seungpil Park
+ */
+OG.geometry.Coordinate = function (x, y) {
+
+ /**
+ * x좌표
+ * @type Number
+ */
+ this.x = undefined;
+
+ /**
+ * y좌표
+ * @type Number
+ */
+ this.y = undefined;
+
+ // Array 좌표를 OG.geometry.Coordinate 로 변환
+ if (arguments.length === 1 && x.constructor === Array) {
+ this.x = x[0];
+ this.y = x[1];
+ } else if (arguments.length === 2 && typeof x === "number" && typeof y === "number") {
+ this.x = x;
+ this.y = y;
+ } else if (arguments.length !== 0) {
+ throw new OG.ParamError();
+ }
+};
+OG.geometry.Coordinate.prototype = {
+
+ /**
+ * 주어진 좌표와의 거리를 계산한다.
+ *
+ * @example
+ * coordinate.distance([10, 10]);
+ * or
+ * coordinate.distance(new OG.Coordinate(10, 10));
+ *
+ *
+ * @param {OG.geometry.Coordinate|Number[]} coordinate 좌표값
+ * @return {Number} 좌표간의 거리값
+ */
+ distance: function (coordinate) {
+ if (coordinate.constructor === Array) {
+ coordinate = new OG.geometry.Coordinate(coordinate[0], coordinate[1]);
+ }
+
+ var dx = this.x - coordinate.x, dy = this.y - coordinate.y;
+ return OG.Util.round(Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2)));
+ },
+
+ /**
+ * 가로, 세로 Offset 만큼 좌표를 이동한다.
+ *
+ * @param {Number} offsetX 가로 Offset
+ * @param {Number} offsetY 세로 Offset
+ * @return {OG.geometry.Coordinate} 이동된 좌표
+ */
+ move: function (offsetX, offsetY) {
+ this.x += offsetX;
+ this.y += offsetY;
+
+ return this;
+ },
+
+ /**
+ * 기준 좌표를 기준으로 주어진 각도 만큼 회전한다.
+ *
+ * @example
+ * coordinate.rotate(90, [10,10]);
+ * or
+ * coordinate.rotate(90, new OG.Coordinate(10, 10));
+ *
+ * @param {Number} angle 회전 각도
+ * @param {OG.geometry.Coordinate|Number[]} origin 기준 좌표
+ * @return {OG.geometry.Coordinate} 회전된 좌표
+ */
+ rotate: function (angle, origin) {
+ if (origin.constructor === Array) {
+ origin = new OG.geometry.Coordinate(origin[0], origin[1]);
+ }
+
+ angle *= Math.PI / 180;
+ var radius = this.distance(origin),
+ theta = angle + Math.atan2(this.y - origin.y, this.x - origin.x);
+ this.x = OG.Util.round(origin.x + (radius * Math.cos(theta)));
+ this.y = OG.Util.round(origin.y + (radius * Math.sin(theta)));
+
+ return this;
+ },
+
+ /**
+ * 주어진 좌표값과 같은지 비교한다.
+ *
+ * @example
+ * coordinate.isEquals([10, 10]);
+ * or
+ * coordinate.isEquals(new OG.Coordinate(10, 10));
+ *
+ * @param {OG.geometry.Coordinate|Number[]} coordinate 좌표값
+ * @return {Boolean} true:같음, false:다름
+ */
+ isEquals: function (coordinate) {
+ if (coordinate.constructor === Array) {
+ coordinate = new OG.geometry.Coordinate(coordinate[0], coordinate[1]);
+ }
+
+ if (coordinate && coordinate instanceof OG.geometry.Coordinate) {
+ if (this.x === coordinate.x && this.y === coordinate.y) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ /**
+ * 객체 프라퍼티 정보를 JSON 스트링으로 반환한다.
+ *
+ * @return {String} 프라퍼티 정보
+ * @override
+ */
+ toString: function () {
+ var s = [];
+ s.push(this.x);
+ s.push(this.y);
+
+ return "[" + s.join() + "]";
+ }
+};
+OG.geometry.Coordinate.prototype.constructor = OG.geometry.Coordinate;
+OG.Coordinate = OG.geometry.Coordinate;
+/**
+ * 2차원 좌표계에서 Envelope 영역을 정의
+ *
+ * @class
+ * @requires OG.geometry.Coordinate
+ *
+ * @example
+ * var boundingBox = new OG.geometry.Envelope([50, 50], 200, 100);
+ *
+ * @param {OG.geometry.Coordinate|Number[]} upperLeft 기준 좌상단 좌표
+ * @param {Number} width 너비
+ * @param {Number} height 높이
+ * @author Seungpil Park
+ */
+OG.geometry.Envelope = function (upperLeft, width, height) {
+
+ /**
+ * @type OG.geometry.Coordinate
+ * @private
+ */
+ this._upperLeft = null;
+
+ /**
+ * @type Number
+ * @private
+ */
+ this._width = width;
+
+ /**
+ * @type Number
+ * @private
+ */
+ this._height = height;
+
+ /**
+ * @type OG.geometry.Coordinate
+ * @private
+ */
+ this._upperRight = null;
+
+ /**
+ * @type OG.geometry.Coordinate
+ * @private
+ */
+ this._lowerLeft = null;
+
+ /**
+ * @type OG.geometry.Coordinate
+ * @private
+ */
+ this._lowerRight = null;
+
+ /**
+ * @type OG.geometry.Coordinate
+ * @private
+ */
+ this._leftCenter = null;
+
+ /**
+ * @type OG.geometry.Coordinate
+ * @private
+ */
+ this._leftCenter = null;
+
+ /**
+ * @type OG.geometry.Coordinate
+ * @private
+ */
+ this._upperCenter = null;
+
+ /**
+ * @type OG.geometry.Coordinate
+ * @private
+ */
+ this._rightCenter = null;
+
+ /**
+ * @type OG.geometry.Coordinate
+ * @private
+ */
+ this._lowerCenter = null;
+
+ /**
+ * @type OG.geometry.Coordinate
+ * @private
+ */
+ this._centroid = null;
+
+ // Array 좌표를 OG.geometry.Coordinate 로 변환
+ if (upperLeft) {
+ if (upperLeft.constructor === Array) {
+ this._upperLeft = new OG.geometry.Coordinate(upperLeft);
+ } else {
+ this._upperLeft = new OG.geometry.Coordinate(upperLeft.x, upperLeft.y);
+ }
+ }
+};
+OG.geometry.Envelope.prototype = {
+ /**
+ * 기준 좌상단 좌표를 반환한다.
+ *
+ * @return {OG.geometry.Coordinate} 좌상단 좌표
+ */
+ getUpperLeft: function () {
+ return this._upperLeft;
+ },
+
+ /**
+ * 주어진 좌표로 기준 좌상단 좌표를 설정한다. 새로 설정된 값으로 이동된다.
+ *
+ * @param {OG.geometry.Coordinate|Number[]} upperLeft 좌상단 좌표
+ */
+ setUpperLeft: function (upperLeft) {
+ if (upperLeft.constructor === Array) {
+ upperLeft = new OG.geometry.Coordinate(upperLeft[0], upperLeft[1]);
+ }
+
+ this._upperLeft = upperLeft;
+ this._reset();
+ },
+
+ /**
+ * 우상단 좌표를 반환한다.
+ *
+ * @return {OG.geometry.Coordinate} 우상단 좌표
+ */
+ getUpperRight: function () {
+ if (!this._upperRight) {
+ this._upperRight = new OG.geometry.Coordinate(this._upperLeft.x + this._width, this._upperLeft.y);
+ }
+ return this._upperRight;
+ },
+
+ /**
+ * 우하단 좌표를 반환한다.
+ *
+ * @return {OG.geometry.Coordinate} 우하단 좌표
+ */
+ getLowerRight: function () {
+ if (!this._lowerRight) {
+ this._lowerRight = new OG.geometry.Coordinate(this._upperLeft.x + this._width, this._upperLeft.y + this._height);
+ }
+ return this._lowerRight;
+ },
+
+ /**
+ * 좌하단 좌표를 반환한다.
+ *
+ * @return {OG.geometry.Coordinate} 좌하단 좌표
+ */
+ getLowerLeft: function () {
+ if (!this._lowerLeft) {
+ this._lowerLeft = new OG.geometry.Coordinate(this._upperLeft.x, this._upperLeft.y + this._height);
+ }
+ return this._lowerLeft;
+ },
+
+ /**
+ * 좌중간 좌표를 반환한다.
+ *
+ * @return {OG.geometry.Coordinate} 좌중간 좌표
+ */
+ getLeftCenter: function () {
+ if (!this._leftCenter) {
+ this._leftCenter = new OG.geometry.Coordinate(this._upperLeft.x, OG.Util.round(this._upperLeft.y + this._height / 2));
+ }
+ return this._leftCenter;
+ },
+
+ /**
+ * 상단중간 좌표를 반환한다.
+ *
+ * @return {OG.geometry.Coordinate} 상단중간 좌표
+ */
+ getUpperCenter: function () {
+ if (!this._upperCenter) {
+ this._upperCenter = new OG.geometry.Coordinate(OG.Util.round(this._upperLeft.x + this._width / 2), this._upperLeft.y);
+ }
+ return this._upperCenter;
+ },
+
+ /**
+ * 우중간 좌표를 반환한다.
+ *
+ * @return {OG.geometry.Coordinate} 우중간 좌표
+ */
+ getRightCenter: function () {
+ if (!this._rightCenter) {
+ this._rightCenter = new OG.geometry.Coordinate(this._upperLeft.x + this._width, OG.Util.round(this._upperLeft.y + this._height / 2));
+ }
+ return this._rightCenter;
+ },
+
+ /**
+ * 하단중간 좌표를 반환한다.
+ *
+ * @return {OG.geometry.Coordinate} 하단중간 좌표
+ */
+ getLowerCenter: function () {
+ if (!this._lowerCenter) {
+ this._lowerCenter = new OG.geometry.Coordinate(OG.Util.round(this._upperLeft.x + this._width / 2), this._upperLeft.y + this._height);
+ }
+ return this._lowerCenter;
+ },
+
+ /**
+ * Envelope 의 중심좌표를 반환한다.
+ *
+ * @return {OG.geometry.Coordinate} 중심좌표
+ */
+ getCentroid: function () {
+ if (!this._centroid) {
+ this._centroid = new OG.geometry.Coordinate(OG.Util.round(this._upperLeft.x + this._width / 2),
+ OG.Util.round(this._upperLeft.y + this._height / 2));
+ }
+
+ return this._centroid;
+ },
+
+ /**
+ * 주어진 좌표로 중심 좌표를 설정한다. 새로 설정된 값으로 이동된다.
+ *
+ * @param {OG.geometry.Coordinate|Number[]} centroid 중심좌표
+ */
+ setCentroid: function (centroid) {
+ if (centroid.constructor === Array) {
+ centroid = new OG.geometry.Coordinate(centroid[0], centroid[1]);
+ }
+
+ this.move(centroid.x - this.getCentroid().x, centroid.y - this.getCentroid().y);
+ },
+
+ /**
+ * Envelope 의 가로 사이즈를 반환한다.
+ *
+ * @return {Number} 너비
+ */
+ getWidth: function () {
+ return this._width;
+ },
+
+ /**
+ * 주어진 값으로 Envelope 의 가로 사이즈를 설정한다.
+ *
+ * @param {Number} width 너비
+ */
+ setWidth: function (width) {
+ this._width = width;
+ this._reset();
+ },
+
+ /**
+ * Envelope 의 세로 사이즈를 반환한다.
+ *
+ * @return {Number} 높이
+ */
+ getHeight: function () {
+ return this._height;
+ },
+
+ /**
+ * 주어진 값으로 Envelope 의 세로 사이즈를 설정한다.
+ *
+ * @param {Number} height 높이
+ */
+ setHeight: function (height) {
+ this._height = height;
+ this._reset();
+ },
+
+ /**
+ * Envelope 모든 꼭지점을 반환한다.
+ * 좌상단좌표부터 시계방향으로 꼭지점 Array 를 반환한다.
+ *
+ * @return {OG.geometry.Coordinate[]} 꼭지점 좌표 Array : [좌상단, 상단중간, 우상단, 우중간, 우하단, 하단중간, 좌하단, 좌중간, 좌상단]
+ */
+ getVertices: function () {
+ var vertices = [];
+
+ vertices.push(this.getUpperLeft());
+ vertices.push(this.getUpperCenter());
+ vertices.push(this.getUpperRight());
+ vertices.push(this.getRightCenter());
+ vertices.push(this.getLowerRight());
+ vertices.push(this.getLowerCenter());
+ vertices.push(this.getLowerLeft());
+ vertices.push(this.getLeftCenter());
+ vertices.push(this.getUpperLeft());
+
+ return vertices;
+ },
+
+ /**
+ * 주어진 좌표값이 Envelope 영역에 포함되는지 비교한다.
+ *
+ * @param {OG.geometry.Coordinate|Number[]} coordinate 좌표값
+ * @return {Boolean} true:포함, false:비포함
+ */
+ isContains: function (coordinate) {
+ if (coordinate.constructor === Array) {
+ return coordinate[0] >= this._upperLeft.x && coordinate[0] <= this.getLowerRight().x &&
+ coordinate[1] >= this._upperLeft.y && coordinate[1] <= this.getLowerRight().y;
+ } else {
+ return coordinate.x >= this._upperLeft.x && coordinate.x <= this.getLowerRight().x &&
+ coordinate.y >= this._upperLeft.y && coordinate.y <= this.getLowerRight().y;
+ }
+ },
+
+ /**
+ * 주어진 모든 좌표값이 Envelope 영역에 포함되는지 비교한다.
+ *
+ * @param {OG.geometry.Coordinate[]} coordinateArray 좌표값 Array
+ * @return {Boolean} true:포함, false:비포함
+ */
+ isContainsAll: function (coordinateArray) {
+ for (var i = 0, leni = coordinateArray.length; i < leni; i++) {
+ if (!this.isContains(coordinateArray[i])) {
+ return false;
+ }
+ }
+
+ return true;
+ },
+
+ /**
+ * 몇개의 좌표값이 Envelope 영역에 포함되는지 비교한다.
+ *
+ * @param {OG.geometry.Coordinate[]} coordinateArray 좌표값 Array
+ * @return {Boolean} true:포함, false:비포함
+ */
+ getHowManyContains: function (coordinateArray) {
+ var i, time = 0;
+ for (var i = 0, leni = coordinateArray.length; i < leni; i++) {
+ if (this.isContains(coordinateArray[i])) {
+ time += 1;
+ }
+ }
+
+ return time;
+ },
+
+ /**
+ * 주어진 좌표값들이 하나라도 Envelope 영역에 포함되는지 비교한다.
+ *
+ * @param {OG.geometry.Coordinate[]} coordinateArray 좌표값 Array
+ * @return {Boolean} true:포함, false:비포함
+ */
+ isContainsOnce: function (coordinateArray) {
+ var i;
+ for (var i = 0, leni = coordinateArray.length; i < leni; i++) {
+ if (this.isContains(coordinateArray[i])) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ /**
+ * 크기는 고정한 채 가로, 세로 Offset 만큼 Envelope 을 이동한다.
+ *
+ * @param {Number} offsetX 가로 Offset
+ * @param {Number} offsetY 세로 Offset
+ * @return {OG.geometry.Envelope} 이동된 Envelope
+ */
+ move: function (offsetX, offsetY) {
+ this._upperLeft.move(offsetX, offsetY);
+ this._reset();
+
+ return this;
+ },
+
+ /**
+ * 상, 하, 좌, 우 외곽선을 이동하여 Envelope 을 리사이즈 한다.
+ *
+ * @param {Number} upper 상단 라인 이동 Offset(위 방향으로 +)
+ * @param {Number} lower 하단 라인 이동 Offset(아래 방향으로 +)
+ * @param {Number} left 좌측 라인 이동 Offset(좌측 방향으로 +)
+ * @param {Number} right 우측 라인 이동 Offset(우측 방향으로 +)
+ * @return {OG.geometry.Envelope} 리사이즈된 Envelope
+ */
+ resize: function (upper, lower, left, right) {
+ upper = upper || 0;
+ lower = lower || 0;
+ left = left || 0;
+ right = right || 0;
+
+ if (this._width + (left + right) < 0 || this._height + (upper + lower) < 0) {
+ throw new OG.ParamError();
+ }
+
+ this._upperLeft.move(-1 * left, -1 * upper);
+ this._width += (left + right);
+ this._height += (upper + lower);
+ this._reset();
+
+ return this;
+ },
+
+ /**
+ * 주어진 Envelope 영역과 같은지 비교한다.
+ *
+ * @param {OG.geometry.Envelope} Envelope 영역
+ * @return {Boolean} true:같음, false:다름
+ */
+ isEquals: function (envelope) {
+ if (envelope && envelope instanceof OG.geometry.Envelope) {
+ if (this.getUpperLeft().isEquals(envelope.getUpperLeft()) &&
+ this.getWidth() === envelope.getWidth() &&
+ this.getHeight() === envelope.getHeight()) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ /**
+ * 객체 프라퍼티 정보를 JSON 스트링으로 반환한다.
+ *
+ * @return {String} 프라퍼티 정보
+ * @override
+ */
+ toString: function () {
+ var s = [];
+ s.push("upperLeft:" + this.getUpperLeft());
+ s.push("width:" + this.getWidth());
+ s.push("height:" + this.getHeight());
+ s.push("upperRight:" + this.getUpperRight());
+ s.push("lowerLeft:" + this.getLowerLeft());
+ s.push("lowerRight:" + this.getLowerRight());
+ s.push("leftCenter:" + this.getLeftCenter());
+ s.push("upperCenter:" + this.getUpperCenter());
+ s.push("rightCenter:" + this.getRightCenter());
+ s.push("lowerCenter:" + this.getLowerCenter());
+ s.push("centroid:" + this.getCentroid());
+
+ return "{" + s.join() + "}";
+ },
+
+ /**
+ * _upperLeft, _width, _height 를 제외한 private 멤버 변수의 값을 리셋한다.
+ *
+ * @private
+ */
+ _reset: function () {
+ this._upperRight = null;
+ this._lowerLeft = null;
+ this._lowerRight = null;
+ this._leftCenter = null;
+ this._upperCenter = null;
+ this._rightCenter = null;
+ this._lowerCenter = null;
+ this._centroid = null;
+ }
+};
+OG.geometry.Envelope.prototype.constructor = OG.geometry.Envelope;
+OG.Envelope = OG.geometry.Envelope;
+/**
+ * 공간 기하 객체(Spatial Geometry Object)의 최상위 추상 클래스
+ *
+ * @class
+ * @requires OG.geometry.Coordinate
+ * @requires OG.geometry.Envelope
+ *
+ * @author Seungpil Park
+ */
+OG.geometry.Geometry = function () {
+
+ /**
+ * 공간 기하 객체 타입
+ * @type Number
+ */
+ this.TYPE = OG.Constants.GEOM_TYPE.NULL;
+
+ /**
+ * 닫힌 기하 객체 인지 여부
+ * @type Boolean
+ */
+ this.IS_CLOSED = false;
+
+ /**
+ * 스타일 속성
+ * @type OG.geometry.Style
+ */
+ this.style = null;
+
+ /**
+ * 공간기하객체를 포함하는 사각형의 Boundary 영역
+ * @type OG.geometry.Envelope
+ */
+ this.boundary = null;
+};
+OG.geometry.Geometry.prototype = {
+
+ // 다른 Geometry 객체와의 Spatial Relation 을 테스트하는 함수들
+
+ /**
+ * 주어진 Geometry 객체와 같은지 비교한다.
+ *
+ * @param {OG.geometry.Geometry} _geometry Geometry 객체
+ * @return {Boolean} true:같음, false:다름
+ */
+ isEquals: function (_geometry) {
+ return _geometry && _geometry.toString() === this.toString();
+ },
+
+ /**
+ * 주어진 공간기하객체를 포함하는지 비교한다.
+ *
+ * @param {OG.geometry.Geometry} _geometry Geometry 객체
+ * @return {Boolean} 포함하면 true
+ */
+ isContains: function (_geometry) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * 주어진 공간기하객체에 포함되는지 비교한다.
+ *
+ * @param {OG.geometry.Geometry} _geometry Geometry 객체
+ * @return {Boolean} 포함되면 true
+ */
+ isWithin: function (_geometry) {
+ throw new OG.NotImplementedException();
+ },
+
+
+ /**
+ * 공간기하객체를 포함하는 사각형의 Boundary 영역을 반환한다.
+ *
+ * @return {OG.geometry.Envelope} Envelope 영역
+ */
+ getBoundary: function () {
+ if (this.boundary === null) {
+ var minX, minY, maxX, maxY, upperLeft, width, height,
+ vertices = this.getVertices(), i;
+ for (var i = 0, leni = vertices.length; i < leni; i++) {
+ if (i === 0) {
+ minX = maxX = vertices[i].x;
+ minY = maxY = vertices[i].y;
+ }
+ minX = vertices[i].x < minX ? vertices[i].x : minX;
+ minY = vertices[i].y < minY ? vertices[i].y : minY;
+ maxX = vertices[i].x > maxX ? vertices[i].x : maxX;
+ maxY = vertices[i].y > maxY ? vertices[i].y : maxY;
+ }
+ upperLeft = new OG.geometry.Coordinate(minX, minY);
+ width = maxX - minX;
+ height = maxY - minY;
+
+ this.boundary = new OG.geometry.Envelope(upperLeft, width, height);
+ }
+
+ return this.boundary;
+ },
+
+ /**
+ * 공간기하객체의 중심좌표를 반환한다.
+ *
+ * @return {OG.geometry.Coordinate} 중심좌표
+ */
+ getCentroid: function () {
+ return this.getBoundary().getCentroid();
+ },
+
+ /**
+ * 공간기하객체의 모든 꼭지점을 반환한다.
+ *
+ * @return {OG.geometry.Coordinate[]} 꼭지점 좌표 Array
+ * @abstract
+ */
+ getVertices: function () {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * 주어진 좌표와의 최단거리를 반환한다.
+ *
+ * @param {OG.geometry.Coordinate} _coordinate 좌표
+ * @return {Number} 최단거리
+ */
+ minDistance: function (_coordinate) {
+ var minDistance = Number.MAX_VALUE,
+ distance = 0,
+ vertices = this.getVertices(),
+ i;
+
+ _coordinate = this.convertCoordinate(_coordinate);
+
+ if (vertices.length === 1) {
+ return _coordinate.distance(vertices[0]);
+ }
+
+ for (var i = 0, leni = vertices.length - 1; i < leni; i++) {
+ distance = this.distanceToLine(_coordinate, [vertices[i], vertices[i + 1]]);
+ if (distance < minDistance) {
+ minDistance = distance;
+ }
+ }
+
+ return minDistance;
+ },
+
+ /**
+ * 주어진 공간기하객체와의 중심점 간의 거리를 반환한다.
+ *
+ * @param {OG.geometry.Geometry} _geometry 공간 기하 객체
+ * @return {Number} 거리
+ */
+ distance: function (_geometry) {
+ return this.getCentroid().distance(_geometry.getCentroid());
+ },
+
+ /**
+ * 공간기하객체의 길이를 반환한다.
+ *
+ * @return {Number} 길이
+ */
+ getLength: function () {
+ var length = 0,
+ vertices = this.getVertices(),
+ i;
+ for (var i = 0, leni = vertices.length - 1; i < leni; i++) {
+ length += vertices[i].distance(vertices[i + 1]);
+ }
+
+ return length;
+ },
+
+ /**
+ * 가로, 세로 Offset 만큼 좌표를 이동한다.
+ *
+ * @param {Number} offsetX 가로 Offset
+ * @param {Number} offsetY 세로 Offset
+ * @return {OG.geometry.Geometry} 이동된 공간 기하 객체
+ * @abstract
+ */
+ move: function (offsetX, offsetY) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * 주어진 중심좌표로 공간기하객체를 이동한다.
+ *
+ * @param {OG.geometry.Coordinate} 중심 좌표
+ */
+ moveCentroid: function (target) {
+ var origin = this.getCentroid();
+ target = new OG.geometry.Coordinate(target);
+
+ this.move(target.x - origin.x, target.y - origin.y);
+ },
+
+ /**
+ * 상, 하, 좌, 우 외곽선을 이동하여 Envelope 을 리사이즈 한다.
+ *
+ * @param {Number} upper 상단 라인 이동 Offset(위 방향으로 +)
+ * @param {Number} lower 하단 라인 이동 Offset(아래 방향으로 +)
+ * @param {Number} left 좌측 라인 이동 Offset(좌측 방향으로 +)
+ * @param {Number} right 우측 라인 이동 Offset(우측 방향으로 +)
+ * @return {OG.geometry.Geometry} 리사이즈된 공간 기하 객체
+ * @abstract
+ */
+ resize: function (upper, lower, left, right) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * 중심좌표는 고정한 채 Bounding Box 의 width, height 를 리사이즈 한다.
+ *
+ * @param {Number} width 너비
+ * @param {Number} height 높이
+ * @return {OG.geometry.Geometry} 리사이즈된 공간 기하 객체
+ */
+ resizeBox: function (width, height) {
+ var boundary = this.getBoundary(),
+ offsetWidth = OG.Util.round((width - boundary.getWidth()) / 2),
+ offsetHeight = OG.Util.round((height - boundary.getHeight()) / 2);
+
+ this.resize(offsetHeight, offsetHeight, offsetWidth, offsetWidth);
+
+ return this;
+ },
+
+ /**
+ * 기준 좌표를 기준으로 주어진 각도 만큼 회전한다.
+ *
+ * @param {Number} angle 회전 각도
+ * @param {OG.geometry.Coordinate} origin 기준 좌표(default:중심좌표)
+ * @return {OG.geometry.Geometry} 회전된 공간 기하 객체
+ * @abstract
+ */
+ rotate: function (angle, origin) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * 주어진 Boundary 영역 안으로 공간 기하 객체를 적용한다.(이동 & 리사이즈)
+ *
+ * @param {OG.geometry.Envelope} envelope Envelope 영역
+ * @return {OG.geometry.Geometry} 적용된 공간 기하 객체
+ */
+ fitToBoundary: function (envelope) {
+ var boundary = this.getBoundary(),
+ upper = boundary.getUpperCenter().y - envelope.getUpperCenter().y,
+ lower = envelope.getLowerCenter().y - boundary.getLowerCenter().y,
+ left = boundary.getLeftCenter().x - envelope.getLeftCenter().x,
+ right = envelope.getRightCenter().x - boundary.getRightCenter().x;
+
+ this.resize(upper, lower, left, right);
+
+ return this;
+ },
+
+ // 유틸리티 함수들
+
+ /**
+ * 파라미터가 [x, y] 형식의 좌표 Array 이면 OG.geometry.Coordinate 인스턴스를 new 하여 반환한다.
+ *
+ * @param {OG.geometry.Coordinate|Number[]} coordinate [x, y] 형식의 좌표 Array 또는 OG.geometry.Coordinate 인스턴스
+ * @return {OG.geometry.Coordinate}
+ */
+ convertCoordinate: function (coordinate) {
+ // Array 좌표를 OG.geometry.Coordinate 로 변환
+ if (coordinate) {
+ if (coordinate.constructor === Array) {
+ return new OG.geometry.Coordinate(coordinate);
+ } else if (coordinate instanceof OG.geometry.Coordinate) {
+ return new OG.geometry.Coordinate(coordinate.x, coordinate.y);
+ } else {
+ throw new OG.ParamError();
+ }
+ } else {
+ return undefined;
+ }
+ },
+
+ /**
+ * 포인트 P 로부터 라인 AB의 거리를 계산한다.
+ * Note: NON-ROBUST!
+ *
+ * @param {OG.geometry.Coordinate|Number[]} p 기준좌표
+ * @param {OG.geometry.Coordinate[]} line 라인 시작좌표, 끝좌표 Array
+ * @return {Number} 거리
+ */
+ distanceToLine: function (p, line) {
+ var A = this.convertCoordinate(line[0]),
+ B = this.convertCoordinate(line[1]),
+ r, s;
+ p = this.convertCoordinate(p);
+
+ // if start==end, then use pt distance
+ if (A.isEquals(B)) {
+ return p.distance(A);
+ }
+
+ r = ((p.x - A.x) * (B.x - A.x) + (p.y - A.y) * (B.y - A.y)) /
+ ((B.x - A.x) * (B.x - A.x) + (B.y - A.y) * (B.y - A.y));
+
+ if (r <= 0.0) {
+ return p.distance(A);
+ }
+ if (r >= 1.0) {
+ return p.distance(B);
+ }
+
+ s = ((A.y - p.y) * (B.x - A.x) - (A.x - p.x) * (B.y - A.y)) /
+ ((B.x - A.x) * (B.x - A.x) + (B.y - A.y) * (B.y - A.y));
+
+ return OG.Util.round(Math.abs(s) *
+ Math.sqrt(((B.x - A.x) * (B.x - A.x) + (B.y - A.y) * (B.y - A.y))));
+ },
+
+ /**
+ * 라인1 로부터 라인2 의 거리를 계산한다.
+ * Note: NON-ROBUST!
+ *
+ * @param {OG.geometry.Coordinate[]} line1 line1 라인 시작좌표, 끝좌표 Array
+ * @param {OG.geometry.Coordinate[]} line2 line2 라인 시작좌표, 끝좌표 Array
+ * @return {Number} 거리
+ */
+ distanceLineToLine: function (line1, line2) {
+ var A = this.convertCoordinate(line1[0]),
+ B = this.convertCoordinate(line1[1]),
+ C = this.convertCoordinate(line2[0]),
+ D = this.convertCoordinate(line2[1]),
+ r_top, r_bot, s_top, s_bot, s, r;
+
+ // check for zero-length segments
+ if (A.isEquals(B)) {
+ return this.distanceToLine(A, [C, D]);
+ }
+ if (C.isEquals(D)) {
+ return this.distanceToLine(D, [A, B]);
+ }
+
+ r_top = (A.y - C.y) * (D.x - C.x) - (A.x - C.x) * (D.y - C.y);
+ r_bot = (B.x - A.x) * (D.y - C.y) - (B.y - A.y) * (D.x - C.x);
+ s_top = (A.y - C.y) * (B.x - A.x) - (A.x - C.x) * (B.y - A.y);
+ s_bot = (B.x - A.x) * (D.y - C.y) - (B.y - A.y) * (D.x - C.x);
+
+ if ((r_bot === 0) || (s_bot === 0)) {
+ return Math.min(this.distanceToLine(A, [C, D]),
+ Math.min(this.distanceToLine(B, [C, D]),
+ Math.min(this.distanceToLine(C, [A, B]), this.distanceToLine(D, [A, B]))));
+
+ }
+ s = s_top / s_bot;
+ r = r_top / r_bot;
+
+ if ((r < 0) || (r > 1) || (s < 0) || (s > 1)) {
+ //no intersection
+ return Math.min(this.distanceToLine(A, [C, D]),
+ Math.min(this.distanceToLine(B, [C, D]),
+ Math.min(this.distanceToLine(C, [A, B]), this.distanceToLine(D, [A, B]))));
+ }
+
+ //intersection exists
+ return 0;
+ },
+
+ /**
+ * 기하도형이 주어진 라인과 교차하는 좌표들을 반환한다.
+ *
+ * @param {OG.geometry.Coordinate[]} line 라인 시작좌표, 끝좌표 Array
+ * @return {OG.geometry.Coordinate[]}
+ */
+ intersectToLine: function (line) {
+ var vertices = this.getVertices(), result = [], point, i,
+ contains = function (coordinateArray, coordinate) {
+ for (var k = 0, lenk = coordinateArray.length; k < lenk; k++) {
+ if (coordinateArray[k].isEquals(coordinate)) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+ for (var i = 0, leni = vertices.length - 1; i < leni; i++) {
+ point = this.intersectLineToLine(line, [vertices[i], vertices[i + 1]]);
+ if (point && !contains(result, point)) {
+ result.push(point);
+ }
+ }
+
+ return result;
+ },
+
+ /**
+ * 기하도형이 주어진 라인과 교차하는 좌표중 시작좌표에 가장 가까운 좌표를 반환한다.
+ *
+ * @param {OG.geometry.Coordinate[]} line 라인 시작좌표, 끝좌표 Array
+ * @return {OG.geometry.Coordinate[]}
+ */
+ shortestIntersectToLine: function (line) {
+ var startp, intersects, i, distance, shortestDistance = 0, shortestIntersection;
+ startp = this.convertCoordinate(line[0]);
+ intersects = this.intersectToLine(line);
+
+ for (i = 0; i < intersects.length; i++) {
+ distance =
+ Math.pow(startp.x - intersects[i].x, 2) + Math.pow(startp.y - intersects[i].y, 2);
+ if (!shortestDistance) {
+ shortestDistance = distance;
+ shortestIntersection = intersects[i];
+ } else {
+ if (shortestDistance > distance) {
+ shortestDistance = distance;
+ shortestIntersection = intersects[i];
+ }
+ }
+ }
+ return shortestIntersection;
+ },
+
+ /**
+ * 라인1 과 라인2 의 교차점을 계산한다.
+ *
+ * @param {OG.geometry.Coordinate[]} line1 line1 라인 시작좌표, 끝좌표 Array
+ * @param {OG.geometry.Coordinate[]} line2 line2 라인 시작좌표, 끝좌표 Array
+ * @param {boolean} extension 라인을 연장하여 교차점을 계산하는 여부
+ * @return {OG.geometry.Coordinate} 교차점
+ */
+ intersectLineToLine: function (line1, line2, extension) {
+ var A = this.convertCoordinate(line1[0]),
+ B = this.convertCoordinate(line1[1]),
+ C = this.convertCoordinate(line2[0]),
+ D = this.convertCoordinate(line2[1]),
+ result,
+ resultText,
+ r_top, r_bot, s_top, s_bot, r, s;
+
+ // check for zero-length segments
+ if (A.isEquals(B)) {
+ return this.distanceToLine(A, [C, D]) === 0 ? A : undefined;
+ }
+ if (C.isEquals(D)) {
+ return this.distanceToLine(C, [A, B]) === 0 ? C : undefined;
+ }
+
+ r_top = (A.y - C.y) * (D.x - C.x) - (A.x - C.x) * (D.y - C.y);
+ r_bot = (B.x - A.x) * (D.y - C.y) - (B.y - A.y) * (D.x - C.x);
+ s_top = (A.y - C.y) * (B.x - A.x) - (A.x - C.x) * (B.y - A.y);
+ s_bot = (B.x - A.x) * (D.y - C.y) - (B.y - A.y) * (D.x - C.x);
+
+ if (r_bot !== 0 && s_bot !== 0) {
+ r = r_top / r_bot;
+ s = s_top / s_bot;
+ if (0 <= r && r <= 1 && 0 <= s && s <= 1) {
+ resultText = "Intersection";
+ result = new OG.Coordinate(OG.Util.round(A.x + r * (B.x - A.x)), OG.Util.round(A.y + r * (B.y - A.y)));
+ } else {
+ resultText = "No Intersection";
+ if (extension) {
+ result = new OG.Coordinate(OG.Util.round(A.x + r * (B.x - A.x)), OG.Util.round(A.y + r * (B.y - A.y)));
+ }
+ }
+ } else {
+ if (r_top === 0 || s_top === 0) {
+ resultText = "Coincident";
+ } else {
+ resultText = "Parallel";
+ }
+ }
+
+ return result;
+ },
+
+ /**
+ * 주어진 원과 주어진 라인의 교차점을 계산한다.
+ *
+ * @param {OG.geometry.Coordinate} center 중심점
+ * @param {Number} radius 반경
+ * @param {OG.geometry.Coordinate} from line 라인 시작좌표
+ * @param {OG.geometry.Coordinate} to line 라인 끝좌표
+ * @return {OG.geometry.Coordinate[]} 교차점
+ */
+ intersectCircleToLine: function (center, radius, from, to) {
+ var result = [],
+ a = (to.x - from.x) * (to.x - from.x) +
+ (to.y - from.y) * (to.y - from.y),
+ b = 2 * ((to.x - from.x) * (from.x - center.x) +
+ (to.y - from.y) * (from.y - center.y)),
+ cc = center.x * center.x + center.y * center.y + from.x * from.x + from.y * from.y -
+ 2 * (center.x * from.x + center.y * from.y) - radius * radius,
+ deter = b * b - 4 * a * cc,
+ resultText,
+ lerp = function (from, to, t) {
+ return new OG.Coordinate(
+ OG.Util.round(from.x + (to.x - from.x) * t),
+ OG.Util.round(from.y + (to.y - from.y) * t)
+ );
+ },
+ e, u1, u2;
+
+ if (deter < 0) {
+ resultText = "Outside";
+ } else if (deter === 0) {
+ resultText = "Tangent";
+ // NOTE: should calculate this point
+ } else {
+ e = Math.sqrt(deter);
+ u1 = (-b + e) / (2 * a);
+ u2 = (-b - e) / (2 * a);
+
+ if ((u1 < 0 || u1 > 1) && (u2 < 0 || u2 > 1)) {
+ if ((u1 < 0 && u2 < 0) || (u1 > 1 && u2 > 1)) {
+ resultText = "Outside";
+ } else {
+ resultText = "Inside";
+ }
+ } else {
+ resultText = "Intersection";
+
+ if (0 <= u1 && u1 <= 1) {
+ result.push(lerp(from, to, u1));
+ }
+
+ if (0 <= u2 && u2 <= 1) {
+ result.push(lerp(from, to, u2));
+ }
+ }
+ }
+
+ return result;
+ },
+
+ /**
+ * 포인트 P 로부터 라인 AB 까지 수직인 가상선을 생각할때, 그 교차점을 계산한다.
+ * Note: NON-ROBUST!
+ *
+ * @param {OG.geometry.Coordinate|Number[]} p 기준좌표
+ * @param {OG.geometry.Coordinate[]} line 라인 시작좌표, 끝좌표 Array
+ * @return {OG.geometry.Coordinate} 교차점
+ */
+ intersectPointToLine: function (point, line) {
+ var A = this.convertCoordinate(line[0]),
+ B = this.convertCoordinate(line[1]),
+ p = this.convertCoordinate(point),
+ m1, b1, m2, b2, x, y;
+
+ //수평선일때
+ if (B.y === A.y) {
+ return this.convertCoordinate([p.x, A.y]);
+ }
+ //수직일때
+ if (B.x === A.x) {
+ return this.convertCoordinate([A.x, p.y]);
+ }
+
+ m1 = (B.y - A.y) / (B.x - A.x);
+ b1 = A.y - (m1 * A.x);
+
+ m2 = (1 / m1) * -1;
+ b2 = p.y - (m2 * p.x);
+
+ x = (b2 - b1) / (m1 - m2);
+ y = (x * m1) + b1;
+
+ return this.convertCoordinate([x, y]);
+ },
+
+ /**
+ * 주어진 좌표에 대해 공간기하객체 바운더리 대비 가로,세로 위치 퍼센테이지 비율을 구한다.
+ *
+ * @param {OG.geometry.Coordinate} _coordinate 좌표
+ * @return {Object} {px , py}
+ */
+ getPercentageDistanceFromPoint: function (_coordinate) {
+
+ var percentageDistance, boundary, upperLeft, width,
+ height, px, py;
+ _coordinate = this.convertCoordinate(_coordinate);
+
+ boundary = this.getBoundary();
+ if (boundary && boundary._upperLeft) {
+ upperLeft = boundary._upperLeft;
+ width = boundary._width;
+ height = boundary._height;
+ px = Math.round(((_coordinate.x - upperLeft.x) / width) * 100);
+ py = Math.round(((_coordinate.y - upperLeft.y) / height) * 100);
+ percentageDistance = {
+ px: px,
+ py: py
+ };
+ }
+
+ return percentageDistance;
+ },
+
+ /**
+ * 공간기하객체가 주어진 좌표를 포함하는지를 반환한다.
+ *
+ * @param {OG.geometry.Coordinate} _coordinate 좌표
+ * @return {boolean} true,false
+ */
+ isContainsPoint: function (_coordinate) {
+
+ var boundary, upperLeft, width,
+ height, flag;
+ _coordinate = this.convertCoordinate(_coordinate);
+ boundary = this.getBoundary();
+ flag = false;
+
+ if (boundary && boundary._upperLeft) {
+ upperLeft = boundary._upperLeft;
+ width = boundary._width;
+ height = boundary._height;
+
+ if (_coordinate.x >= upperLeft.x && _coordinate.x <= upperLeft.x + width
+ && _coordinate.y >= upperLeft.y && _coordinate.y <= upperLeft.y + height) {
+ flag = true;
+ }
+ }
+
+ return flag;
+ },
+
+ /**
+ * 공간기하객체에 대한 퍼센테이지 좌표의 실제 좌표를 구한다.
+ *
+ * @param {Array} pXpY 퍼센테이지 좌표
+ * @return {OG.geometry.Coordinate} 실 좌표
+ */
+ getPointFromPercentageDistance: function (pXpY) {
+
+ var _coordinate, boundary, px, py, upperLeft, width, height, x, y;
+ boundary = this.getBoundary();
+ if (boundary && boundary._upperLeft) {
+ px = pXpY[0];
+ py = pXpY[1];
+ upperLeft = boundary._upperLeft;
+ width = boundary._width;
+ height = boundary._height;
+
+ x = upperLeft.x + (width * (px / 100));
+ y = upperLeft.y + (height * (py / 100));
+ _coordinate = this.convertCoordinate([Math.round(x), Math.round(y)]);
+ }
+
+ return _coordinate;
+ },
+
+ /**
+ * 주어진 선분과 일정 거리에 있는 평행한 선분을 반환한다.
+ * @param {OG.geometry.Coordinate} from 라인 시작좌표
+ * @param {OG.geometry.Coordinate} to 라인 끝좌표
+ * @param distance
+ * @return {OG.geometry.Coordinate[]} 평행선 시작좌표, 끝좌표 Array
+ */
+
+ getParallelLine: function (from, to, distance) {
+ var me = this;
+ var direction = 'plus';
+ var moveX = 0;
+ var moveY = 0;
+ var p1 = this.convertCoordinate(from);
+ var p2 = this.convertCoordinate(to);
+ //캔버스의 좌표계는 y 좌표가 위에서부터 0으로 시작하기 때문에 방정식 좌표계에 맞추기 위해, 두 점의 y 를 교환하고,
+ //방정식의 상수를 마추기 위해 첫번째 x,y 를 0 으로 맞추고 두번째 x,y 도 그 거리만큼 이동한다.
+ //계산 후의 두 좌표를 리턴할때 y 를 서로 교체하도록 한다.
+ var reverseY = function (p1, p2) {
+ p1.y = p1.y * -1;
+ p2.y = p2.y * -1;
+ moveX = 0 - p1.x;
+ moveY = 0 - p1.y;
+ p1.x = p1.x + moveX;
+ p2.x = p2.x + moveX;
+ p1.y = p1.y + moveY;
+ p2.y = p2.y + moveY;
+ return [p1, p2]
+ };
+
+ var revertY = function (p1, p2) {
+ p1 = me.convertCoordinate(p1);
+ p2 = me.convertCoordinate(p2);
+ p1.x = p1.x - moveX;
+ p2.x = p2.x - moveX;
+ p1.y = p1.y - moveY;
+ p2.y = p2.y - moveY;
+ p1.y = p1.y * -1;
+ p2.y = p2.y * -1;
+ return [p1, p2]
+ };
+
+ reverseY(p1, p2);
+
+ var x1 = p1.x;
+ var x2 = p2.x;
+ var y1 = p1.y;
+ var y2 = p2.y;
+
+ //distance 이 0 인 경우
+ if (y1 == y2 && x1 == x2) {
+ return revertY(p1, p2);
+ }
+
+ //두 점이 동일할 경우
+ if (y1 == y2 && x1 == x2) {
+ return revertY(p1, p2);
+ }
+
+ //distance 이 직선이 어느방향인가에 따라 틀려져야 한다.
+ //두 점이 y 축과 평행할 경우 선분이 위 방향이면 distance 은 - 값이다.
+ if (x1 == x2) {
+ if (y2 > y1) {
+ distance = distance * -1;
+ direction = 'minus';
+ }
+ }
+ //그 외의 경우 선분이 좌측방향이면 distance 은 - 값이다
+ else {
+ if (x2 < x1) {
+ distance = distance * -1;
+ direction = 'minus';
+ }
+ }
+
+ //두 점이 x 축과 평행할 경우
+ if (y1 == y2) {
+ return revertY([x1, y1 + distance], [x2, y2 + distance]);
+ }
+
+ //두 점이 y 축과 평행할 경우
+ if (x1 == x2) {
+ return revertY([x1 + distance, y1], [x2 + distance, y2]);
+ }
+
+ //두 점 사이에 기울기가 존재할 경우
+ var m = -1 / ((y2 - y1) / (x2 - x1));
+
+ //상수 c1 는 p1 과 수직인 선의 방정식 상수
+ var c1 = y1 - (m * x1);
+
+ //상수 c2 는 p2 과 수직인 선의 방정식 상수
+ var c2 = y2 - (m * x2);
+
+ //점x1 ,y1 을 지나는 원의 방정식: (x-x1)제곱 + (y-y1)제곱 = distance (반지름) 제곱
+ //근의 방정식: ax제곱 + bx + d = 0; ==> -b +- 루트(b제곱 - 4ad) / 2a
+ var getInjectPoint = function (x1, y1, m, c, distance) {
+ var x, y, meetX1, meetX2, meetY1, meetY2;
+ var a, b, d;
+ a = 1 + Math.pow(m, 2);
+ b = (-2 * x1) + (2 * m * c) - (2 * m * y1);
+ d = Math.pow(x1, 2) + Math.pow(c - y1, 2) - Math.pow(distance, 2);
+
+ meetX1 = (-b + Math.sqrt(Math.pow(b, 2) - (4 * a * d))) / (2 * a);
+ meetY1 = meetX1 * m + c;
+
+ meetX2 = (-b - Math.sqrt(Math.pow(b, 2) - (4 * a * d))) / (2 * a);
+ meetY2 = meetX2 * m + c;
+
+ //원 방정식으로 구한 두 접점을 벡터의 방향에 따라 한 접점을 선택한다.
+ //distance 이 음수값일 경우
+ if (distance < 0) {
+ if (meetY1 < meetY2) {
+ x = meetX1;
+ y = meetY1;
+ } else {
+ x = meetX2;
+ y = meetY2;
+ }
+ }
+ //distance 이 양수일 경우
+ else {
+ if (meetY1 > meetY2) {
+ x = meetX1;
+ y = meetY1;
+ } else {
+ x = meetX2;
+ y = meetY2;
+ }
+ }
+ return [x, y];
+ };
+ return revertY(getInjectPoint(x1, y1, m, c1, distance), getInjectPoint(x2, y2, m, c2, distance));
+ },
+
+ /**
+ * 주어진 라인과 일정 거리에 있는 평행한 라인을 반환한다.
+ * @param {OG.geometry.Coordinate[]} line 라인 좌표 Array
+ * @param distance
+ */
+ getParallelPath: function (line, distance) {
+ var me = this;
+ var lines = [];
+ var vertices = [];
+ for (var i = 0, leni = line.length; i < leni; i++) {
+ if (i < leni - 1) {
+ lines.push(me.getParallelLine(line[i], line[i + 1], distance));
+ }
+ }
+ for (var c = 0, lenc = lines.length; c < lenc; c++) {
+ if (c == 0) {
+ vertices[0] = lines[c][0];
+ }
+ if (c < lenc - 1) {
+ var inject = me.intersectLineToLine(lines[c], lines[c + 1], true);
+ //교점이 존재할 경우
+ if (inject) {
+ vertices[c + 1] = inject;
+ } else {
+ vertices[c + 1] = lines[c][1]
+ }
+ }
+ if (c == lenc - 1) {
+ vertices[c + 1] = lines[c][1]
+ }
+ }
+ return vertices;
+ },
+
+ /**
+ * 저장된 boundary 를 클리어하여 새로 계산하도록 한다.
+ */
+ reset: function () {
+ this.boundary = null;
+ }
+};
+OG.geometry.Geometry.prototype.constructor = OG.geometry.Geometry;
+/**
+ * PolyLine 공간 기하 객체(Spatial Geometry Object)
+ *
+ * @class
+ * @extends OG.geometry.Geometry
+ * @requires OG.geometry.Coordinate
+ * @requires OG.geometry.Envelope
+ * @requires OG.geometry.Geometry
+ *
+ * @example
+ * var geom = new OG.geometry.PolyLine([[20, 5], [30, 15], [40, 25], [50, 15]]);
+ *
+ * @param {OG.geometry.Coordinate[]} vertices Line Vertex 좌표 Array
+ * @author Seungpil Park
+ */
+OG.geometry.PolyLine = function (vertices) {
+
+ var i;
+
+ this.TYPE = OG.Constants.GEOM_TYPE.POLYLINE;
+ this.style = new OG.geometry.Style();
+
+ /**
+ * Line Vertex 좌표 Array
+ * @type OG.geometry.Coordinate[]
+ */
+ this.vertices = [];
+
+ // Array 좌표를 OG.geometry.Coordinate 로 변환
+ if (vertices && vertices.length > 0) {
+ for (var i = 0, leni = vertices.length; i < leni; i++) {
+ this.vertices.push(this.convertCoordinate(vertices[i]));
+ }
+ }
+};
+OG.geometry.PolyLine.prototype = new OG.geometry.Geometry();
+OG.geometry.PolyLine.superclass = OG.geometry.Geometry;
+OG.geometry.PolyLine.prototype.constructor = OG.geometry.PolyLine;
+OG.PolyLine = OG.geometry.PolyLine;
+
+/**
+ * 공간기하객체의 모든 꼭지점을 반환한다.
+ *
+ * @return {OG.geometry.Coordinate[]} 꼭지점 좌표 Array
+ * @override
+ */
+OG.geometry.PolyLine.prototype.getVertices = function () {
+ return this.vertices;
+};
+
+OG.geometry.PolyLine.prototype.setVertices = function (vertices) {
+ this.vertices = vertices;
+};
+
+/**
+ * 가로, 세로 Offset 만큼 좌표를 이동한다.
+ *
+ * @param {Number} offsetX 가로 Offset
+ * @param {Number} offsetY 세로 Offset
+ * @return {OG.geometry.Geometry} 이동된 공간 기하 객체
+ * @override
+ */
+OG.geometry.PolyLine.prototype.move = function (offsetX, offsetY) {
+ this.getBoundary().move(offsetX, offsetY);
+ for (var i = 0, leni = this.vertices.length; i < leni; i++) {
+ this.vertices[i].move(offsetX, offsetY);
+ }
+
+ return this;
+};
+
+/**
+ * 상, 하, 좌, 우 외곽선을 이동하여 Envelope 을 리사이즈 한다.
+ *
+ * @param {Number} upper 상단 라인 이동 Offset(위 방향으로 +)
+ * @param {Number} lower 하단 라인 이동 Offset(아래 방향으로 +)
+ * @param {Number} left 좌측 라인 이동 Offset(좌측 방향으로 +)
+ * @param {Number} right 우측 라인 이동 Offset(우측 방향으로 +)
+ * @return {OG.geometry.Geometry} 리사이즈된 공간 기하 객체
+ * @override
+ */
+OG.geometry.PolyLine.prototype.resize = function (upper, lower, left, right) {
+ var boundary = this.getBoundary(),
+ offsetX = left + right,
+ offsetY = upper + lower,
+ width = boundary.getWidth() + offsetX,
+ height = boundary.getHeight() + offsetY,
+ rateWidth = boundary.getWidth() === 0 ? 1 : width / boundary.getWidth(),
+ rateHeight = boundary.getHeight() === 0 ? 1 : height / boundary.getHeight(),
+ upperLeft = boundary.getUpperLeft(),
+ i;
+
+ if (width < 0 || height < 0) {
+ throw new OG.ParamError();
+ }
+
+ for (var i = 0, leni = this.vertices.length; i < leni; i++) {
+ this.vertices[i].x = OG.Util.round((upperLeft.x - left) + (this.vertices[i].x - upperLeft.x) * rateWidth);
+ this.vertices[i].y = OG.Util.round((upperLeft.y - upper) + (this.vertices[i].y - upperLeft.y) * rateHeight);
+ }
+ boundary.resize(upper, lower, left, right);
+
+ return this;
+};
+
+/**
+ * 기준 좌표를 기준으로 주어진 각도 만큼 회전한다.
+ *
+ * @param {Number} angle 회전 각도
+ * @param {OG.geometry.Coordinate} origin 기준 좌표
+ * @return {OG.geometry.Geometry} 회전된 공간 기하 객체
+ * @override
+ */
+OG.geometry.PolyLine.prototype.rotate = function (angle, origin) {
+ origin = origin || this.getCentroid();
+ for (var i = 0, leni = this.vertices.length; i < leni; i++) {
+ this.vertices[i].rotate(angle, origin);
+ }
+ this.reset();
+
+ return this;
+};
+
+/**
+ * 객체 프라퍼티 정보를 JSON 스트링으로 반환한다.
+ *
+ * @return {String} 프라퍼티 정보
+ * @override
+ */
+OG.geometry.PolyLine.prototype.toString = function () {
+ var s = [];
+ s.push("type:'" + OG.Constants.GEOM_NAME[this.TYPE] + "'");
+ s.push("vertices:[" + this.vertices + "]");
+
+ return "{" + s.join() + "}";
+};
+
+/**
+ * 공간기하객체의 두 꼭지점 사이에 가상의 선을 그렸을때, 그 기울기를 구한다.
+ *
+ * @param {OG.geometry.Coordinate} prev 꼭지점 1
+ * @param {OG.geometry.Coordinate} next 꼭지점 2
+ *
+ * @return {Number} 기울기
+ * @override
+ */
+OG.geometry.PolyLine.prototype.angleBetweenPoints = function (prev, next) {
+ var p1, p2, angleRadians, angleDeg;
+ p1 = {
+ x: prev.x,
+ y: prev.y
+ };
+
+ p2 = {
+ x: next.x,
+ y: next.y
+ };
+
+ angleRadians = Math.atan2(p2.y - p1.y, p2.x - p1.x);
+
+ angleDeg = Math.atan2(p2.y - p1.y, p2.x - p1.x) * 180 / Math.PI;
+ return angleDeg;
+};
+
+/**
+ * 공간기하객체의 두 꼭지점 사이의 기울기가 수평또는 수직인지 판별한다.
+ *
+ * @param {OG.geometry.Coordinate} prev 꼭지점 1
+ * @param {OG.geometry.Coordinate} next 꼭지점 2
+ *
+ * @return {Object} {flag : true or false, type: horizontal or vertical or none}
+ * @override
+ */
+OG.geometry.PolyLine.prototype.isRightAngleBetweenPoints = function (prev, next) {
+ var horizontalAngles, verticalAngles, angleDeg;
+ horizontalAngles = [0, 180, 360];
+ verticalAngles = [90, 270];
+ angleDeg = Math.abs(this.angleBetweenPoints(prev, next));
+ if (horizontalAngles.indexOf(angleDeg) !== -1) {
+ return {
+ flag: true,
+ type: 'horizontal'
+ };
+ }
+ if (verticalAngles.indexOf(angleDeg) !== -1) {
+ return {
+ flag: true,
+ type: 'vertical'
+ };
+ }
+ return {
+ flag: false,
+ type: 'none'
+ };
+};
+
+/**
+ * 공간기하객체의 세 꼭지점 사이의 각도 중 작은 각도를 반환한다.
+ *
+ * @param {OG.geometry.Coordinate} prev 꼭지점 1
+ * @param {OG.geometry.Coordinate} next 꼭지점 2
+ *
+ * @return {Number} 기울기
+ * @override
+ */
+OG.geometry.PolyLine.prototype.angleBetweenThreePoints = function (prev, center, next) {
+ var AB = Math.sqrt(Math.pow(center.x - prev.x, 2) + Math.pow(center.y - prev.y, 2)),
+ BC = Math.sqrt(Math.pow(center.x - next.x, 2) + Math.pow(center.y - next.y, 2)),
+ AC = Math.sqrt(Math.pow(next.x - prev.x, 2) + Math.pow(next.y - prev.y, 2)),
+ angleRadians, angleDeg;
+
+
+ angleRadians = Math.acos((BC * BC + AB * AB - AC * AC) / (2 * BC * AB));
+
+ angleDeg = angleRadians * 180 / Math.PI;
+
+ return angleDeg;
+};
+/**
+ * Catmull-Rom Spline Curve 공간 기하 객체(Spatial Geometry Object)
+ * 모든 콘트롤포인트를 지나는 곡선을 나타낸다.
+ *
+ * @class
+ * @extends OG.geometry.PolyLine
+ * @requires OG.geometry.Coordinate
+ * @requires OG.geometry.Envelope
+ * @requires OG.geometry.Geometry
+ * @requires OG.common.CurveUtil
+ *
+ * @example
+ * var geom = new OG.geometry.Curve([[200, 100], [100, 300], [-100, -100], [-200, 100]]);
+ *
+ * @param {OG.geometry.Coordinate[]} controlPoints Curve Vertex 좌표 Array
+ * @author Seungpil Park
+ */
+OG.geometry.Curve = function (controlPoints) {
+
+ OG.geometry.Curve.superclass.call(this, controlPoints);
+
+ var t, cmRomSpline = OG.CurveUtil.CatmullRomSpline(eval("[" + this.vertices.toString() + "]"));
+
+ // t 는 0 ~ maxT 의 값으로, t 값의 증분값이 작을수록 세밀한 Curve 를 그린다.
+ this.vertices = [];
+ for (var t = 0, lent = cmRomSpline.maxT; t <= lent; t += 0.1) {
+ this.vertices.push(new OG.geometry.Coordinate(
+ cmRomSpline.getX(t),
+ cmRomSpline.getY(t)
+ ));
+ }
+
+ this.TYPE = OG.Constants.GEOM_TYPE.CURVE;
+ this.style = new OG.geometry.Style();
+};
+OG.geometry.Curve.prototype = new OG.geometry.PolyLine();
+OG.geometry.Curve.superclass = OG.geometry.PolyLine;
+OG.geometry.Curve.prototype.constructor = OG.geometry.Curve;
+OG.Curve = OG.geometry.Curve;
+
+/**
+ * 콘트롤 포인트 목록을 반환한다.
+ *
+ * @return {OG.geometry.Coordinate[]} controlPoints Array
+ */
+OG.geometry.Curve.prototype.getControlPoints = function () {
+ var controlPoints = [], i;
+ for (var i = 10, leni = this.vertices.length - 10; i <= leni; i += 10) {
+ controlPoints.push(this.vertices[i]);
+ }
+
+ return controlPoints;
+};
+
+/**
+ * 공간기하객체의 모든 꼭지점을 반환한다.
+ *
+ * @return {OG.geometry.Coordinate[]} 꼭지점 좌표 Array
+ * @override
+ */
+OG.geometry.Curve.prototype.getVertices = function () {
+ var vertices = [], i;
+ for (var i = 10, leni = this.vertices.length - 10; i <= leni; i++) {
+ vertices.push(this.vertices[i]);
+ }
+
+ return vertices;
+};
+
+
+/**
+ * 객체 프라퍼티 정보를 JSON 스트링으로 반환한다.
+ *
+ * @return {String} 프라퍼티 정보
+ * @override
+ */
+OG.geometry.Curve.prototype.toString = function () {
+ var s = [];
+ s.push("type:'" + OG.Constants.GEOM_NAME[this.TYPE] + "'");
+ s.push("vertices:[" + this.getVertices() + "]");
+ s.push("controlPoints:[" + this.getControlPoints() + "]");
+
+ return "{" + s.join() + "}";
+};
+/**
+ * Ellipse 공간 기하 객체(Spatial Geometry Object)
+ *
+ * @class
+ * @extends OG.geometry.Curve
+ * @requires OG.geometry.Coordinate
+ * @requires OG.geometry.Envelope
+ * @requires OG.geometry.Geometry
+ *
+ * @example
+ * var geom = new OG.geometry.Ellipse([10, 10], 10, 5);
+ *
+ * @param {OG.geometry.Coordinate} center Ellipse 중심 좌표
+ * @param {Number} radiusX X축 반경
+ * @param {Number} radiusY Y축 반경
+ * @param {Number} angle X축 기울기
+ * @author Seungpil Park
+ */
+OG.geometry.Ellipse = function (center, radiusX, radiusY, angle) {
+
+ var _angle = angle || 0, _center = this.convertCoordinate(center), controlPoints = [], theta, i;
+
+ if (_center) {
+ for (i = -45; i <= 405; i += 45) {
+ theta = Math.PI / 180 * i;
+ controlPoints.push((new OG.geometry.Coordinate(
+ OG.Util.round(_center.x + radiusX * Math.cos(theta)),
+ OG.Util.round(_center.y + radiusY * Math.sin(theta))
+ )).rotate(_angle, _center));
+ }
+ }
+
+ OG.geometry.Ellipse.superclass.call(this, controlPoints);
+
+ this.TYPE = OG.Constants.GEOM_TYPE.ELLIPSE;
+ this.IS_CLOSED = true;
+ this.style = new OG.geometry.Style();
+};
+OG.geometry.Ellipse.prototype = new OG.geometry.Curve();
+OG.geometry.Ellipse.superclass = OG.geometry.Curve;
+OG.geometry.Ellipse.prototype.constructor = OG.geometry.Ellipse;
+OG.Ellipse = OG.geometry.Ellipse;
+
+/**
+ * 공간기하객체의 모든 꼭지점을 반환한다.
+ *
+ * @return {OG.geometry.Coordinate[]} 꼭지점 좌표 Array
+ * @override
+ */
+OG.geometry.Ellipse.prototype.getVertices = function () {
+ var vertices = [], i;
+ for (var i = 20, leni = this.vertices.length - 20; i < leni; i++) {
+ vertices.push(this.vertices[i]);
+ }
+
+ return vertices;
+};
+
+/**
+ * 콘트롤 포인트 목록을 반환한다.
+ *
+ * @return {OG.geometry.Coordinate[]} controlPoints Array
+ * @override
+ */
+OG.geometry.Ellipse.prototype.getControlPoints = function () {
+ var controlPoints = [], i;
+ for (var i = 10, leni = this.vertices.length - 10; i <= leni; i += 10) {
+ controlPoints.push(this.vertices[i]);
+ }
+
+ return controlPoints;
+};
+
+/**
+ * 공간기하객체의 길이를 반환한다.
+ *
+ * @return {Number} 길이
+ * @override
+ */
+OG.geometry.Ellipse.prototype.getLength = function () {
+ // π{5(a+b)/4 - ab/(a+b)}
+ var controlPoints = this.getControlPoints(),
+ center = this.getCentroid(),
+ radiusX = center.distance(controlPoints[1]),
+ radiusY = center.distance(controlPoints[3]);
+ return Math.PI * (5 * (radiusX + radiusY) / 4 - radiusX * radiusY / (radiusX + radiusY));
+};
+
+/**
+ * 객체 프라퍼티 정보를 JSON 스트링으로 반환한다.
+ *
+ * @return {String} 프라퍼티 정보
+ * @override
+ */
+OG.geometry.Ellipse.prototype.toString = function () {
+ var s = [],
+ controlPoints = this.getControlPoints(),
+ center = this.getCentroid(),
+ radiusX = center.distance(controlPoints[1]),
+ radiusY = center.distance(controlPoints[3]),
+ angle = OG.Util.round(Math.atan2(controlPoints[1].y - center.y, controlPoints[1].x - center.x) * 180 / Math.PI);
+
+ s.push("type:'" + OG.Constants.GEOM_NAME[this.TYPE] + "'");
+ s.push("center:" + center);
+ s.push("radiusX:" + radiusX);
+ s.push("radiusY:" + radiusY);
+ s.push("angle:" + angle);
+
+ return "{" + s.join() + "}";
+};
+/**
+ * Cubic Bezier Curve 공간 기하 객체(Spatial Geometry Object)
+ * 콘트롤포인트1, 콘트롤포인트2에 의해 시작좌표, 끝좌표를 지나는 곡선을 나타낸다.
+ *
+ * @class
+ * @extends OG.geometry.PolyLine
+ * @requires OG.geometry.Coordinate
+ * @requires OG.geometry.Envelope
+ * @requires OG.geometry.Geometry
+ * @requires OG.common.CurveUtil
+ *
+ * @example
+ * var geom = new OG.geometry.BezierCurve([[200, 100], [100, 300], [-100, -100], [-200, 100]]);
+ *
+ * @param {OG.geometry.Coordinate[]} controlPoints [from, control_point1, control_point2, to]
+ * @author Seungpil Park
+ */
+OG.geometry.BezierCurve = function (controlPoints) {
+ var bezier, t, i;
+
+ if (!controlPoints && controlPoints.length !== 4) {
+ throw new OG.ParamError();
+ }
+
+ /**
+ * Bezier Curve 콘트롤 좌표 Array
+ * @type OG.geometry.Coordinate[]
+ */
+ this.controlPoints = [];
+
+ // Array 좌표를 OG.geometry.Coordinate 로 변환
+ if (controlPoints && controlPoints.length > 0) {
+ for (var i = 0, leni = controlPoints.length; i < leni; i++) {
+ this.controlPoints.push(this.convertCoordinate(controlPoints[i]));
+ }
+ }
+
+ // Bezier Curve
+ bezier = OG.CurveUtil.Bezier(eval("[" + this.controlPoints.toString() + "]"));
+
+ // t 는 0 ~ maxT 의 값으로, t 값의 증분값이 작을수록 세밀한 BezierCurve 를 그린다.
+ this.vertices = [];
+ for (var t = 0, lent = bezier.maxT; t <= lent; t += 0.02) {
+ this.vertices.push(new OG.geometry.Coordinate(
+ OG.Util.round(bezier.getX(t)),
+ OG.Util.round(bezier.getY(t))
+ ));
+ }
+
+ this.TYPE = OG.Constants.GEOM_TYPE.BEZIER_CURVE;
+ this.style = new OG.geometry.Style();
+};
+OG.geometry.BezierCurve.prototype = new OG.geometry.PolyLine();
+OG.geometry.BezierCurve.superclass = OG.geometry.PolyLine;
+OG.geometry.BezierCurve.prototype.constructor = OG.geometry.BezierCurve;
+OG.BezierCurve = OG.geometry.BezierCurve;
+
+/**
+ * 콘트롤 포인트 목록을 반환한다.
+ *
+ * @return {OG.geometry.Coordinate[]} controlPoints Array
+ */
+OG.geometry.BezierCurve.prototype.getControlPoints = function () {
+ return this.controlPoints;
+};
+
+/**
+ * 공간기하객체의 모든 꼭지점을 반환한다.
+ *
+ * @return {OG.geometry.Coordinate[]} 꼭지점 좌표 Array
+ * @override
+ */
+OG.geometry.BezierCurve.prototype.getVertices = function () {
+ var bezier, t, i;
+ if (!this.vertices) {
+ // Bezier Curve
+ bezier = OG.CurveUtil.Bezier(eval("[" + this.controlPoints.toString() + "]"));
+
+ // t 는 0 ~ maxT 의 값으로, t 값의 증분값이 작을수록 세밀한 BezierCurve 를 그린다.
+ this.vertices = [];
+ for (var t = 0, lent = bezier.maxT; t <= lent; t += 0.02) {
+ this.vertices.push(new OG.geometry.Coordinate(
+ OG.Util.round(bezier.getX(t)),
+ OG.Util.round(bezier.getY(t))
+ ));
+ }
+ }
+
+ return this.vertices;
+};
+
+
+/**
+ * 가로, 세로 Offset 만큼 좌표를 이동한다.
+ *
+ * @param {Number} offsetX 가로 Offset
+ * @param {Number} offsetY 세로 Offset
+ * @return {OG.geometry.Geometry} 이동된 공간 기하 객체
+ * @override
+ */
+OG.geometry.BezierCurve.prototype.move = function (offsetX, offsetY) {
+ for (var i = 0, leni = this.controlPoints.length; i < leni; i++) {
+ this.controlPoints[i].move(offsetX, offsetY);
+ }
+ this.reset();
+
+ return this;
+};
+
+/**
+ * 상, 하, 좌, 우 외곽선을 이동하여 Envelope 을 리사이즈 한다.
+ *
+ * @param {Number} upper 상단 라인 이동 Offset(위 방향으로 +)
+ * @param {Number} lower 하단 라인 이동 Offset(아래 방향으로 +)
+ * @param {Number} left 좌측 라인 이동 Offset(좌측 방향으로 +)
+ * @param {Number} right 우측 라인 이동 Offset(우측 방향으로 +)
+ * @return {OG.geometry.Geometry} 리사이즈된 공간 기하 객체
+ * @override
+ */
+OG.geometry.BezierCurve.prototype.resize = function (upper, lower, left, right) {
+ throw new OG.NotSupportedException('OG.geometry.BezierCurve.resize() Not Supported!');
+};
+
+/**
+ * 기준 좌표를 기준으로 주어진 각도 만큼 회전한다.
+ *
+ * @param {Number} angle 회전 각도
+ * @param {OG.geometry.Coordinate} origin 기준 좌표
+ * @return {OG.geometry.Geometry} 회전된 공간 기하 객체
+ * @override
+ */
+OG.geometry.BezierCurve.prototype.rotate = function (angle, origin) {
+ origin = origin || this.getCentroid();
+ for (var i = 0, leni = this.controlPoints.length; i < leni; i++) {
+ this.controlPoints[i].rotate(angle, origin);
+ }
+ this.reset();
+
+ return this;
+};
+
+/**
+ * 객체 프라퍼티 정보를 JSON 스트링으로 반환한다.
+ *
+ * @return {String} 프라퍼티 정보
+ * @override
+ */
+OG.geometry.BezierCurve.prototype.toString = function () {
+ var s = [];
+ s.push("type:'" + OG.Constants.GEOM_NAME[this.TYPE] + "'");
+ s.push("vertices:[" + this.getVertices() + "]");
+ s.push("controlPoints:[" + this.getControlPoints() + "]");
+
+ return "{" + s.join() + "}";
+};
+
+/**
+ * 저장된 boundary 를 클리어하여 새로 계산하도록 한다.
+ * @override
+ */
+OG.geometry.BezierCurve.prototype.reset = function () {
+ this.boundary = null;
+ this.vertices = null;
+};
+/**
+ * Circle 공간 기하 객체(Spatial Geometry Object)
+ *
+ * @class
+ * @extends OG.geometry.Ellipse
+ * @requires OG.geometry.Coordinate
+ * @requires OG.geometry.Envelope
+ * @requires OG.geometry.Geometry
+ *
+ * @example
+ * var geom = new OG.geometry.Circle([10, 10], 5);
+ *
+ * @param {OG.geometry.Coordinate} center Circle 중심 좌표
+ * @param {Number} radius radius 반경
+ * @author Seungpil Park
+ */
+OG.geometry.Circle = function (center, radius) {
+
+ OG.geometry.Circle.superclass.call(this, center, radius, radius, 0);
+
+ this.TYPE = OG.Constants.GEOM_TYPE.CIRCLE;
+ this.style = new OG.geometry.Style();
+};
+OG.geometry.Circle.prototype = new OG.geometry.Ellipse();
+OG.geometry.Circle.superclass = OG.geometry.Ellipse;
+OG.geometry.Circle.prototype.constructor = OG.geometry.Circle;
+OG.Circle = OG.geometry.Circle;
+
+/**
+ * 공간기하객체의 길이를 반환한다.
+ *
+ * @return {Number} 길이
+ * @override
+ */
+OG.geometry.Circle.prototype.getLength = function () {
+ var controlPoints = this.getControlPoints(),
+ center = this.getCentroid(),
+ radiusX = center.distance(controlPoints[1]);
+ return 2 * Math.PI * radiusX;
+};
+
+/**
+ * 객체 프라퍼티 정보를 JSON 스트링으로 반환한다.
+ *
+ * @return {String} 프라퍼티 정보
+ * @override
+ */
+OG.geometry.Circle.prototype.toString = function () {
+ var s = [],
+ controlPoints = this.getControlPoints(),
+ center = this.getCentroid(),
+ radiusX = center.distance(controlPoints[1]),
+ radiusY = center.distance(controlPoints[3]),
+ angle = OG.Util.round(Math.atan2(controlPoints[1].y - center.y, controlPoints[1].x - center.x) * 180 / Math.PI);
+
+ if (radiusX === radiusY) {
+ s.push("type:'" + OG.Constants.GEOM_NAME[this.TYPE] + "'");
+ s.push("center:" + center);
+ s.push("radius:" + radiusX);
+ } else {
+ s.push("type:'" + OG.Constants.GEOM_NAME[OG.Constants.GEOM_TYPE.ELLIPSE] + "'");
+ s.push("center:" + center);
+ s.push("radiusX:" + radiusX);
+ s.push("radiusY:" + radiusY);
+ s.push("angle:" + angle);
+ }
+
+ return "{" + s.join() + "}";
+};
+/**
+ * 공간 기하 객체(Spatial Geometry Object) Collection
+ *
+ * @class
+ * @extends OG.geometry.Geometry
+ * @requires OG.geometry.Coordinate
+ * @requires OG.geometry.Envelope
+ * @requires OG.geometry.Geometry
+ *
+ * @example
+ * var geom1 = new OG.geometry.Point([20, 5]),
+ * geom2 = new OG.geometry.Line([20, 5], [30, 15]),
+ * geom3 = new OG.geometry.PolyLine([[20, 5], [30, 15], [40, 25], [50, 15]]);
+ *
+ * var collection = new OG.geometry.GeometryCollection([geom1, geom2, geom3]);
+ *
+ * @param geometries {OG.geometry.Geometry[]} 공간 기하 객체 Array
+ * @author Seungpil Park
+ */
+OG.geometry.GeometryCollection = function (geometries) {
+
+ this.TYPE = OG.Constants.GEOM_TYPE.COLLECTION;
+ this.style = new OG.geometry.Style();
+
+ /**
+ * 공간 기하 객체 Array
+ * @type OG.geometry.Geometry[]
+ */
+ this.geometries = geometries;
+};
+OG.geometry.GeometryCollection.prototype = new OG.geometry.Geometry();
+OG.geometry.GeometryCollection.superclass = OG.geometry.Geometry;
+OG.geometry.GeometryCollection.prototype.constructor = OG.geometry.GeometryCollection;
+OG.GeometryCollection = OG.geometry.GeometryCollection;
+
+/**
+ * 공간기하객체의 모든 꼭지점을 반환한다.
+ *
+ * @return {OG.geometry.Coordinate[]} 꼭지점 좌표 Array
+ * @override
+ */
+OG.geometry.GeometryCollection.prototype.getVertices = function () {
+ var vertices = [], _vertices, i, j;
+ for (var i = 0, leni = this.geometries.length; i < leni; i++) {
+ _vertices = this.geometries[i].getVertices();
+ for (var j = 0, lenj = _vertices.length; j < lenj; j++) {
+ vertices.push(_vertices[j]);
+ }
+ }
+
+ return vertices;
+};
+OG.geometry.GeometryCollection.prototype.getVerticess = function () {
+
+};
+
+/**
+ * 가로, 세로 Offset 만큼 좌표를 이동한다.
+ *
+ * @param {Number} offsetX 가로 Offset
+ * @param {Number} offsetY 세로 Offset
+ * @return {OG.geometry.Geometry} 이동된 공간 기하 객체
+ * @override
+ */
+OG.geometry.GeometryCollection.prototype.move = function (offsetX, offsetY) {
+ this.getBoundary().move(offsetX, offsetY);
+ for (var i = 0, leni = this.geometries.length; i < leni; i++) {
+ this.geometries[i].move(offsetX, offsetY);
+ this.geometries[i].reset();
+ }
+
+ return this;
+};
+
+/**
+ * 상, 하, 좌, 우 외곽선을 이동하여 Envelope 을 리사이즈 한다.
+ *
+ * @param {Number} upper 상단 라인 이동 Offset(위 방향으로 +)
+ * @param {Number} lower 하단 라인 이동 Offset(아래 방향으로 +)
+ * @param {Number} left 좌측 라인 이동 Offset(좌측 방향으로 +)
+ * @param {Number} right 우측 라인 이동 Offset(우측 방향으로 +)
+ * @return {OG.geometry.Geometry} 리사이즈된 공간 기하 객체
+ * @override
+ */
+OG.geometry.GeometryCollection.prototype.resize = function (upper, lower, left, right) {
+ var boundary = this.getBoundary(),
+ offsetX = left + right,
+ offsetY = upper + lower,
+ width = boundary.getWidth() + offsetX,
+ height = boundary.getHeight() + offsetY,
+ rateWidth = boundary.getWidth() === 0 ? 1 : width / boundary.getWidth(),
+ rateHeight = boundary.getHeight() === 0 ? 1 : height / boundary.getHeight(),
+ upperLeft = boundary.getUpperLeft(),
+ vertices, i, j;
+
+ if (width < 0 || height < 0) {
+ throw new OG.ParamError();
+ }
+
+ for (var i = 0, leni = this.geometries.length; i < leni; i++) {
+ vertices = this.geometries[i].vertices;
+ for (var j = 0, lenj = vertices.length; j < lenj; j++) {
+ vertices[j].x = OG.Util.round((upperLeft.x - left) + (vertices[j].x - upperLeft.x) * rateWidth);
+ vertices[j].y = OG.Util.round((upperLeft.y - upper) + (vertices[j].y - upperLeft.y) * rateHeight);
+ }
+ this.geometries[i].reset();
+ }
+ boundary.resize(upper, lower, left, right);
+
+ return this;
+};
+
+/**
+ * 중심좌표는 고정한 채 Bounding Box 의 width, height 를 리사이즈 한다.
+ *
+ * @param {Number} width 너비
+ * @param {Number} height 높이
+ * @return {OG.geometry.Geometry} 리사이즈된 공간 기하 객체
+ * @override
+ */
+OG.geometry.GeometryCollection.prototype.resizeBox = function (width, height) {
+ var boundary = this.getBoundary(),
+ offsetWidth = OG.Util.round((width - boundary.getWidth()) / 2),
+ offsetHeight = OG.Util.round((height - boundary.getHeight()) / 2);
+
+ this.resize(offsetHeight, offsetHeight, offsetWidth, offsetWidth);
+
+ return this;
+};
+
+/**
+ * 기준 좌표를 기준으로 주어진 각도 만큼 회전한다.
+ *
+ * @param {Number} angle 회전 각도
+ * @param {OG.geometry.Coordinate} origin 기준 좌표(default:중심좌표)
+ * @return {OG.geometry.Geometry} 회전된 공간 기하 객체
+ * @override
+ */
+OG.geometry.GeometryCollection.prototype.rotate = function (angle, origin) {
+ origin = origin || this.getCentroid();
+ for (var i = 0, leni = this.geometries.length; i < leni; i++) {
+ this.geometries[i].rotate(angle, origin);
+ this.geometries[i].reset();
+ }
+ this.reset();
+
+ return this;
+};
+
+/**
+ * 주어진 Boundary 영역 안으로 공간 기하 객체를 적용한다.(이동 & 리사이즈)
+ *
+ * @param {OG.geometry.Envelope} envelope Envelope 영역
+ * @return {OG.geometry.Geometry} 적용된 공간 기하 객체
+ * @override
+ */
+OG.geometry.GeometryCollection.prototype.fitToBoundary = function (envelope) {
+ var boundary = this.getBoundary(),
+ upper = boundary.getUpperCenter().y - envelope.getUpperCenter().y,
+ lower = envelope.getLowerCenter().y - boundary.getLowerCenter().y,
+ left = boundary.getLeftCenter().x - envelope.getLeftCenter().x,
+ right = envelope.getRightCenter().x - boundary.getRightCenter().x;
+
+ this.resize(upper, lower, left, right);
+
+ return this;
+};
+
+/**
+ * 객체 프라퍼티 정보를 JSON 스트링으로 반환한다.
+ *
+ * @return {String} 프라퍼티 정보
+ * @override
+ */
+OG.geometry.GeometryCollection.prototype.toString = function () {
+ var s = [], i;
+
+ for (var i = 0, leni = this.geometries.length; i < leni; i++) {
+ s.push(this.geometries[i].toString());
+ }
+
+ return "{type:'" + OG.Constants.GEOM_NAME[this.TYPE] + "',geometries:[" + s.join() + "]}";
+};
+/**
+ * Line 공간 기하 객체(Spatial Geometry Object)
+ *
+ * @class
+ * @extends OG.geometry.PolyLine
+ * @requires OG.geometry.Coordinate
+ * @requires OG.geometry.Envelope
+ * @requires OG.geometry.Geometry
+ *
+ * @example
+ * var geom = new OG.geometry.Line([20, 5], [30, 15]);
+ *
+ * @param {OG.geometry.Coordinate} from 라인 시작 좌표값
+ * @param {OG.geometry.Coordinate} to 라인 끝 좌표값
+ * @author Seungpil Park
+ */
+OG.geometry.Line = function (from, to, poi) {
+
+ var _from = this.convertCoordinate(from),
+ _to = this.convertCoordinate(to);
+
+ OG.geometry.Line.superclass.call(this, [
+ [_from.x, _from.y],
+ [_to.x, _to.y]
+ ], poi);
+
+ this.TYPE = OG.Constants.GEOM_TYPE.LINE;
+ this.style = new OG.geometry.Style();
+};
+OG.geometry.Line.prototype = new OG.geometry.PolyLine();
+OG.geometry.Line.superclass = OG.geometry.PolyLine;
+OG.geometry.Line.prototype.constructor = OG.geometry.Line;
+OG.Line = OG.geometry.Line;
+
+
+/**
+ * 객체 프라퍼티 정보를 JSON 스트링으로 반환한다.
+ *
+ * @return {String} 프라퍼티 정보
+ * @override
+ */
+OG.geometry.Line.prototype.toString = function () {
+ var s = [];
+ s.push("type:'" + OG.Constants.GEOM_NAME[this.TYPE] + "'");
+ s.push("from:" + this.vertices[0]);
+ s.push("to:" + this.vertices[1]);
+
+ return "{" + s.join() + "}";
+};
+/**
+ * Point 공간 기하 객체(Spatial Geometry Object)
+ *
+ * @class
+ * @extends OG.geometry.Geometry
+ * @requires OG.geometry.Coordinate
+ * @requires OG.geometry.Envelope
+ * @requires OG.geometry.Geometry
+ *
+ * @example
+ * var geom = new OG.geometry.Point([20, 5]);
+ *
+ * @param {OG.geometry.Coordinate} coordinate 좌표값
+ * @author Seungpil Park
+ */
+OG.geometry.Point = function (coordinate) {
+
+ this.TYPE = OG.Constants.GEOM_TYPE.POINT;
+ this.style = new OG.geometry.Style();
+
+ /**
+ * 좌표값
+ * @type OG.geometry.Coordinate
+ */
+ this.coordinate = this.convertCoordinate(coordinate);
+
+ /**
+ * Line Vertex 좌표 Array
+ * @type OG.geometry.Coordinate[]
+ */
+ this.vertices = [this.coordinate];
+};
+OG.geometry.Point.prototype = new OG.geometry.Geometry();
+OG.geometry.Point.superclass = OG.geometry.Geometry;
+OG.geometry.Point.prototype.constructor = OG.geometry.Point;
+OG.Point = OG.geometry.Point;
+
+/**
+ * 공간기하객체의 모든 꼭지점을 반환한다.
+ *
+ * @return {OG.geometry.Coordinate[]} 꼭지점 좌표 Array
+ * @override
+ */
+OG.geometry.Point.prototype.getVertices = function () {
+ return this.vertices;
+};
+
+/**
+ * 가로, 세로 Offset 만큼 좌표를 이동한다.
+ *
+ * @param {Number} offsetX 가로 Offset
+ * @param {Number} offsetY 세로 Offset
+ * @return {OG.geometry.Geometry} 이동된 공간 기하 객체
+ * @override
+ */
+OG.geometry.Point.prototype.move = function (offsetX, offsetY) {
+ this.getBoundary().move(offsetX, offsetY);
+ this.coordinate.move(offsetX, offsetY);
+ this.vertices = [this.coordinate];
+
+ return this;
+};
+
+/**
+ * 주어진 중심좌표로 공간기하객체를 이동한다.
+ *
+ * @param {OG.geometry.Coordinate} 중심 좌표
+ * @override
+ */
+OG.geometry.Point.prototype.moveCentroid = function (target) {
+ this.getBoundary().setUpperLeft(target);
+ this.coordinate = new OG.geometry.Coordinate(target);
+ this.vertices = [this.coordinate];
+};
+
+/**
+ * 상, 하, 좌, 우 외곽선을 이동하여 Envelope 을 리사이즈 한다.
+ *
+ * @param {Number} upper 상단 라인 이동 Offset(위 방향으로 +)
+ * @param {Number} lower 하단 라인 이동 Offset(아래 방향으로 +)
+ * @param {Number} left 좌측 라인 이동 Offset(좌측 방향으로 +)
+ * @param {Number} right 우측 라인 이동 Offset(우측 방향으로 +)
+ * @return {OG.geometry.Geometry} 리사이즈된 공간 기하 객체
+ * @override
+ */
+OG.geometry.Point.prototype.resize = function (upper, lower, left, right) {
+ var boundary = this.getBoundary();
+ boundary.resize(upper, lower, left, right);
+
+ this.coordinate = boundary.getCentroid();
+ this.vertices = [this.coordinate];
+ this.boundary = new OG.Envelope(this.coordinate, 0, 0);
+
+ return this;
+};
+
+/**
+ * 중심좌표는 고정한 채 Bounding Box 의 width, height 를 리사이즈 한다.
+ *
+ * @param {Number} width 너비
+ * @param {Number} height 높이
+ * @return {OG.geometry.Geometry} 리사이즈된 공간 기하 객체
+ * @override
+ */
+OG.geometry.Point.prototype.resizeBox = function (width, height) {
+ return this;
+};
+
+/**
+ * 기준 좌표를 기준으로 주어진 각도 만큼 회전한다.
+ *
+ * @param {Number} angle 회전 각도
+ * @param {OG.geometry.Coordinate} origin 기준 좌표
+ * @return {OG.geometry.Geometry} 회전된 공간 기하 객체
+ * @override
+ */
+OG.geometry.Point.prototype.rotate = function (angle, origin) {
+ origin = origin || this.getCentroid();
+
+ this.coordinate.rotate(angle, origin);
+ this.vertices = [this.coordinate];
+ this.reset();
+
+ return this;
+};
+
+/**
+ * 주어진 Boundary 영역 안으로 공간 기하 객체를 적용한다.(이동 & 리사이즈)
+ *
+ * @param {OG.geometry.Envelope} envelope Envelope 영역
+ * @return {OG.geometry.Geometry} 적용된 공간 기하 객체
+ * @override
+ */
+OG.geometry.Point.prototype.fitToBoundary = function (envelope) {
+ this.coordinate = envelope.getCentroid();
+ this.vertices = [this.coordinate];
+ this.boundary = new OG.Envelope(this.coordinate, 0, 0);
+
+ return this;
+};
+
+/**
+ * 객체 프라퍼티 정보를 JSON 스트링으로 반환한다.
+ *
+ * @return {String} 프라퍼티 정보
+ * @override
+ */
+OG.geometry.Point.prototype.toString = function () {
+ var s = [];
+ s.push("type:'" + OG.Constants.GEOM_NAME[this.TYPE] + "'");
+ s.push("coordinate:" + this.coordinate);
+
+ return "{" + s.join() + "}";
+};
+/**
+ * Polygon 공간 기하 객체(Spatial Geometry Object)
+ *
+ * @class
+ * @extends OG.geometry.PolyLine
+ * @requires OG.geometry.Coordinate
+ * @requires OG.geometry.Envelope
+ * @requires OG.geometry.Geometry
+ *
+ * @example
+ * var geom = new OG.geometry.Polygon([[20, 5], [30, 15], [40, 25], [50, 15], [60, 5], [20, 5]]);
+ *
+ * @param {OG.geometry.Coordinate[]} vertices Line Vertex 좌표 Array
+ * @author Seungpil Park
+ */
+OG.geometry.Polygon = function (vertices) {
+
+ OG.geometry.Polygon.superclass.call(this, vertices);
+
+ // Polygon 은 첫번째 좌표와 마지막 좌표가 같음
+ if (this.vertices.length > 0 && !this.vertices[0].isEquals(this.vertices[this.vertices.length - 1])) {
+ this.vertices.push(new OG.geometry.Coordinate(this.vertices[0].x, this.vertices[0].y));
+ }
+
+ this.TYPE = OG.Constants.GEOM_TYPE.POLYGON;
+ this.IS_CLOSED = true;
+ this.style = new OG.geometry.Style();
+};
+OG.geometry.Polygon.prototype = new OG.geometry.PolyLine();
+OG.geometry.Polygon.superclass = OG.geometry.PolyLine;
+OG.geometry.Polygon.prototype.constructor = OG.geometry.Polygon;
+OG.Polygon = OG.geometry.Polygon;
+/**
+ * Rectangle 공간 기하 객체(Spatial Geometry Object)
+ *
+ * @class
+ * @extends OG.geometry.Polygon
+ * @requires OG.geometry.Coordinate
+ * @requires OG.geometry.Envelope
+ * @requires OG.geometry.Geometry
+ *
+ * @example
+ * var geom = new OG.geometry.Rectangle([20, 5], 10, 10);
+ *
+ * @param {OG.geometry.Coordinate} upperLeft 좌상단좌표
+ * @param {Number} width 너비
+ * @param {Number} height 높이
+ * @author Seungpil Park
+ */
+OG.geometry.Rectangle = function (upperLeft, width, height) {
+
+ var _upperLeft = this.convertCoordinate(upperLeft),
+ _lowerRight = this.convertCoordinate([_upperLeft.x + width, _upperLeft.y + height]);
+
+ // 파라미터 유효성 체크
+ if (_upperLeft.x > _lowerRight.x || _upperLeft.y > _lowerRight.y) {
+ throw new OG.ParamError();
+ }
+
+ OG.geometry.Rectangle.superclass.call(this, [
+ [_upperLeft.x, _upperLeft.y],
+ [_upperLeft.x + (_lowerRight.x - _upperLeft.x), _upperLeft.y],
+ [_lowerRight.x, _lowerRight.y],
+ [_upperLeft.x, _upperLeft.y + (_lowerRight.y - _upperLeft.y)],
+ [_upperLeft.x, _upperLeft.y]
+ ]);
+
+ this.TYPE = OG.Constants.GEOM_TYPE.RECTANGLE;
+ this.style = new OG.geometry.Style();
+};
+OG.geometry.Rectangle.prototype = new OG.geometry.Polygon();
+OG.geometry.Rectangle.superclass = OG.geometry.Polygon;
+OG.geometry.Rectangle.prototype.constructor = OG.geometry.Rectangle;
+OG.Rectangle = OG.geometry.Rectangle;
+
+/**
+ * 객체 프라퍼티 정보를 JSON 스트링으로 반환한다.
+ *
+ * @return {String} 프라퍼티 정보
+ * @override
+ */
+OG.geometry.Rectangle.prototype.toString = function () {
+ var s = [],
+ angle = OG.Util.round(Math.atan2(this.vertices[1].y - this.vertices[0].y,
+ this.vertices[1].x - this.vertices[0].x) * 180 / Math.PI);
+
+ s.push("type:'" + OG.Constants.GEOM_NAME[this.TYPE] + "'");
+ s.push("upperLeft:" + this.vertices[0]);
+ s.push("width:" + (this.vertices[0].distance(this.vertices[1])));
+ s.push("height:" + (this.vertices[0].distance(this.vertices[3])));
+ s.push("angle:" + angle);
+
+ return "{" + s.join() + "}";
+};
+/**
+ * 도형 Path 의 Marker 정보 최상위 인터페이스
+ *
+ * @class
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @author Seungpil Park
+ */
+OG.marker.IMarker = function () {
+
+ /**
+ * marker 을 구분하는 marker ID(marker 클래스명과 일치)
+ * @type String
+ */
+ this.MARKER_ID = null;
+
+ /**
+ * marker 모양을 나타내는 공간기하객체(Geometry)
+ * @type OG.geometry.Geometry
+ */
+ this.geom = null;
+};
+OG.marker.IMarker.prototype = {
+
+
+ /**
+ * 드로잉할 marker 를 생성하여 반환한다.
+ * @return {*} Marker 정보
+ * @abstract
+ */
+ createMarker: function () {
+ throw new OG.NotImplementedException("OG.shape.IMarker.createMarker");
+ }
+};
+OG.marker.IMarker.prototype.constructor = OG.marker.IMarker;
+OG.IMarker = OG.marker.IMarker;
+/**
+ * Rectangle Maker
+ *
+ * @class
+ * @extends OG.marker.IMarker
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @author Seungpil Park
+ */
+OG.marker.ArrowMarker = function () {
+ OG.marker.ArrowMarker.superclass.call(this);
+
+ this.MARKER_ID = 'OG.marker.ArrowMarker';
+};
+OG.marker.ArrowMarker.prototype = new OG.marker.IMarker();
+OG.marker.ArrowMarker.superclass = OG.marker.IMarker;
+OG.marker.ArrowMarker.prototype.constructor = OG.marker.ArrowMarker;
+OG.ArrowMarker = OG.marker.ArrowMarker;
+
+/**
+ * 드로잉할 marker 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} marker 정보
+ * @override
+ */
+OG.marker.ArrowMarker.prototype.createMarker = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ //this.geom = new OG.geometry.Rectangle([0, 0], 100, 100);
+ this.geom = new OG.geometry.Polygon([[0, 0], [30, 20], [0, 40], [0, 0]]);
+ this.geom.style = new OG.geometry.Style({
+ 'fill-opacity': 1,
+ 'fill': 'black'
+ });
+ return this.geom;
+};
+/**
+ * Circle Marker
+ *
+ * @class
+ * @extends OG.marker.IMarker
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @author Seungpil Park
+ */
+OG.marker.CircleMarker = function () {
+ OG.marker.CircleMarker.superclass.call(this);
+
+ this.MARKER_ID = 'OG.marker.CircleMarker';
+};
+OG.marker.CircleMarker.prototype = new OG.marker.IMarker();
+OG.marker.CircleMarker.superclass = OG.marker.IMarker;
+OG.marker.CircleMarker.prototype.constructor = OG.marker.CircleMarker;
+OG.CircleMarker = OG.marker.CircleMarker;
+
+/**
+ * 드로잉할 marker 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} marker 정보
+ * @override
+ */
+OG.marker.CircleMarker.prototype.createMarker = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.geometry.Circle([50, 50], 50);
+ return this.geom;
+};
+/**
+ * Rectangle Maker
+ *
+ * @class
+ * @extends OG.marker.IMarker
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @author Seungpil Park
+ */
+OG.marker.RectangleMarker = function () {
+ OG.marker.RectangleMarker.superclass.call(this);
+
+ this.MARKER_ID = 'OG.marker.RectangleMarker';
+};
+OG.marker.RectangleMarker.prototype = new OG.marker.IMarker();
+OG.marker.RectangleMarker.superclass = OG.marker.IMarker;
+OG.marker.RectangleMarker.prototype.constructor = OG.marker.RectangleMarker;
+OG.RectangleMarker = OG.marker.RectangleMarker;
+
+/**
+ * 드로잉할 marker 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} marker 정보
+ * @override
+ */
+OG.marker.RectangleMarker.prototype.createMarker = function () {
+ var geom1, geom2, geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geom1 = new OG.geometry.Circle([50, 50], 50);
+ geom1.style = new OG.geometry.Style({
+ "stroke-width": 4
+ });
+
+ geom2 = new OG.geometry.Polygon([
+ [20, 75],
+ [40, 30],
+ [60, 60],
+ [80, 20],
+ [60, 75],
+ [40, 50]
+
+ ]);
+ geom2.style = new OG.geometry.Style({
+ "fill": "black",
+ "fill-opacity": 1
+ });
+
+ geomCollection.push(geom1);
+ geomCollection.push(geom2);
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+
+ return this.geom;
+};
+/**
+ * Rectangle Maker
+ *
+ * @class
+ * @extends OG.marker.IMarker
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @author Seungpil Park
+ */
+OG.marker.SwitchLMarker = function () {
+ OG.marker.SwitchLMarker.superclass.call(this);
+
+ this.MARKER_ID = 'OG.marker.SwitchLMarker';
+};
+OG.marker.SwitchLMarker.prototype = new OG.marker.IMarker();
+OG.marker.SwitchLMarker.superclass = OG.marker.IMarker;
+OG.marker.SwitchLMarker.prototype.constructor = OG.marker.SwitchLMarker;
+OG.SwitchLMarker = OG.marker.SwitchLMarker;
+
+/**
+ * 드로잉할 marker 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} marker 정보
+ * @override
+ */
+OG.marker.SwitchLMarker.prototype.createMarker = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.geometry.PolyLine([[0, 0], [20, 5]]);
+ this.geom.style = new OG.geometry.Style({
+ 'fill-opacity': 1
+ });
+ return this.geom;
+};
+/**
+ * Rectangle Maker
+ *
+ * @class
+ * @extends OG.marker.IMarker
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @author Seungpil Park
+ */
+OG.marker.SwitchRMarker = function () {
+ OG.marker.SwitchRMarker.superclass.call(this);
+
+ this.MARKER_ID = 'OG.marker.SwitchRMarker';
+};
+OG.marker.SwitchRMarker.prototype = new OG.marker.IMarker();
+OG.marker.SwitchRMarker.superclass = OG.marker.IMarker;
+OG.marker.SwitchRMarker.prototype.constructor = OG.marker.SwitchRMarker;
+OG.SwitchRMarker = OG.marker.SwitchRMarker;
+
+/**
+ * 드로잉할 marker 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} marker 정보
+ * @override
+ */
+OG.marker.SwitchRMarker.prototype.createMarker = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.geometry.PolyLine([[0, 5], [20, 0]]);
+ this.geom.style = new OG.geometry.Style({
+ 'fill-opacity': 1
+ });
+ return this.geom;
+};
+/**
+ * Rectangle Maker
+ *
+ * @class
+ * @extends OG.marker.IMarker
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @author Seungpil Park
+ */
+OG.marker.SwitchXMarker = function () {
+ OG.marker.SwitchXMarker.superclass.call(this);
+
+ this.MARKER_ID = 'OG.marker.SwitchXMarker';
+};
+OG.marker.SwitchXMarker.prototype = new OG.marker.IMarker();
+OG.marker.SwitchXMarker.superclass = OG.marker.IMarker;
+OG.marker.SwitchXMarker.prototype.constructor = OG.marker.SwitchXMarker;
+OG.SwitchXMarker = OG.marker.SwitchXMarker;
+
+/**
+ * 드로잉할 marker 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} marker 정보
+ * @override
+ */
+OG.marker.SwitchXMarker.prototype.createMarker = function () {
+ var geom1, geom2, geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geom1 = new OG.geometry.Line([-10, 10], [10, -10]);
+ geom2 = new OG.geometry.Line([-10, -10], [10, 10]);
+
+ geomCollection.push(geom1);
+ geomCollection.push(geom2);
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+
+ return this.geom;
+};
+/**
+ * 도형 Pattern 정보 최상위 인터페이스
+ *
+ * @class
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @author Seungpil Park
+ */
+OG.pattern.IPattern = function () {
+
+ /**
+ * pattern 을 구분하는 pattern ID(pattern 클래스명과 일치)
+ * @type String
+ */
+ this.PATTERN_ID = null;
+
+ /**
+ * pattern 모양을 나타내는 공간기하객체(Geometry)
+ * @type OG.geometry.Geometry
+ */
+ this.geom = null;
+};
+OG.pattern.IPattern.prototype = {
+
+
+ /**
+ * 드로잉할 pattern 를 생성하여 반환한다.
+ * @return {*} pattern 정보
+ * @abstract
+ */
+ createPattern: function () {
+ throw new OG.NotImplementedException("OG.shape.IPattern.createPattern");
+ }
+};
+OG.pattern.IPattern.prototype.constructor = OG.pattern.IPattern;
+OG.IPattern = OG.pattern.IPattern;
+/**
+ * Hatched Pattern
+ *
+ * @class
+ * @extends OG.pattern.IPattern
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @author Seungpil Park
+ */
+OG.pattern.HatchedPattern = function () {
+ OG.pattern.HatchedPattern.superclass.call(this);
+
+ this.PATTERN_ID = 'OG.pattern.HatchedPattern';
+};
+OG.pattern.HatchedPattern.prototype = new OG.pattern.IPattern();
+OG.pattern.HatchedPattern.superclass = OG.pattern.IPattern;
+OG.pattern.HatchedPattern.prototype.constructor = OG.pattern.HatchedPattern;
+OG.HatchedPattern = OG.pattern.HatchedPattern;
+
+/**
+ * 드로잉할 pattern 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} pattern 정보
+ * @override
+ */
+OG.pattern.HatchedPattern.prototype.createPattern = function () {
+ var geom1, geom2, geom3, geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geom1 = new OG.geometry.Line([-1, 1], [1, -1]);
+ geom2 = new OG.geometry.Line([0, 4], [4, 0]);
+ geom3 = new OG.geometry.Line([3, 5], [5, 3]);
+
+ geomCollection.push(geom1);
+ geomCollection.push(geom2);
+ geomCollection.push(geom3);
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+
+ return this.geom;
+};
+/**
+ * Rect Pattern
+ *
+ * @class
+ * @extends OG.pattern.IPattern
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @author Seungpil Park
+ */
+OG.pattern.RectPattern = function () {
+ OG.pattern.RectPattern.superclass.call(this);
+
+ this.PATTERN_ID = 'OG.pattern.RectPattern';
+};
+OG.pattern.RectPattern.prototype = new OG.pattern.IPattern();
+OG.pattern.RectPattern.superclass = OG.pattern.IPattern;
+OG.pattern.RectPattern.prototype.constructor = OG.pattern.RectPattern;
+OG.RectPattern = OG.pattern.RectPattern;
+
+/**
+ * 드로잉할 pattern 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} pattern 정보
+ * @override
+ */
+OG.pattern.RectPattern.prototype.createPattern = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.geometry.Rectangle([0, 0], 100, 100);
+ return this.geom;
+};
+/**
+ * 도형, 텍스트, 이미지 등의 드로잉 될 Object 의 정보를 저장하는 Shape 정보 최상위 인터페이스
+ *
+ * @class
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @author Seungpil Park
+ */
+OG.shape.IShape = function () {
+ /**
+ * Shape 유형(GEOM, TEXT, HTML, IMAGE, EDGE, GROUP)
+ * @type String
+ */
+ this.TYPE = null;
+
+ /**
+ * Shape 을 구분하는 Shape ID(Shape 클래스명과 일치)
+ * @type String
+ */
+ this.SHAPE_ID = null;
+
+ /**
+ * Shape 모양을 나타내는 공간기하객체(Geometry)
+ * @type OG.geometry.Geometry
+ */
+ this.geom = null;
+
+ /**
+ * Shape 라벨 텍스트
+ * @type String
+ */
+ this.label = null;
+
+ /**
+ * Shape 의 Collapse 여부
+ * @type Boolean
+ */
+ this.isCollapsed = false;
+
+// 이벤트 속성
+ /**
+ * 선택 가능여부
+ * @type Boolean
+ */
+ this.SELECTABLE = true;
+
+ /**
+ * 이동 가능여부
+ * @type Boolean
+ */
+ this.MOVABLE = true;
+
+ /**
+ * 리사이즈 가능여부
+ * @type Boolean
+ */
+ this.RESIZABLE = true;
+
+ /**
+ * 가로방향 리사이즈 가능
+ * @type {boolean}
+ */
+ this.RESIZEX = true;
+
+ /**
+ * 세로 방향 리사이즈 가능
+ * @type {boolean}
+ */
+ this.RESIZEY = true;
+
+ /**
+ * 연결 가능여부
+ * @type Boolean
+ */
+ this.CONNECTABLE = true;
+
+ /**
+ * From 연결 가능여부 (From(Shape) => To)
+ * @type Boolean
+ */
+ this.ENABLE_FROM = true;
+
+ /**
+ * To 연결 가능여부 (From => To(Shape))
+ * @type Boolean
+ */
+ this.ENABLE_TO = true;
+
+ /**
+ * Self 연결 가능여부
+ * @type Boolean
+ */
+ this.SELF_CONNECTABLE = false;
+
+ /**
+ * 가이드에 자기자신을 복사하는 컨트롤러 여부.
+ * @type Boolean
+ */
+ this.CONNECT_CLONEABLE = true;
+
+ /**
+ * 드래그하여 연결시 연결대상 있는 경우에만 Edge 드로잉 처리 여부
+ * @type Boolean
+ */
+ this.CONNECT_REQUIRED = true;
+
+ /**
+ * 드래그하여 연결시 그룹을 건너뛸때 스타일 변경 여부
+ * @type Boolean
+ */
+ this.CONNECT_STYLE_CHANGE = true;
+
+ /**
+ * 가이드에 삭제 컨트롤러 여부
+ * @type Boolean
+ */
+ this.DELETABLE = true;
+
+ /**
+ * 라벨 수정여부
+ * @type Boolean
+ */
+ this.LABEL_EDITABLE = true;
+
+ /**
+ * 복사 가능 여부
+ * @type {boolean}
+ */
+ this.COPYABLE = true;
+
+
+ this.exceptionType = '';
+
+ /**
+ * 도형의 데이터
+ * @type Object
+ */
+ this.data = null;
+
+ /**
+ * 도형 선연결시 선연결 컨트롤러 목록
+ * @type {Array}
+ */
+ this.textList = [];
+
+
+ /**
+ * 도형 특수 컨트롤러 목록
+ * @type {Array}
+ */
+ this.controllers = [];
+
+ /**
+ * 기본 컨텍스트 메뉴 정보
+ * @type {Object} json
+ */
+ this.contextMenu = null;
+
+ /**
+ * 사용자 지정 컨텍스트메뉴
+ * @type {Object} json
+ */
+ this.customContextMenu = null;
+
+ /**
+ * shape 이 적용된 Dom Element
+ * @type {Element} Dom Element
+ */
+ this.currentElement = null;
+
+ /**
+ * shape 이 적용된 Canvas
+ * @type {OG.Canvas} canvas
+ */
+ this.currentCanvas = null;
+
+ /**
+ * toJson 시에 이 요소를 무시함.
+ * @type {boolean}
+ */
+ this.ignoreExport = false;
+
+ /**
+ * x,y 축만 이동 가능여부. Y | N | none
+ * @type {null}
+ */
+ this.AXIS = 'none';
+
+};
+OG.shape.IShape.prototype = {
+
+ /**
+ * 드로잉할 Shape 를 생성하여 반환한다.
+ *
+ * @return {*} Shape 정보
+ * @abstract
+ */
+ createShape: function () {
+ throw new OG.NotImplementedException("OG.shape.IShape.createShape");
+ },
+
+ /**
+ * Shape 을 복사하여 새로인 인스턴스로 반환한다.
+ *
+ * @return {OG.shape.IShape} 복사된 인스턴스
+ * @abstract
+ */
+ clone: function () {
+ throw new OG.NotImplementedException("OG.shape.IShape.clone");
+ },
+ addEve: function () {
+ },
+
+ // (void) 특수한 컨트롤을 생성하기 위한 함수
+ drawCustomControl: function () {
+ },
+
+ setData: function (data) {
+ this.data = data;
+ },
+
+ getData: function () {
+ return this.data;
+ },
+ onResize: function (offset) {
+
+ },
+ onDrawLabel: function (text) {
+
+ },
+ onLabelChanged: function (text, beforeText) {
+
+ },
+ onBeforeRemoveShape: function () {
+
+ },
+ onRemoveShape: function () {
+
+ },
+ onDrawShape: function () {
+
+ },
+ onBeforeLabelChange: function (text, beforeText) {
+
+ },
+ onRedrawShape: function () {
+
+ },
+ onBeforeConnectShape: function (edge, fromShape, toShape) {
+
+ },
+ onConnectShape: function (edge, fromShape, toShape) {
+
+ },
+ onDisconnectShape: function (edge, fromShape, toShape) {
+
+ },
+ onGroup: function (groupShapeEle) {
+
+ },
+ onUnGroup: function () {
+
+ },
+ onMoveShape: function (offset) {
+
+ },
+ onRotateShape: function (angle) {
+
+ },
+ onDuplicated: function (edge, target, rectShape) {
+
+ },
+ onPasteShape: function (copied, pasted) {
+
+ },
+ /**
+ * 자신에게 도형들이 그룹으로 들어왔을때의 이벤트
+ * @param groupElement
+ * @param elements
+ */
+ onAddToGroup: function (groupElement, elements, eventOffset) {
+
+ },
+ /**
+ * 자신이 그룹속으로 들어갔을 때의 이벤트
+ * @param groupElement
+ * @param element
+ */
+ onAddedToGroup: function (groupElement, element, eventOffset) {
+
+ },
+ onSelectShape: function () {
+
+ },
+ onDeSelectShape: function () {
+
+ }
+};
+OG.shape.IShape.prototype.constructor = OG.shape.IShape;
+OG.IShape = OG.shape.IShape;
+/**
+ * Geometry Shape
+ *
+ * @class
+ * @extends OG.shape.IShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @author Seungpil Park
+ */
+OG.shape.GeomShape = function () {
+ OG.shape.GeomShape.superclass.call(this);
+
+ this.TYPE = OG.Constants.SHAPE_TYPE.GEOM;
+};
+OG.shape.GeomShape.prototype = new OG.shape.IShape();
+OG.shape.GeomShape.superclass = OG.shape.IShape;
+OG.shape.GeomShape.prototype.constructor = OG.shape.GeomShape;
+OG.GeomShape = OG.shape.GeomShape;
+
+/**
+ * Shape 을 복사하여 새로인 인스턴스로 반환한다.
+ *
+ * @return {OG.shape.IShape} 복사된 인스턴스
+ * @override
+ */
+OG.shape.GeomShape.prototype.clone = function () {
+ var shape = eval('new ' + this.SHAPE_ID + '()');
+ shape.label = this.label;
+ shape.setData(JSON.parse(JSON.stringify(this.getData())));
+
+ return shape;
+};
+/**
+ * Text Shape
+ *
+ * @class
+ * @extends OG.shape.IShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} text 텍스트
+ * @author Seungpil Park
+ */
+OG.shape.TextShape = function (text) {
+ OG.shape.TextShape.superclass.call(this);
+
+ this.TYPE = OG.Constants.SHAPE_TYPE.TEXT;
+ this.SHAPE_ID = 'OG.shape.TextShape';
+
+ /**
+ * 드로잉할 텍스트
+ * @type String
+ */
+ this.text = text || "Text Here";
+
+ /**
+ * 회전각도
+ * @type Number
+ */
+ this.angle = 0;
+};
+OG.shape.TextShape.prototype = new OG.shape.IShape();
+OG.shape.TextShape.superclass = OG.shape.IShape;
+OG.shape.TextShape.prototype.constructor = OG.shape.TextShape;
+OG.TextShape = OG.shape.TextShape;
+
+/**
+ * 드로잉할 텍스트를 반환한다.
+ *
+ * @return {String} 텍스트
+ * @override
+ */
+OG.shape.TextShape.prototype.createShape = function () {
+ return this.text;
+};
+
+/**
+ * Shape 을 복사하여 새로인 인스턴스로 반환한다.
+ *
+ * @return {OG.shape.IShape} 복사된 인스턴스
+ * @override
+ */
+OG.shape.TextShape.prototype.clone = function () {
+ var shape = eval('new ' + this.SHAPE_ID + '()');
+ shape.text = this.text;
+ shape.angle = this.angle;
+ shape.setData(JSON.parse(JSON.stringify(this.getData())));
+ return shape;
+};
+/**
+ * Image Shape
+ *
+ * @class
+ * @extends OG.shape.IShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} image 이미지 URL
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ */
+OG.shape.ImageShape = function (image, label) {
+ OG.shape.ImageShape.superclass.call(this);
+
+ this.TYPE = OG.Constants.SHAPE_TYPE.IMAGE;
+ this.SHAPE_ID = 'OG.shape.ImageShape';
+ this.label = label;
+
+ /**
+ * 드로잉할 이미지 URL
+ * @type String
+ */
+ this.image = image;
+
+ /**
+ * 회전각도
+ * @type Number
+ */
+ this.angle = 0;
+};
+OG.shape.ImageShape.prototype = new OG.shape.IShape();
+OG.shape.ImageShape.superclass = OG.shape.IShape;
+OG.shape.ImageShape.prototype.constructor = OG.shape.ImageShape;
+OG.ImageShape = OG.shape.ImageShape;
+
+/**
+ * 드로잉할 이미지 URL을 반환한다.
+ *
+ * @return {String} 이미지 URL
+ * @override
+ */
+OG.shape.ImageShape.prototype.createShape = function () {
+ return this.image;
+};
+
+/**
+ * Shape 을 복사하여 새로인 인스턴스로 반환한다.
+ *
+ * @return {OG.shape.IShape} 복사된 인스턴스
+ * @override
+ */
+OG.shape.ImageShape.prototype.clone = function () {
+ var shape = eval('new ' + this.SHAPE_ID + '()');
+ shape.image = this.image;
+ shape.label = this.label;
+ shape.angle = this.angle;
+ shape.setData(JSON.parse(JSON.stringify(this.getData())));
+ return shape;
+};
+/**
+ * Edge Shape
+ *
+ * @class
+ * @extends OG.shape.IShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {Number[]} from 와이어 시작 좌표
+ * @param {Number[]} to 와이어 끝 좌표
+ * @param {String} label 라벨 [Optional]
+ * @param {String} fromLabel 시작점 라벨 [Optional]
+ * @param {String} toLabel 끝점 라벨 [Optional]
+ * @author Seungpil Park
+ */
+OG.shape.EdgeShape = function (from, to, label, fromLabel, toLabel) {
+ OG.shape.EdgeShape.superclass.call(this);
+
+ this.TYPE = OG.Constants.SHAPE_TYPE.EDGE;
+ this.SHAPE_ID = 'OG.shape.EdgeShape';
+
+ /**
+ * Edge 시작 좌표
+ * @type Number[]
+ */
+ this.from = from;
+
+ /**
+ * Edge 끝 좌표
+ * @type Number[]
+ */
+ this.to = to;
+
+ this.label = label;
+
+ /**
+ * Edge 시작점 라벨
+ * @type String
+ */
+ this.fromLabel = fromLabel;
+
+ /**
+ * Edge 끝점 라벨
+ * @type String
+ */
+ this.toLabel = toLabel;
+};
+OG.shape.EdgeShape.prototype = new OG.shape.IShape();
+OG.shape.EdgeShape.superclass = OG.shape.IShape;
+OG.shape.EdgeShape.prototype.constructor = OG.shape.EdgeShape;
+OG.EdgeShape = OG.shape.EdgeShape;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.EdgeShape.prototype.createShape = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.PolyLine([this.from || [0, 0], this.to || [70, 0]]);
+ return this.geom;
+};
+
+/**
+ * Shape 을 복사하여 새로인 인스턴스로 반환한다.
+ *
+ * @return {OG.shape.IShape} 복사된 인스턴스
+ * @override
+ */
+OG.shape.EdgeShape.prototype.clone = function () {
+ var shape = eval('new ' + this.SHAPE_ID + '()');
+ shape.from = this.from;
+ shape.to = this.to;
+ shape.label = this.label;
+ shape.fromLabel = this.fromLabel;
+ shape.toLabel = this.toLabel;
+ shape.setData(JSON.parse(JSON.stringify(this.getData())));
+
+ return shape;
+};
+/**
+ * Svg Shape
+ *
+ * @class
+ * @extends OG.shape.IShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} xml xml String
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ */
+OG.shape.SvgShape = function (xml, label) {
+ OG.shape.SvgShape.superclass.call(this);
+
+ this.TYPE = OG.Constants.SHAPE_TYPE.IMAGE;
+ this.SHAPE_ID = 'OG.shape.SvgShape';
+ this.label = label;
+
+ /**
+ * 드로잉할 xml
+ * @type String
+ */
+ this.xml = xml;
+
+ /**
+ * 회전각도
+ * @type Number
+ */
+ this.angle = 0;
+};
+OG.shape.SvgShape.prototype = new OG.shape.IShape();
+OG.shape.SvgShape.superclass = OG.shape.IShape;
+OG.shape.SvgShape.prototype.constructor = OG.shape.SvgShape;
+OG.SvgShape = OG.shape.SvgShape;
+
+/**
+ * 드로잉할 xml 스트링을 반환한다.
+ *
+ * @return {String} xml String
+ * @override
+ */
+OG.shape.SvgShape.prototype.createShape = function () {
+ return this.xml;
+};
+
+/**
+ * Shape 을 복사하여 새로인 인스턴스로 반환한다.
+ *
+ * @return {OG.shape.IShape} 복사된 인스턴스
+ * @override
+ */
+OG.shape.SvgShape.prototype.clone = function () {
+ var shape = eval('new ' + this.SHAPE_ID + '()');
+ shape.xml = this.xml;
+ shape.label = this.label;
+ shape.angle = this.angle;
+ shape.setData(JSON.parse(JSON.stringify(this.getData())));
+ return shape;
+};
+/**
+ * Circle Shape
+ *
+ * @class
+ * @extends OG.shape.GeomShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ */
+OG.shape.CircleShape = function (label) {
+ OG.shape.CircleShape.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.CircleShape';
+ this.label = label;
+};
+OG.shape.CircleShape.prototype = new OG.shape.GeomShape();
+OG.shape.CircleShape.superclass = OG.shape.GeomShape;
+OG.shape.CircleShape.prototype.constructor = OG.shape.CircleShape;
+OG.CircleShape = OG.shape.CircleShape;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.CircleShape.prototype.createShape = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.geometry.Circle([50, 50], 50);
+ return this.geom;
+};
+/**
+ * Ellipse Shape
+ *
+ * @class
+ * @extends OG.shape.GeomShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ */
+OG.shape.EllipseShape = function (label) {
+ OG.shape.EllipseShape.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.EllipseShape';
+ this.label = label;
+};
+OG.shape.EllipseShape.prototype = new OG.shape.GeomShape();
+OG.shape.EllipseShape.superclass = OG.shape.GeomShape;
+OG.shape.EllipseShape.prototype.constructor = OG.shape.EllipseShape;
+OG.EllipseShape = OG.shape.EllipseShape;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.EllipseShape.prototype.createShape = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.geometry.Ellipse([50, 50], 50, 30);
+ return this.geom;
+};
+/**
+ * SpotShape Shape
+ *
+ * @class
+ * @extends OG.shape.GeomShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.From = function (label) {
+ OG.shape.From.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.From';
+ this.label = label;
+ this.MOVABLE = false;
+ this.RESIZABLE = false;
+ this.SELF_CONNECTABLE = false;
+ this.CONNECT_CLONEABLE = false;
+ this.LABEL_EDITABLE = false;
+ this.DELETABLE = false;
+ this.CONNECT_STYLE_CHANGE = false;
+ this.ENABLE_TO = false;
+};
+OG.shape.From.prototype = new OG.shape.GeomShape();
+OG.shape.From.superclass = OG.shape.GeomShape;
+OG.shape.From.prototype.constructor = OG.shape.From;
+OG.From = OG.shape.From;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.From.prototype.createShape = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.geometry.Circle([10, 10], 10);
+ return this.geom;
+};
+/**
+ * Group Shape
+ *
+ * @class
+ * @extends OG.shape.IShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ */
+OG.shape.GroupShape = function (label) {
+ OG.shape.GroupShape.superclass.call(this);
+
+ this.TYPE = OG.Constants.SHAPE_TYPE.GROUP;
+ this.SHAPE_ID = 'OG.shape.GroupShape';
+ this.label = label;
+
+ this.CONNECTABLE = false;
+ this.SELF_CONNECTABLE = false;
+
+ /**
+ * 그룹핑 가능여부
+ * @type Boolean
+ */
+ this.GROUP_DROPABLE = true;
+
+ /**
+ * 최소화 가능여부
+ * @type Boolean
+ */
+ this.GROUP_COLLAPSIBLE = false;
+};
+OG.shape.GroupShape.prototype = new OG.shape.IShape();
+OG.shape.GroupShape.superclass = OG.shape.IShape;
+OG.shape.GroupShape.prototype.constructor = OG.shape.GroupShape;
+OG.GroupShape = OG.shape.GroupShape;
+
+OG.shape.GroupShape.prototype.layoutChild = function () {
+ //NONE
+
+}
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.GroupShape.prototype.createShape = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.geometry.Rectangle([0, 0], 100, 100);
+ this.geom.style = new OG.geometry.Style({
+ 'fill': '#ffffff',
+ 'fill-opacity': 0,
+ "stroke": 'none'
+ });
+
+ return this.geom;
+};
+
+/**
+ * Shape 을 복사하여 새로인 인스턴스로 반환한다.
+ *
+ * @return {OG.shape.IShape} 복사된 인스턴스
+ * @override
+ */
+OG.shape.GroupShape.prototype.clone = function () {
+ var shape = eval('new ' + this.SHAPE_ID + '()');
+ shape.label = this.label;
+ shape.setData(JSON.parse(JSON.stringify(this.getData())));
+
+ return shape;
+};
+/**
+ * Horizontal Swimlane Shape
+ *
+ * @class
+ * @extends OG.shape.GroupShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ */
+OG.shape.HorizontalLaneShape = function (label) {
+ OG.shape.HorizontalLaneShape.superclass.call(this, label);
+
+ this.SHAPE_ID = 'OG.shape.HorizontalLaneShape';
+
+};
+OG.shape.HorizontalLaneShape.prototype = new OG.shape.GroupShape();
+OG.shape.HorizontalLaneShape.superclass = OG.shape.GroupShape;
+OG.shape.HorizontalLaneShape.prototype.constructor = OG.shape.HorizontalLaneShape;
+OG.HorizontalLaneShape = OG.shape.HorizontalLaneShape;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.HorizontalLaneShape.prototype.createShape = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.geometry.Rectangle([0, 0], 100, 100);
+ this.geom.style = new OG.geometry.Style({
+ 'label-direction': 'vertical',
+ 'vertical-align': 'top',
+ 'fill': '#ffffff',
+ 'fill-opacity': 0
+ });
+
+ return this.geom;
+};
+/**
+ * Horizontal Pool Shape
+ *
+ * @class
+ * @extends OG.shape.GroupShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ */
+OG.shape.HorizontalPoolShape = function (label) {
+ OG.shape.HorizontalPoolShape.superclass.call(this, label);
+
+ this.SHAPE_ID = 'OG.shape.HorizontalPoolShape';
+ this.label = label;
+ this.CONNECTABLE = true;
+ this.LoopType = 'None';
+ this.GROUP_COLLAPSIBLE = false;
+};
+OG.shape.HorizontalPoolShape.prototype = new OG.shape.GroupShape();
+OG.shape.HorizontalPoolShape.superclass = OG.shape.GroupShape;
+OG.shape.HorizontalPoolShape.prototype.constructor = OG.shape.HorizontalPoolShape;
+OG.HorizontalPoolShape = OG.shape.HorizontalPoolShape;
+
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.HorizontalPoolShape.prototype.createShape = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.geometry.Rectangle([0, 0], 100, 100);
+ this.geom.style = new OG.geometry.Style({
+ 'label-direction': 'vertical',
+ 'vertical-align': 'top',
+ 'fill': '#ffffff',
+ 'fill-opacity': 0,
+ 'title-size': 32
+ });
+
+ return this.geom;
+};
+
+OG.shape.HorizontalPoolShape.prototype.createSubShape = function () {
+ this.sub = [];
+
+ var loopShape;
+ switch (this.LoopType) {
+ case 'Standard' :
+ loopShape = new OG.ImageShape(this.currentCanvas._CONFIG.IMAGE_BASE + 'loop_standard.png');
+ break;
+ case 'MIParallel' :
+ loopShape = new OG.MIParallel();
+ break;
+ case 'MISequential' :
+ loopShape = new OG.MISequential();
+ break;
+ }
+ if (loopShape) {
+ this.sub.push({
+ shape: loopShape,
+ width: '15px',
+ height: '15px',
+ bottom: '5px',
+ align: 'center',
+ style: {}
+ })
+ }
+
+ return this.sub;
+};
+/**
+ * ForeignObject HTML Shape
+ *
+ * @class
+ * @extends OG.shape.IShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} html 임베드 HTML String
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ */
+OG.shape.HtmlShape = function (html, label) {
+ OG.shape.HtmlShape.superclass.call(this);
+
+ this.TYPE = OG.Constants.SHAPE_TYPE.HTML;
+ this.SHAPE_ID = 'OG.shape.HtmlShape';
+ this.label = label;
+
+ /**
+ * 드로잉할 임베드 HTML String
+ * @type String
+ */
+ this.html = html || "";
+
+ /**
+ * 회전각도
+ * @type Number
+ */
+ this.angle = 0;
+};
+OG.shape.HtmlShape.prototype = new OG.shape.IShape();
+OG.shape.HtmlShape.superclass = OG.shape.IShape;
+OG.shape.HtmlShape.prototype.constructor = OG.shape.HtmlShape;
+OG.HtmlShape = OG.shape.HtmlShape;
+
+/**
+ * 드로잉할 임베드 HTML String을 반환한다.
+ *
+ * @return {String} 임베드 HTML String
+ * @override
+ */
+OG.shape.HtmlShape.prototype.createShape = function () {
+ return this.html;
+};
+
+/**
+ * Shape 을 복사하여 새로인 인스턴스로 반환한다.
+ *
+ * @return {OG.shape.IShape} 복사된 인스턴스
+ * @override
+ */
+OG.shape.HtmlShape.prototype.clone = function () {
+ var shape = eval('new ' + this.SHAPE_ID + '()');
+ shape.html = this.html;
+ shape.label = this.label;
+ shape.angle = this.angle;
+ shape.setData(JSON.parse(JSON.stringify(this.getData())));
+
+ return shape;
+};
+/**
+ * Rectangle Shape
+ *
+ * @class
+ * @extends OG.shape.GeomShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ */
+OG.shape.RectangleShape = function (label) {
+ OG.shape.RectangleShape.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.RectangleShape';
+ this.label = label;
+};
+OG.shape.RectangleShape.prototype = new OG.shape.GeomShape();
+OG.shape.RectangleShape.superclass = OG.shape.GeomShape;
+OG.shape.RectangleShape.prototype.constructor = OG.shape.RectangleShape;
+OG.RectangleShape = OG.shape.getElementByIdRectangleShape;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.RectangleShape.prototype.createShape = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.geometry.Rectangle([0, 0], 100, 100);
+ return this.geom;
+};
+/**
+ * SpotShape Shape
+ *
+ * @class
+ * @extends OG.shape.GeomShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ */
+OG.shape.SpotShape = function (label) {
+ OG.shape.SpotShape.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.SpotShape';
+ this.label = label;
+};
+OG.shape.SpotShape.prototype = new OG.shape.GeomShape();
+OG.shape.SpotShape.superclass = OG.shape.GeomShape;
+OG.shape.SpotShape.prototype.constructor = OG.shape.SpotShape;
+OG.SpotShape = OG.shape.SpotShape;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.SpotShape.prototype.createShape = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.geometry.Circle([10, 10], 10);
+ return this.geom;
+};
+/**
+ * SpotShape Shape
+ *
+ * @class
+ * @extends OG.shape.GeomShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.To = function (label) {
+ OG.shape.To.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.To';
+ this.label = label;
+ this.MOVABLE = false;
+ this.RESIZABLE = false;
+ this.SELF_CONNECTABLE = false;
+ this.CONNECT_CLONEABLE = false;
+ this.LABEL_EDITABLE = false;
+ this.DELETABLE = false;
+ this.CONNECT_STYLE_CHANGE = false;
+ this.ENABLE_FROM = false;
+};
+OG.shape.To.prototype = new OG.shape.GeomShape();
+OG.shape.To.superclass = OG.shape.GeomShape;
+OG.shape.To.prototype.constructor = OG.shape.To;
+OG.To = OG.shape.To;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.To.prototype.createShape = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.geometry.Circle([10, 10], 10);
+ return this.geom;
+};
+/**
+ * BPMN : Transformer Shape
+ *
+ * @class
+ * @extends OG.shape.bpmn.A_Task
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ * @requires OG.shape.bpmn.A_Task
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.Transformer = function (label) {
+ OG.shape.Transformer.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.Transformer';
+ this.label = label;
+ this.CONNECTABLE = false;
+ this.MOVABLE = true;
+ this.RESIZABLE = false;
+ this.SELF_CONNECTABLE = false;
+ this.CONNECT_CLONEABLE = false;
+ this.LABEL_EDITABLE = false;
+}
+OG.shape.Transformer.prototype = new OG.shape.GroupShape();
+OG.shape.Transformer.superclass = OG.shape.GroupShape;
+OG.shape.Transformer.prototype.constructor = OG.shape.Transformer;
+OG.Transformer = OG.shape.Transformer;
+
+OG.shape.Transformer.prototype.createShape = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.geometry.Rectangle([0, 0], 100, 100);
+ this.geom.style = new OG.geometry.Style({
+ 'label-direction': 'horizontal',
+ 'vertical-align': 'top',
+ fill: '#ffffff',
+ 'fill-opacity': 0
+ });
+
+ return this.geom;
+};
+/**
+ * Vertical Swimlane Shape
+ *
+ * @class
+ * @extends OG.shape.GroupShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨
+ * @author Seungpil Park
+ */
+OG.shape.VerticalLaneShape = function (label) {
+ OG.shape.VerticalLaneShape.superclass.call(this, label);
+
+ this.SHAPE_ID = 'OG.shape.VerticalLaneShape';
+};
+OG.shape.VerticalLaneShape.prototype = new OG.shape.GroupShape();
+OG.shape.VerticalLaneShape.superclass = OG.shape.GroupShape;
+OG.shape.VerticalLaneShape.prototype.constructor = OG.shape.VerticalLaneShape;
+OG.VerticalLaneShape = OG.shape.VerticalLaneShape;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.VerticalLaneShape.prototype.createShape = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.geometry.Rectangle([0, 0], 100, 100);
+ this.geom.style = new OG.geometry.Style({
+ 'label-direction': 'horizontal',
+ 'vertical-align': 'top',
+ 'title-size': 24,
+ fill: '#ffffff',
+ 'fill-opacity': 0
+ });
+
+ return this.geom;
+};
+/**
+ * Vertical Pool Shape
+ *
+ * @class
+ * @extends OG.shape.GroupShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨
+ */
+OG.shape.VerticalPoolShape = function (label) {
+ OG.shape.VerticalPoolShape.superclass.call(this, label);
+
+ this.SHAPE_ID = 'OG.shape.VerticalPoolShape';
+ this.CONNECTABLE = true;
+ this.GROUP_COLLAPSIBLE = false;
+};
+OG.shape.VerticalPoolShape.prototype = new OG.shape.GroupShape();
+OG.shape.VerticalPoolShape.superclass = OG.shape.GroupShape;
+OG.shape.VerticalPoolShape.prototype.constructor = OG.shape.VerticalPoolShape;
+OG.VerticalPoolShape = OG.shape.VerticalPoolShape;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.VerticalPoolShape.prototype.createShape = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.geometry.Rectangle([0, 0], 100, 100);
+ this.geom.style = new OG.geometry.Style({
+ 'label-direction': 'horizontal',
+ 'vertical-align': 'top',
+ 'fill': '#ffffff',
+ 'fill-opacity': 0
+ });
+
+ return this.geom;
+};
+/**
+ * BPMN : Task Activity Shape
+ *
+ * @class
+ * @extends OG.shape.GeomShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.A_Task = function (label) {
+ OG.shape.bpmn.A_Task.superclass.call(this);
+
+ this.GROUP_DROPABLE = false;
+ this.SHAPE_ID = 'OG.shape.bpmn.A_Task';
+ this.label = label;
+ this.CONNECTABLE = true;
+ this.GROUP_COLLAPSIBLE = false;
+ this.LoopType = "None";
+ this.TaskType = "None";
+ this.status = "None";
+ this.Events = [];
+
+};
+OG.shape.bpmn.A_Task.prototype = new OG.shape.GroupShape();
+OG.shape.bpmn.A_Task.superclass = OG.shape.GroupShape;
+OG.shape.bpmn.A_Task.prototype.constructor = OG.shape.bpmn.A_Task;
+OG.A_Task = OG.shape.bpmn.A_Task;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.A_Task.prototype.createShape = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.geometry.Rectangle([0, 0], 100, 100);
+ this.geom.style = new OG.geometry.Style({
+ //fill: 'r[(10, 10)]#FFFFFF-#FFFFCC',
+ 'fill-r': 1,
+ 'fill-cx': .1,
+ 'fill-cy': .1,
+ "stroke-width": 1.2,
+ fill: 'r(.1, .1)#FFFFFF-#FFFFCC',
+ 'fill-opacity': 1,
+ r: '10'
+ });
+
+ return this.geom;
+};
+
+OG.shape.bpmn.A_Task.prototype.createSubShape = function () {
+ this.sub = [];
+
+ var loopShape;
+ switch (this.LoopType) {
+ case 'Standard' :
+ loopShape = new OG.ImageShape(this.currentCanvas._CONFIG.IMAGE_BASE + 'loop_standard.png');
+ break;
+ case 'MIParallel' :
+ loopShape = new OG.MIParallel();
+ break;
+ case 'MISequential' :
+ loopShape = new OG.MISequential();
+ break;
+ }
+ if (loopShape) {
+ this.sub.push({
+ shape: loopShape,
+ width: '15px',
+ height: '15px',
+ bottom: '5px',
+ align: 'center',
+ style: {}
+ })
+ }
+
+ var taskTypeShape;
+ switch (this.TaskType) {
+ case "User":
+ taskTypeShape = new OG.ImageShape(this.currentCanvas._CONFIG.IMAGE_BASE + "User.png");
+ break;
+ case "Send":
+ taskTypeShape = new OG.ImageShape(this.currentCanvas._CONFIG.IMAGE_BASE + 'Send.png');
+ break;
+ case "Receive":
+ taskTypeShape = new OG.ImageShape(this.currentCanvas._CONFIG.IMAGE_BASE + 'Receive.png');
+ break;
+ case "Manual":
+ taskTypeShape = new OG.ImageShape(this.currentCanvas._CONFIG.IMAGE_BASE + 'Manual.png');
+ break;
+ case "Service":
+ taskTypeShape = new OG.ImageShape(this.currentCanvas._CONFIG.IMAGE_BASE + 'Service.png');
+ break;
+ case "BusinessRule":
+ taskTypeShape = new OG.ImageShape(this.currentCanvas._CONFIG.IMAGE_BASE + 'BusinessRule.png');
+ break;
+ case "Script":
+ taskTypeShape = new OG.ImageShape(this.currentCanvas._CONFIG.IMAGE_BASE + 'Script.png');
+ break;
+ case "Mapper":
+ taskTypeShape = new OG.ImageShape(this.currentCanvas._CONFIG.IMAGE_BASE + 'Mapper.png');
+ break;
+ case "WebService":
+ taskTypeShape = new OG.ImageShape(this.currentCanvas._CONFIG.IMAGE_BASE + 'WebService.png');
+ break;
+ }
+ if (taskTypeShape) {
+ this.sub.push({
+ shape: taskTypeShape,
+ width: '20px',
+ height: '20px',
+ top: '5px',
+ left: '5px',
+ style: {}
+ })
+ }
+
+ var statusShape, statusAnimation;
+ switch (this.status) {
+ case "Completed":
+ statusShape = new OG.ImageShape(this.currentCanvas._CONFIG.IMAGE_BASE + 'complete.png');
+ break;
+ case "Running":
+ statusShape = new OG.ImageShape(this.currentCanvas._CONFIG.IMAGE_BASE + 'running.png');
+ statusAnimation = new OG.RectangleShape();
+ break;
+ }
+ if (statusShape) {
+ this.sub.push({
+ shape: statusShape,
+ width: '20px',
+ height: '20px',
+ right: '25px',
+ top: '5px',
+ style: {}
+ })
+ }
+ if (statusAnimation) {
+ this.sub.push({
+ shape: statusAnimation,
+ 'z-index': -1,
+ width: '120%',
+ height: '120%',
+ left: '-10%',
+ top: '-10%',
+ style: {
+ 'fill-opacity': 1,
+ animation: [
+ {
+ start: {
+ fill: 'white'
+ },
+ to: {
+ fill: '#C9E2FC'
+ },
+ ms: 1000
+ },
+ {
+ start: {
+ fill: '#C9E2FC'
+ },
+ to: {
+ fill: 'white'
+ },
+ ms: 1000,
+ delay: 1000
+ }
+ ],
+ 'animation-repeat': true,
+ "fill": "#C9E2FC",
+ "stroke-width": "0.2",
+ "r": "10",
+ 'stroke-dasharray': '--'
+ }
+ })
+ }
+
+ return this.sub;
+};
+
+OG.shape.bpmn.A_Task.prototype.createContextMenu = function () {
+ var me = this;
+ this.contextMenu = {
+ 'delete': true,
+ 'copy': true,
+ 'format': true,
+ 'text': true,
+ 'bringToFront': true,
+ 'sendToBack': true,
+ 'changeshape': {
+ name: '변경',
+ items: {
+ 'A_Task': {
+ name: '추상',
+ type: 'radio',
+ radio: 'changeshape',
+ value: 'OG.shape.bpmn.A_Task',
+ events: {
+ change: function (e) {
+ me.currentCanvas.getEventHandler().changeShape(e.target.value);
+ }
+ }
+ },
+ 'A_HumanTask': {
+ name: '사용자',
+ type: 'radio',
+ radio: 'changeshape',
+ value: 'OG.shape.bpmn.A_HumanTask',
+ events: {
+ change: function (e) {
+ me.currentCanvas.getEventHandler().changeShape(e.target.value);
+ }
+ }
+ },
+ 'A_WebServiceTask': {
+ name: '서비스',
+ type: 'radio',
+ radio: 'changeshape',
+ value: 'OG.shape.bpmn.A_WebServiceTask',
+ events: {
+ change: function (e) {
+ me.currentCanvas.getEventHandler().changeShape(e.target.value);
+ }
+ }
+ },
+ 'A_ManualTask': {
+ name: '수동',
+ type: 'radio',
+ radio: 'changeshape',
+ value: "OG.shape.bpmn.A_ManualTask",
+ events: {
+ change: function (e) {
+ me.currentCanvas.getEventHandler().changeShape(e.target.value);
+ }
+ }
+ }
+ }
+ },
+ 'addEvent': {
+ name: '이벤트 추가',
+ items: {
+ 'Message': {
+ name: '메시지',
+ type: 'radio',
+ radio: 'addEvent',
+ value: 'Message',
+ events: {
+ change: function (e) {
+ me.currentCanvas.getEventHandler().setAddEventSelectedShape(e.target.value);
+ }
+ }
+ },
+ 'Timer': {
+ name: '타이머',
+ type: 'radio',
+ radio: 'addEvent',
+ value: 'Timer',
+ events: {
+ change: function (e) {
+ me.currentCanvas.getEventHandler().setAddEventSelectedShape(e.target.value);
+ }
+ }
+ },
+ 'Error': {
+ name: '에러',
+ type: 'radio',
+ radio: 'addEvent',
+ value: 'Error',
+ events: {
+ change: function (e) {
+ me.currentCanvas.getEventHandler().setAddEventSelectedShape(e.target.value);
+ }
+ }
+ },
+ 'Conditional': {
+ name: '조건부',
+ type: 'radio',
+ radio: 'addEvent',
+ value: "Conditional",
+ events: {
+ change: function (e) {
+ me.currentCanvas.getEventHandler().setAddEventSelectedShape(e.target.value);
+ }
+ }
+ }
+ }
+ },
+ 'property': {
+ name: '속성', callback: function () {
+ me.currentCanvas.getEventHandler().showProperty();
+ }
+ }
+ };
+ return this.contextMenu;
+};
+OG.shape.bpmn.Event = function (label) {
+ OG.shape.bpmn.Event.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.Event';
+ this.label = label;
+};
+OG.shape.bpmn.Event.prototype = new OG.shape.GeomShape();
+OG.shape.bpmn.Event.superclass = OG.shape.GeomShape;
+OG.shape.bpmn.Event.prototype.constructor = OG.shape.bpmn.Event;
+OG.Event = OG.shape.bpmn.Event;
+
+OG.shape.bpmn.Event.prototype.createContextMenu = function () {
+ var me = this;
+ this.contextMenu = {
+ 'delete': true,
+ 'copy': true,
+ 'format': true,
+ 'text': true,
+ 'bringToFront': true,
+ 'sendToBack': true,
+
+ 'change': {
+ name: '변경',
+ items: {
+ 'start': {
+ name: '시작',
+ items: {
+ 'start': {
+ name: '시작',
+ type: 'radio',
+ radio: 'start',
+ value: 'OG.shape.bpmn.E_Start',
+ events: {
+ change: function (e) {
+ me.currentCanvas.getEventHandler().changeShape(e.target.value);
+ }
+ }
+ },
+ 'start_message': {
+ name: '메시지 시작',
+ type: 'radio',
+ radio: 'start',
+ value: 'OG.shape.bpmn.E_Start_Message',
+ events: {
+ change: function (e) {
+ me.currentCanvas.getEventHandler().changeShape(e.target.value);
+ }
+ }
+ },
+ 'start_timer': {
+ name: '타이머 시작',
+ type: 'radio',
+ radio: 'start',
+ value: 'OG.shape.bpmn.E_Start_Timer',
+ events: {
+ change: function (e) {
+ me.currentCanvas.getEventHandler().changeShape(e.target.value);
+ }
+ }
+ },
+ 'start_conditional': {
+ name: '조건부 시작',
+ type: 'radio',
+ radio: 'start',
+ value: 'OG.shape.bpmn.E_Start_Rule',
+ events: {
+ change: function (e) {
+ me.currentCanvas.getEventHandler().changeShape(e.target.value);
+ }
+ }
+ }
+ }
+ },
+ 'intermediate': {
+ name: '중간',
+ items: {
+ 'intermediate': {
+ name: '중간',
+ type: 'radio',
+ radio: 'intermediate',
+ value: 'OG.shape.bpmn.E_Intermediate',
+ events: {
+ change: function (e) {
+ me.currentCanvas.getEventHandler().changeShape(e.target.value);
+ }
+ }
+ },
+ 'intermediate_openMessage': {
+ name: '열린 메시지 중간',
+ type: 'radio',
+ radio: 'intermediate',
+ value: 'OG.shape.bpmn.E_Intermediate_Message',
+ events: {
+ change: function (e) {
+ me.currentCanvas.getEventHandler().changeShape(e.target.value);
+ }
+ }
+ },
+ 'intermediate_closeMessage': {
+ name: '닫힌 메시지 중간',
+ type: 'radio',
+ radio: 'intermediate',
+ value: 'OG.shape.bpmn.E_Intermediate_MessageFill',
+ events: {
+ change: function (e) {
+ me.currentCanvas.getEventHandler().changeShape(e.target.value);
+ }
+ }
+ },
+ 'intermediate_timer': {
+ name: '타이머 중간',
+ type: 'radio',
+ radio: 'intermediate',
+ value: 'OG.shape.bpmn.E_Intermediate_Timer',
+ events: {
+ change: function (e) {
+ me.currentCanvas.getEventHandler().changeShape(e.target.value);
+ }
+ }
+ },
+ 'intermediate_conditional': {
+ name: '조건부 중간',
+ type: 'radio',
+ radio: 'intermediate',
+ value: 'OG.shape.bpmn.E_Intermediate_Rule',
+ events: {
+ change: function (e) {
+ me.currentCanvas.getEventHandler().changeShape(e.target.value);
+ }
+ }
+ }
+ }
+ },
+ 'end': {
+ name: '종료',
+ items: {
+ 'end': {
+ name: '종료',
+ type: 'radio',
+ radio: 'end',
+ value: 'OG.shape.bpmn.E_End',
+ events: {
+ change: function (e) {
+ me.changeShape(e.target.value);
+ }
+ }
+ },
+ 'end_message': {
+ name: '메시지 종료',
+ type: 'radio',
+ radio: 'end',
+ value: 'OG.shape.bpmn.E_End_Message',
+ events: {
+ change: function (e) {
+ me.changeShape(e.target.value);
+ }
+ }
+ },
+ 'end_process': {
+ name: '프로세스 종료',
+ type: 'radio',
+ radio: 'end',
+ value: 'OG.shape.bpmn.E_Terminate',
+ events: {
+ change: function (e) {
+ me.changeShape(e.target.value);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ };
+ return this.contextMenu;
+};
+/**
+ * BPMN : End Event Shape
+ *
+ * @class
+ * @extends OG.shape.GeomShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.E_End = function (label) {
+ OG.shape.bpmn.E_End.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.E_End';
+ this.label = label;
+ this.inclusion = false;
+};
+OG.shape.bpmn.E_End.prototype = new OG.shape.bpmn.Event();
+OG.shape.bpmn.E_End.superclass = OG.shape.bpmn.Event;
+OG.shape.bpmn.E_End.prototype.constructor = OG.shape.bpmn.E_End;
+OG.E_End = OG.shape.bpmn.E_End;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.E_End.prototype.createShape = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.geometry.Circle([50, 50], 50);
+ this.geom.style = new OG.geometry.Style({
+ "stroke-width": 3,
+ 'label-position': 'bottom'
+ });
+
+ return this.geom;
+};
+
+OG.shape.bpmn.E_End.prototype.createSubShape = function () {
+ this.sub = [];
+
+ if (this.inclusion) {
+ this.sub.push({
+ shape: new OG.ImageShape(this.currentCanvas._CONFIG.IMAGE_BASE + 'complete.png'),
+ width: '20px',
+ height: '20px',
+ right: '0px',
+ bottom: '20px',
+ style: {}
+ })
+ }
+
+ return this.sub;
+};
+/**
+ * BPMN : Intermediate Event Shape
+ *
+ * @class
+ * @extends OG.shape.GeomShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.E_Intermediate = function (label) {
+ OG.shape.bpmn.E_Intermediate.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.E_Intermediate';
+ this.label = label;
+};
+OG.shape.bpmn.E_Intermediate.prototype = new OG.shape.bpmn.Event();
+OG.shape.bpmn.E_Intermediate.superclass = OG.shape.bpmn.Event;
+OG.shape.bpmn.E_Intermediate.prototype.constructor = OG.shape.bpmn.E_Intermediate;
+OG.E_Intermediate = OG.shape.bpmn.E_Intermediate;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.E_Intermediate.prototype.createShape = function () {
+ var geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geomCollection.push(new OG.geometry.Circle([50, 50], 50));
+ geomCollection.push(new OG.geometry.Circle([50, 50], 42));
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+ this.geom.style = new OG.geometry.Style({
+ 'label-position': 'bottom'
+ });
+
+ return this.geom;
+};
+/**
+ * BPMN : Start Event Shape
+ *
+ * @class
+ * @extends OG.shape.GeomShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.E_Start = function (label) {
+ OG.shape.bpmn.E_Start.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.E_Start';
+ this.label = label;
+ this.inclusion = false;
+};
+OG.shape.bpmn.E_Start.prototype = new OG.shape.bpmn.Event();
+OG.shape.bpmn.E_Start.superclass = OG.shape.bpmn.Event;
+OG.shape.bpmn.E_Start.prototype.constructor = OG.shape.bpmn.E_Start;
+OG.E_Start = OG.shape.bpmn.E_Start;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.E_Start.prototype.createShape = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.geometry.Circle([50, 50], 50);
+ this.geom.style = new OG.geometry.Style({
+ 'label-position': 'bottom',
+ "stroke-width": 1.5
+ });
+
+ return this.geom;
+};
+
+OG.shape.bpmn.E_Start.prototype.createSubShape = function () {
+ this.sub = [];
+
+ if (this.inclusion) {
+ this.sub.push({
+ shape: new OG.ImageShape(this.currentCanvas._CONFIG.IMAGE_BASE + 'complete.png'),
+ width: '20px',
+ height: '20px',
+ right: '0px',
+ bottom: '20px',
+ style: {}
+ })
+ }
+
+ return this.sub;
+};
+/**
+ * BPMN : Gateway Shape
+ *
+ * @class
+ * @extends OG.shape.GeomShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.G_Gateway = function (label) {
+ OG.shape.bpmn.G_Gateway.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.G_Gateway';
+ this.label = label;
+};
+OG.shape.bpmn.G_Gateway.prototype = new OG.shape.bpmn.Event();
+OG.shape.bpmn.G_Gateway.superclass = OG.shape.bpmn.Event;
+OG.shape.bpmn.G_Gateway.prototype.constructor = OG.shape.bpmn.G_Gateway;
+OG.G_Gateway = OG.shape.bpmn.G_Gateway;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.G_Gateway.prototype.createShape = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.geometry.Polygon([
+ [0, 50],
+ [50, 100],
+ [100, 50],
+ [50, 0]
+ ]);
+
+ return this.geom;
+};
+
+OG.shape.bpmn.G_Gateway.prototype.createContextMenu = function () {
+ var me = this;
+ this.contextMenu = {
+ 'delete': true,
+ 'copy': true,
+ 'format': true,
+ 'text': true,
+ 'bringToFront': true,
+ 'sendToBack': true,
+ 'changegateway': {
+ name: '변경',
+ items: {
+ 'G_Gateway': {
+ name: '베타적',
+ type: 'radio',
+ radio: 'changegateway',
+ value: 'OG.shape.bpmn.G_Gateway',
+ events: {
+ change: function (e) {
+ me.currentCanvas.getEventHandler().changeShape(e.target.value);
+ }
+ }
+ },
+ 'G_Parallel': {
+ name: '병렬',
+ type: 'radio',
+ radio: 'changegateway',
+ value: 'OG.shape.bpmn.G_Parallel',
+ events: {
+ change: function (e) {
+ me.currentCanvas.getEventHandler().changeShape(e.target.value);
+ }
+ }
+ },
+ 'G_Inclusive': {
+ name: '포괄적',
+ type: 'radio',
+ radio: 'changegateway',
+ value: 'OG.shape.bpmn.G_Inclusive',
+ events: {
+ change: function (e) {
+ me.currentCanvas.getEventHandler().changeShape(e.target.value);
+ }
+ }
+ }
+ }
+ }
+ };
+ return this.contextMenu;
+};
+/**
+ * BPMN : Human Task Shape
+ *
+ * @class
+ * @extends OG.shape.bpmn.A_Task
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ * @requires OG.shape.bpmn.A_Task
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.A_HumanTask = function (label) {
+ OG.shape.bpmn.A_HumanTask.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.A_HumanTask';
+ this.label = label;
+ this.CONNECTABLE = true;
+ this.GROUP_COLLAPSIBLE = false;
+ this.LoopType = "None";
+ this.TaskType = "User";
+ this.status = "None";
+}
+OG.shape.bpmn.A_HumanTask.prototype = new OG.shape.bpmn.A_Task();
+OG.shape.bpmn.A_HumanTask.superclass = OG.shape.bpmn.A_Task;
+OG.shape.bpmn.A_HumanTask.prototype.constructor = OG.shape.bpmn.A_HumanTask;
+OG.A_HumanTask = OG.shape.bpmn.A_HumanTask;
+/**
+ * BPMN : Loop Task Shape
+ *
+ * @class
+ * @extends OG.shape.bpmn.A_Task
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ * @requires OG.shape.bpmn.A_Task
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.A_LoopTask = function (label) {
+ OG.shape.bpmn.A_LoopTask.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.A_LoopTask';
+ this.label = label;
+ this.CONNECTABLE = true;
+ this.GROUP_COLLAPSIBLE = false;
+ this.LoopType = "Standard";
+ this.TaskType = "None";
+ this.status = "None";
+}
+OG.shape.bpmn.A_LoopTask.prototype = new OG.shape.bpmn.A_Task();
+OG.shape.bpmn.A_LoopTask.superclass = OG.shape.bpmn.A_Task;
+OG.shape.bpmn.A_LoopTask.prototype.constructor = OG.shape.bpmn.A_LoopTask;
+OG.A_LoopTask = OG.shape.bpmn.A_LoopTask;
+/**
+ * BPMN : Manual Task Shape
+ *
+ * @class
+ * @extends OG.shape.bpmn.A_Task
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ * @requires OG.shape.bpmn.A_Task
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.A_ManualTask = function (label) {
+ OG.shape.bpmn.A_ManualTask.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.A_ManualTask';
+ this.label = label;
+ this.CONNECTABLE = true;
+ this.GROUP_COLLAPSIBLE = false;
+ this.LoopType = "None";
+ this.TaskType = "Manual";
+ this.status = "None";
+}
+OG.shape.bpmn.A_ManualTask.prototype = new OG.shape.bpmn.A_Task();
+OG.shape.bpmn.A_ManualTask.superclass = OG.shape.bpmn.A_Task;
+OG.shape.bpmn.A_ManualTask.prototype.constructor = OG.shape.bpmn.A_ManualTask;
+OG.A_ManualTask = OG.shape.bpmn.A_ManualTask;
+/**
+ * BPMN : Mapper Task Shape
+ *
+ * @class
+ * @extends OG.shape.bpmn.A_Task
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ * @requires OG.shape.bpmn.A_Task
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.A_MapperTask = function (label) {
+ OG.shape.bpmn.A_MapperTask.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.A_MapperTask';
+ this.label = label;
+ this.CONNECTABLE = true;
+ this.GROUP_COLLAPSIBLE = false;
+ this.LoopType = "None";
+ this.TaskType = "Mapper";
+ this.status = "None";
+}
+OG.shape.bpmn.A_MapperTask.prototype = new OG.shape.bpmn.A_Task();
+OG.shape.bpmn.A_MapperTask.superclass = OG.shape.bpmn.A_Task;
+OG.shape.bpmn.A_MapperTask.prototype.constructor = OG.shape.bpmn.A_MapperTask;
+OG.A_MapperTask = OG.shape.bpmn.A_MapperTask;
+/**
+ * BPMN : Service(Invokation) Task Shape
+ *
+ * @class
+ * @extends OG.shape.bpmn.A_Task
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ * @requires OG.shape.bpmn.A_Task
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.A_ServiceTask = function (label) {
+ OG.shape.bpmn.A_HumanTask.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.A_ServiceTask';
+ this.label = label;
+ this.CONNECTABLE = true;
+ this.GROUP_COLLAPSIBLE = false;
+ this.LoopType = "None";
+ this.TaskType = "Service";
+}
+OG.shape.bpmn.A_ServiceTask.prototype = new OG.shape.bpmn.A_Task();
+OG.shape.bpmn.A_ServiceTask.superclass = OG.shape.bpmn.A_Task;
+OG.shape.bpmn.A_ServiceTask.prototype.constructor = OG.shape.bpmn.A_ServiceTask;
+OG.A_ServiceTask = OG.shape.bpmn.A_ServiceTask;
+/**
+ * BPMN : Subprocess Activity Shape
+ *
+ * @class
+ * @extends OG.shape.GroupShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.A_Subprocess = function (label) {
+ OG.shape.bpmn.A_Subprocess.superclass.call(this);
+
+ this.label = label;
+ this.SHAPE_ID = 'OG.shape.bpmn.A_Subprocess';
+ this.GROUP_COLLAPSIBLE = false;
+ this.HaveButton = true;
+ this.status = "None";
+ this.inclusion = false;
+};
+OG.shape.bpmn.A_Subprocess.prototype = new OG.shape.GeomShape();
+OG.shape.bpmn.A_Subprocess.superclass = OG.shape.GeomShape;
+OG.shape.bpmn.A_Subprocess.prototype.constructor = OG.shape.bpmn.A_Subprocess;
+OG.A_Subprocess = OG.shape.bpmn.A_Subprocess;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.A_Subprocess.prototype.createShape = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+ this.CONNECTABLE = true;
+
+
+ this.geom = new OG.geometry.Rectangle([0, 0], 100, 100);
+ this.geom.style = new OG.geometry.Style({
+ "stroke-width": 1.2,
+ 'r': 6,
+ fill: '#FFFFFF - #FFFFCC',
+ 'fill-opacity': 1
+ });
+
+ return this.geom;
+};
+
+OG.shape.bpmn.A_Subprocess.prototype.createSubShape = function () {
+ this.sub = [];
+
+ var statusShape, statusAnimation;
+ switch (this.status) {
+ case "Completed":
+ statusShape = new OG.ImageShape(this.currentCanvas._CONFIG.IMAGE_BASE + "complete.png");
+ break;
+ case "Running":
+ statusShape = new OG.ImageShape(this.currentCanvas._CONFIG.IMAGE_BASE + 'running.png');
+ statusAnimation = new OG.RectangleShape();
+ break;
+ }
+ if (statusShape) {
+ this.sub.push({
+ shape: statusShape,
+ width: '20px',
+ height: '20px',
+ right: '25px',
+ top: '5px',
+ style: {}
+ })
+ }
+ if (statusAnimation) {
+ this.sub.push({
+ shape: statusAnimation,
+ 'z-index': -1,
+ width: '120%',
+ height: '120%',
+ left: '-10%',
+ top: '-10%',
+ style: {
+ 'fill-opacity': 1,
+ animation: [
+ {
+ start: {
+ fill: 'white'
+ },
+ to: {
+ fill: '#C9E2FC'
+ },
+ ms: 1000
+ },
+ {
+ start: {
+ fill: '#C9E2FC'
+ },
+ to: {
+ fill: 'white'
+ },
+ ms: 1000,
+ delay: 1000
+ }
+ ],
+ 'animation-repeat': true,
+ "fill": "#C9E2FC",
+ "stroke-width": "0.2",
+ "r": "10",
+ 'stroke-dasharray': '--'
+ }
+ })
+ }
+
+ if (this.inclusion) {
+ this.sub.push({
+ shape: new OG.ImageShape(this.currentCanvas._CONFIG.IMAGE_BASE + 'complete.png'),
+ width: '20px',
+ height: '20px',
+ right: '0px',
+ bottom: '20px',
+ style: {}
+ })
+ }
+
+ if (this.HaveButton) {
+ this.sub.push({
+ shape: new OG.ImageShape(this.currentCanvas._CONFIG.IMAGE_BASE + 'subprocess.png'),
+ width: '20px',
+ height: '20px',
+ align: 'center',
+ bottom: '5px',
+ style: {
+ "stroke-width": 1,
+ fill: "white",
+ "fill-opacity": 0,
+ "shape-rendering": "crispEdges"
+ }
+ })
+ }
+
+ return this.sub;
+};
+
+OG.shape.bpmn.A_Subprocess.prototype.createContextMenu = function () {
+ var me = this;
+ this.contextMenu = {
+ 'delete': true,
+ 'copy': true,
+ 'format': true,
+ 'text': true,
+ 'bringToFront': true,
+ 'sendToBack': true,
+ 'property': {
+ name: '속성', callback: function () {
+ me.currentCanvas.getEventHandler().showProperty();
+ }
+ }
+ };
+ return this.contextMenu;
+};
+/**
+ * BPMN : WebService(Invokation) Task Shape
+ *
+ * @class
+ * @extends OG.shape.bpmn.A_Task
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ * @requires OG.shape.bpmn.A_Task
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.A_WebServiceTask = function (label) {
+ OG.shape.bpmn.A_WebServiceTask.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.A_WebServiceTask';
+ this.label = label;
+ this.CONNECTABLE = true;
+ this.GROUP_COLLAPSIBLE = false;
+ this.LoopType = "None";
+ this.TaskType = "WebService";
+}
+OG.shape.bpmn.A_WebServiceTask.prototype = new OG.shape.bpmn.A_Task();
+OG.shape.bpmn.A_WebServiceTask.superclass = OG.shape.bpmn.A_Task;
+OG.shape.bpmn.A_WebServiceTask.prototype.constructor = OG.shape.bpmn.A_WebServiceTask;
+OG.A_WebServiceTask = OG.shape.bpmn.A_WebServiceTask;
+/**
+ * BPMN : Annotation Association Connector Shape
+ *
+ * @class
+ * @extends OG.shape.EdgeShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {Number[]} from 와이어 시작 좌표
+ * @param {Number[]} to 와이어 끝 좌표
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.C_Association = function (from, to, label) {
+ OG.shape.bpmn.C_Association.superclass.call(this, from, to, label);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.C_Association';
+};
+OG.shape.bpmn.C_Association.prototype = new OG.shape.EdgeShape();
+OG.shape.bpmn.C_Association.superclass = OG.shape.EdgeShape;
+OG.shape.bpmn.C_Association.prototype.constructor = OG.shape.bpmn.C_Association;
+OG.C_Association = OG.shape.bpmn.C_Association;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.C_Association.prototype.createShape = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.Line(this.from || [0, 0], this.to || [70, 0]);
+ this.geom.style = new OG.geometry.Style({
+ "edge-type": "straight",
+ "arrow-start": "none",
+ "arrow-end": "none",
+ 'stroke-dasharray': '. '
+ });
+
+ return this.geom;
+};
+/**
+ * BPMN : Conditional Connector Shape
+ *
+ * @class
+ * @extends OG.shape.EdgeShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {Number[]} from 와이어 시작 좌표
+ * @param {Number[]} to 와이어 끝 좌표
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.C_Conditional = function (from, to, label) {
+ OG.shape.bpmn.C_Conditional.superclass.call(this, from, to, label);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.C_Conditional';
+};
+OG.shape.bpmn.C_Conditional.prototype = new OG.shape.EdgeShape();
+OG.shape.bpmn.C_Conditional.superclass = OG.shape.EdgeShape;
+OG.shape.bpmn.C_Conditional.prototype.constructor = OG.shape.bpmn.C_Conditional;
+OG.C_Conditional = OG.shape.bpmn.C_Conditional;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.C_Conditional.prototype.createShape = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.Line(this.from || [0, 0], this.to || [70, 0]);
+ this.geom.style = new OG.geometry.Style({
+ "edge-type": "straight",
+ "arrow-start": "open_diamond-wide-long",
+ "arrow-end": "open_block-wide-long"
+ });
+
+ return this.geom;
+};
+/**
+ * BPMN : Data Association Connector Shape
+ *
+ * @class
+ * @extends OG.shape.EdgeShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {Number[]} from 와이어 시작 좌표
+ * @param {Number[]} to 와이어 끝 좌표
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.C_DataAssociation = function (from, to, label) {
+ OG.shape.bpmn.C_DataAssociation.superclass.call(this, from, to, label);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.C_DataAssociation';
+};
+OG.shape.bpmn.C_DataAssociation.prototype = new OG.shape.EdgeShape();
+OG.shape.bpmn.C_DataAssociation.superclass = OG.shape.EdgeShape;
+OG.shape.bpmn.C_DataAssociation.prototype.constructor = OG.shape.bpmn.C_DataAssociation;
+OG.C_DataAssociation = OG.shape.bpmn.C_DataAssociation;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.C_DataAssociation.prototype.createShape = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.Line(this.from || [0, 0], this.to || [70, 0]);
+ this.geom.style = new OG.geometry.Style({
+ "edge-type": "straight",
+ "arrow-start": "none",
+ "arrow-end": "classic-wide-long",
+ 'stroke-dasharray': '. '
+ });
+
+ return this.geom;
+};
+/**
+ * BPMN : Message Connector Shape
+ *
+ * @class
+ * @extends OG.shape.EdgeShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {Number[]} from 와이어 시작 좌표
+ * @param {Number[]} to 와이어 끝 좌표
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.C_Message = function (from, to, label) {
+ OG.shape.bpmn.C_Message.superclass.call(this, from, to, label);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.C_Message';
+};
+OG.shape.bpmn.C_Message.prototype = new OG.shape.EdgeShape();
+OG.shape.bpmn.C_Message.superclass = OG.shape.EdgeShape;
+OG.shape.bpmn.C_Message.prototype.constructor = OG.shape.bpmn.C_Message;
+OG.C_Message = OG.shape.bpmn.C_Message;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.C_Message.prototype.createShape = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.Line(this.from || [0, 0], this.to || [80, 0]);
+ this.geom.style = new OG.geometry.Style({
+ "edge-type": "straight",
+ "arrow-start": "open_oval-wide-long",
+ "arrow-end": "open_block-wide-long",
+ 'stroke-dasharray': '--'
+ });
+
+ return this.geom;
+};
+/**
+ * BPMN : Sequence Connector Shape
+ *
+ * @class
+ * @extends OG.shape.EdgeShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {Number[]} from 와이어 시작 좌표
+ * @param {Number[]} to 와이어 끝 좌표
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.C_Sequence = function (from, to, label) {
+ OG.shape.bpmn.C_Sequence.superclass.call(this, from, to, label);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.C_Sequence';
+};
+OG.shape.bpmn.C_Sequence.prototype = new OG.shape.EdgeShape();
+OG.shape.bpmn.C_Sequence.superclass = OG.shape.EdgeShape;
+OG.shape.bpmn.C_Sequence.prototype.constructor = OG.shape.bpmn.C_Sequence;
+OG.C_Sequence = OG.shape.bpmn.C_Sequence;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.C_Sequence.prototype.createShape = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.Line(this.from || [0, 0], this.to || [80, 0]);
+ this.geom.style = new OG.geometry.Style({
+ "edge-type": "plain",
+ "arrow-start": "none",
+ "arrow-end": "block-wide-long"
+ });
+
+ return this.geom;
+};
+/**
+ * BPMN : Data Object Shape
+ *
+ * @class
+ * @extends OG.shape.GeomShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.D_Data = function (label) {
+ OG.shape.bpmn.D_Data.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.D_Data';
+ this.label = label;
+};
+OG.shape.bpmn.D_Data.prototype = new OG.shape.GeomShape();
+OG.shape.bpmn.D_Data.superclass = OG.shape.GeomShape;
+OG.shape.bpmn.D_Data.prototype.constructor = OG.shape.bpmn.D_Data;
+OG.D_Data = OG.shape.bpmn.D_Data;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.D_Data.prototype.createShape = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.PolyLine([
+ [0, 0],
+ [0, 100],
+ [100, 100],
+ [100, 20],
+ [80, 0],
+ [0, 0],
+ [80, 0],
+ [80, 20],
+ [100, 20]
+ ]);
+
+ return this.geom;
+};
+/**
+ * BPMN : Data Store Shape
+ *
+ * @class
+ * @extends OG.shape.GeomShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.D_Store = function (label) {
+ OG.shape.bpmn.D_Store.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.D_Store';
+ this.label = label;
+};
+OG.shape.bpmn.D_Store.prototype = new OG.shape.GeomShape();
+OG.shape.bpmn.D_Store.superclass = OG.shape.GeomShape;
+OG.shape.bpmn.D_Store.prototype.constructor = OG.shape.bpmn.D_Store;
+OG.D_Store = OG.shape.bpmn.D_Store;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.D_Store.prototype.createShape = function () {
+ var geom1, geom2, geom3, geom4, geom5, geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geom1 = new OG.geometry.Ellipse([50, 10], 50, 10);
+ geom2 = new OG.geometry.Line([0, 10], [0, 90]);
+ geom3 = new OG.geometry.Line([100, 10], [100, 90]);
+ geom4 = new OG.geometry.Curve([
+ [100, 90],
+ [96, 94],
+ [85, 97],
+ [50, 100],
+ [15, 97],
+ [4, 94],
+ [0, 90]
+ ]);
+ geom5 = new OG.geometry.Rectangle([0, 10], 100, 80);
+ geom5.style = new OG.geometry.Style({
+ "stroke": 'none'
+ });
+
+ geomCollection.push(geom1);
+ geomCollection.push(geom2);
+ geomCollection.push(geom3);
+ geomCollection.push(geom4);
+ geomCollection.push(geom5);
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+
+ return this.geom;
+};
+/**
+ * BPMN : Cancel End Event Shape
+ *
+ * @class
+ * @extends OG.shape.GeomShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.E_End_Cancel = function (label) {
+ OG.shape.bpmn.E_End_Cancel.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.E_End_Cancel';
+ this.label = label;
+};
+OG.shape.bpmn.E_End_Cancel.prototype = new OG.shape.bpmn.Event();
+OG.shape.bpmn.E_End_Cancel.superclass = OG.shape.bpmn.Event;
+OG.shape.bpmn.E_End_Cancel.prototype.constructor = OG.shape.bpmn.E_End_Cancel;
+OG.E_End_Cancel = OG.shape.bpmn.E_End_Cancel;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.E_End_Cancel.prototype.createShape = function () {
+ var geom1, geom2, geom3, geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geom1 = new OG.geometry.Circle([50, 50], 50);
+ geom1.style = new OG.geometry.Style({
+ "stroke-width": 3
+ });
+
+ geom2 = new OG.geometry.Line([25, 25], [75, 75]);
+ geom2.style = new OG.geometry.Style({
+ "stroke-width": 5
+ });
+
+ geom3 = new OG.geometry.Line([25, 75], [75, 25]);
+ geom3.style = new OG.geometry.Style({
+ "stroke-width": 5
+ });
+
+ geomCollection.push(geom1);
+ geomCollection.push(geom2);
+ geomCollection.push(geom3);
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+ this.geom.style = new OG.geometry.Style({
+ 'label-position': 'bottom'
+ });
+
+ return this.geom;
+};
+/**
+ * BPMN : Compensation End Event Shape
+ *
+ * @class
+ * @extends OG.shape.GeomShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.E_End_Compensation = function (label) {
+ OG.shape.bpmn.E_End_Compensation.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.E_End_Compensation';
+ this.label = label;
+};
+OG.shape.bpmn.E_End_Compensation.prototype = new OG.shape.bpmn.Event();
+OG.shape.bpmn.E_End_Compensation.superclass = OG.shape.bpmn.Event;
+OG.shape.bpmn.E_End_Compensation.prototype.constructor = OG.shape.bpmn.E_End_Compensation;
+OG.E_End_Compensation = OG.shape.bpmn.E_End_Compensation;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.E_End_Compensation.prototype.createShape = function () {
+ var geom1, geom2, geom3, geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geom1 = new OG.geometry.Circle([50, 50], 50);
+ geom1.style = new OG.geometry.Style({
+ "stroke-width": 3
+ });
+
+ geom2 = new OG.geometry.Polygon([
+ [15, 50],
+ [45, 70],
+ [45, 30]
+ ]);
+ geom2.style = new OG.geometry.Style({
+ "fill": "black",
+ "fill-opacity": 1
+ });
+
+ geom3 = new OG.geometry.Polygon([
+ [45, 50],
+ [75, 70],
+ [75, 30]
+ ]);
+ geom3.style = new OG.geometry.Style({
+ "fill": "black",
+ "fill-opacity": 1
+ });
+
+ geomCollection.push(geom1);
+ geomCollection.push(geom2);
+ geomCollection.push(geom3);
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+ this.geom.style = new OG.geometry.Style({
+ 'label-position': 'bottom'
+ });
+
+ return this.geom;
+};
+/**
+ * BPMN : Link End Event Shape
+ *
+ * @class
+ * @extends OG.shape.GeomShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.E_End_Connector = function (label) {
+ OG.shape.bpmn.E_End_Connector.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.E_End_Connector';
+ this.label = label;
+};
+OG.shape.bpmn.E_End_Connector.prototype = new OG.shape.bpmn.Event();
+OG.shape.bpmn.E_End_Connector.superclass = OG.shape.bpmn.Event;
+OG.shape.bpmn.E_End_Connector.prototype.constructor = OG.shape.bpmn.E_End_Connector;
+OG.E_End_Connector = OG.shape.bpmn.E_End_Connector;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.E_End_Connector.prototype.createShape = function () {
+ var geom1, geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geom1 = new OG.geometry.Polygon([
+ [20, 34],
+ [20, 65],
+ [60, 65],
+ [60, 80],
+ [85, 50],
+ [60, 20],
+ [60, 34]
+ ]);
+ geom1.style = new OG.geometry.Style({
+ "fill": "black",
+ "fill-opacity": 1
+ });
+ geomCollection.push(new OG.geometry.Circle([50, 50], 50));
+ geomCollection.push(new OG.geometry.Circle([50, 50], 42));
+ geomCollection.push(geom1);
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+ this.geom.style = new OG.geometry.Style({
+ 'label-position': 'bottom'
+ });
+
+ return this.geom;
+};
+/**
+ * BPMN : Error End Event Shape
+ *
+ * @class
+ * @extends OG.shape.GeomShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.E_End_Error = function (label) {
+ OG.shape.bpmn.E_End_Error.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.E_End_Error';
+ this.label = label;
+};
+OG.shape.bpmn.E_End_Error.prototype = new OG.shape.bpmn.Event();
+OG.shape.bpmn.E_End_Error.superclass = OG.shape.bpmn.Event;
+OG.shape.bpmn.E_End_Error.prototype.constructor = OG.shape.bpmn.E_End_Error;
+OG.E_End_Error = OG.shape.bpmn.E_End_Error;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.E_End_Error.prototype.createShape = function () {
+ var geom1, geom2, geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geom1 = new OG.geometry.Circle([50, 50], 50);
+ geom1.style = new OG.geometry.Style({
+ "stroke-width": 4
+ });
+
+ geom2 = new OG.geometry.Polygon([
+ [20, 75],
+ [40, 30],
+ [60, 60],
+ [80, 20],
+ [60, 75],
+ [40, 50]
+
+ ]);
+ geom2.style = new OG.geometry.Style({
+ "fill": "black",
+ "fill-opacity": 1
+ });
+
+ geomCollection.push(geom1);
+ geomCollection.push(geom2);
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+ this.geom.style = new OG.geometry.Style({
+ 'label-position': 'bottom'
+ });
+
+ return this.geom;
+};
+/**
+ * BPMN : Link End Event Shape
+ *
+ * @class
+ * @extends OG.shape.GeomShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.E_End_Link = function (label) {
+ OG.shape.bpmn.E_End_Link.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.E_End_Link';
+ this.label = label;
+};
+OG.shape.bpmn.E_End_Link.prototype = new OG.shape.bpmn.Event();
+OG.shape.bpmn.E_End_Link.superclass = OG.shape.bpmn.Event;
+OG.shape.bpmn.E_End_Link.prototype.constructor = OG.shape.bpmn.E_End_Link;
+OG.E_End_Link = OG.shape.bpmn.E_End_Link;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.E_End_Link.prototype.createShape = function () {
+ var geom1, geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geom1 = new OG.geometry.Polygon([
+ [20, 35],
+ [20, 65],
+ [60, 65],
+ [60, 80],
+ [85, 50],
+ [60, 20],
+ [60, 35]
+ ]);
+ geom1.style = new OG.geometry.Style({
+ "fill": "black",
+ "fill-opacity": 1
+ });
+
+ geomCollection.push(new OG.geometry.Circle([50, 50], 50));
+ geomCollection.push(new OG.geometry.Circle([50, 50], 42));
+ geomCollection.push(geom1);
+
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+ this.geom.style = new OG.geometry.Style({
+ 'label-position': 'bottom'
+ });
+
+ return this.geom;
+};
+/**
+ * BPMN : Message End Event Shape
+ *
+ * @class
+ * @extends OG.shape.GeomShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.E_End_Message = function (label) {
+ OG.shape.bpmn.E_End_Message.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.E_End_Message';
+ this.label = label;
+};
+OG.shape.bpmn.E_End_Message.prototype = new OG.shape.bpmn.Event();
+OG.shape.bpmn.E_End_Message.superclass = OG.shape.bpmn.Event;
+OG.shape.bpmn.E_End_Message.prototype.constructor = OG.shape.bpmn.E_End_Message;
+OG.E_End_Message = OG.shape.bpmn.E_End_Message;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.E_End_Message.prototype.createShape = function () {
+ var geom1, geom2, geom3, geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geom1 = new OG.geometry.Circle([50, 50], 50);
+ geom1.style = new OG.geometry.Style({
+ "stroke-width": 3
+ });
+
+ geom2 = new OG.geometry.PolyLine([
+ [20, 25],
+ [50, 45],
+ [80, 25],
+ [20, 25]
+ ]);
+ geom2.style = new OG.geometry.Style({
+ "fill": "black",
+ "fill-opacity": 1
+ });
+
+ geom3 = new OG.geometry.PolyLine([
+ [20, 35],
+ [20, 70],
+ [80, 70],
+ [80, 35],
+ [50, 55],
+ [20, 35]
+ ]);
+ geom3.style = new OG.geometry.Style({
+ "fill": "black",
+ "fill-opacity": 1
+ });
+
+ geomCollection.push(geom1);
+ geomCollection.push(geom2);
+ geomCollection.push(geom3);
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+ this.geom.style = new OG.geometry.Style({
+ 'label-position': 'bottom'
+ });
+
+ return this.geom;
+};
+/**
+ * BPMN : Multiple End Event Shape
+ *
+ * @class
+ * @extends OG.shape.GeomShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.E_End_Multiple = function (label) {
+ OG.shape.bpmn.E_End_Multiple.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.E_End_Multiple';
+ this.label = label;
+};
+OG.shape.bpmn.E_End_Multiple.prototype = new OG.shape.bpmn.Event();
+OG.shape.bpmn.E_End_Multiple.superclass = OG.shape.bpmn.Event;
+OG.shape.bpmn.E_End_Multiple.prototype.constructor = OG.shape.bpmn.E_End_Multiple;
+OG.E_End_Multiple = OG.shape.bpmn.E_End_Multiple;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.E_End_Multiple.prototype.createShape = function () {
+ var geom1, geom2, geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geom1 = new OG.geometry.Circle([50, 50], 50);
+ geom1.style = new OG.geometry.Style({
+ "stroke-width": 3
+ });
+
+ geom2 = new OG.geometry.Polygon([
+ [50, 15],
+ [39, 33],
+ [20, 33],
+ [29, 50],
+ [19, 67],
+ [40, 67],
+ [50, 85],
+ [60, 68],
+ [80, 68],
+ [70, 50],
+ [79, 33],
+ [60, 33]
+ ]);
+ geom2.style = new OG.geometry.Style({
+ "fill": "black",
+ "fill-opacity": 1
+ });
+
+ geomCollection.push(geom1);
+ geomCollection.push(geom2);
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+ this.geom.style = new OG.geometry.Style({
+ 'label-position': 'bottom'
+ });
+
+ return this.geom;
+};
+/**
+ * BPMN : Compensation Intermediate Event Shape
+ *
+ * @class
+ * @extends OG.shape.GeomShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.E_Intermediate_Compensation = function (label) {
+ OG.shape.bpmn.E_Intermediate_Compensation.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.E_Intermediate_Compensation';
+ this.label = label;
+ this.selectable = true;
+ this.movable = true;
+};
+OG.shape.bpmn.E_Intermediate_Compensation.prototype = new OG.shape.bpmn.Event();
+OG.shape.bpmn.E_Intermediate_Compensation.superclass = OG.shape.bpmn.Event;
+OG.shape.bpmn.E_Intermediate_Compensation.prototype.constructor = OG.shape.bpmn.E_Intermediate_Compensation;
+OG.E_Intermediate_Compensation = OG.shape.bpmn.E_Intermediate_Compensation;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.E_Intermediate_Compensation.prototype.createShape = function () {
+ var geom1, geom2, geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geom1 = new OG.geometry.Polygon([
+ [15, 50],
+ [45, 70],
+ [45, 30]
+ ]);
+
+ geom2 = new OG.geometry.Polygon([
+ [45, 50],
+ [75, 70],
+ [75, 30]
+ ]);
+
+ geomCollection.push(new OG.geometry.Circle([50, 50], 50));
+ geomCollection.push(new OG.geometry.Circle([50, 50], 40));
+ geomCollection.push(geom1);
+ geomCollection.push(geom2);
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+ this.geom.style = new OG.geometry.Style({
+ 'label-position': 'bottom',
+ "stroke": "#969149",
+ "stroke-width": 1.5,
+ fill: "white",
+ "fill-opacity": 1
+ });
+
+ return this.geom;
+};
+/**
+ * BPMN : Error Intermediate Event Shape
+ *
+ * @class
+ * @extends OG.shape.GeomShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.E_Intermediate_Error = function (label) {
+ OG.shape.bpmn.E_Intermediate_Error.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.E_Intermediate_Error';
+ this.label = label;
+};
+OG.shape.bpmn.E_Intermediate_Error.prototype = new OG.shape.bpmn.Event();
+OG.shape.bpmn.E_Intermediate_Error.superclass = OG.shape.bpmn.Event;
+OG.shape.bpmn.E_Intermediate_Error.prototype.constructor = OG.shape.bpmn.E_Intermediate_Error;
+OG.E_Intermediate_Error = OG.shape.bpmn.E_Intermediate_Error;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.E_Intermediate_Error.prototype.createShape = function () {
+ var geom1, geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geom1 = new OG.geometry.Polygon([
+ [20, 75],
+ [40, 30],
+ [60, 60],
+ [80, 20],
+ [60, 75],
+ [40, 50]
+ ]);
+ geom1.style = new OG.geometry.Style({
+ fill: "#ffffff"
+ });
+
+ geomCollection.push(new OG.geometry.Circle([50, 50], 50));
+ geomCollection.push(new OG.geometry.Circle([50, 50], 42));
+ geomCollection.push(geom1);
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+ this.geom.style = new OG.geometry.Style({
+ 'label-position': 'bottom',
+ //"stroke" : "#969149",
+ "stroke-width": 1.5,
+ fill: "white",
+ "fill-opacity": 1
+ });
+
+ return this.geom;
+};
+OG.shape.bpmn.E_Intermediate_Escalation = function (label) {
+ OG.shape.bpmn.E_Intermediate_Escalation.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.E_Intermediate_Escalation';
+ this.label = label;
+};
+OG.shape.bpmn.E_Intermediate_Escalation.prototype = new OG.shape.bpmn.Event();
+OG.shape.bpmn.E_Intermediate_Escalation.superclass = OG.shape.bpmn.Event;
+OG.shape.bpmn.E_Intermediate_Escalation.prototype.constructor = OG.shape.bpmn.E_Intermediate_Escalation;
+OG.E_Intermediate_Escalation = OG.shape.bpmn.E_Intermediate_Escalation;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.E_Intermediate_Escalation.prototype.createShape = function () {
+ var geom1, geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geom1 = new OG.geometry.Polygon([
+ [20, 80],
+ [50, 20],
+ [80, 80],
+ [50, 50]
+ ]);
+
+ geomCollection.push(new OG.geometry.Circle([50, 50], 50));
+ geomCollection.push(new OG.geometry.Circle([50, 50], 40));
+ geomCollection.push(geom1);
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+ this.geom.style = new OG.geometry.Style({
+ 'label-position': 'bottom',
+ // "stroke" : "#969149",
+ "stroke-width": 1.5,
+ fill: "white",
+ "fill-opacity": 1
+ });
+
+ return this.geom;
+};
+/**
+ * BPMN : Link Intermediate Event Shape
+ *
+ * @class
+ * @extends OG.shape.GeomShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.E_Intermediate_Link = function (label) {
+ OG.shape.bpmn.E_Intermediate_Link.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.E_Intermediate_Link';
+ this.label = label;
+};
+OG.shape.bpmn.E_Intermediate_Link.prototype = new OG.shape.bpmn.Event();
+OG.shape.bpmn.E_Intermediate_Link.superclass = OG.shape.bpmn.Event;
+OG.shape.bpmn.E_Intermediate_Link.prototype.constructor = OG.shape.bpmn.E_Intermediate_Link;
+OG.E_Intermediate_Link = OG.shape.bpmn.E_Intermediate_Link;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.E_Intermediate_Link.prototype.createShape = function () {
+
+ if (this.geom) {
+ return this.geom;
+ }
+ this.geom = new OG.geometry.Polygon([
+ [0, 0],
+ [80, 0],
+ [100, 50],
+ [80, 100],
+ [0, 100],
+ [20, 50]
+ ]);
+
+ return this.geom;
+};
+/**
+ * BPMN : Message Intermediate Event Shape
+ *
+ * @class
+ * @extends OG.shape.GeomShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.E_Intermediate_Message = function (label) {
+ OG.shape.bpmn.E_Intermediate_Message.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.E_Intermediate_Message';
+ this.label = label;
+};
+OG.shape.bpmn.E_Intermediate_Message.prototype = new OG.shape.bpmn.Event();
+OG.shape.bpmn.E_Intermediate_Message.superclass = OG.shape.bpmn.Event;
+OG.shape.bpmn.E_Intermediate_Message.prototype.constructor = OG.shape.bpmn.E_Intermediate_Message;
+OG.E_Intermediate_Message = OG.shape.bpmn.E_Intermediate_Message;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.E_Intermediate_Message.prototype.createShape = function () {
+ var geom1, geom2, geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geom1 = new OG.geometry.PolyLine([
+ [20, 25],
+ [50, 45],
+ [80, 25],
+ [20, 25]
+ ]);
+
+ geom2 = new OG.geometry.PolyLine([
+ [20, 35],
+ [20, 70],
+ [80, 70],
+ [80, 35],
+ [50, 55],
+ [20, 35]
+ ]);
+
+
+ geomCollection.push(new OG.geometry.Circle([50, 50], 50));
+ geomCollection.push(new OG.geometry.Circle([50, 50], 45));
+ geomCollection.push(geom1);
+ geomCollection.push(geom2);
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+ this.geom.style = new OG.geometry.Style({
+ 'label-position': 'bottom',
+ "stroke": "black",
+ "stroke-width": 1,
+ fill: "white",
+ "fill-opacity": 1
+ });
+
+ return this.geom;
+};
+OG.shape.bpmn.E_Intermediate_MessageFill = function (label) {
+ OG.shape.bpmn.E_Intermediate_MessageFill.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.E_Intermediate_MessageFill';
+ this.label = label;
+};
+OG.shape.bpmn.E_Intermediate_MessageFill.prototype = new OG.shape.bpmn.Event();
+OG.shape.bpmn.E_Intermediate_MessageFill.superclass = OG.shape.bpmn.Event;
+OG.shape.bpmn.E_Intermediate_MessageFill.prototype.constructor = OG.shape.bpmn.E_Intermediate_MessageFill;
+OG.E_Intermediate_MessageFill = OG.shape.bpmn.E_Intermediate_MessageFill;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.E_Intermediate_MessageFill.prototype.createShape = function () {
+ var geom1, geom2, geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geom1 = new OG.geometry.PolyLine([
+ [20, 25],
+ [50, 45],
+ [80, 25],
+ [20, 25]
+ ]);
+ geom1.style = new OG.geometry.Style({
+ "fill": "black",
+ "fill-opacity": 1
+ });
+
+ geom2 = new OG.geometry.PolyLine([
+ [20, 35],
+ [20, 70],
+ [80, 70],
+ [80, 35],
+ [50, 55],
+ [20, 35]
+ ]);
+ geom2.style = new OG.geometry.Style({
+ "fill": "black",
+ "fill-opacity": 1
+ });
+
+
+ geomCollection.push(new OG.geometry.Circle([50, 50], 50));
+ geomCollection.push(new OG.geometry.Circle([50, 50], 45));
+ geomCollection.push(geom1);
+ geomCollection.push(geom2);
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+ this.geom.style = new OG.geometry.Style({
+ 'label-position': 'bottom',
+ "stroke": "black",
+ "stroke-width": 1,
+ fill: "white",
+ "fill-opacity": 1
+ });
+
+ return this.geom;
+};
+/**
+ * BPMN : Multiple Intermediate Event Shape
+ *
+ * @class
+ * @extends OG.shape.GeomShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.E_Intermediate_Multiple = function (label) {
+ OG.shape.bpmn.E_Intermediate_Multiple.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.E_Intermediate_Multiple';
+ this.label = label;
+};
+OG.shape.bpmn.E_Intermediate_Multiple.prototype = new OG.shape.bpmn.Event();
+OG.shape.bpmn.E_Intermediate_Multiple.superclass = OG.shape.bpmn.Event;
+OG.shape.bpmn.E_Intermediate_Multiple.prototype.constructor = OG.shape.bpmn.E_Intermediate_Multiple;
+OG.E_Intermediate_Multiple = OG.shape.bpmn.E_Intermediate_Multiple;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.E_Intermediate_Multiple.prototype.createShape = function () {
+ var geom1, geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geom1 = new OG.geometry.Polygon([
+ [20, 50],
+ [50, 20],
+ [80, 50],
+ [65, 75],
+ [35, 75]
+ ]);
+
+ geomCollection.push(new OG.geometry.Circle([50, 50], 50));
+ geomCollection.push(new OG.geometry.Circle([50, 50], 40));
+ geomCollection.push(geom1);
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+ this.geom.style = new OG.geometry.Style({
+ 'label-position': 'bottom',
+ "stroke": "#969149",
+ "stroke-width": 1.5,
+ fill: "white",
+ "fill-opacity": 1
+ });
+
+ return this.geom;
+};
+/**
+ * BPMN : Rule Intermediate Event Shape
+ *
+ * @class
+ * @extends OG.shape.GeomShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.E_Intermediate_Rule = function (label) {
+ OG.shape.bpmn.E_Intermediate_Rule.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.E_Intermediate_Rule';
+ this.label = label;
+};
+OG.shape.bpmn.E_Intermediate_Rule.prototype = new OG.shape.bpmn.Event();
+OG.shape.bpmn.E_Intermediate_Rule.superclass = OG.shape.bpmn.Event;
+OG.shape.bpmn.E_Intermediate_Rule.prototype.constructor = OG.shape.bpmn.E_Intermediate_Rule;
+OG.E_Intermediate_Rule = OG.shape.bpmn.E_Intermediate_Rule;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.E_Intermediate_Rule.prototype.createShape = function () {
+ var geom1, geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geom1 = new OG.geometry.Rectangle([25, 20], 50, 60);
+
+ geomCollection.push(new OG.geometry.Circle([50, 50], 50));
+ geomCollection.push(new OG.geometry.Circle([50, 50], 42));
+ geomCollection.push(geom1);
+ geomCollection.push(new OG.geometry.Line([30, 30], [70, 30]));
+ geomCollection.push(new OG.geometry.Line([30, 45], [70, 45]));
+ geomCollection.push(new OG.geometry.Line([30, 60], [70, 60]));
+ geomCollection.push(new OG.geometry.Line([30, 70], [70, 70]));
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+ this.geom.style = new OG.geometry.Style({
+ 'label-position': 'bottom',
+ "stroke": "black",
+ "stroke-width": 1.5,
+ fill: "white",
+ "fill-opacity": 1
+ });
+
+ return this.geom;
+};
+/**
+ * BPMN : Timer Intermediate Event Shape
+ *
+ * @class
+ * @extends OG.shape.GeomShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.E_Intermediate_Timer = function (label) {
+ OG.shape.bpmn.E_Intermediate_Timer.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.E_Intermediate_Timer';
+ this.label = label;
+};
+OG.shape.bpmn.E_Intermediate_Timer.prototype = new OG.shape.bpmn.Event();
+OG.shape.bpmn.E_Intermediate_Timer.superclass = OG.shape.bpmn.Event;
+OG.shape.bpmn.E_Intermediate_Timer.prototype.constructor = OG.shape.bpmn.E_Intermediate_Timer;
+OG.E_Intermediate_Timer = OG.shape.bpmn.E_Intermediate_Timer;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.E_Intermediate_Timer.prototype.createShape = function () {
+ var geom1, geom2, geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geom1 = new OG.geometry.Circle([50, 50], 32);
+
+ geom2 = new OG.geometry.PolyLine([
+ [50, 30],
+ [50, 50],
+ [70, 50]
+ ]);
+
+ geomCollection.push(new OG.geometry.Circle([50, 50], 50));
+ geomCollection.push(new OG.geometry.Circle([50, 50], 42));
+ geomCollection.push(geom1);
+ geomCollection.push(new OG.geometry.Line([50, 18], [50, 25]));
+ geomCollection.push(new OG.geometry.Line([50, 82], [50, 75]));
+ geomCollection.push(new OG.geometry.Line([18, 50], [25, 50]));
+ geomCollection.push(new OG.geometry.Line([82, 50], [75, 50]));
+ geomCollection.push(geom2);
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+ this.geom.style = new OG.geometry.Style({
+ 'label-position': 'bottom',
+ //"stroke" : "#969149",
+ "stroke": "black",
+ "stroke-width": 1.5,
+ fill: "white",
+ "fill-opacity": 1
+ });
+
+ return this.geom;
+};
+/**
+ * BPMN : Link Start Event Shape
+ *
+ * @class
+ * @extends OG.shape.GeomShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.E_Start_Connector = function (label) {
+ OG.shape.bpmn.E_Start_Connector.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.E_Start_Connector';
+ this.label = label;
+
+};
+OG.shape.bpmn.E_Start_Connector.prototype = new OG.shape.bpmn.Event();
+OG.shape.bpmn.E_Start_Connector.superclass = OG.shape.bpmn.Event;
+OG.shape.bpmn.E_Start_Connector.prototype.constructor = OG.shape.bpmn.E_Start_Connector;
+OG.E_Start_Connector = OG.shape.bpmn.E_Start_Connector;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.E_Start_Connector.prototype.createShape = function () {
+ var geom1, geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geom1 = new OG.geometry.Polygon([
+ [20, 34],
+ [20, 65],
+ [60, 65],
+ [60, 80],
+ [85, 50],
+ [60, 20],
+ [60, 34]
+ ]);
+
+ geomCollection.push(new OG.geometry.Circle([50, 50], 50));
+ geomCollection.push(new OG.geometry.Circle([50, 50], 42));
+ geomCollection.push(geom1);
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+ this.geom.style = new OG.geometry.Style({
+ 'label-position': 'bottom'
+ });
+
+ return this.geom;
+};
+/**
+ * BPMN : Error Start Event Shape
+ *
+ * @class
+ * @extends OG.shape.GeomShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.E_Start_Error = function (label) {
+ OG.shape.bpmn.E_Start_Error.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.E_Start_Error';
+ this.label = label;
+};
+OG.shape.bpmn.E_Start_Error.prototype = new OG.shape.bpmn.Event();
+OG.shape.bpmn.E_Start_Error.superclass = OG.shape.bpmn.Event;
+OG.shape.bpmn.E_Start_Error.prototype.constructor = OG.shape.bpmn.E_Start_Error;
+OG.E_Start_Error = OG.shape.bpmn.E_Start_Error;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.E_Start_Error.prototype.createShape = function () {
+ var geom1, geom2, geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geom1 = new OG.geometry.Circle([50, 50], 50);
+
+ geom2 = new OG.geometry.Polygon([
+ [20, 75],
+ [40, 30],
+ [60, 60],
+ [80, 20],
+ [60, 75],
+ [40, 50]
+ ]);
+
+ geomCollection.push(geom1);
+ geomCollection.push(geom2);
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+ this.geom.style = new OG.geometry.Style({
+ 'label-position': 'bottom',
+ "stroke-width": 1.5
+ });
+
+ return this.geom;
+};
+/**
+ * BPMN : Link Start Event Shape
+ *
+ * @class
+ * @extends OG.shape.GeomShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.E_Start_Link = function (label) {
+ OG.shape.bpmn.E_Start_Link.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.E_Start_Link';
+ this.label = label;
+};
+OG.shape.bpmn.E_Start_Link.prototype = new OG.shape.bpmn.Event();
+OG.shape.bpmn.E_Start_Link.superclass = OG.shape.bpmn.Event;
+OG.shape.bpmn.E_Start_Link.prototype.constructor = OG.shape.bpmn.E_Start_Link;
+OG.E_Start_Link = OG.shape.bpmn.E_Start_Link;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.E_Start_Link.prototype.createShape = function () {
+ var geom1, geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geom1 = new OG.geometry.Polygon([
+ [20, 35],
+ [20, 65],
+ [60, 65],
+ [60, 80],
+ [85, 50],
+ [60, 20],
+ [60, 35]
+ ]);
+
+ geomCollection.push(new OG.geometry.Circle([50, 50], 50));
+ geomCollection.push(new OG.geometry.Circle([50, 50], 42));
+ geomCollection.push(geom1);
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+ this.geom.style = new OG.geometry.Style({
+ 'label-position': 'bottom'
+ });
+
+ return this.geom;
+};
+/**
+ * BPMN : Message Start Event Shape
+ *
+ * @class
+ * @extends OG.shape.GeomShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.E_Start_Message = function (label) {
+ OG.shape.bpmn.E_Start_Message.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.E_Start_Message';
+ this.label = label;
+};
+OG.shape.bpmn.E_Start_Message.prototype = new OG.shape.bpmn.Event();
+OG.shape.bpmn.E_Start_Message.superclass = OG.shape.bpmn.Event;
+OG.shape.bpmn.E_Start_Message.prototype.constructor = OG.shape.bpmn.E_Start_Message;
+OG.E_Start_Message = OG.shape.bpmn.E_Start_Message;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.E_Start_Message.prototype.createShape = function () {
+ var geom1, geom2, geom3, geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geom1 = new OG.geometry.Circle([50, 50], 50);
+ geom1.style = new OG.geometry.Style({
+ "stroke-width": 1.5
+ });
+
+ geom2 = new OG.geometry.PolyLine([
+ [20, 25],
+ [50, 45],
+ [80, 25],
+ [20, 25]
+ ]);
+
+ geom3 = new OG.geometry.PolyLine([
+ [20, 35],
+ [20, 70],
+ [80, 70],
+ [80, 35],
+ [50, 55],
+ [20, 35]
+ ]);
+
+ geomCollection.push(geom1);
+ geomCollection.push(geom2);
+ geomCollection.push(geom3);
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+ this.geom.style = new OG.geometry.Style({
+ 'label-position': 'bottom'
+ });
+
+ return this.geom;
+};
+/**
+ * BPMN : Multiple Start Event Shape
+ *
+ * @class
+ * @extends OG.shape.GeomShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.E_Start_Multiple = function (label) {
+ OG.shape.bpmn.E_Start_Multiple.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.E_Start_Multiple';
+ this.label = label;
+};
+OG.shape.bpmn.E_Start_Multiple.prototype = new OG.shape.bpmn.Event();
+OG.shape.bpmn.E_Start_Multiple.superclass = OG.shape.bpmn.Event;
+OG.shape.bpmn.E_Start_Multiple.prototype.constructor = OG.shape.bpmn.E_Start_Multiple;
+OG.E_Start_Multiple = OG.shape.bpmn.E_Start_Multiple;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.E_Start_Multiple.prototype.createShape = function () {
+ var geom1, geom2, geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geom1 = new OG.geometry.Circle([50, 50], 50);
+
+ geom2 = new OG.geometry.Polygon([
+ [50, 15],
+ [39, 33],
+ [20, 33],
+ [29, 50],
+ [19, 67],
+ [40, 67],
+ [50, 85],
+ [60, 68],
+ [80, 68],
+ [70, 50],
+ [79, 33],
+ [60, 33]
+ ]);
+
+ geomCollection.push(geom1);
+ geomCollection.push(geom2);
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+ this.geom.style = new OG.geometry.Style({
+ 'label-position': 'bottom'
+ });
+
+ return this.geom;
+};
+/**
+ * BPMN : Rule Start Event Shape
+ *
+ * @class
+ * @extends OG.shape.GeomShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.E_Start_Rule = function (label) {
+ OG.shape.bpmn.E_Start_Rule.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.E_Start_Rule';
+ this.label = label;
+};
+OG.shape.bpmn.E_Start_Rule.prototype = new OG.shape.bpmn.Event();
+OG.shape.bpmn.E_Start_Rule.superclass = OG.shape.bpmn.Event;
+OG.shape.bpmn.E_Start_Rule.prototype.constructor = OG.shape.bpmn.E_Start_Rule;
+OG.E_Start_Rule = OG.shape.bpmn.E_Start_Rule;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.E_Start_Rule.prototype.createShape = function () {
+ var geom1, geom2, geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geom1 = new OG.geometry.Circle([50, 50], 50);
+ geom1.style = new OG.geometry.Style({
+ "stroke-width": 1.5
+ });
+
+ geom2 = new OG.geometry.Rectangle([25, 20], 50, 60);
+
+ geomCollection.push(geom1);
+ geomCollection.push(geom2);
+ geomCollection.push(new OG.geometry.Line([30, 30], [70, 30]));
+ geomCollection.push(new OG.geometry.Line([30, 45], [70, 45]));
+ geomCollection.push(new OG.geometry.Line([30, 60], [70, 60]));
+ geomCollection.push(new OG.geometry.Line([30, 70], [70, 70]));
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+ this.geom.style = new OG.geometry.Style({
+ 'label-position': 'bottom'
+ });
+
+ return this.geom;
+};
+/**
+ * BPMN : Timer Start Event Shape
+ *
+ * @class
+ * @extends OG.shape.GeomShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.E_Start_Timer = function (label) {
+ OG.shape.bpmn.E_Start_Timer.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.E_Start_Timer';
+ this.label = label;
+};
+OG.shape.bpmn.E_Start_Timer.prototype = new OG.shape.bpmn.Event();
+OG.shape.bpmn.E_Start_Timer.superclass = OG.shape.bpmn.Event;
+OG.shape.bpmn.E_Start_Timer.prototype.constructor = OG.shape.bpmn.E_Start_Timer;
+OG.E_Start_Timer = OG.shape.bpmn.E_Start_Timer;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.E_Start_Timer.prototype.createShape = function () {
+ var geom1, geom2, geom3, geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geom1 = new OG.geometry.Circle([50, 50], 50);
+ geom1.style = new OG.geometry.Style({
+ "stroke-width": 1.5
+ });
+
+ geom2 = new OG.geometry.Circle([50, 50], 32);
+
+ geom3 = new OG.geometry.PolyLine([
+ [50, 30],
+ [50, 50],
+ [70, 50]
+ ]);
+
+ geomCollection.push(geom1);
+ geomCollection.push(geom2);
+ geomCollection.push(new OG.geometry.Line([50, 18], [50, 25]));
+ geomCollection.push(new OG.geometry.Line([50, 82], [50, 75]));
+ geomCollection.push(new OG.geometry.Line([18, 50], [25, 50]));
+ geomCollection.push(new OG.geometry.Line([82, 50], [75, 50]));
+ geomCollection.push(geom3);
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+ this.geom.style = new OG.geometry.Style({
+ 'label-position': 'bottom'
+ });
+
+ return this.geom;
+};
+/**
+ * BPMN : Terminate Event Shape
+ *
+ * @class
+ * @extends OG.shape.bpmn.E_End
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.E_Terminate = function (label) {
+ OG.shape.bpmn.E_Terminate.superclass.call(this, label);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.E_Terminate';
+};
+OG.shape.bpmn.E_Terminate.prototype = new OG.shape.bpmn.E_End();
+OG.shape.bpmn.E_Terminate.superclass = OG.shape.bpmn.E_End;
+OG.shape.bpmn.E_Terminate.prototype.constructor = OG.shape.bpmn.E_Terminate;
+OG.E_Terminate = OG.shape.bpmn.E_Terminate;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.E_Terminate.prototype.createShape = function () {
+ var geom1, geom2, geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geom1 = new OG.geometry.Circle([50, 50], 50);
+ geom1.style = new OG.geometry.Style({
+ "stroke-width": 3
+ });
+
+ geom2 = new OG.geometry.Circle([50, 50], 30);
+ geom2.style = new OG.geometry.Style({
+ "fill": "black",
+ "fill-opacity": 1
+ });
+
+ geomCollection.push(geom1);
+ geomCollection.push(geom2);
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+ this.geom.style = new OG.geometry.Style({
+ 'label-position': 'bottom'
+ });
+
+ return this.geom;
+};
+/**
+ * BPMN : Complex Gateway Shape
+ *
+ * @class
+ * @extends OG.shape.GeomShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.G_Complex = function (label) {
+ OG.shape.bpmn.G_Complex.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.G_Complex';
+ this.label = label;
+};
+OG.shape.bpmn.G_Complex.prototype = new OG.shape.bpmn.G_Gateway();
+OG.shape.bpmn.G_Complex.superclass = OG.shape.bpmn.G_Gateway;
+OG.shape.bpmn.G_Complex.prototype.constructor = OG.shape.bpmn.G_Complex;
+OG.G_Complex = OG.shape.bpmn.G_Complex;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.G_Complex.prototype.createShape = function () {
+ var geom1, geom2, geom3, geom4, geom5, geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geom1 = new OG.geometry.Polygon([
+ [0, 50],
+ [50, 100],
+ [100, 50],
+ [50, 0]
+ ]);
+
+ geom2 = new OG.geometry.Line([30, 30], [70, 70]);
+ geom2.style = new OG.geometry.Style({
+ "stroke-width": 3
+ });
+
+ geom3 = new OG.geometry.Line([30, 70], [70, 30]);
+ geom3.style = new OG.geometry.Style({
+ "stroke-width": 3
+ });
+
+ geom4 = new OG.geometry.Line([20, 50], [80, 50]);
+ geom4.style = new OG.geometry.Style({
+ "stroke-width": 3
+ });
+
+ geom5 = new OG.geometry.Line([50, 20], [50, 80]);
+ geom5.style = new OG.geometry.Style({
+ "stroke-width": 3
+ });
+
+ geomCollection.push(geom1);
+ geomCollection.push(geom2);
+ geomCollection.push(geom3);
+ geomCollection.push(geom4);
+ geomCollection.push(geom5);
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+
+ return this.geom;
+};
+/**
+ * BPMN : Exclusive Gateway Shape
+ *
+ * @class
+ * @extends OG.shape.GeomShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.G_Exclusive = function (label) {
+ OG.shape.bpmn.G_Exclusive.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.G_Exclusive';
+ this.label = label;
+};
+OG.shape.bpmn.G_Exclusive.prototype = new OG.shape.bpmn.G_Gateway();
+OG.shape.bpmn.G_Exclusive.superclass = OG.shape.bpmn.G_Gateway;
+OG.shape.bpmn.G_Exclusive.prototype.constructor = OG.shape.bpmn.G_Exclusive;
+OG.G_Exclusive = OG.shape.bpmn.G_Exclusive;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.G_Exclusive.prototype.createShape = function () {
+ var geom1, geom2, geom3, geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geom1 = new OG.geometry.Polygon([
+ [0, 50],
+ [50, 100],
+ [100, 50],
+ [50, 0]
+ ]);
+
+ geom2 = new OG.geometry.Line([30, 30], [70, 70]);
+ geom2.style = new OG.geometry.Style({
+ "stroke-width": 5
+ });
+
+ geom3 = new OG.geometry.Line([30, 70], [70, 30]);
+ geom3.style = new OG.geometry.Style({
+ "stroke-width": 5
+ });
+
+ geomCollection.push(geom1);
+ geomCollection.push(geom2);
+ geomCollection.push(geom3);
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+
+ return this.geom;
+};
+/**
+ * BPMN : Inclusive Gateway Shape
+ *
+ * @class
+ * @extends OG.shape.GeomShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.G_Inclusive = function (label) {
+ OG.shape.bpmn.G_Inclusive.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.G_Inclusive';
+ this.label = label;
+};
+OG.shape.bpmn.G_Inclusive.prototype = new OG.shape.bpmn.G_Gateway();
+OG.shape.bpmn.G_Inclusive.superclass = OG.shape.bpmn.G_Gateway;
+OG.shape.bpmn.G_Inclusive.prototype.constructor = OG.shape.bpmn.G_Inclusive;
+OG.G_Inclusive = OG.shape.bpmn.G_Inclusive;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.G_Inclusive.prototype.createShape = function () {
+ var geom1, geom2, geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geom1 = new OG.geometry.Polygon([
+ [0, 50],
+ [50, 100],
+ [100, 50],
+ [50, 0]
+ ]);
+
+ geom2 = new OG.geometry.Circle([50, 50], 25);
+ geom2.style = new OG.geometry.Style({
+ "stroke-width": 3
+ });
+
+ geomCollection.push(geom1);
+ geomCollection.push(geom2);
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+
+ return this.geom;
+};
+/**
+ * BPMN : Parallel Gateway Shape
+ *
+ * @class
+ * @extends OG.shape.GeomShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.G_Parallel = function (label) {
+ OG.shape.bpmn.G_Parallel.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.G_Parallel';
+ this.label = label;
+};
+OG.shape.bpmn.G_Parallel.prototype = new OG.shape.bpmn.G_Gateway();
+OG.shape.bpmn.G_Parallel.superclass = OG.shape.bpmn.G_Gateway;
+OG.shape.bpmn.G_Parallel.prototype.constructor = OG.shape.bpmn.G_Parallel;
+OG.G_Parallel = OG.shape.bpmn.G_Parallel;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.G_Parallel.prototype.createShape = function () {
+ var geom1, geom2, geom3, geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geom1 = new OG.geometry.Polygon([
+ [0, 50],
+ [50, 100],
+ [100, 50],
+ [50, 0]
+ ]);
+
+ geom2 = new OG.geometry.Line([20, 50], [80, 50]);
+ geom2.style = new OG.geometry.Style({
+ "stroke-width": 5
+ });
+
+ geom3 = new OG.geometry.Line([50, 20], [50, 80]);
+ geom3.style = new OG.geometry.Style({
+ "stroke-width": 5
+ });
+
+ geomCollection.push(geom1);
+ geomCollection.push(geom2);
+ geomCollection.push(geom3);
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+
+ return this.geom;
+};
+OG.shape.bpmn.MIParallel = function (label) {
+ OG.shape.bpmn.MIParallel.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.MIParallel';
+ this.label = label;
+};
+OG.shape.bpmn.MIParallel.prototype = new OG.shape.GeomShape();
+OG.shape.bpmn.MIParallel.superclass = OG.shape.GeomShape;
+OG.shape.bpmn.MIParallel.prototype.constructor = OG.shape.bpmn.MIParallel;
+OG.MIParallel = OG.shape.bpmn.MIParallel;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.MIParallel.prototype.createShape = function () {
+ var geom1, geom2, geom3, geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geom1 = new OG.geometry.Line([0, 30], [0, 0]);
+ geom2 = new OG.geometry.Line([15, 30], [15, 0]);
+ geom3 = new OG.geometry.Line([30, 30], [30, 0]);
+
+ geomCollection.push(geom1);
+ geomCollection.push(geom2);
+ geomCollection.push(geom3);
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+ return this.geom;
+};
+OG.shape.bpmn.MISequential = function (label) {
+ OG.shape.bpmn.MISequential.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.MISequential';
+ this.label = label;
+};
+OG.shape.bpmn.MISequential.prototype = new OG.shape.GeomShape();
+OG.shape.bpmn.MISequential.superclass = OG.shape.GeomShape;
+OG.shape.bpmn.MISequential.prototype.constructor = OG.shape.bpmn.MISequential;
+OG.MISequential = OG.shape.bpmn.MISequential;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.MISequential.prototype.createShape = function () {
+ var geom1, geom2, geom3, geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geom1 = new OG.geometry.Line([0, 30], [30, 30]);
+ geom2 = new OG.geometry.Line([0, 15], [30, 15]);
+ geom3 = new OG.geometry.Line([0, 0], [30, 0]);
+
+ geomCollection.push(geom1);
+ geomCollection.push(geom2);
+ geomCollection.push(geom3);
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+ return this.geom;
+};
+OG.shape.bpmn.M_Annotation = function (label) {
+ OG.shape.bpmn.M_Annotation.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.M_Annotation';
+ this.label = label;
+};
+OG.shape.bpmn.M_Annotation.prototype = new OG.shape.GeomShape();
+OG.shape.bpmn.M_Annotation.superclass = OG.shape.GeomShape;
+OG.shape.bpmn.M_Annotation.prototype.constructor = OG.shape.bpmn.M_Annotation;
+OG.M_Annotation = OG.shape.bpmn.M_Annotation;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.M_Annotation.prototype.createShape = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ var geom1, geom2, geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geom1 = new OG.geometry.Rectangle([0, 0], 100, 100);
+ geom1.style = new OG.geometry.Style({
+ "stroke": 'none'
+ });
+
+ geom2 = new OG.geometry.PolyLine([
+ [10, 0],
+ [0, 0],
+ [0, 100],
+ [10, 100]
+ ]);
+ geom2.style = new OG.geometry.Style({
+ "stroke": 'black'
+ });
+
+ geomCollection.push(geom1);
+ geomCollection.push(geom2);
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+ this.geom.style = new OG.geometry.Style({});
+
+ return this.geom;
+};
+/**
+ * BPMN : Group Shape
+ *
+ * @class
+ * @extends OG.shape.GeomShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.M_Group = function (label) {
+ OG.shape.bpmn.M_Group.superclass.call(this);
+
+
+ this.CONNECTABLE = true;
+
+
+ this.SHAPE_ID = 'OG.shape.bpmn.M_Group';
+ this.label = label;
+};
+
+
+OG.shape.bpmn.M_Group.prototype = new OG.shape.GroupShape();
+OG.shape.bpmn.M_Group.superclass = OG.shape.GroupShape;
+OG.shape.bpmn.M_Group.prototype.constructor = OG.shape.bpmn.M_Group;
+OG.M_Group = OG.shape.bpmn.M_Group;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.M_Group.prototype.createShape = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.geometry.Rectangle([0, 0], 100, 100);
+ this.geom.style = new OG.geometry.Style({
+ //'stroke-dasharray': '- ',
+ 'r': 6,
+ 'fill': '#ffffff',
+ 'fill-opacity': 0,
+ "vertical-align": "top",
+ "text-anchor": "start"
+
+ });
+
+ return this.geom;
+};
+/**
+ * BPMN : Text Shape
+ *
+ * @class
+ * @extends OG.shape.GeomShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.M_Text = function (label) {
+ OG.shape.bpmn.M_Text.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.M_Text';
+ this.label = label || 'Text';
+ //this.SELECTABLE = false;
+ //this.CONNECTABLE = false;
+ //this.MOVABLE = false;
+};
+OG.shape.bpmn.M_Text.prototype = new OG.shape.GeomShape();
+OG.shape.bpmn.M_Text.superclass = OG.shape.GeomShape;
+OG.shape.bpmn.M_Text.prototype.constructor = OG.shape.bpmn.M_Text;
+OG.M_Text = OG.shape.bpmn.M_Text;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.M_Text.prototype.createShape = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.geometry.Rectangle([0, 0], 100, 100);
+ this.geom.style = new OG.geometry.Style({
+ stroke: "none"
+ });
+
+ return this.geom;
+};
+OG.shape.bpmn.ParallelMultiple = function (label) {
+ OG.shape.bpmn.ParallelMultiple.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.ParallelMultiple';
+ this.label = label;
+};
+OG.shape.bpmn.ParallelMultiple.prototype = new OG.shape.GeomShape();
+OG.shape.bpmn.ParallelMultiple.superclass = OG.shape.GeomShape;
+OG.shape.bpmn.ParallelMultiple.prototype.constructor = OG.shape.bpmn.ParallelMultiple;
+OG.ParallelMultiple = OG.shape.bpmn.ParallelMultiple;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.ParallelMultiple.prototype.createShape = function () {
+ var geom1, geom2, geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geom1 = new OG.geometry.Circle([50, 50], 50);
+ geom2 = new OG.geometry.Circle([50, 50], 40);
+ geom3 = new OG.Polygon([
+ [20, 40],
+ [20, 60],
+ [40, 60],
+ [40, 80],
+ [60, 80],
+ [60, 60],
+ [80, 60],
+ [80, 40],
+ [60, 40],
+ [60, 20],
+ [40, 20],
+ [40, 40]
+ ]);
+
+ geomCollection.push(geom1);
+ geomCollection.push(geom2);
+ geomCollection.push(geom3);
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+ this.geom.style = new OG.geometry.Style({
+ 'label-position': 'bottom',
+ "stroke-width": 1.5,
+ "stroke": "#969149",
+ fill: "white",
+ "fill-opacity": 1
+ });
+
+ return this.geom;
+};
+OG.shape.bpmn.ScopeActivity = function (label) {
+ OG.shape.bpmn.ScopeActivity.superclass.call(this);
+
+ this.GROUP_COLLAPSIBLE = false;
+ this.CONNECTABLE = true;
+ this.Events = [];
+
+ this.SHAPE_ID = 'OG.shape.bpmn.ScopeActivity';
+ this.label = label;
+};
+
+OG.shape.bpmn.ScopeActivity.prototype = new OG.shape.bpmn.M_Group();
+OG.shape.bpmn.ScopeActivity.superclass = OG.shape.bpmn.M_Group;
+OG.shape.bpmn.ScopeActivity.prototype.constructor = OG.shape.bpmn.ScopeActivity;
+OG.ScopeActivity = OG.shape.bpmn.ScopeActivity;
+
+
+OG.shape.bpmn.ScopeActivity.prototype.layoutChild = function () {
+ for (var event in this.Events) {
+ //TODO:
+ //var shapeOfEvent = event.shape;
+ //shapeOfEvent.x = .... ;
+ }
+}
+/**
+ * BPMN : Signal Activity Shape
+ *
+ * @class
+ * @extends OG.shape.GeomShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.bpmn.Signal = function (label) {
+ OG.shape.bpmn.Signal.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.Signal';
+ this.label = label;
+};
+OG.shape.bpmn.Signal.prototype = new OG.shape.bpmn.Event();
+OG.shape.bpmn.Signal.superclass = OG.shape.bpmn.Event;
+OG.shape.bpmn.Signal.prototype.constructor = OG.shape.bpmn.Signal;
+OG.Signal = OG.shape.bpmn.Signal;
+
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.Signal.prototype.createShape = function () {
+ var geom1, geom2, geom3, geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geom1 = new OG.geometry.Circle([50, 50], 50);
+ geom2 = new OG.geometry.Circle([50, 50], 40);
+ geom3 = new OG.Polygon([
+ [20, 75],
+ [50, 10],
+ [80, 75]
+ ]);
+
+ geomCollection.push(geom1);
+ geomCollection.push(geom2);
+ geomCollection.push(geom3);
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+ this.geom.style = new OG.geometry.Style({
+ 'label-position': 'bottom',
+ "stroke-width": 1.5,
+ "stroke": "#969149",
+ fill: "white",
+ "fill-opacity": 1
+ });
+
+ return this.geom;
+};
+OG.shape.bpmn.Value_Chain = function (label) {
+ OG.shape.bpmn.Value_Chain.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.Value_Chain';
+ this.label = label;
+ this.HaveButton = true;
+ this.LoopType = "None";
+ this.inclusion = false;
+
+};
+OG.shape.bpmn.Value_Chain.prototype = new OG.shape.GeomShape();
+OG.shape.bpmn.Value_Chain.superclass = OG.shape.GeomShape;
+OG.shape.bpmn.Value_Chain.prototype.constructor = OG.shape.bpmn.Value_Chain;
+OG.Value_Chain = OG.shape.bpmn.Value_Chain;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.Value_Chain.prototype.createShape = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.geometry.Polygon([
+ [0, 0],
+ [10, 50],
+ [0, 100],
+ [90, 100],
+ [100, 50],
+ [90, 0]
+ ]);
+
+ this.geom.style = new OG.geometry.Style({
+ fill: "#FFFFFF-#9FD7FF",
+ "fill-opacity": 1,
+ "stroke": '#9FD7FF'
+ });
+
+ return this.geom;
+};
+
+OG.shape.bpmn.Value_Chain.prototype.createSubShape = function () {
+ this.sub = [];
+
+ if (this.inclusion) {
+ this.sub.push({
+ shape: new OG.ImageShape(this.currentCanvas._CONFIG.IMAGE_BASE + 'complete.png'),
+ width: '20px',
+ height: '20px',
+ right: '0px',
+ bottom: '20px',
+ style: {}
+ })
+ }
+
+ if (this.HaveButton) {
+ this.sub.push({
+ shape: new OG.ImageShape(this.currentCanvas._CONFIG.IMAGE_BASE + 'subprocess.png'),
+ width: '20px',
+ height: '20px',
+ align: 'center',
+ bottom: '5px',
+ style: {
+ "stroke-width": 1,
+ fill: "white",
+ "fill-opacity": 0,
+ "shape-rendering": "crispEdges"
+ }
+ })
+ }
+
+ return this.sub;
+};
+
+OG.shape.bpmn.Value_Chain.prototype.createContextMenu = function () {
+ var me = this;
+ this.contextMenu = {
+ 'delete': true,
+ 'copy': true,
+ 'format': true,
+ 'text': true,
+ 'bringToFront': true,
+ 'sendToBack': true,
+ 'property': {
+ name: '속성', callback: function () {
+ me.currentCanvas.getEventHandler().showProperty();
+ }
+ }
+ };
+ return this.contextMenu;
+};
+OG.shape.bpmn.Value_Chain_Module = function (label) {
+ OG.shape.bpmn.Value_Chain_Module.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.bpmn.Value_Chain_Module';
+ this.label = label;
+ this.HaveButton = true;
+ this.LoopType = "None";
+ this.inclusion = false;
+
+};
+OG.shape.bpmn.Value_Chain_Module.prototype = new OG.shape.bpmn.Value_Chain();
+OG.shape.bpmn.Value_Chain_Module.superclass = OG.shape.bpmn.Value_Chain;
+OG.shape.bpmn.Value_Chain_Module.prototype.constructor = OG.shape.bpmn.Value_Chain_Module;
+OG.Value_Chain_Module = OG.shape.bpmn.Value_Chain_Module;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.bpmn.Value_Chain_Module.prototype.createShape = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.geometry.Polygon([
+ [0, 0],
+ [0, 100],
+ [90, 100],
+ [100, 50],
+ [90, 0]
+ ]);
+
+ this.geom.style = new OG.geometry.Style({
+ fill: "#FFFFFF-#9FD7FF",
+ "fill-opacity": 1,
+ "stroke": '#9FD7FF'
+ });
+
+ return this.geom;
+};
+
+OG.shape.bpmn.Value_Chain.prototype.createSubShape = function () {
+ this.sub = [];
+
+ if (this.inclusion) {
+ this.sub.push({
+ shape: new OG.ImageShape(this.currentCanvas._CONFIG.IMAGE_BASE + 'complete.png'),
+ width: '20px',
+ height: '20px',
+ right: '0px',
+ bottom: '20px',
+ style: {}
+ })
+ }
+
+ if (this.HaveButton) {
+ this.sub.push({
+ shape: new OG.ImageShape(this.currentCanvas._CONFIG.IMAGE_BASE + 'subprocess.png'),
+ width: '20px',
+ height: '20px',
+ align: 'center',
+ bottom: '5px',
+ style: {
+ "stroke-width": 1,
+ fill: "white",
+ "fill-opacity": 0,
+ "shape-rendering": "crispEdges"
+ }
+ })
+ }
+
+ return this.sub;
+};
+/**
+ * ELECTRONIC : Wire Shape
+ *
+ * @class
+ * @extends OG.shape.WireShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {Number[]} from 와이어 시작 좌표
+ * @param {Number[]} to 와이어 끝 좌표
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.elec.WireShape = function (from, to, label) {
+ OG.shape.elec.WireShape.superclass.call(this, from, to, label);
+
+ this.SHAPE_ID = 'OG.shape.elec.WireShape';
+};
+OG.shape.elec.WireShape.prototype = new OG.shape.EdgeShape();
+OG.shape.elec.WireShape.superclass = OG.shape.EdgeShape;
+OG.shape.elec.WireShape.prototype.constructor = OG.shape.elec.WireShape;
+OG.WireShape = OG.shape.elec.WireShape;
+
+OG.shape.elec.WireShape.prototype.createContextMenu = function () {
+ var me = this;
+ this.contextMenu = {
+ 'delete': true,
+ 'format': true,
+ 'text': true,
+ 'bringToFront': true,
+ 'sendToBack': true,
+ 'property': {
+ name: '정보보기', callback: function () {
+ $(me.currentCanvas.getRootElement()).trigger('showProperty', [me.currentElement]);
+ }
+ },
+ 'changeShape': {
+ name: '변경',
+ items: {
+ 'Wire': {
+ name: 'Cable',
+ type: 'radio',
+ radio: 'changeShape',
+ value: 'OG.shape.elec.CableShape',
+ events: {
+ change: function (e) {
+ me.currentCanvas.getEventHandler().changeShape(e.target.value, null);
+ }
+ }
+ },
+ 'IPB': {
+ name: 'IPB',
+ type: 'radio',
+ radio: 'changeShape',
+ value: 'OG.shape.elec.BusductShape',
+ events: {
+ change: function (e) {
+ me.currentCanvas.getEventHandler().changeShape(e.target.value, 'IPB');
+ }
+ }
+ },
+ 'SPB': {
+ name: 'SPB',
+ type: 'radio',
+ radio: 'changeShape',
+ value: 'OG.shape.elec.BusductShape',
+ events: {
+ change: function (e) {
+ me.currentCanvas.getEventHandler().changeShape(e.target.value, 'SPB');
+ }
+ }
+ },
+ 'NSPB': {
+ name: 'NSPB',
+ type: 'radio',
+ radio: 'changeShape',
+ value: 'OG.shape.elec.BusductShape',
+ events: {
+ change: function (e) {
+ me.currentCanvas.getEventHandler().changeShape(e.target.value, 'NSPB');
+ }
+ }
+ },
+ 'CRB': {
+ name: 'CRB',
+ type: 'radio',
+ radio: 'changeShape',
+ value: 'OG.shape.elec.BusductShape',
+ events: {
+ change: function (e) {
+ me.currentCanvas.getEventHandler().changeShape(e.target.value, 'CRB');
+ }
+ }
+ }
+ }
+ }
+ };
+ return this.contextMenu;
+};
+
+OG.shape.elec.Load = function (label) {
+ OG.shape.elec.Load.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.elec.Load';
+ this.label = label;
+};
+OG.shape.elec.Load.prototype = new OG.shape.GeomShape();
+OG.shape.elec.Load.superclass = OG.shape.GeomShape;
+OG.shape.elec.Load.prototype.constructor = OG.shape.elec.Load;
+OG.Load = OG.shape.elec.Load;
+
+OG.shape.elec.Load.prototype.createContextMenu = function () {
+ var me = this;
+ this.contextMenu = {
+ 'delete': true,
+ 'format': true,
+ 'text': true,
+ 'bringToFront': true,
+ 'sendToBack': true,
+ 'property': {
+ name: '정보보기', callback: function () {
+ $(me.currentCanvas.getRootElement()).trigger('showProperty', [me.currentElement]);
+ }
+ }
+ };
+ return this.contextMenu;
+};
+OG.shape.elec.BLDG = function (label) {
+ OG.shape.elec.BLDG.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.elec.BLDG';
+ this.label = label;
+ this.CONNECT_CLONEABLE = false;
+ this.LABEL_EDITABLE = false;
+ this.CONNECTABLE = false;
+};
+OG.shape.elec.BLDG.prototype = new OG.shape.HorizontalPoolShape();
+OG.shape.elec.BLDG.superclass = OG.shape.HorizontalPoolShape;
+OG.shape.elec.BLDG.prototype.constructor = OG.shape.elec.BLDG;
+OG.BLDG = OG.shape.elec.BLDG;
+OG.shape.elec.BLDG.prototype.createShape = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.geometry.Rectangle([0, 0], 100, 100);
+ this.geom.style = new OG.geometry.Style({
+ 'label-direction': 'horizontal',
+ 'vertical-align': 'top',
+ 'fill': '#ffffff',
+ 'fill-opacity': 0,
+ 'title-size': 26,
+ 'label-fill': '#428bca',
+ 'label-fill-opacity': 1,
+ 'font-weight': 700,
+ 'font-color': 'white'
+ });
+
+ return this.geom;
+};
+
+
+OG.shape.elec.BLDG.prototype.createContextMenu = function () {
+ var me = this;
+ this.contextMenu = {
+ 'delete': true,
+ 'format': true,
+ 'text': true,
+ 'bringToFront': true,
+ 'sendToBack': true,
+ 'property': {
+ name: '정보보기', callback: function () {
+ $(me.currentCanvas.getRootElement()).trigger('showProperty', [me.currentElement]);
+ }
+ }
+ };
+ return this.contextMenu;
+};
+
+/**
+ * ELECTRONIC : Busduct Shape
+ *
+ * @class
+ * @extends OG.shape.BusductShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {Number[]} from 와이어 시작 좌표
+ * @param {Number[]} to 와이어 끝 좌표
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.elec.BusductShape = function (from, to, label) {
+ OG.shape.elec.BusductShape.superclass.call(this, from, to, label);
+
+ this.SHAPE_ID = 'OG.shape.elec.BusductShape';
+};
+OG.shape.elec.BusductShape.prototype = new OG.shape.elec.WireShape();
+OG.shape.elec.BusductShape.superclass = OG.shape.elec.WireShape;
+OG.shape.elec.BusductShape.prototype.constructor = OG.shape.elec.BusductShape;
+OG.BusductShape = OG.shape.elec.BusductShape;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.elec.BusductShape.prototype.createShape = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.PolyLine([this.from || [0, 0], this.to || [70, 0]]);
+ this.geom.style = new OG.geometry.Style({
+ 'multi': [
+ {
+ top: -10,
+ from: '20px',
+ to: 'end-20px',
+ style: {
+ 'marker': {
+ 'start': {
+ 'id': 'OG.marker.SwitchRMarker',
+ 'size': [8, 8],
+ 'ref': [8, 0]
+ },
+ 'end': {
+ 'id': 'OG.marker.SwitchLMarker',
+ 'size': [8, 8],
+ 'ref': [1, 0]
+ }
+ }
+ }
+ },
+ {
+ top: 10,
+ from: '20px',
+ to: 'end-20px',
+ style: {
+ 'marker': {
+ 'start': {
+ 'id': 'OG.marker.SwitchLMarker',
+ 'size': [8, 8],
+ 'ref': [7, 8]
+ },
+ 'end': {
+ 'id': 'OG.marker.SwitchRMarker',
+ 'size': [8, 8],
+ 'ref': [0, 8]
+ }
+ }
+ }
+ },
+ {
+ top: 0,
+ from: 'start',
+ to: 'end',
+ style: {}
+ }
+ ]
+ });
+ return this.geom;
+};
+
+/**
+ * ELECTRONIC : CableShape
+ *
+ * @class
+ * @extends OG.shape.CableShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {Number[]} from 와이어 시작 좌표
+ * @param {Number[]} to 와이어 끝 좌표
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.elec.CableShape = function (from, to, label) {
+ OG.shape.elec.CableShape.superclass.call(this, from, to, label);
+
+ this.SHAPE_ID = 'OG.shape.elec.CableShape';
+};
+OG.shape.elec.CableShape.prototype = new OG.shape.elec.WireShape();
+OG.shape.elec.CableShape.superclass = OG.shape.elec.WireShape;
+OG.shape.elec.CableShape.prototype.constructor = OG.shape.elec.CableShape;
+OG.CableShape = OG.shape.elec.CableShape;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.elec.CableShape.prototype.createShape = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.PolyLine([this.from || [0, 0], this.to || [70, 0]]);
+ this.geom.style = new OG.geometry.Style({
+ 'multi': [
+ {
+ top: 0,
+ from: 'start',
+ to: 'center',
+ style: {
+ 'marker': {
+ 'end': {
+ 'id': 'OG.marker.SwitchLMarker',
+ 'size': [20, 8],
+ 'ref': [3, 0]
+ }
+ }
+ }
+ },
+ {
+ top: 0,
+ from: 'center',
+ to: 'end',
+ style: {
+ 'marker': {
+ 'start': {
+ 'id': 'OG.marker.SwitchXMarker',
+ 'size': [6, 6]
+ }
+ }
+ }
+ }
+ ]
+ });
+ return this.geom;
+};
+
+OG.shape.elec.EHLoad = function (label) {
+ OG.shape.elec.EHLoad.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.elec.EHLoad';
+ this.label = label;
+ this.CONNECT_CLONEABLE = false;
+ this.LABEL_EDITABLE = false;
+ this.ENABLE_FROM = false;
+};
+OG.shape.elec.EHLoad.prototype = new OG.shape.elec.Load();
+OG.shape.elec.EHLoad.superclass = OG.shape.elec.Load;
+OG.shape.elec.EHLoad.prototype.constructor = OG.shape.elec.EHLoad;
+OG.EHLoad = OG.shape.elec.EHLoad;
+
+OG.shape.elec.EHLoad.prototype.createShape = function () {
+ var geom1, geom2, geom3, geom4, geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geom1 = new OG.geometry.Circle([30, 30], 30);
+
+ geom2 = new OG.geometry.Curve([
+ [0, 0],
+ [40, 10],
+ [20, 20]
+ ]);
+ geom3 = new OG.geometry.Curve([
+ [20, 20],
+ [40, 30],
+ [20, 40]
+ ]);
+ geom4 = new OG.geometry.Curve([
+ [20, 40],
+ [40, 50],
+ [0, 60]
+ ]);
+
+ geomCollection.push(geom1);
+ geomCollection.push(geom2);
+ geomCollection.push(geom3);
+ geomCollection.push(geom4);
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+ this.geom.style = new OG.geometry.Style({
+ 'label-position': 'bottom',
+ 'label-width': 200,
+ 'stroke-width': 2
+ });
+
+ return this.geom;
+};
+
+
+OG.shape.elec.EHLoad.prototype.createSubShape = function () {
+ if (!this.data) {
+ return;
+ }
+
+ this.sub = [
+ {
+ shape: new OG.TextShape(this.data['LO_TYPE'] + ' Load'),
+ width: '200%',
+ height: '15%',
+ left: '-50%',
+ top: '-20%',
+ style: {
+ 'font-size': 8,
+ 'font-color': 'red',
+ 'text-anchor': 'middle'
+ }
+ }
+ ];
+ return this.sub;
+};
+OG.shape.elec.EHSLoad = function (label) {
+ OG.shape.elec.EHSLoad.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.elec.EHSLoad';
+ this.label = label;
+ this.CONNECT_CLONEABLE = false;
+ this.LABEL_EDITABLE = false;
+ this.ENABLE_FROM = false;
+};
+OG.shape.elec.EHSLoad.prototype = new OG.shape.elec.Load();
+OG.shape.elec.EHSLoad.superclass = OG.shape.elec.Load;
+OG.shape.elec.EHSLoad.prototype.constructor = OG.shape.elec.EHSLoad;
+OG.EHSLoad = OG.shape.elec.EHSLoad;
+
+OG.shape.elec.EHSLoad.prototype.createShape = function () {
+ var geom1, geom2, geom3, geom4, geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geom1 = new OG.geometry.Circle([30, 30], 30);
+
+ geom2 = new OG.geometry.Curve([
+ [0, 0],
+ [40, 10],
+ [20, 20]
+ ]);
+ geom3 = new OG.geometry.Curve([
+ [20, 20],
+ [40, 30],
+ [20, 40]
+ ]);
+ geom4 = new OG.geometry.Curve([
+ [20, 40],
+ [40, 50],
+ [0, 60]
+ ]);
+
+ geomCollection.push(geom1);
+ geomCollection.push(geom2);
+ geomCollection.push(geom3);
+ geomCollection.push(geom4);
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+ this.geom.style = new OG.geometry.Style({
+ 'label-position': 'bottom',
+ 'label-width': 200,
+ 'stroke-width': 2
+ });
+
+ return this.geom;
+};
+
+
+OG.shape.elec.EHSLoad.prototype.createSubShape = function () {
+ if (!this.data) {
+ return;
+ }
+
+ this.sub = [
+ {
+ shape: new OG.TextShape(this.data['LO_TYPE'] + ' Load'),
+ width: '200%',
+ height: '15%',
+ left: '-50%',
+ top: '-20%',
+ style: {
+ 'font-size': 8,
+ 'font-color': 'red',
+ 'text-anchor': 'middle'
+ }
+ }
+ ];
+ return this.sub;
+};
+OG.shape.elec.HierarchyBldg = function (label) {
+ OG.shape.elec.HierarchyBldg.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.elec.HierarchyBldg';
+ this.label = label;
+ this.CONNECT_CLONEABLE = false;
+ this.LABEL_EDITABLE = false;
+ this.CONNECTABLE = false;
+};
+OG.shape.elec.HierarchyBldg.prototype = new OG.shape.HorizontalPoolShape();
+OG.shape.elec.HierarchyBldg.superclass = OG.shape.HorizontalPoolShape;
+OG.shape.elec.HierarchyBldg.prototype.constructor = OG.shape.elec.HierarchyBldg;
+OG.HierarchyBldg = OG.shape.elec.HierarchyBldg;
+
+OG.shape.elec.HierarchyBldg.prototype.createShape = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.geometry.Rectangle([0, 0], 100, 100);
+ this.geom.style = new OG.geometry.Style({
+ 'label-direction': 'vertical',
+ 'vertical-align': 'top',
+ 'fill': '#ffffff',
+ 'fill-opacity': 0,
+ 'title-size': 26,
+ 'label-fill': '#428bca',
+ 'label-fill-opacity': 1,
+ 'font-weight': 700,
+ 'font-color': 'white'
+ });
+
+ return this.geom;
+};
+
+
+OG.shape.elec.HierarchyBldg.prototype.createContextMenu = function () {
+ var me = this;
+ this.contextMenu = {
+ 'delete': true,
+ 'format': true,
+ 'text': true,
+ 'bringToFront': true,
+ 'sendToBack': true,
+ 'property': {
+ name: '정보보기', callback: function () {
+ $(me.currentCanvas.getRootElement()).trigger('showProperty', [me.currentElement]);
+ }
+ }
+ };
+ return this.contextMenu;
+};
+OG.shape.elec.HierarchyFeeder = function (label) {
+ OG.shape.elec.HierarchyFeeder.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.elec.HierarchyFeeder';
+ this.label = label;
+ this.CONNECT_CLONEABLE = false;
+ this.LABEL_EDITABLE = false;
+
+ this.textList = [
+ {
+ text: 'cable',
+ label: '',
+ shape: 'OG.CableShape'
+ },
+ {
+ text: 'IPB',
+ label: 'IPB',
+ shape: 'OG.BusductShape'
+ },
+ {
+ text: 'SPB',
+ label: 'SPB',
+ shape: 'OG.BusductShape'
+ },
+ {
+ text: 'NSPB',
+ label: 'NSPB',
+ shape: 'OG.BusductShape'
+ },
+ {
+ text: 'CRB',
+ label: 'CRB',
+ shape: 'OG.BusductShape'
+ }
+ ];
+};
+OG.shape.elec.HierarchyFeeder.prototype = new OG.shape.GeomShape();
+OG.shape.elec.HierarchyFeeder.superclass = OG.shape.GeomShape;
+OG.shape.elec.HierarchyFeeder.prototype.constructor = OG.shape.elec.HierarchyFeeder;
+OG.HierarchyFeeder = OG.shape.elec.HierarchyFeeder;
+
+OG.shape.elec.HierarchyFeeder.prototype.createShape = function () {
+ var geom1, geom2, geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geom1 = new OG.geometry.Circle([50, 50], 50);
+ geom1.style = new OG.geometry.Style({
+ "stroke-width": 4
+ });
+
+ geom2 = new OG.geometry.Polygon([
+ [30, 80],
+ [30, 20],
+ [70, 20],
+ [70, 30],
+ [40, 30],
+ [40, 40],
+ [70, 40],
+ [70, 50],
+ [40, 50],
+ [40, 80],
+ [30, 80]
+ ]);
+ geom2.style = new OG.geometry.Style({
+ "fill": "black",
+ "fill-opacity": 1
+ });
+
+ geomCollection.push(geom1);
+ geomCollection.push(geom2);
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+ this.geom.style = new OG.geometry.Style({
+ 'label-position': 'bottom',
+ 'label-width': 200
+ });
+
+ return this.geom;
+};
+
+
+OG.shape.elec.HierarchyFeeder.prototype.createSubShape = function () {
+ if (!this.data) {
+ return;
+ }
+
+ this.sub = [
+ {
+ shape: new OG.TextShape(this.data['SWGR_TYPE'] + ' Swgr'),
+ width: '200%',
+ height: '15%',
+ left: '-50%',
+ top: '-20%',
+ style: {
+ 'font-size': 8,
+ 'font-color': 'red',
+ 'text-anchor': 'middle'
+ }
+ }
+ ];
+ return this.sub;
+};
+
+
+OG.shape.elec.HierarchyFloor = function (label) {
+ OG.shape.elec.HierarchyFloor.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.elec.HierarchyFloor';
+ this.label = label;
+ this.CONNECT_CLONEABLE = false;
+ this.LABEL_EDITABLE = false;
+ this.CONNECTABLE = false;
+};
+OG.shape.elec.HierarchyFloor.prototype = new OG.shape.HorizontalPoolShape();
+OG.shape.elec.HierarchyFloor.superclass = OG.shape.HorizontalPoolShape;
+OG.shape.elec.HierarchyFloor.prototype.constructor = OG.shape.elec.HierarchyFloor;
+OG.HierarchyFloor = OG.shape.elec.HierarchyFloor;
+
+
+OG.shape.elec.HierarchyFloor.prototype.createShape = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.geometry.Rectangle([0, 0], 100, 100);
+ this.geom.style = new OG.geometry.Style({
+ 'label-direction': 'vertical',
+ 'vertical-align': 'top',
+ 'fill': '#ffffff',
+ 'fill-opacity': 0,
+ 'title-size': 26,
+ 'label-fill': '#428bca',
+ 'label-fill-opacity': 1,
+ 'font-weight': 700,
+ 'font-color': 'white'
+ });
+
+ return this.geom;
+};
+
+OG.shape.elec.HierarchyFloor.prototype.createContextMenu = function () {
+ var me = this;
+ this.contextMenu = {
+ 'delete': true,
+ 'format': true,
+ 'text': true,
+ 'bringToFront': true,
+ 'sendToBack': true,
+ 'property': {
+ name: '정보보기', callback: function () {
+ $(me.currentCanvas.getRootElement()).trigger('showProperty', [me.currentElement]);
+ }
+ }
+ };
+ return this.contextMenu;
+};
+
+
+OG.shape.elec.HierarchyFloor.prototype.createContextMenu = function () {
+ var me = this;
+ this.contextMenu = {
+ 'delete': true,
+ 'format': true,
+ 'text': true,
+ 'bringToFront': true,
+ 'sendToBack': true,
+ 'property': {
+ name: '정보보기', callback: function () {
+ $(me.currentCanvas.getRootElement()).trigger('showProperty', [me.currentElement]);
+ }
+ }
+ };
+ return this.contextMenu;
+};
+OG.shape.elec.Location = function (label) {
+ OG.shape.elec.Location.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.elec.Location';
+ this.label = label;
+ this.CONNECT_CLONEABLE = false;
+ this.LABEL_EDITABLE = false;
+
+ this.textList = [
+ {
+ text: 'Raceway',
+ label: '',
+ shape: 'OG.RacewayShape'
+ }
+ ];
+};
+OG.shape.elec.Location.prototype = new OG.shape.GeomShape();
+OG.shape.elec.Location.superclass = OG.shape.GeomShape;
+OG.shape.elec.Location.prototype.constructor = OG.shape.elec.Location;
+OG.Location = OG.shape.elec.Location;
+OG.shape.elec.Location.prototype.createShape = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.geometry.Rectangle([0, 0], 100, 100);
+ this.geom.style = new OG.geometry.Style({
+ 'fill-r': 1,
+ 'fill-cx': .1,
+ 'fill-cy': .1,
+ "stroke-width": 1.2,
+ fill: 'r(.1, .1)#428bca-#ffffff',
+ 'fill-opacity': 1,
+ r: '10'
+ });
+
+ return this.geom;
+};
+
+
+OG.shape.elec.Location.prototype.createSubShape = function () {
+ if (!this.data) {
+ return;
+ }
+
+ this.sub = [];
+ return this.sub;
+};
+
+OG.shape.elec.Location.prototype.createContextMenu = function () {
+ var me = this;
+ this.contextMenu = {
+ 'delete': true,
+ 'format': true,
+ 'text': true,
+ 'bringToFront': true,
+ 'sendToBack': true,
+ 'property': {
+ name: '정보보기', callback: function () {
+ $(me.currentCanvas.getRootElement()).trigger('showProperty', [me.currentElement]);
+ }
+ }
+ };
+ return this.contextMenu;
+};
+OG.shape.elec.MILoad = function (label) {
+ OG.shape.elec.MILoad.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.elec.MILoad';
+ this.label = label;
+ this.CONNECT_CLONEABLE = false;
+ this.LABEL_EDITABLE = false;
+ this.ENABLE_FROM = false;
+};
+OG.shape.elec.MILoad.prototype = new OG.shape.elec.Load();
+OG.shape.elec.MILoad.superclass = OG.shape.elec.Load;
+OG.shape.elec.MILoad.prototype.constructor = OG.shape.elec.MILoad;
+OG.MILoad = OG.shape.elec.MILoad;
+
+OG.shape.elec.MILoad.prototype.createShape = function () {
+ var geom1, geom2, geom3, geom4, geom5,
+ geom6, geom7, geom8, geom9, geom10,
+ geom11, geom12, geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geom1 = new OG.geometry.Polygon([[10, 0], [27, 10], [10, 20]]);
+ geom2 = new OG.geometry.Polygon([[44, 0], [27, 10], [44, 20]]);
+ geom3 = new OG.geometry.Line([27, 10], [27, 20]);
+ geom4 = new OG.geometry.Circle([27, 27], 7);
+ geom5 = new OG.geometry.PolyLine([[24, 30], [24, 24], [27, 28], [30, 24], [30, 30]]);
+
+ geom6 = new OG.geometry.Polygon([[10, 40], [27, 50], [10, 60]]);
+ geom7 = new OG.geometry.Polygon([[44, 40], [27, 50], [44, 60]]);
+ geom8 = new OG.geometry.Line([27, 50], [27, 60]);
+ geom9 = new OG.geometry.Circle([27, 67], 7);
+ geom10 = new OG.geometry.PolyLine([[24, 70], [24, 64], [27, 68], [30, 64], [30, 70]]);
+
+ geom11 = new OG.geometry.PolyLine([[10, 10], [0, 10], [0, 34], [10, 34], [10, 40]]);
+ geom12 = new OG.geometry.PolyLine([[44, 10], [54, 10], [54, 34], [44, 34], [44, 40]]);
+
+ geomCollection.push(geom1);
+ geomCollection.push(geom2);
+ geomCollection.push(geom3);
+ geomCollection.push(geom4);
+ geomCollection.push(geom5);
+ geomCollection.push(geom6);
+ geomCollection.push(geom7);
+ geomCollection.push(geom8);
+ geomCollection.push(geom9);
+ geomCollection.push(geom10);
+ geomCollection.push(geom11);
+ geomCollection.push(geom12);
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+ this.geom.style = new OG.geometry.Style({
+ 'label-position': 'bottom',
+ 'label-width': 200,
+ 'stroke-width': 2
+ });
+
+ return this.geom;
+};
+
+
+OG.shape.elec.MILoad.prototype.createSubShape = function () {
+ if (!this.data) {
+ return;
+ }
+
+ this.sub = [
+ {
+ shape: new OG.TextShape(this.data['LO_TYPE'] + ' Load'),
+ width: '200%',
+ height: '15%',
+ left: '-50%',
+ top: '-20%',
+ style: {
+ 'font-size': 8,
+ 'font-color': 'red',
+ 'text-anchor': 'middle'
+ }
+ }
+ ];
+ return this.sub;
+};
+OG.shape.elec.MKLoad = function (label) {
+ OG.shape.elec.MKLoad.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.elec.MKLoad';
+ this.label = label;
+ this.CONNECT_CLONEABLE = false;
+ this.LABEL_EDITABLE = false;
+ this.ENABLE_FROM = false;
+};
+OG.shape.elec.MKLoad.prototype = new OG.shape.elec.Load();
+OG.shape.elec.MKLoad.superclass = OG.shape.elec.Load;
+OG.shape.elec.MKLoad.prototype.constructor = OG.shape.elec.MKLoad;
+OG.MKLoad = OG.shape.elec.MKLoad;
+
+OG.shape.elec.MKLoad.prototype.createShape = function () {
+ var geom1, geom2, geom3, geom4, geom5, geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geom1 = new OG.geometry.Polygon([[10, 0], [27, 10], [10, 20]]);
+ geom2 = new OG.geometry.Polygon([[44, 0], [27, 10], [44, 20]]);
+ geom3 = new OG.geometry.Line([27, 10], [27, 20]);
+ geom4 = new OG.geometry.Circle([27, 27], 7);
+ geom5 = new OG.geometry.PolyLine([[24, 30], [24, 24], [27, 28], [30, 24], [30, 30]]);
+
+ geomCollection.push(geom1);
+ geomCollection.push(geom2);
+ geomCollection.push(geom3);
+ geomCollection.push(geom4);
+ geomCollection.push(geom5);
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+ this.geom.style = new OG.geometry.Style({
+ 'label-position': 'bottom',
+ 'label-width': 200,
+ 'stroke-width': 2
+ });
+
+ return this.geom;
+};
+
+
+OG.shape.elec.MKLoad.prototype.createSubShape = function () {
+ if (!this.data) {
+ return;
+ }
+
+ this.sub = [
+ {
+ shape: new OG.TextShape(this.data['LO_TYPE'] + ' Load'),
+ width: '200%',
+ height: '15%',
+ left: '-50%',
+ top: '-20%',
+ style: {
+ 'font-size': 8,
+ 'font-color': 'red',
+ 'text-anchor': 'middle'
+ }
+ }
+ ];
+ return this.sub;
+};
+OG.shape.elec.MOLoad = function (label) {
+ OG.shape.elec.MOLoad.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.elec.MOLoad';
+ this.label = label;
+ this.CONNECT_CLONEABLE = false;
+ this.LABEL_EDITABLE = false;
+ this.ENABLE_FROM = false;
+};
+OG.shape.elec.MOLoad.prototype = new OG.shape.elec.Load();
+OG.shape.elec.MOLoad.superclass = OG.shape.elec.Load;
+OG.shape.elec.MOLoad.prototype.constructor = OG.shape.elec.MOLoad;
+OG.MOLoad = OG.shape.elec.MOLoad;
+
+OG.shape.elec.MOLoad.prototype.createShape = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.geometry.Circle([50, 50], 50);
+
+ this.geom.style = new OG.geometry.Style({
+ 'label-position': 'bottom',
+ 'label-width': 200,
+ 'stroke-width': 2
+ });
+
+ return this.geom;
+};
+
+
+OG.shape.elec.MOLoad.prototype.createSubShape = function () {
+ this.sub = [
+ {
+ shape: new OG.TextShape('M'),
+ width: '100%',
+ height: '50%',
+ align: 'center',
+ 'vertical-align': 'middle',
+ style: {
+ 'font-size': 20,
+ 'font-color': 'black',
+ 'text-anchor': 'middle'
+ }
+ }
+ ];
+ if (this.data) {
+ this.sub.push(
+ {
+ shape: new OG.TextShape(this.data['LO_TYPE'] + ' Load'),
+ width: '200%',
+ height: '15%',
+ left: '-50%',
+ top: '-20%',
+ style: {
+ 'font-size': 8,
+ 'font-color': 'red',
+ 'text-anchor': 'middle'
+ }
+ }
+ )
+ }
+ return this.sub;
+};
+OG.shape.elec.Manhole = function (image, label) {
+ OG.shape.elec.Manhole.superclass.call(this, image, label);
+
+ this.SHAPE_ID = 'OG.shape.elec.Manhole';
+ this.label = label;
+ this.CONNECT_CLONEABLE = false;
+ this.LABEL_EDITABLE = false;
+
+ if (!image) {
+ this.image = 'resources/images/elec/manhole.svg'
+ }
+
+ this.textList = [
+ {
+ text: 'Raceway',
+ label: '',
+ shape: 'OG.RacewayShape'
+ }
+ ];
+};
+OG.shape.elec.Manhole.prototype = new OG.shape.ImageShape();
+OG.shape.elec.Manhole.superclass = OG.shape.ImageShape;
+OG.shape.elec.Manhole.prototype.constructor = OG.shape.elec.Manhole;
+OG.Manhole = OG.shape.elec.Manhole;
+
+OG.shape.elec.Manhole.prototype.createSubShape = function () {
+ if (!this.data) {
+ return;
+ }
+
+ this.sub = [];
+ return this.sub;
+};
+
+
+OG.shape.elec.Manhole.prototype.createContextMenu = function () {
+ var me = this;
+ this.contextMenu = {
+ 'delete': true,
+ 'format': true,
+ 'text': true,
+ 'bringToFront': true,
+ 'sendToBack': true,
+ 'property': {
+ name: '정보보기', callback: function () {
+ $(me.currentCanvas.getRootElement()).trigger('showProperty', [me.currentElement]);
+ }
+ }
+ };
+ return this.contextMenu;
+};
+OG.shape.elec.NMLoad = function (label) {
+ OG.shape.elec.NMLoad.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.elec.NMLoad';
+ this.label = label;
+ this.CONNECT_CLONEABLE = false;
+ this.LABEL_EDITABLE = false;
+ this.ENABLE_FROM = false;
+};
+OG.shape.elec.NMLoad.prototype = new OG.shape.elec.Load();
+OG.shape.elec.NMLoad.superclass = OG.shape.elec.Load;
+OG.shape.elec.NMLoad.prototype.constructor = OG.shape.elec.NMLoad;
+OG.NMLoad = OG.shape.elec.NMLoad;
+
+OG.shape.elec.NMLoad.prototype.createShape = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.geometry.Rectangle([0, 0], 100, 80);
+
+ this.geom.style = new OG.geometry.Style({
+ 'label-position': 'bottom',
+ 'label-width': 200,
+ 'stroke-width': 2
+ });
+
+ return this.geom;
+};
+
+
+OG.shape.elec.NMLoad.prototype.createSubShape = function () {
+ this.sub = [
+ {
+ shape: new OG.TextShape('NM'),
+ width: '100%',
+ height: '50%',
+ align: 'center',
+ 'vertical-align': 'middle',
+ style: {
+ 'font-size': 20,
+ 'font-color': 'black',
+ 'text-anchor': 'middle'
+ }
+ }
+ ];
+ if (this.data) {
+ this.sub.push(
+ {
+ shape: new OG.TextShape(this.data['LO_TYPE'] + ' Load'),
+ width: '200%',
+ height: '15%',
+ left: '-50%',
+ top: '-20%',
+ style: {
+ 'font-size': 8,
+ 'font-color': 'red',
+ 'text-anchor': 'middle'
+ }
+ }
+ )
+ }
+ return this.sub;
+};
+OG.shape.elec.PKGLoad = function (label) {
+ OG.shape.elec.PKGLoad.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.elec.PKGLoad';
+ this.label = label;
+ this.CONNECT_CLONEABLE = false;
+ this.LABEL_EDITABLE = false;
+ this.ENABLE_FROM = false;
+};
+OG.shape.elec.PKGLoad.prototype = new OG.shape.elec.Load();
+OG.shape.elec.PKGLoad.superclass = OG.shape.elec.Load;
+OG.shape.elec.PKGLoad.prototype.constructor = OG.shape.elec.PKGLoad;
+OG.PKGLoad = OG.shape.elec.PKGLoad;
+
+OG.shape.elec.PKGLoad.prototype.createShape = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.geometry.Rectangle([0, 0], 100, 80);
+
+ this.geom.style = new OG.geometry.Style({
+ 'label-position': 'bottom',
+ 'label-width': 200,
+ 'stroke-width': 2
+ });
+
+ return this.geom;
+};
+
+
+OG.shape.elec.PKGLoad.prototype.createSubShape = function () {
+ this.sub = [
+ {
+ shape: new OG.TextShape('PKG'),
+ width: '100%',
+ height: '50%',
+ align: 'center',
+ 'vertical-align': 'middle',
+ style: {
+ 'font-size': 20,
+ 'font-color': 'black',
+ 'text-anchor': 'middle'
+ }
+ }
+ ];
+ if (this.data) {
+ this.sub.push(
+ {
+ shape: new OG.TextShape(this.data['LO_TYPE'] + ' Load'),
+ width: '200%',
+ height: '15%',
+ left: '-50%',
+ top: '-20%',
+ style: {
+ 'font-size': 8,
+ 'font-color': 'red',
+ 'text-anchor': 'middle'
+ }
+ }
+ )
+ }
+ return this.sub;
+};
+/**
+ * ELECTRONIC : Raceway Shape
+ *
+ * @class
+ * @extends OG.shape.RacewayShape
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ *
+ * @param {Number[]} from 와이어 시작 좌표
+ * @param {Number[]} to 와이어 끝 좌표
+ * @param {String} label 라벨 [Optional]
+ * @author Seungpil Park
+ * @private
+ */
+OG.shape.elec.RacewayShape = function (from, to, label) {
+ OG.shape.elec.RacewayShape.superclass.call(this, from, to, label);
+
+ this.SHAPE_ID = 'OG.shape.elec.RacewayShape';
+};
+OG.shape.elec.RacewayShape.prototype = new OG.shape.EdgeShape();
+OG.shape.elec.RacewayShape.superclass = OG.shape.EdgeShape;
+OG.shape.elec.RacewayShape.prototype.constructor = OG.shape.elec.RacewayShape;
+OG.RacewayShape = OG.shape.elec.RacewayShape;
+
+/**
+ * 드로잉할 Shape 을 생성하여 반환한다.
+ *
+ * @return {OG.geometry.Geometry} Shape 정보
+ * @override
+ */
+OG.shape.elec.RacewayShape.prototype.createShape = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.Line(this.from || [0, 0], this.to || [70, 0]);
+
+ var style = {
+ 'multi': [
+ {
+ top: -10,
+ from: 'start',
+ to: 'end',
+ style: {}
+ },
+ {
+ top: 10,
+ from: 'start',
+ to: 'end',
+ style: {}
+ },
+ {
+ top: 0,
+ from: 'start',
+ to: 'end',
+ style: {
+ pattern: {
+ 'id': 'OG.pattern.HatchedPattern',
+ 'thickness': 10,
+ 'unit-width': 12,
+ 'unit-height': 12,
+ 'pattern-width': 8,
+ 'pattern-height': 8,
+ 'style': {
+ 'stroke': 'black'
+ }
+ },
+ 'stroke': 'none',
+ }
+ },
+ {
+ top: 0,
+ from: 'start',
+ to: 'end',
+ style: {
+ //'stroke': 'none',
+ 'fill-opacity': 1,
+ animation: [
+ {
+ start: {
+ stroke: 'white'
+ },
+ to: {
+ stroke: '#d9534f'
+ },
+ ms: 1000
+ },
+ {
+ start: {
+ stroke: '#d9534f'
+ },
+ to: {
+ stroke: 'white'
+ },
+ ms: 1000,
+ delay: 1000
+ }
+ ],
+ 'animation-repeat': true,
+ "stroke": "#d9534f",
+ "stroke-width": "5"
+ }
+ }
+ ]
+ };
+ if (this.data && this.data.selected) {
+ style['stroke'] = '#d9534f';
+ style['stroke-width'] = 2;
+ style['multi'][2]['style']['pattern']['style']['stroke'] = '#d9534f';
+ }
+ this.geom.style = new OG.geometry.Style(style);
+ return this.geom;
+};
+
+
+OG.shape.elec.RacewayShape.prototype.createContextMenu = function () {
+ var me = this;
+ var options = {'': ''};
+ if (this.data && this.data.pathList) {
+ for (var i = 0; i < this.data.pathList.length; i++) {
+ options[this.data.pathList[i]['value']] = this.data.pathList[i]['name'];
+ }
+ }
+
+ this.contextMenu = {
+ 'delete': true,
+ 'format': true,
+ 'text': true,
+ 'bringToFront': true,
+ 'sendToBack': true,
+ 'property': {
+ name: '정보보기', callback: function () {
+ $(me.currentCanvas.getRootElement()).trigger('showProperty', [me.currentElement]);
+ }
+ },
+ 'pathList': {
+ name: '라우트 보기',
+ items: {
+ 'selectPath': {
+ name: '선택',
+ type: 'select',
+ options: options,
+ selected: '',
+ events: {
+ change: function (e) {
+ if (e.target.value !== '') {
+ $(me.currentCanvas.getRootElement()).trigger('showRouteList', [me.currentElement, e.target.value]);
+ }
+ }
+ }
+ }
+ }
+ },
+ 'alternative': {
+ name: '라우트 변경', callback: function () {
+ $(me.currentCanvas.getRootElement()).trigger('changeRoute', [me.currentElement]);
+ }
+ }
+ };
+ return this.contextMenu;
+};
+
+OG.shape.elec.SHLoad = function (label) {
+ OG.shape.elec.SHLoad.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.elec.SHLoad';
+ this.label = label;
+ this.CONNECT_CLONEABLE = false;
+ this.LABEL_EDITABLE = false;
+ this.ENABLE_FROM = false;
+};
+OG.shape.elec.SHLoad.prototype = new OG.shape.elec.Load();
+OG.shape.elec.SHLoad.superclass = OG.shape.elec.Load;
+OG.shape.elec.SHLoad.prototype.constructor = OG.shape.elec.SHLoad;
+OG.SHLoad = OG.shape.elec.SHLoad;
+
+OG.shape.elec.SHLoad.prototype.createShape = function () {
+ var geom1, geom2, geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geom1 = new OG.geometry.Circle([50, 50], 50);
+ geom1.style = new OG.geometry.Style({
+ "stroke-width": 4
+ });
+
+ geom2 = new OG.geometry.Polygon([
+ [20, 20],
+ [20, 80],
+ [30, 80],
+ [30, 50],
+ [70, 50],
+ [70, 80],
+ [80, 80],
+ [80, 20],
+ [70, 20],
+ [70, 40],
+ [30, 40],
+ [30, 20],
+ [20, 20]
+ ]);
+ geom2.style = new OG.geometry.Style({
+ "fill": "black",
+ "fill-opacity": 1
+ });
+
+ geomCollection.push(geom1);
+ geomCollection.push(geom2);
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+ this.geom.style = new OG.geometry.Style({
+ 'label-position': 'bottom',
+ 'label-width': 200
+ });
+
+ return this.geom;
+};
+
+OG.shape.elec.SHLoad.prototype.createSubShape = function () {
+ if (!this.data) {
+ return;
+ }
+
+ this.sub = [
+ {
+ shape: new OG.TextShape(this.data['LO_TYPE'] + ' Load'),
+ width: '200%',
+ height: '15%',
+ left: '-50%',
+ top: '-20%',
+ style: {
+ 'font-size': 8,
+ 'font-color': 'red',
+ 'text-anchor': 'middle'
+ }
+ }
+ ];
+ return this.sub;
+};
+OG.shape.elec.SwitchGear = function (label) {
+ OG.shape.elec.SwitchGear.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.elec.SwitchGear';
+ this.label = label;
+ this.DELETABLE = false;
+ this.ENABLE_TO = false;
+ this.CONNECT_CLONEABLE = false;
+ this.LABEL_EDITABLE = false;
+
+ this.textList = [
+ {
+ text: 'cable',
+ label: '',
+ shape: 'OG.CableShape'
+ },
+ {
+ text: 'IPB',
+ label: 'IPB',
+ shape: 'OG.BusductShape'
+ },
+ {
+ text: 'SPB',
+ label: 'SPB',
+ shape: 'OG.BusductShape'
+ },
+ {
+ text: 'NSPB',
+ label: 'NSPB',
+ shape: 'OG.BusductShape'
+ },
+ {
+ text: 'CRB',
+ label: 'CRB',
+ shape: 'OG.BusductShape'
+ }
+ ];
+};
+OG.shape.elec.SwitchGear.prototype = new OG.shape.GeomShape();
+OG.shape.elec.SwitchGear.superclass = OG.shape.GeomShape;
+OG.shape.elec.SwitchGear.prototype.constructor = OG.shape.elec.SwitchGear;
+OG.SwitchGear = OG.shape.elec.SwitchGear;
+
+OG.shape.elec.SwitchGear.prototype.createShape = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.geometry.Line([-100, 0], [100, 0]);
+ this.geom.style = new OG.geometry.Style({
+ 'cursor': 'default',
+ 'stroke': '#d9534f',
+ 'stroke-width': '3',
+ 'fill': 'none',
+ 'fill-opacity': 0
+ });
+ return this.geom;
+};
+
+
+OG.shape.elec.SwitchGear.prototype.createSubShape = function () {
+ if (!this.data) {
+ return;
+ }
+
+ this.sub = [
+ {
+ shape: new OG.TextShape(this.data['PJT_SQ']),
+ width: '100%',
+ height: '15px',
+ right: '0px',
+ top: '-25px',
+ style: {
+ 'font-size': 12,
+ 'font-color': 'red',
+ 'text-anchor': 'end'
+ }
+ }
+ ];
+
+ return this.sub;
+};
+
+
+OG.shape.elec.SwitchGear.prototype.createContextMenu = function () {
+ var me = this;
+ this.contextMenu = {
+ 'format': true,
+ 'text': true,
+ 'bringToFront': true,
+ 'sendToBack': true,
+ 'property': {
+ name: '정보보기', callback: function () {
+ $(me.currentCanvas.getRootElement()).trigger('showProperty', [me.currentElement]);
+ }
+ }
+ };
+ return this.contextMenu;
+};
+OG.shape.elec.SwitchTransformer = function (label) {
+ OG.shape.elec.SwitchTransformer.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.elec.SwitchTransformer';
+ this.label = label;
+ this.DELETABLE = false;
+ this.ENABLE_TO = false;
+ this.CONNECT_CLONEABLE = false;
+ this.LABEL_EDITABLE = false;
+};
+OG.shape.elec.SwitchTransformer.prototype = new OG.shape.GeomShape();
+OG.shape.elec.SwitchTransformer.superclass = OG.shape.GeomShape;
+OG.shape.elec.SwitchTransformer.prototype.constructor = OG.shape.elec.SwitchTransformer;
+OG.SwitchTransformer = OG.shape.elec.SwitchTransformer;
+
+OG.shape.elec.SwitchTransformer.prototype.createShape = function () {
+ var geom1, geom2, geomCollection = [];
+ if (this.geom) {
+ return this.geom;
+ }
+
+ geom1 = new OG.geometry.Circle([50, 50], 50);
+ geom2 = new OG.geometry.Circle([50, 125], 50);
+
+ geomCollection.push(geom1);
+ geomCollection.push(geom2);
+
+ this.geom = new OG.geometry.GeometryCollection(geomCollection);
+ this.geom.style = new OG.geometry.Style({
+ 'label-position': 'bottom',
+ 'label-width': 300,
+ 'vertical-align': 'top'
+ });
+
+ return this.geom;
+};
+
+OG.shape.component.DataTable = function () {
+
+ //리사이즈 후 셀 가이드 다시 그리기. ok
+ //오른쪽으로 리사이즈 드래그 시 화면 사이즈 조정. ok
+ //콘텐트 매핑시 소팅 기능. ok
+ //행 추가 api. ok
+
+ OG.shape.component.DataTable.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.component.DataTable';
+ this.options = {
+ pageLength: 25,
+ currentPage: 1,
+ cellEditable: true,
+ axis: 'none'
+ }
+ this.CONNECT_CLONEABLE = false;
+ this.LABEL_EDITABLE = false;
+ this.CONNECTABLE = false;
+ this.RESIZABLE = false;
+ this.MOVABLE = false;
+
+
+ var renderer = function (value) {
+ var result = {
+ contents: [],
+ contentsPosition: {
+ /**
+ * 컨텐츠 배열
+ */
+ arrangement: 'horizontal', //수평 || vertical 수직
+
+ /**
+ * 컨텐츠 배열 마진 (number,px,%)
+ */
+ arrangementMargin: '10',
+ /**
+ * 좌측으로 부터 위치값. (number,px,%)
+ */
+ left: '0',
+ /**
+ * 상단으로 부터 위치값. (number,px,%)
+ */
+ top: '0',
+ /**
+ * 우측으로 부터 위치값. (number,px,%)
+ */
+ right: '0',
+ /**
+ * 하단으로 부터 위치값. (number,px,%)
+ */
+ bottom: '0',
+ /**
+ * 가로 정렬 (left,center,right).
+ */
+ align: 'center',
+ /**
+ * 세로 정렬 (top,middle,bottom)
+ */
+ 'vertical-align': 'middle'
+ }
+ }
+ if (!value) {
+ return result;
+ }
+ if (value && typeof value != 'object') {
+ return result;
+ }
+
+ for (var i = 0; i < value.length; i++) {
+ var contentData = value[i];
+ if (contentData.type == 'text') {
+ result.contents.push({
+ shape: new OG.TextShape(value.value),
+ width: '80px',
+ height: '78px',
+ style: {
+ 'fill': '#f8f8f8',
+ 'fill-opacity': 1,
+ 'font-size': 9
+ }
+ });
+ }
+
+ if (contentData.type == 'activity') {
+ var shape = new OG.A_Task(contentData.value);
+ shape.CONNECTABLE = true;
+ shape.GROUP_COLLAPSIBLE = false;
+ shape.DELETABLE = false;
+ shape.LABEL_EDITABLE = false;
+ shape.RESIZABLE = false;
+ shape.GUIDE_BBOX = {
+ stroke: "#ff5b00",
+ 'stroke-width': 4,
+ fill: "white",
+ "fill-opacity": 0,
+ "shape-rendering": "crispEdges",
+ cursor: "move"
+ }
+ result.contents.push({
+ /**
+ * 도형 shape
+ */
+ shape: shape,
+ /**
+ * 도형 가로 (number,px,%)
+ */
+ width: '80px',
+ /**
+ * 도형 세로 (number,px,%)
+ */
+ height: '38px',
+ /**
+ * 도형 스타일
+ */
+ style: {
+ 'fill': '#fff',
+ 'fill-opacity': 1,
+ 'font-size': 9
+ }
+ })
+ }
+ }
+ return result;
+ }
+
+ //옵션데이터
+ this.options = {
+ selectable: 'column',
+
+ //mode: 'view',
+ /**
+ * 컨텐트 외부 드래그 가능 여부
+ */
+ enableMoveOutSide: false,
+ /**
+ * 컨텐트 이동 방향
+ */
+ moveAxis: 'X',
+ /**
+ * 리사이즈 방향
+ */
+ resizeAxis: 'X',
+ /**
+ * 셀 콘텐트 axis 무브
+ */
+ axis: 'none',
+ /**
+ * 페이지당 row 수
+ */
+ pageLength: 100,
+ /**
+ * 시작 페이지
+ */
+ currentPage: 1,
+ /**
+ * 디폴트 칼럼 높이
+ */
+ columnHeight: 30,
+ /**
+ * 디폴트 칼럼 가로폭
+ */
+ columnWidth: 140,
+ /**
+ * 칼럼 최소폭
+ */
+ columnMinWidth: 100,
+ /**
+ * 디폴트 칼럼 스타일
+ */
+ columnStyle: {
+ 'font-color': '#fff',
+ 'fill': '#abaaad',
+ 'fill-opacity': 1,
+ 'stroke': 'none',
+ 'border-bottom': {
+ 'stroke': '#616063',
+ 'stroke-width': '4'
+ }
+ },
+ columnEditable: false,
+
+ rowDividingLine: {
+ 'stroke': '#abaaad',
+ 'stroke-width': '1'
+ },
+
+ /**
+ * 디폴트 cell 높이
+ */
+ cellHeight: 40,
+ /**
+ * 디폴트 셀 스타일
+ */
+ cellStyle: {
+ 'fill': '#fff',
+ 'fill-opacity': 0,
+ 'font-size': 8,
+ 'border-right': {
+ 'stroke': '#ebeaed',
+ 'stroke-width': '1'
+ }
+ },
+ /**
+ * 칼럼 정의
+ */
+ columns: [
+ {
+ /**
+ * 데이터 필드 이름
+ */
+ data: 'activity',
+ /**
+ * 칼럼 타이틀
+ */
+ title: '주요 Activity\n일정(D day)',
+ /**
+ * 디폴트 컨텐츠
+ */
+ defaultContent: '',
+ /**
+ * 칼럼 너비
+ */
+ columnWidth: 100,
+ /**
+ * 칼럼 스타일
+ */
+ columnStyle: {
+ 'border-left': {
+ 'stroke': '#abaaad',
+ 'stroke-width': '1'
+ },
+ 'border-right': {
+ 'stroke': '#616063',
+ 'stroke-width': '3'
+ }
+ },
+ /**
+ * 칼럼에 소속된 셀 스타일
+ */
+ cellStyle: {
+ 'border-right': {
+ 'stroke': '#616063',
+ 'stroke-width': '3'
+ },
+ 'border-left': {
+ 'stroke': '#abaaad',
+ 'stroke-width': '1'
+ }
+ },
+ columnEditable: false
+ },
+ {
+ data: '90_',
+ title: '견적 착수\n90',
+ defaultContent: '',
+ renderer: renderer,
+ columnEditable: true
+ },
+ {
+ data: '85_',
+ title: 'IRS\n85',
+ defaultContent: '',
+ renderer: renderer
+ },
+ {
+ data: '80_',
+ title: 'Ref.PJT 선정',
+ defaultContent: '',
+ renderer: renderer
+ },
+ {
+ data: '75_',
+ title: 'HBD\n75',
+ defaultContent: '',
+ renderer: renderer
+ },
+ {
+ data: '70_',
+ title: 'WBD\n70',
+ defaultContent: '',
+ renderer: renderer
+ },
+ {
+ data: '65_',
+ title: 'P&ID\n65',
+ defaultContent: '',
+ renderer: renderer
+ },
+ {
+ data: '60_',
+ title: 'P&ID\n60',
+ defaultContent: '',
+ renderer: renderer
+ },
+ {
+ data: '55_',
+ title: 'P&ID\n55',
+ defaultContent: '',
+ renderer: renderer
+ },
+ {
+ data: '50_',
+ title: 'P&ID\n50',
+ defaultContent: '',
+ renderer: renderer
+ },
+ {
+ data: '49_',
+ title: 'P&ID\n50',
+ defaultContent: '',
+ renderer: renderer
+ },
+ {
+ data: '48_',
+ title: 'P&ID\n50',
+ defaultContent: '',
+ renderer: renderer
+ },
+ {
+ data: '47_',
+ title: 'P&ID\n50',
+ defaultContent: '',
+ renderer: renderer
+ },
+ {
+ data: '46_',
+ title: 'P&ID\n50',
+ defaultContent: '',
+ renderer: renderer
+ },
+ {
+ data: '45_',
+ title: 'P&ID\n50',
+ defaultContent: '',
+ renderer: renderer
+ }, {
+ data: '44_',
+ title: 'P&ID\n50',
+ defaultContent: '',
+ renderer: renderer
+ },
+ {
+ data: '43_',
+ title: 'P&ID\n50',
+ defaultContent: '',
+ renderer: renderer
+ },
+ {
+ data: '42_',
+ title: 'P&ID\n50',
+ defaultContent: '',
+ renderer: renderer
+ },
+
+ {
+ data: '40_',
+ title: 'Plot Plan\n40',
+ defaultContent: '',
+ renderer: renderer,
+ columnStyle: {
+ 'border-right': {
+ 'stroke': '#abaaad',
+ 'stroke-width': '1'
+ }
+ }
+ }
+ ]
+ }
+
+ this.data = {};
+
+ this.data.tableData = [];
+ //테이블 뷰 데이터
+ this.data.viewData = {
+ pageLength: undefined,
+ currentPage: undefined,
+ columnHeight: undefined,
+ columns: {},
+ rows: [],
+ grid: []
+ }
+
+ //최초 draw 여부
+ this.firstRender = false;
+
+ //어레이 move 추가.
+ Array.prototype.move = function (old_index, new_index) {
+ while (old_index < 0) {
+ old_index += this.length;
+ }
+ while (new_index < 0) {
+ new_index += this.length;
+ }
+ if (new_index >= this.length) {
+ var k = new_index - this.length;
+ while ((k--) + 1) {
+ this.push(undefined);
+ }
+ }
+ this.splice(new_index, 0, this.splice(old_index, 1)[0]);
+ return this; // for testing purposes
+ };
+};
+OG.shape.component.DataTable.prototype = new OG.shape.GroupShape();
+OG.shape.component.DataTable.superclass = OG.shape.GroupShape;
+OG.shape.component.DataTable.prototype.constructor = OG.shape.component.DataTable;
+OG.DataTable = OG.shape.component.DataTable;
+
+OG.shape.component.DataTable.prototype.createShape = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.geometry.Rectangle([0, 0], 100, 100);
+ this.geom.style = new OG.geometry.Style({
+ 'fill': '#ffffff',
+ 'fill-opacity': 0,
+ 'font-size': 10,
+ 'font-color': 'white',
+ 'stroke': 'none'
+ });
+ return this.geom;
+};
+
+OG.shape.component.DataTable.prototype.createSubShape = function () {
+ if (!this.geom.style.map) {
+ return;
+ }
+ this.sub = [];
+ var grid, gridData;
+ if (this.data.viewData.grid) {
+ for (var i = 0, leni = this.data.viewData.grid.length; i < leni; i++) {
+ gridData = this.data.viewData.grid[i];
+ grid = JSON.parse(JSON.stringify(gridData));
+ if (grid.value) {
+ grid.shape = eval('new ' + grid.shape + '(grid.value)');
+ } else {
+ grid.shape = eval('new ' + grid.shape + '()');
+ }
+ this.sub.push(grid);
+ }
+ }
+ return this.sub;
+}
+
+/**
+ * 테이블의 옵션을 변경한다.
+ * @param options
+ */
+OG.shape.component.DataTable.prototype.setOptions = function (options) {
+ if (options) {
+ for (var key in options) {
+ this.options[key] = options[key];
+ }
+ }
+}
+
+/**
+ * 테이블의 옵션을 반환한다.
+ * @return options
+ */
+OG.shape.component.DataTable.prototype.getOptions = function () {
+ return this.options;
+}
+
+/**
+ * 테이블의 데이터를 업데이트 한다.
+ * @param data
+ */
+OG.shape.component.DataTable.prototype.setData = function (data) {
+ this.data.tableData = data;
+}
+
+
+/**
+ * 주어진 칼럼과 데이터를 바탕으로, 범위에서 벗어난 셀들을 모두 삭제한다.
+ */
+OG.shape.component.DataTable.prototype.removeOutRangeCells = function (columns, dataToDraw) {
+ var me = this
+ //칼럼 삭제
+ var columnViews = me.data.viewData.columns;
+ var columnList = [];
+ for (var l = 0, lenl = columns.length; l < lenl; l++) {
+ columnList.push(columns[l].data);
+ }
+ for (var column in columnViews) {
+ if (columnList.indexOf(column) == -1) {
+ delete columnViews[column];
+ }
+ }
+
+ //row 삭제
+ var row;
+ var toDrawRowLength = dataToDraw.data.length;
+ var currentRowLength = me.data.viewData.rows.length;
+
+ for (var r = 0; r < currentRowLength; r++) {
+ row = me.data.viewData.rows[r];
+ for (var key in row.cells) {
+ var contents = row.cells[key].contents;
+ var cellToRemove = false;
+
+ //데이터 영역 밖의 row 는 전부 삭제한다.
+ if ((r + 1) > toDrawRowLength) {
+ cellToRemove = true;
+ }
+
+ //데이터 영역 안의 row 중 columnList 에 없는 것은 삭제한다.
+ else if (columnList.indexOf(key) == -1) {
+ cellToRemove = true;
+ }
+
+ if (cellToRemove) {
+ if (contents && contents.length) {
+ $.each(contents, function (c, contentId) {
+ if (me.currentCanvas.getElementById(contentId)) {
+ me.currentCanvas.removeShape(contentId);
+ }
+ })
+ }
+ delete row.cells[key];
+ }
+ }
+ }
+ me.data.viewData.rows.splice(toDrawRowLength, currentRowLength);
+}
+
+/**
+ * 주어진 좌표를 포함하는 셀뷰를 얻어온다.
+ */
+OG.shape.component.DataTable.prototype.getCellViewFromOffset = function (offset) {
+ var me = this;
+ var rows, cells, cellView, expectCellView, cellOffset, width, height;
+ rows = me.data.viewData.rows;
+ $.each(rows, function (index, row) {
+ cells = row.cells;
+ if (cells) {
+ for (var field in cells) {
+ cellView = cells[field];
+ cellOffset = cellView.offset;
+ width = cellView.width;
+ height = cellView.height;
+
+ //offset x,y 가 셀 바운더리 영역에 포함될 경우
+ if (cellOffset[0] <= offset[0] &&
+ cellOffset[0] + width >= offset[0] &&
+ cellOffset[1] <= offset[1] &&
+ cellOffset[1] + height >= offset[1]) {
+ expectCellView = cellView;
+ }
+ }
+ }
+ })
+
+ for (var field in me.data.viewData.columns) {
+ cellView = me.data.viewData.columns[field];
+ cellOffset = cellView.offset;
+ width = cellView.width;
+ height = cellView.height;
+
+ //offset x,y 가 셀 바운더리 영역에 포함될 경우
+ if (cellOffset[0] <= offset[0] &&
+ cellOffset[0] + width >= offset[0] &&
+ cellOffset[1] <= offset[1] &&
+ cellOffset[1] + height >= offset[1]) {
+ expectCellView = cellView;
+ }
+ }
+
+ return expectCellView;
+}
+
+/**
+ * 컨텐트 엘리먼트로부터 셀뷰를 얻어온다.
+ * @param contentElement
+ * @return OG.Cell
+ */
+OG.shape.component.DataTable.prototype.getCellViewFromContent = function (contentElement) {
+ var me = this;
+ var rows, expectCellView, cellView = null;
+ if (contentElement && contentElement.nodeType == 1 && me.currentCanvas.getRenderer().isShape(contentElement)) {
+ rows = me.data.viewData.rows;
+ $.each(rows, function (index, row) {
+ for (var field in row.cells) {
+ cellView = row.cells[field];
+ if (cellView['contents'] && cellView['contents'].length) {
+ $.each(cellView['contents'], function (idx, contentId) {
+ if (contentId == contentElement.id) {
+ expectCellView = cellView;
+ }
+ })
+ }
+ }
+ })
+ }
+ return expectCellView;
+}
+
+
+/**
+ * 셀의 데이터를 및 콘텐트를 삭제한다.
+ * @param cellView
+ * @param preventRemove
+ */
+OG.shape.component.DataTable.prototype.emptyCell = function (cellView, preventRemove) {
+ var me = this;
+ var data = me.getCellInformation(cellView);
+
+ //value 삭제
+ me.data.viewData.rows[data.rowIndex].cells[data.column]['value'] = null;
+ me.data.tableData[data.rowDataIndex][data.column] = null;
+
+ if (data.contents && data.contents.length) {
+ $.each(data.contents, function (i, contentElementId) {
+ if (!preventRemove) {
+ me.currentCanvas.removeShape(contentElementId, true);
+ }
+ })
+ }
+ me.data.viewData.rows[data.rowIndex].cells[data.column]['contents'] = [];
+ me.drawCell(cellView, true);
+}
+
+/**
+ * 주어진 컨텐트 엘리먼트를 셀에 추가시킨다.
+ * @param cellView
+ * @param contentElementsWithValue [{element:element,value:value}]
+ */
+OG.shape.component.DataTable.prototype.addCellContent = function (cellView, contentElementsWithValue) {
+ var me = this, contentElement, isExist, beforeIndex, newIndex, boundary, value;
+ var data = me.getCellInformation(cellView);
+ if (!data.contents) {
+ data.contents = [];
+ me.data.viewData.rows[data.rowIndex].cells[data.column]['contents'] = [];
+ }
+
+ if (!data.contentsPosition) {
+ data.contentsPosition = {}
+ }
+ if (!data.contentsPosition['arrangement']) {
+ data.contentsPosition['arrangement'] = 'horizontal';
+ }
+
+ var mergeToComparePosition = function (myElement, contentElementsWithValue) {
+ var list = [], compareElement;
+ for (var i = 0; i < contentElementsWithValue.length; i++) {
+ compareElement = contentElementsWithValue[i].element;
+ if (compareElement && compareElement.nodeType == 1 && me.currentCanvas.getRenderer().isShape(compareElement)) {
+ if (myElement.id != compareElement.id) {
+ list.push(compareElement);
+ }
+ }
+ }
+ for (var g = 0; g < data.contentElements.length; g++) {
+ list.push(data.contentElements[g]);
+ }
+ return list;
+ }
+ var elementsToAdd = [];
+ for (var i = 0; i < contentElementsWithValue.length; i++) {
+ contentElement = contentElementsWithValue[i].element;
+ if (contentElement && contentElement.nodeType == 1 && me.currentCanvas.getRenderer().isShape(contentElement)) {
+ //신규 컨텐트의 센터와 기존 컨텐트의 센터들 사이간의 위치 인덱스를 구한다.
+ isExist = false;
+ beforeIndex = 0;
+ newIndex = 0;
+ boundary = me.currentCanvas.getBoundary(contentElement);
+ var mergedElements = mergeToComparePosition(contentElement, contentElementsWithValue);
+ $.each(mergedElements, function (i, existContent) {
+ if (existContent.id == contentElement.id) {
+ isExist = true;
+ beforeIndex = i;
+ }
+ //가로 방향 정렬일경우 x 포지션 비교
+ if (data.contentsPosition['arrangement'] == 'horizontal') {
+ var x = me.currentCanvas.getBoundary(existContent).getCentroid().x;
+ if (x < boundary.getCentroid().x) {
+ newIndex = i + 1;
+ }
+ }
+ //세로 방향 정렬일경우 y 포지션 비교
+ else {
+ var y = me.currentCanvas.getBoundary(existContent).getCentroid().y;
+ if (y < boundary.getCentroid().y) {
+ newIndex = i + 1;
+ }
+ }
+ })
+ elementsToAdd.push({
+ element: contentElement,
+ value: contentElementsWithValue[i].value,
+ isExist: isExist,
+ beforeIndex: beforeIndex,
+ newIndex: newIndex
+ });
+ }
+ }
+
+ for (var c = 0; c < elementsToAdd.length; c++) {
+ isExist = elementsToAdd[c].isExist;
+ beforeIndex = elementsToAdd[c].beforeIndex;
+ newIndex = elementsToAdd[c].newIndex;
+ value = elementsToAdd[c].value;
+ contentElement = elementsToAdd[c].element;
+
+ //신규 컨텐트를 추가하는 경우
+ if (!isExist) {
+ //다른 셀에서 contentElement 를 사용한다면 연결을 해제한다.
+ var removeValue = me.removeCellContent(contentElement, true);
+ //주어진 value 가 없고, 이전 사용중인 셀이 있다면 이전 사용중인 셀에서 가져온 value 로 대체한다.
+ if (!value && removeValue) {
+ value = removeValue;
+ }
+ //그래도 value 가 없다면 contentElement 의 데이터로 대체한다.
+ if (!value) {
+ value = contentElement.shape.data ? contentElement.shape.data : {};
+ }
+
+ //뷰테이블 추가
+ me.data.viewData.rows[data.rowIndex].cells[data.column]['contents'].splice(newIndex, 0, contentElement.id);
+
+ //해당 value 를 인덱스에 추가한다.
+ if (!me.data.viewData.rows[data.rowIndex].cells[data.column]['value']) {
+ me.data.viewData.rows[data.rowIndex].cells[data.column]['value'] = [];
+ me.data.tableData[data.rowDataIndex][data.column] = [];
+ }
+ if (typeof me.data.viewData.rows[data.rowIndex].cells[data.column]['value'] == 'object') {
+ me.data.viewData.rows[data.rowIndex].cells[data.column]['value'].splice(newIndex, 0, value);
+ me.data.tableData[data.rowDataIndex][data.column] =
+ JSON.parse(JSON.stringify(me.data.viewData.rows[data.rowIndex].cells[data.column]['value']));
+ }
+
+ me.drawCell(cellView, true);
+ }
+ //같은 셀 내부에서 컨텐트를 이동하는 경우
+ else {
+ //위치가 같은 경우 순서를 조작하지 않음.
+ if (beforeIndex == newIndex) {
+ me.redrawCell(cellView);
+ }
+ else {
+ //뒤로 순서가 이동하는 경우, 자신의 기존 인덱스를 고려하여 newIndex 에서 1 후퇴.
+ if (newIndex > beforeIndex) {
+ newIndex = newIndex - 1;
+ }
+ me.data.viewData.rows[data.rowIndex].cells[data.column]['contents'].move(beforeIndex, newIndex);
+ me.data.viewData.rows[data.rowIndex].cells[data.column]['value'].move(beforeIndex, newIndex);
+ me.data.tableData[data.rowDataIndex][data.column] =
+ JSON.parse(JSON.stringify(me.data.viewData.rows[data.rowIndex].cells[data.column]['value']));
+ me.redrawCell(me.refreshCellView(cellView));
+ }
+ }
+ }
+};
+
+/**
+ * 주어진 컨텐트 엘리먼트를 셀에서 제외시킨다.
+ * @param contentElement
+ * @param preventRemove
+ * @return removeValue 엘리먼트가 삭제되면서 같이 삭제된 value
+ */
+OG.shape.component.DataTable.prototype.removeCellContent = function (contentElement, preventRemove) {
+ var me = this;
+ var cellView = me.getCellViewFromContent(contentElement);
+ if (!cellView) {
+ return null;
+ }
+ var data = me.getCellInformation(cellView);
+ var removeValue;
+ if (data.contents && data.contents.length) {
+ $.each(data.contents, function (i, contentElementId) {
+ if (contentElementId == contentElement.id) {
+ if (!preventRemove) {
+ me.currentCanvas.removeShape(contentElement, true);
+ }
+ //뷰테이블 내용 삭제
+ me.data.viewData.rows[data.rowIndex].cells[data.column]['contents'].splice(i, 1);
+
+ //해당 value 인덱스가 있다면 삭제.
+ var value = me.data.viewData.rows[data.rowIndex].cells[data.column]['value'];
+ if (value && typeof value == 'object') {
+ var copy = JSON.parse(JSON.stringify(value));
+ value.splice(i, 1);
+ me.data.tableData[data.rowDataIndex][data.column] = value;
+ removeValue = copy[i];
+ }
+ me.drawCell(cellView, true);
+ }
+ })
+ }
+ return removeValue;
+}
+/**
+ * 셀을 원상복귀하여 다시 그린다.
+ * @param cellView
+ */
+OG.shape.component.DataTable.prototype.redrawCell = function (cellView) {
+ var me = this;
+ me.drawCell(cellView, 'saved');
+}
+
+/**
+ * Cell 의 viewData 에 저장된 연결정보를 바탕으로 Cell 의 컨텐트에 Edge 를 재연결시킨다.
+ * Cell 아이디를 기반으로 찾아가게 해야 한다.
+ * @param cellView
+ */
+OG.shape.component.DataTable.prototype.reconnectEdgesToContent = function (cellView) {
+ var me = this;
+ var edge, shapeId, replace, fromTerminal, toTerminal, direction, contentIndex, contentElement;
+ var data = me.getCellInformation(cellView);
+ var connection = me.data.viewData.rows[data.rowDataIndex].cells[data.column]['connection'];
+ if (connection && connection.length && data.contentElements) {
+ for (var i = 0, leni = connection.length; i < leni; i++) {
+ contentIndex = connection[i].contentIndex;
+ edge = me.currentCanvas.getElementById(connection[i].edgeId);
+ fromTerminal = connection[i].from;
+ toTerminal = connection[i].to;
+ direction = connection[i].direction;
+ contentElement = data.contentElements[contentIndex];
+ if (!edge) {
+ continue;
+ }
+ if (direction == 'from') {
+ shapeId = fromTerminal.substring(0, fromTerminal.indexOf(OG.Constants.TERMINAL));
+ replace = fromTerminal.replace(shapeId, contentElement.id);
+ me.currentCanvas.getRenderer().connect(replace, null, edge, null, null, true);
+ }
+ if (direction == 'to') {
+ shapeId = toTerminal.substring(0, toTerminal.indexOf(OG.Constants.TERMINAL));
+ replace = toTerminal.replace(shapeId, contentElement.id);
+ me.currentCanvas.getRenderer().connect(null, replace, edge, null, null, true);
+ }
+ }
+ me.data.viewData.rows[data.rowDataIndex].cells[data.column]['connection'] = [];
+ } else if (connection && connection.length && !data.contentElements) {
+ for (var i = 0, leni = connection.length; i < leni; i++) {
+ edge = me.currentCanvas.getElementById(connection[i].edgeId);
+ if (edge) {
+ me.currentCanvas.removeShape(edge, true);
+ }
+ }
+ }
+}
+
+/**
+ * CellView 의 콘텐츠와 연결된 Edge 를 연결 해제하고, 재연결 정보를 viewData 에 저장한다.
+ * @param cellView
+ */
+OG.shape.component.DataTable.prototype.keepEdgesFromContent = function (cellView) {
+ var me = this;
+ var data = me.getCellInformation(cellView);
+ var edge, fromShape, toShape, fromXY, toXY;
+ $.each(data.contentElements, function (contentIndex, contentElement) {
+ var prevEdges = me.currentCanvas.getPrevEdges(contentElement);
+ var nextEdges = me.currentCanvas.getNextEdges(contentElement);
+ var edges = prevEdges.concat(nextEdges);
+ for (var i = 0, leni = edges.length; i < leni; i++) {
+ edge = edges[i];
+ var fromTerminal = $(edge).attr("_from");
+ var toTerminal = $(edge).attr("_to");
+ var direction;
+
+ if (fromTerminal) {
+ fromShape = me.currentCanvas.getRenderer()._getShapeFromTerminal(fromTerminal);
+ fromXY = me.currentCanvas.getRenderer()._getPositionFromTerminal(fromTerminal);
+ if (fromShape && fromShape.id == contentElement.id) {
+ me.currentCanvas.getRenderer().disconnectOneWay(edge, 'from');
+ direction = 'from';
+ }
+ }
+
+ if (toTerminal) {
+ toShape = me.currentCanvas.getRenderer()._getShapeFromTerminal(toTerminal);
+ toXY = me.currentCanvas.getRenderer()._getPositionFromTerminal(toTerminal);
+ if (toShape && toShape.id == contentElement.id) {
+ me.currentCanvas.getRenderer().disconnectOneWay(edge, 'to');
+ direction = 'to';
+ }
+ }
+ if (!me.data.viewData.rows[data.rowDataIndex].cells[data.column]['connection']) {
+ me.data.viewData.rows[data.rowDataIndex].cells[data.column]['connection'] = [];
+ }
+
+ var connection = me.data.viewData.rows[data.rowDataIndex].cells[data.column]['connection'];
+ connection.push({
+ contentIndex: contentIndex,
+ edgeId: edge.id,
+ from: fromTerminal,
+ to: toTerminal,
+ direction: direction
+ });
+ }
+ });
+}
+
+/**
+ * 셀의 정보를 가져온다.
+ * @param cell
+ * @return {Object}
+ * {
+ * ...
+ * columnOption : {Object}
+ * contentElements: [OG Element]
+ * tableElement: OG Element
+ * textElement: OG Element
+ * }
+ */
+OG.shape.component.DataTable.prototype.getCellInformation = function (cellView) {
+ var me = this;
+ var view;
+ if (cellView.type == 'column') {
+ view = me.data.viewData.columns[cellView.column];
+ } else {
+ view = me.data.viewData.rows[cellView.rowIndex].cells[cellView.column];
+ }
+ if (!view) {
+ return null;
+ }
+ var info = JSON.parse(JSON.stringify(view));
+ var contentElement;
+ info.contentElements = [];
+ if (info.contents && info.contents.length) {
+ $.each(info.contents, function (index, contentId) {
+ contentElement = me.currentCanvas.getElementById(contentId);
+ if (contentElement) {
+ info.contentElements.push(contentElement);
+ }
+ })
+ }
+
+ info.tableElement = me.currentElement;
+ info.columnOption = me.getColumnByField(info.column);
+ return info;
+}
+
+OG.shape.component.DataTable.prototype.refreshCellView = function (cellView) {
+ var me = this;
+ var view;
+ if (cellView.type == 'column') {
+ view = me.data.viewData.columns[cellView.column];
+ } else {
+ view = me.data.viewData.rows[cellView.rowIndex].cells[cellView.column];
+ }
+ if (!view) {
+ return null;
+ }
+ return JSON.parse(JSON.stringify(view));
+}
+
+
+/**
+ * 필드명에 해당하는 옵션의 컬럼 정보를 반환한다.
+ * @param field
+ * @returns {*}
+ */
+OG.shape.component.DataTable.prototype.getColumnByField = function (field) {
+ var me = this;
+ for (var i = 0, leni = me.options.columns.length; i < leni; i++) {
+ if (me.options.columns[i].data == field) {
+ return me.options.columns[i];
+ }
+ }
+}
+
+/**
+ * 현재 테이블의 cell, row 인덱스 값으로 cellView 을 반환한다.
+ * @param cellIndex
+ * @param rowIndex
+ * @return OG.Cell
+ */
+OG.shape.component.DataTable.prototype.getCellFromTableIndex = function (cellIndex, rowIndex) {
+ var me = this;
+ var column = me.options.columns[cellIndex];
+ if (!column) {
+ throw new Error('cellIndex ' + cellIndex + ' is out bound from table columns');
+ }
+ var row = me.data.viewData.rows[rowIndex];
+ if (!row) {
+ throw new Error('rowIndex ' + rowIndex + ' is out bound from table rows');
+ }
+ var field = column['data'];
+ return me.data.viewData.rows[rowIndex].cells[field];
+}
+/**
+ * 리사이즈로 인한 draw 여부.
+ * @param isResize
+ * @param isAddColumn
+ */
+OG.shape.component.DataTable.prototype.draw = function (isResize, isAddColumn) {
+
+ var startDate = new Date();
+ var me = this;
+ me.data.viewData.grid = [];
+
+ var boundary = me.currentCanvas.getBoundary(me.currentElement);
+ if (!me.options.columns) {
+ throw new Error('No column options to render');
+ }
+ if (!me.data || !me.data.tableData) {
+ throw new Error('No table data to render');
+ }
+
+ var startP = boundary.getUpperLeft();
+ var startX = 0;
+ var startY = 0;
+ var nextY;
+
+ //칼럼 리스트
+ var columns = me.options.columns;
+ var column;
+
+
+ //드로잉 할 데이터 영역구하기
+ var dataToDraw = me.getDataToDraw();
+ //칼럼, dataToDraw 영역 밖에 요소 삭제하기.
+ if (!isResize) {
+ me.removeOutRangeCells(columns, dataToDraw);
+ } else if (isAddColumn) {
+ me.removeOutRangeCells(columns, dataToDraw);
+ }
+
+ var cellStyle, cellSize, style;
+ //칼럼 그리기
+ for (var i = 0, leni = columns.length; i < leni; i++) {
+ column = columns[i];
+ cellStyle = me.getCellStyle('column', column);
+ cellSize = cellStyle.size;
+ style = cellStyle.style;
+
+ //뷰 데이터에 저장
+ if (!me.data.viewData.columns[column.data]) {
+ me.data.viewData.columns[column.data] = {};
+ }
+ me.data.viewData.columns[column.data]['type'] = 'column';
+ me.data.viewData.columns[column.data]['width'] = cellSize[0];
+ me.data.viewData.columns[column.data]['height'] = cellSize[1];
+ me.data.viewData.columns[column.data]['top'] = startY;
+ me.data.viewData.columns[column.data]['left'] = startX;
+ me.data.viewData.columns[column.data]['offset'] = [startX + startP.x, startY + startP.y];
+ me.data.viewData.columns[column.data]['style'] = style;
+ me.data.viewData.columns[column.data]['tableId'] = me.currentElement.id;
+ me.data.viewData.columns[column.data]['column'] = column.data;
+ me.data.viewData.columns[column.data]['text'] = column.title;
+ me.data.viewData.columns[column.data]['cellIndex'] = i;
+
+ //칼럼 subShape 를 추가한다.
+ me.data.viewData.grid.push({
+ value: column.title,
+ shape: 'OG.Cell',
+ width: cellSize[0] + 'px',
+ height: cellSize[1] + 'px',
+ top: startY + 'px',
+ left: startX + 'px',
+ style: style
+ });
+
+ //다음 셀의 시작 x 를 증가시킨다.
+ startX = startX + cellSize[0];
+
+ //nextY 에는 다음 row 를 위한 값을 지정한다.
+ nextY = startY + cellSize[1];
+ }
+ //최종 가로 사이즈.
+ var totalWidth = startX;
+
+
+ //데이터 그리기
+ me.data.viewData.currentPage = dataToDraw.currentPage;
+ var rowData;
+ var rowDataIndex;
+ var rowIndex;
+ for (var d = 0, lend = dataToDraw.data.length; d < lend; d++) {
+
+ //그려야 할 row
+ rowData = dataToDraw.data[d];
+ rowIndex = d;
+ rowDataIndex = me.data.viewData.pageLength * (me.data.viewData.currentPage - 1) + d;
+
+ //starX 는 원점으로 돌린다. //startY 는 nextY 를 상속한다.
+ startX = 0;
+ startY = nextY;
+
+ //셀 그리기
+ for (var c = 0, lenc = columns.length; c < lenc; c++) {
+ column = columns[c];
+ cellStyle = me.getCellStyle('cell', column, rowIndex, rowDataIndex);
+ cellSize = cellStyle.size;
+ style = cellStyle.style;
+
+ //rowData 에 칼럼에 해당하는 필드가 있는지 찾는다.
+ var value = null;
+ for (var key in rowData) {
+ if (column.data == key) {
+ value = rowData[key];
+ }
+ }
+
+ //뷰 데이터에 저장
+ if (!me.data.viewData.rows[rowIndex].cells[column.data]) {
+ me.data.viewData.rows[rowIndex].cells[column.data] = {}
+ }
+ me.data.viewData.rows[rowIndex].cells[column.data]['type'] = 'cell';
+ me.data.viewData.rows[rowIndex].cells[column.data]['width'] = cellSize[0];
+ me.data.viewData.rows[rowIndex].cells[column.data]['height'] = cellSize[1];
+ me.data.viewData.rows[rowIndex].cells[column.data]['top'] = startY;
+ me.data.viewData.rows[rowIndex].cells[column.data]['left'] = startX;
+ me.data.viewData.rows[rowIndex].cells[column.data]['offset'] = [startX + startP.x, startY + startP.y];
+ me.data.viewData.rows[rowIndex].cells[column.data]['style'] = style;
+ me.data.viewData.rows[rowIndex].cells[column.data]['tableId'] = me.currentElement.id;
+ me.data.viewData.rows[rowIndex].cells[column.data]['value'] = value;
+ me.data.viewData.rows[rowIndex].cells[column.data]['rowDataIndex'] = rowDataIndex;
+ me.data.viewData.rows[rowIndex].cells[column.data]['rowIndex'] = rowIndex;
+ me.data.viewData.rows[rowIndex].cells[column.data]['cellIndex'] = c;
+ me.data.viewData.rows[rowIndex].cells[column.data]['column'] = column.data;
+
+ //value 가 스트링 또는 number 일 경우 subShape 를 추가한다.
+ if (typeof value == 'string' || typeof value == 'number') {
+ var ignoreBorderStyle = JSON.parse(JSON.stringify(style));
+ ignoreBorderStyle['border-left'] = null;
+ ignoreBorderStyle['border-right'] = null;
+ ignoreBorderStyle['border-top'] = null;
+ ignoreBorderStyle['border-bottom'] = null;
+ me.data.viewData.grid.push({
+ value: value,
+ shape: 'OG.Cell',
+ width: cellSize[0] + 'px',
+ height: cellSize[1] + 'px',
+ top: startY + 'px',
+ left: startX + 'px',
+ style: ignoreBorderStyle
+ });
+ me.data.viewData.rows[rowIndex].cells[column.data]['text'] = value;
+ }
+
+ if (isResize || isAddColumn) {
+ me.drawCell(me.data.viewData.rows[rowIndex].cells[column.data], 'saved', false);
+ } else {
+ me.drawCell(me.data.viewData.rows[rowIndex].cells[column.data], 'saved', true);
+ }
+
+ //다음 셀의 시작 x 를 증가시킨다.
+ startX = startX + cellSize[0];
+
+ //nextY 에는 다음 row 를 위한 값을 지정한다.
+ nextY = startY + cellSize[1];
+ }
+
+ //가로 줄을 추가한다. 가로 줄은 row 의 하단에 그린다.
+ me.options.rowDividingLine['arrow-end'] = 'none';
+ me.options.rowDividingLine['arrow-start'] = 'none';
+ me.data.viewData.grid.push({
+ shape: 'OG.EdgeShape',
+ vertices: [[0, nextY], [totalWidth, nextY]],
+ style: me.options.rowDividingLine
+ })
+ }
+
+ //세로 줄을 추가한다.
+ if (me.data.viewData.rows.length) {
+ for (var r = 0, lenr = columns.length; r < lenr; r++) {
+ column = columns[r];
+ cellStyle = me.data.viewData.rows[0].cells[column.data].style;
+ var left = me.data.viewData.rows[0].cells[column.data].left;
+ var width = me.data.viewData.rows[0].cells[column.data].width;
+ var columnHeight = me.data.viewData.columnHeight;
+ if (cellStyle['border-left']) {
+ cellStyle['border-left']['arrow-end'] = 'none';
+ cellStyle['border-left']['arrow-start'] = 'none';
+ me.data.viewData.grid.push({
+ shape: 'OG.EdgeShape',
+ vertices: [[left, columnHeight], [left, nextY]],
+ style: cellStyle['border-left']
+ })
+ }
+ if (cellStyle['border-right']) {
+ cellStyle['border-right']['arrow-end'] = 'none';
+ cellStyle['border-right']['arrow-start'] = 'none';
+ me.data.viewData.grid.push({
+ shape: 'OG.EdgeShape',
+ vertices: [[left + width, columnHeight], [left + width, nextY]],
+ style: cellStyle['border-right']
+ })
+ }
+ }
+ }
+
+ //subShape 들을 역순으로 배치한다.
+ me.data.viewData.grid.reverse();
+
+ //totalWidth, nextY 가 최종 테이블의 사이즈가 된다.
+ var currentWidth = boundary.getWidth();
+ var currentHeight = boundary.getHeight();
+ me.currentCanvas.resize(me.currentElement, [0, nextY - currentHeight, 0, totalWidth - currentWidth]);
+ var lowerRight = boundary.getLowerRight();
+ var canvasSize = me.currentCanvas.getCanvasSize();
+ //
+ var resizeCanvas = false;
+ if (canvasSize[0] < lowerRight.x + 5) {
+ canvasSize[0] = lowerRight.x + 5;
+ resizeCanvas = true;
+ }
+ if (canvasSize[1] < lowerRight.y + 5) {
+ canvasSize[1] = lowerRight.y + 5;
+ resizeCanvas = true;
+ }
+ if (resizeCanvas) {
+ me.currentCanvas.setCanvasSize(canvasSize);
+ }
+
+ if (!me.firstRender) {
+ me.firstRender = true;
+ me.bindCellEvent();
+ }
+
+ me.currentElement.data = me.data;
+}
+
+/**
+ * 데이터 테이블 클릭시 임시 셀을 생성하여 선택처리한다.
+ */
+OG.shape.component.DataTable.prototype.bindCellEvent = function () {
+ var me = this, offset, cellView;
+ $(me.currentElement).click(function (event) {
+ offset = me.currentCanvas._HANDLER._getOffset(event);
+ cellView = me.getCellViewFromOffset([offset.x, offset.y]);
+ me.createCellGuid(cellView);
+ });
+}
+
+OG.shape.component.DataTable.prototype.createCellGuid = function (cellView) {
+ var me = this;
+
+ if (me.options.selectable == 'column') {
+ if (cellView.type != 'column') {
+ return;
+ }
+ }
+ if (me.options.selectable == 'cell') {
+ if (cellView.type != 'cell') {
+ return;
+ }
+ }
+
+ //기존 등록된 임시 셀을 모두 삭제토록.
+ var childs = me.currentCanvas.getChilds(me.currentElement);
+ for (var i = 0, leni = childs.length; i < leni; i++) {
+ if (childs[i].shape instanceof OG.Cell) {
+ me.currentCanvas.removeShape(childs[i]);
+ }
+ }
+
+ if (!cellView) {
+ return;
+ }
+
+ //columnEditable
+ var shape;
+ if (cellView.type == 'column') {
+ var columnOption = me.getColumnByField(cellView.column);
+ var columnEditable = columnOption.columnEditable ? columnOption.columnEditable : me.options.columnEditable;
+ shape = new OG.Cell(cellView.text);
+ shape.LABEL_EDITABLE = columnEditable;
+ } else {
+ shape = new OG.Cell();
+ shape.LABEL_EDITABLE = false;
+ }
+
+ //셀의 cellEditable 을 설정한다.
+ if (!shape.data) {
+ shape.data = {};
+ }
+ shape.data.dataTable = JSON.parse(JSON.stringify(cellView));
+
+ var cellElement = me.currentCanvas.drawShape(
+ [cellView.offset[0], cellView.offset[1]],
+ shape,
+ [cellView.width, cellView.height],
+ // {
+ // 'fill': '#fff',
+ // 'fill-opacity': '0'
+ // },
+ cellView.style,
+ null,
+ me.currentElement.id
+ )
+ me.fitToBoundary(cellElement, cellView.width, cellView.height, cellView.offset[0], cellView.offset[1]);
+
+ //칼럼이 아닌경우는 셀 컨텐트를 방해하지 않기 위해 뒤쪽으로 이동시킨다.
+ if (cellView.type != 'column') {
+ var firstChild = OG.Util.isIE() ? me.currentElement.childNodes[1] : me.currentElement.children[1];
+ if (firstChild.id != cellElement.id) {
+ me.currentElement.insertBefore(cellElement, OG.Util.isIE() ? me.currentElement.childNodes[1] : me.currentElement.children[1]);
+ }
+ }
+
+ $(cellElement).click();
+}
+
+OG.shape.component.DataTable.prototype.getCellStyle = function (type, column, rowIndex, rowDataIndex) {
+ var me = this;
+ var viewHeight, viewWidth, style;
+
+ //값의 우선순위 : options 값 < option 칼럼값 < view 값
+ if (type == 'column') {
+ //높이 얻기
+ viewHeight = me.data.viewData.columnHeight;
+
+ //viewHeight 없거나 X 리사이즈만 허용이라면
+ if (!viewHeight || me.options.resizeAxis == 'X') {
+ me.data.viewData.columnHeight = me.options.columnHeight;
+ viewHeight = me.options.columnHeight;
+ }
+
+ //가로 얻기
+ var viewColumn = me.data.viewData.columns[column.data];
+ if (!viewColumn || me.options.resizeAxis == 'Y') {
+ viewWidth = column.columnWidth ? column.columnWidth : me.options.columnWidth;
+ } else {
+ viewWidth = viewColumn.width
+ }
+
+ //스타일 얻기
+ style = column.columnStyle;
+ if (!style) {
+ style = JSON.parse(JSON.stringify(me.options.columnStyle));
+ } else {
+ var copy = JSON.parse(JSON.stringify(me.options.columnStyle));
+ for (var key in style) {
+ copy[key] = style[key];
+ }
+ style = copy;
+ }
+ }
+
+ //값의 우선순위 - height: options.cellHeight 값 < viewData.rows[rowIndex].rowHeight 값
+ //값의 우선순위 - width: viewData.columns[column.data].width
+ // 값의 우선순위 - style : options.cellStyle 값 < column.cellStyle 값
+ else if (type == 'cell') {
+ //rowIndex 뷰데이터 채우기
+ var rowIndexView = me.data.viewData.rows[rowIndex];
+ //뷰데이터에 해당 row 가 없을경우 새로 생성한다.
+ if (!rowIndexView) {
+ me.data.viewData.rows[rowIndex] = {
+ rowHeight: me.options.cellHeight,
+ rowIndex: rowIndex,
+ cells: {}
+ }
+ }
+ //rowDataIndex 는 매번 달라질 수 있으므로 뷰데이터에 덮어쓰도록 한다.
+ me.data.viewData.rows[rowIndex].rowDataIndex = rowDataIndex;
+
+ //높이 얻기
+ viewHeight = me.data.viewData.rows[rowIndex]['rowHeight'];
+ if (!viewHeight || me.options.resizeAxis == 'X') {
+ me.data.viewData.rows[rowIndex]['rowHeight'] = me.options.cellHeight;
+ viewHeight = me.options.cellHeight;
+ }
+
+ //가로 얻기
+ viewWidth = me.data.viewData.columns[column.data].width;
+
+ //스타일 얻기
+ style = column.cellStyle;
+ if (!style) {
+ style = JSON.parse(JSON.stringify(me.options.cellStyle));
+ } else {
+ var copy = JSON.parse(JSON.stringify(me.options.cellStyle));
+ for (var key in style) {
+ copy[key] = style[key];
+ }
+ style = copy;
+ }
+ }
+
+ return {
+ size: [viewWidth, viewHeight],
+ style: style
+ };
+}
+
+/**
+ * 셀 내부의 컨텐츠를 그린다.
+ * @param cellView
+ * @param info
+ * @param renderData
+ */
+OG.shape.component.DataTable.prototype.drawCellContent = function (cellView, info, renderData) {
+ var getLength = function (standard, value) {
+ var length;
+
+ //값이 없고, 0 이 아닐경우
+ if (!value && value != 0) {
+ length = undefined;
+ }
+ //숫자 형태인 경우
+ else if (typeof value == 'number') {
+ length = value;
+ } else if (typeof value == 'string') {
+ //픽셀인 경우
+ if (value.indexOf('px') != -1) {
+ length = parseFloat(value.replace('px', ''));
+ }
+ //퍼센테이지 경우
+ else if (value.indexOf('%') != -1) {
+ value = parseFloat(value.replace('%', ''));
+ length = standard * (value / 100);
+ }
+ //그외에는 숫자취급
+ else {
+ length = parseFloat(value);
+ }
+ }
+ return length;
+ };
+
+ var me = this;
+ var rowIndex = info.rowIndex;
+ var cellView = me.data.viewData.rows[info.rowIndex].cells[info.column];
+
+ //contentsPosition 이 없을경우 빈 오브젝트
+ if (!renderData['contentsPosition']) {
+ renderData['contentsPosition'] = {}
+ }
+ var contentsPosition = JSON.parse(JSON.stringify(renderData['contentsPosition']));
+ if (!contentsPosition['arrangement']) {
+ contentsPosition['arrangement'] = 'horizontal';
+ }
+ if (!contentsPosition['arrangementMargin']) {
+ contentsPosition['arrangementMargin'] = '10';
+ }
+
+ var contentB;
+ var contentW;
+ var contentH;
+
+
+ //기존 컨텐트 엘리먼트가 있는 경우
+ var isExistContents;
+ var contentSizeArr = [];
+ if (renderData.contentElements && renderData.contentElements.length) {
+ isExistContents = true;
+ $.each(renderData.contentElements, function (i, contentElement) {
+ contentB = me.currentCanvas.getBoundary(contentElement);
+ contentW = contentB.getWidth();
+ contentH = contentB.getHeight();
+ contentSizeArr.push({
+ width: contentW,
+ height: contentH
+ });
+ });
+ }
+ //신규 컨텐트인 경우
+ else if (renderData.contents && renderData.contents.length) {
+ isExistContents = false;
+ $.each(renderData.contents, function (i, contentData) {
+ contentW = getLength(info.width, contentData.width);
+ contentH = getLength(info.width, contentData.height);
+ contentSizeArr.push({
+ width: contentW,
+ height: contentH
+ });
+ })
+ }
+ // 그외의 경우
+ else {
+ return;
+ }
+
+ //컨텐츠 그룹의 크기를 구하고, 그룹안에서의 포지션을 정한다.
+ //수평일때는 가로를 더해가며, 세로는 가장 큰 사이즈로 잡는다.
+ //수직일때는 세로를 더해가며, 가로는 가장 큰 사이즈로 잡는다.
+ var groupWidth = 0;
+ var groupHeight = 0;
+ var arrangementMargin = 0;
+ if (contentsPosition.arrangement != 'horizontal' && contentsPosition.arrangement != 'vertical') {
+ contentsPosition.arrangement = 'horizontal';
+ }
+ if (contentsPosition.arrangement == 'horizontal') {
+ arrangementMargin = getLength(info.width, contentsPosition.arrangementMargin);
+ } else {
+ arrangementMargin = getLength(info.height, contentsPosition.arrangementMargin);
+ }
+ for (var i = 0; i < contentSizeArr.length; i++) {
+ contentW = contentSizeArr[i].width;
+ contentH = contentSizeArr[i].height;
+ if (contentsPosition.arrangement == 'horizontal') {
+ if (i > 0) {
+ contentSizeArr[i].left = groupWidth + arrangementMargin;
+ groupWidth = groupWidth + contentW + arrangementMargin;
+ } else {
+ contentSizeArr[i].left = groupWidth;
+ groupWidth = groupWidth + contentW;
+ }
+ if (groupHeight < contentH) {
+ groupHeight = contentH;
+ }
+ } else {
+ if (i > 0) {
+ contentSizeArr[i].top = groupHeight + arrangementMargin;
+ groupHeight = groupHeight + contentH + arrangementMargin;
+ } else {
+ contentSizeArr[i].top = groupHeight;
+ groupHeight = groupHeight + contentH;
+ }
+ if (groupWidth < contentW) {
+ groupWidth = contentW;
+ }
+ }
+ }
+
+ //최종 높이 또는 최종 넓이에 맞추어 그룹안에서의 포지션을 정한다.
+ var contentL, contentT;
+ for (var c = 0; c < contentSizeArr.length; c++) {
+ contentW = contentSizeArr[c].width;
+ contentH = contentSizeArr[c].height;
+ if (contentsPosition.arrangement == 'horizontal') {
+ contentT = (groupHeight - contentH) / 2;
+ contentSizeArr[c].top = contentT;
+ } else {
+ contentL = (groupWidth - contentW) / 2;
+ contentSizeArr[c].left = contentL;
+ }
+ }
+
+ //그룹이 셀에서 위치하는 포지션을 정한다.
+ var width = groupWidth;
+ var height = groupHeight;
+ var left = contentsPosition.left;
+ var top = contentsPosition.top;
+ var right = contentsPosition.right;
+ var bottom = contentsPosition.bottom;
+ var align = contentsPosition.align;
+ var verticalAlign = contentsPosition['vertical-align'];
+
+ var bW = info.width;
+ var bH = info.height;
+ var bL = info.offset[0];
+ var bT = info.offset[1];
+
+ //left , right , align 이 없을 경우 align 을 센터로.
+ if ((!left && left != 0) && (!right && right != 0) && !align) {
+ align = 'center';
+ }
+ //top , bottom , align 이 없을 경우 vertice-algin 을 미들로
+ if ((!top && top != 0) && (!bottom && bottom != 0) && !verticalAlign) {
+ verticalAlign = 'middle';
+ }
+
+ left = getLength(bW, left) + bL;
+ right = bL + bW - getLength(bW, right) - width;
+ top = getLength(bH, top) + bT;
+ bottom = bT + bH - getLength(bH, bottom) - height;
+
+ //right 가 있다면 left 보다 우선하고, bottom 이 있다면 top 보다 우선한다.
+ if (!right && right != 0) {
+
+ } else {
+ left = right;
+ }
+ if (!bottom && bottom != 0) {
+
+ } else {
+ top = bottom;
+ }
+
+ //align 이나 vertice-algin 이 있을 경우 left 와 top 값을 오버라이드 한다.
+ if (align == 'left') {
+ left = bL;
+ } else if (align == 'center') {
+ left = bL + (bW / 2) - (width / 2);
+ } else if (align == 'right') {
+ left = bL + bW - width;
+ }
+
+ if (verticalAlign == 'top') {
+ top = bT;
+ } else if (verticalAlign == 'middle') {
+ top = bT + (bH / 2) - (height / 2);
+ } else if (verticalAlign == 'bottom') {
+ top = bT + bH - height;
+ }
+
+ //그룹의 top,left,width,height 를 구했으므로, contentSizeArr 배치 정보에 따라 각 도형을 그려나가도록 한다.
+ var contentElement, contentCenter, renderShape, renderStyle;
+ var rootB = me.currentCanvas.getBoundary(me.currentElement);
+
+ //뷰데이터의 콘텐츠 정보 초기화.
+ me.data.viewData.rows[info.rowIndex].cells[info.column].contents = [];
+ $.each(contentSizeArr, function (index, contentSize) {
+ contentW = Math.round(contentSize.width);
+ contentH = Math.round(contentSize.height);
+ contentL = Math.round(contentSize.left + left);
+ contentT = Math.round(contentSize.top + top);
+ contentCenter = [Math.round(contentL + contentW / 2), Math.round(contentT + contentH / 2)];
+
+ //뷰 모드인경우
+ if (me.options.mode == 'view') {
+ renderShape = renderData.contents[index].shape;
+ renderStyle = renderData.contents[index].style;
+
+ me.data.viewData.grid.push({
+ value: renderShape.label,
+ shape: renderShape.SHAPE_ID,
+ width: contentW,
+ height: contentH,
+ top: contentCenter[1] - rootB.getUpperLeft().y,
+ left: contentCenter[0] - rootB.getUpperLeft().x,
+ style: renderStyle
+ });
+ me.data.viewData.rows[info.rowIndex].cells[info.column].contentsPosition = contentsPosition;
+ }
+ //에디트 모드인경우
+ else {
+ //기존 컨텐트인 경우
+ if (isExistContents) {
+ contentElement = renderData.contentElements[index];
+ var existBoundary = me.currentCanvas.getBoundary(contentElement);
+ if (existBoundary.getWidth() != contentW || existBoundary.getHeight() != contentH) {
+ me.currentCanvas.resizeBox(contentElement, [contentW, contentH], true);
+ }
+ if (existBoundary.getCentroid().x != contentCenter[0] || existBoundary.getCentroid().y != contentCenter[1]) {
+ me.currentCanvas.moveCentroid(contentElement, contentCenter, true);
+ }
+ //Edge 일 경우 캔버스 상단으로 위치시킨다.
+ if (contentElement.shape instanceof OG.shape.EdgeShape) {
+ if (me.currentCanvas.getParent(contentElement)) {
+ me.currentCanvas.toFront(contentElement);
+ }
+ }
+ //콘텐트 엘리먼트 속성을 설정한다.
+ if (contentElement.shape.CONNECT_CLONEABLE) {
+ contentElement.shape.CONNECT_CLONEABLE = false;
+ me.currentCanvas.getRenderer().redrawShape(contentElement);
+ }
+ }
+ //신규 컨텐트인 경우
+ else {
+ renderShape = renderData.contents[index].shape;
+ renderStyle = renderData.contents[index].style;
+
+ //콘텐트 엘리먼트에 속성을 설정한다.
+ //renderShape.RESIZABLE = false;
+ renderShape.COPYABLE = false;
+ renderShape.CONNECT_CLONEABLE = false;
+
+ //Edge 이면서 width,height 로 표현하는 경우, Edge 를 생성한후 리사이징, 이동시킨다.
+ if (renderShape instanceof OG.shape.EdgeShape) {
+ contentElement = me.currentCanvas.drawShape(
+ null,
+ renderShape,
+ null,
+ renderStyle,
+ null
+ //me.currentElement.id
+ );
+ me.currentCanvas.resizeBox(contentElement, [contentW, contentH], true);
+ me.currentCanvas.moveCentroid(contentElement, contentCenter, true);
+ }
+ else {
+ contentElement = me.currentCanvas.drawShape(
+ contentCenter,
+ renderShape,
+ [contentW, contentH],
+ renderStyle,
+ null,
+ me.currentElement.id
+ );
+ }
+ }
+
+ //셀의 axis 를 콘텐트 엘리먼트에 설정한다.
+ contentElement.shape.AXIS = me.options.axis;
+
+ //콘텐트 삭제시 처리
+ contentElement.shape.onRemoveShape = function () {
+ me.removeCellContent(contentElement);
+ }
+ //콘텐트 이동시 처리
+ contentElement.shape.onAddedToGroup = function (groupElement, element) {
+ //그룹이 소속된 테이블이 아닐 경우
+ if (groupElement.id != me.currentElement.id) {
+
+ //외부 드래그 허용이 아닌경우 테이블 그룹에 다시 추가 후 셀을 다시 그린다.
+ if (!me.options.enableMoveOutSide) {
+ me.currentElement.appendChild(contentElement);
+ me.redrawCell(cellView);
+ }
+ else {
+ //셀에 자신의 정보를 삭제한 후, 등록된 이벤트 핸들러들을 스스로 초기화시킨다.
+ me.removeCellContent(element, true);
+ element.shape.onRemoveShape = function () {
+ };
+ element.shape.onAddedToGroup = function () {
+ };
+ element.shape.onResize = function () {
+ };
+ }
+ }
+ }
+ //콘텐트 리사이즈시 처리
+ contentElement.shape.onResize = function (offset) {
+ me.redrawCell(cellView);
+ }
+
+ //셀 뷰데이터를 꾸민다.
+ me.data.viewData.rows[info.rowIndex].cells[info.column].contents.push(contentElement.id);
+ me.data.viewData.rows[info.rowIndex].cells[info.column].contentsPosition = contentsPosition;
+ }
+ });
+}
+
+/**
+ * 주어진 Boundary 영역 안으로 공간 기하 객체를 적용한다. left,top 기준(이동 & 리사이즈)
+ * @param element
+ * @param width
+ * @param height
+ * @param left
+ * @param top
+ * @return {Element}
+ */
+OG.shape.component.DataTable.prototype.fitToBoundary = function (element, width, height, left, top) {
+ var boundary = element.shape.geom.boundary,
+ newUpper = boundary.getUpperCenter().y - top,
+ newLower = (top + height) - boundary.getLowerCenter().y,
+ newLeft = boundary.getLeftCenter().x - left,
+ newRight = (left + width) - boundary.getRightCenter().x;
+ this.currentCanvas.getRenderer().resize(element, [newUpper, newLower, newLeft, newRight], true);
+ return element;
+}
+
+OG.shape.component.DataTable.prototype.fitToCenter = function (element, width, height, centerX, centerY) {
+ var boundary = element.shape.geom.boundary,
+ newUpper = boundary.getUpperCenter().y - top,
+ newLower = (top + height) - boundary.getLowerCenter().y,
+ newLeft = boundary.getLeftCenter().x - left,
+ newRight = (left + width) - boundary.getRightCenter().x;
+ this.currentCanvas.getRenderer().resize(element, [newUpper, newLower, newLeft, newRight], true);
+ return element;
+}
+
+/**
+ * 셀을 그린다.
+ * @param cellView
+ * @param ignoreRenderer
+ * @param forceRedraw
+ */
+OG.shape.component.DataTable.prototype.drawCell = function (cellView, ignoreRenderer, forceRedraw) {
+
+ var me = this;
+ var info = me.getCellInformation(cellView);
+
+ //forceRedraw 일 경우는 기존 api 또는 사용자 액션으로 일어난 콘텐츠의 뒤바뀜을 모두 원상복귀한다.
+ if (forceRedraw) {
+ //ignoreRenderer 를 false 로 고정한다.
+ ignoreRenderer = false;
+
+ //contentElements 가 있다면 Edge 를 연결해제 하고 저장한 후, Content 는 삭제한다.
+ if (info['contentElements'].length) {
+ me.keepEdgesFromContent(cellView);
+ $.each(info['contentElements'], function (i, element) {
+ me.currentCanvas.removeShape(element, true);
+ })
+
+ me.data.viewData.rows[info.rowIndex].cells[info.column]['contents'] = [];
+ me.data.viewData.rows[info.rowIndex].cells[info.column]['contentsPosition'] = null;
+ }
+ info['contentElements'] = [];
+ }
+
+ //ignoreRenderer 값을 재설정한다.
+ if (ignoreRenderer == 'saved') {
+ if (info.ignoreRenderer) {
+ ignoreRenderer = true;
+ } else {
+ ignoreRenderer = false;
+ }
+ } else if (ignoreRenderer && ignoreRenderer != 'saved') {
+ ignoreRenderer = true;
+ } else {
+ ignoreRenderer = false;
+ }
+
+
+ var renderData = {
+ contents: [],
+ contentsPosition: {},
+ contentElements: null
+ };
+ var useRenderData = false;
+
+ //ignoreRenderer 를 없애라.
+ //한번 렌더링 된 녀석은 draw 메소드가 오기 전까지 그대로 간다.
+ //updateCell 로 오게될 경우 컨텐트가 없어도 렌더링을 사용하지 않는다.
+ //텍스트, 컨텐트는 별개인데, 강제로 업데이트 시킬경우 표식이 필요.
+
+ //기존 컨텐트가 있는 경우는 ignoreRenderer 에 상관없이 컨텐트를 우선 표현한다.
+ if (info.contentElements && info.contentElements.length) {
+ renderData.contentElements = info.contentElements;
+ renderData.contentsPosition = info.contentsPosition;
+ useRenderData = true;
+ }
+ //컨텐트 엘리먼트가 없는 경우 렌더러가 있을 경우 표현한다.
+ // ignoreRenderer 일 경우는 렌더데이터를 사용하지 않으며, 렌더러가 없는 경우도 렌더데이터를 사용하지 않는다.
+ else {
+ if (!ignoreRenderer) {
+ if (info.columnOption.renderer) {
+ renderData = info.columnOption.renderer(info.value);
+ useRenderData = true;
+ } else {
+ useRenderData = false;
+ }
+ } else {
+ useRenderData = false;
+ }
+ }
+
+ //뷰 데이터에 꾸미기
+ me.data.viewData.rows[info.rowIndex].cells[info.column].ignoreRenderer = ignoreRenderer;
+
+ //셀 콘텐트 꾸미기
+ if (useRenderData) {
+ me.drawCellContent(cellView, info, renderData);
+ }
+ me.reconnectEdgesToContent(cellView);
+}
+
+/**
+ * tableData 중 현재 페이지에 그려질 범위를 반환한다.
+ * @returns {{pageLength: *, currentPage: *, data: Array}}
+ */
+OG.shape.component.DataTable.prototype.getDataToDraw = function () {
+ var me = this;
+ var tableData = me.data.tableData;
+ var pageLength = me.data.viewData.pageLength =
+ me.data.viewData.pageLength ? me.data.viewData.pageLength : me.options.pageLength;
+ var currentPage = me.data.viewData.currentPage =
+ me.data.viewData.currentPage ? me.data.viewData.currentPage : me.options.currentPage;
+ var startIdx = pageLength * (currentPage - 1);
+ var endIdx = startIdx + pageLength - 1;
+
+ var dataToDraw = [];
+ for (var i = startIdx; i <= endIdx; i++) {
+ if (tableData[i]) {
+ dataToDraw.push(tableData[i]);
+ }
+ }
+ return {
+ pageLength: pageLength,
+ currentPage: currentPage,
+ data: dataToDraw
+ }
+}
+
+
+/**
+ * 셀이 리사이즈 되었을때의 핸들러
+ */
+OG.shape.component.DataTable.prototype.onCellResize = function (cell, offset) {
+ //이웃한 셀의 크기 조정
+ //소속한 row 의 height 조정
+ var me = this;
+ if (me.options.resizeAxis == 'X') {
+ offset[0] == 0;
+ offset[1] == 0;
+ } else if (me.options.resizeAxis == 'Y') {
+ offset[3] == 0;
+ offset[4] == 0;
+ }
+
+ var boundary = me.currentCanvas.getBoundary(cell);
+ var cellView = cell.shape.data.dataTable;
+ var column = cellView.column;
+ var cellIndex = cellView.cellIndex;
+ var rowIndex = cellView.rowIndex;
+
+ if (cellView.type == 'column') {
+ //뷰 데이터의 columnHeight 를 변경한다.
+ if (me.options.resizeAxis != 'X') {
+ me.data.viewData.columnHeight = boundary.getHeight();
+ }
+ }
+ else if (cellView.type == 'cell') {
+ //뷰 데이터의 rowHeight 를 변경한다.
+ if (me.options.resizeAxis != 'X') {
+ me.data.viewData.rows[rowIndex].rowHeight = boundary.getHeight();
+ }
+ }
+
+ //뷰 칼럼의 width 를 변경한다.
+ if (me.options.resizeAxis != 'Y') {
+ var columnViews = me.data.viewData.columns;
+ columnViews[column].width = boundary.getWidth();
+ // if (columnViews[column].width < me.options.columnMinWidth) {
+ // columnViews[column].width = me.options.columnMinWidth;
+ // }
+
+ //이웃한 칼럼의 width 를 변경한다.
+ //offset 은 상,하,좌,우
+ var moveLeft = offset[2];
+ var moveRight = offset[3];
+
+ //좌측이 움직였을 경우
+ if (moveLeft != 0) {
+ var leftCell = me.options.columns[cellIndex - 1];
+ if (leftCell) {
+ columnViews[leftCell.data].width = columnViews[leftCell.data].width - moveLeft;
+ if (columnViews[leftCell.data].width < me.options.columnMinWidth) {
+ columnViews[leftCell.data].width = me.options.columnMinWidth;
+ }
+ }
+ }
+
+ //우측이 움직였을 경우는 이웃 칼럼의 처리를 하지 않음.
+ if (moveRight != 0) {
+
+ }
+ }
+ me.draw(true);
+ var refreshCellView = me.refreshCellView(cellView);
+ me.createCellGuid(refreshCellView);
+}
+
+//컨텍스트 메뉴에 셀
+OG.shape.component.DataTable.prototype.createContextMenu = function () {
+ return {};
+};
+
+/**
+ * 어떠한 도형이 사용자의 행위로 테이블로 끌어당겨졌을 경우
+ * @param groupElement
+ * @param elements
+ */
+OG.shape.component.DataTable.prototype.onAddToGroup = function (groupElement, elements, eventOffset) {
+ //해당 엘리먼트가 등록된 셀을 조회한다.
+ //있다면, 기존셀에서 현재셀로 콘텐트를 이동한다.
+ //없다면, 신규 콘텐트로 등록한다.
+ var me = this;
+ var beforeCell;
+ var dropCell, dropElements;
+ if (groupElement.id == me.currentElement.id) {
+
+ //셀 컨텐트를 부여하고 난 이후에 dropCell 이 달라지기 때문에 미리 배정을 한다.
+ var dropCellMap = {};
+ var noneDropCellList = [];
+ for (var b = 0; b < elements.length; b++) {
+ //셀이 이동되었을 경우 셀 무시
+ if (elements[b].shape instanceof OG.Cell) {
+ continue;
+ }
+
+ //콘텐트의 중심을 포함한 셀을 찾는다.
+ //var centroid = me.currentCanvas.getBoundary(elements[b]).getCentroid();
+ //var toDropCell = me.getCellViewFromOffset([centroid.x, centroid.y]);
+ var toDropCell = me.getCellViewFromOffset([eventOffset.x, eventOffset.y]);
+ if (toDropCell && toDropCell.type == 'column') {
+ toDropCell = null;
+ }
+ if (toDropCell) {
+ var rowIndex = toDropCell.rowIndex;
+ var cellIndex = toDropCell.cellIndex;
+ var dropKey = rowIndex + '_' + cellIndex + '_';
+ if (dropCellMap[dropKey]) {
+ dropCellMap[dropKey]['elements'].push(elements[b]);
+ } else {
+ dropCellMap[dropKey] = {
+ dropCell: toDropCell,
+ elements: [elements[b]]
+ }
+ }
+ } else {
+ noneDropCellList.push(elements[b]);
+ }
+ }
+
+ //드랍셀이 없는 엘리먼트를 먼저 처리한다.
+ for (var i = 0; i < noneDropCellList.length; i++) {
+ //콘텐트를 가지고 있던 기존 셀을 구한다.
+ beforeCell = me.getCellViewFromContent(noneDropCellList[i]);
+
+ //드랍셀이 없고 이전 셀도 없다면 콘텐트를 테이블 밖으로 빼야 한다.
+ //이 경우는 외부에서 드랍되었는데 칼럼으로 떨어진 경우다.
+ if (!beforeCell) {
+ me.currentCanvas.addToGroup(me.currentCanvas.getRootGroup(), [noneDropCellList[i]]);
+ continue;
+ }
+ //드랍셀이 없고 이전 셀이 있다면 원복시킨다.
+ //이 경우는 테이블 내에서 이동시켰는데 칼럼으로 떨어진 경우다.
+ if (beforeCell) {
+ me.redrawCell(beforeCell);
+ continue;
+ }
+ }
+
+ var beforeCellsToRedraw = [];
+ var addBeforeCellsToRedraw = function (cellView) {
+ var isExist = false;
+ $.each(beforeCellsToRedraw, function (b, beforeCellToRedraw) {
+ if (beforeCellToRedraw.rowIndex == cellView.rowIndex
+ && beforeCellToRedraw.cellIndex == cellView.cellIndex) {
+ isExist = true;
+ }
+ })
+ if (!isExist) {
+ beforeCellsToRedraw.push(cellView);
+ }
+ }
+ //드랍셀이 있는 경우의 처리.
+ for (var key in dropCellMap) {
+ dropCell = dropCellMap[key]['dropCell'];
+ dropElements = dropCellMap[key]['elements'];
+
+ //moveAxis 가 x 일 경우, dropElement 의 셀뷰 rowIndex 와 dropCell 의 rowIndex 가 하나라도 틀리면 리젝한다.
+ var enable = true;
+ if (me.options.moveAxis == 'X' || me.options.moveAxis == 'Y') {
+ $.each(dropElements, function (i, dropElement) {
+ beforeCell = me.getCellViewFromContent(dropElement);
+ if (beforeCell) {
+ if (me.options.moveAxis == 'X') {
+ if (beforeCell.rowIndex != dropCell.rowIndex) {
+ enable = false;
+ addBeforeCellsToRedraw(beforeCell);
+ }
+ }
+ if (me.options.moveAxis == 'Y') {
+ if (beforeCell.cellIndex != dropCell.cellIndex) {
+ enable = false;
+ addBeforeCellsToRedraw(beforeCell);
+ }
+ }
+ }
+ })
+ }
+
+ if (enable) {
+ var elementsWithValues = [];
+ for (var d = 0; d < dropElements.length; d++) {
+ elementsWithValues.push({
+ element: dropElements[d],
+ value: null
+ })
+ }
+ me.addCellContent(dropCell, elementsWithValues);
+ }
+ }
+
+ //beforeCellsToRedraw 에 속한 셀뷰를 리드로우 한다.
+ $.each(beforeCellsToRedraw, function (c, cellView) {
+ me.redrawCell(cellView);
+ })
+ }
+}
+
+OG.shape.component.DataTable.prototype.addColumn = function (columnOption, index) {
+ var me = this;
+ me.options.columns.splice(index, 0, columnOption);
+ //기존 등록된 임시 셀을 모두 삭제토록.
+ var childs = me.currentCanvas.getChilds(me.currentElement);
+ for (var i = 0, leni = childs.length; i < leni; i++) {
+ if (childs[i].shape instanceof OG.Cell) {
+ me.currentCanvas.removeShape(childs[i]);
+ }
+ }
+ me.draw(true);
+}
+
+OG.shape.component.DataTable.prototype.removeColumn = function (index) {
+ var me = this;
+ //기존 등록된 임시 셀을 모두 삭제토록.
+ var childs = me.currentCanvas.getChilds(me.currentElement);
+ for (var i = 0, leni = childs.length; i < leni; i++) {
+ if (childs[i].shape instanceof OG.Cell) {
+ me.currentCanvas.removeShape(childs[i]);
+ }
+ }
+
+ //잘라내기에 등록된 경우 잘라내기를 없앤다.
+ if (me.cutColumn == me.options.columns[index].data) {
+ me.cutColumn = null;
+ }
+
+ me.options.columns.splice(index, 1);
+ me.draw(false, true);
+}
+
+OG.shape.component.DataTable.prototype.cutAndPaste = function (beforeColumn, afterColumn) {
+ var me = this;
+ var beforeCell, afterCell, cellInformation;
+ $.each(me.data.viewData.rows, function (i, row) {
+ beforeCell = row.cells[beforeColumn];
+ afterCell = row.cells[afterColumn];
+ if (beforeCell && afterCell) {
+ cellInformation = me.getCellInformation(beforeCell);
+ if (cellInformation.contentElements && cellInformation.contentElements.length) {
+ var elementsWithValues = [];
+ $.each(cellInformation.contentElements, function (c, contentElement) {
+ elementsWithValues.push({
+ element: contentElement,
+ value: null
+ })
+ })
+ me.addCellContent(afterCell, elementsWithValues);
+ }
+ }
+ })
+}
+
+
+OG.shape.component.Cell = function (label) {
+ OG.shape.component.Cell.superclass.call(this);
+
+ this.SHAPE_ID = 'OG.shape.component.Cell';
+ this.label = label;
+ this.CONNECT_CLONEABLE = false;
+ this.LABEL_EDITABLE = false;
+ this.CONNECTABLE = false;
+ this.DELETABLE = false;
+ this.MOVABLE = false;
+ this.COPYABLE = true;
+};
+OG.shape.component.Cell.prototype = new OG.shape.GeomShape();
+OG.shape.component.Cell.superclass = OG.shape.GeomShape;
+OG.shape.component.Cell.prototype.constructor = OG.shape.component.Cell;
+OG.Cell = OG.shape.component.Cell;
+OG.shape.component.Cell.prototype.createShape = function () {
+ if (this.geom) {
+ return this.geom;
+ }
+
+ this.geom = new OG.geometry.Rectangle([0, 0], 100, 100);
+ this.geom.style = new OG.geometry.Style({
+ 'fill': 'none',
+ 'fill-opacity': 0,
+ "stroke": 'none',
+ 'font-size': 9
+ });
+
+ return this.geom;
+};
+
+OG.shape.component.Cell.prototype.createSubShape = function () {
+ if (!this.geom.style.map) {
+ return;
+ }
+
+ var createSub = function (direction, style) {
+ var copy = JSON.parse(JSON.stringify(style));
+ copy['arrow-end'] = 'none';
+ copy['arrow-start'] = 'none';
+ if (direction == 'left') {
+ return {
+ shape: new OG.EdgeShape(),
+ vertices: [['0%', '0%'], ['0%', '100%']],
+ style: copy
+ }
+ }
+ if (direction == 'right') {
+ return {
+ shape: new OG.EdgeShape(),
+ vertices: [['100%', '0%'], ['100%', '100%']],
+ style: copy
+ }
+ }
+ if (direction == 'top') {
+ return {
+ shape: new OG.EdgeShape(),
+ vertices: [['0%', '0%'], ['100%', '0%']],
+ style: copy
+ }
+ }
+ if (direction == 'bottom') {
+ return {
+ shape: new OG.EdgeShape(),
+ vertices: [['0%', '100%'], ['100%', '100%']],
+ style: copy
+ }
+ }
+ }
+ var me = this;
+ this.sub = [];
+ if (me.geom.style.map['border-left']) {
+ this.sub.push(
+ createSub('left', me.geom.style.map['border-left'])
+ )
+ }
+ if (me.geom.style.map['border-right']) {
+ this.sub.push(
+ createSub('right', me.geom.style.map['border-right'])
+ )
+ }
+ if (me.geom.style.map['border-top']) {
+ this.sub.push(
+ createSub('top', me.geom.style.map['border-top'])
+ )
+ }
+ if (me.geom.style.map['border-bottom']) {
+ this.sub.push(
+ createSub('bottom', me.geom.style.map['border-bottom'])
+ )
+ }
+ return this.sub;
+};
+
+OG.shape.component.Cell.prototype.onResize = function (offset) {
+ var me = this;
+ if (offset[0] == 0 && offset[1] == 0 && offset[2] == 0 && offset[3] == 0) {
+ return;
+ }
+ if (me.data && me.data.dataTable) {
+ var tableId = me.data.dataTable.tableId;
+ var table = me.currentCanvas.getElementById(tableId);
+ if (table) {
+ table.shape.onCellResize(me.currentElement, offset);
+ }
+ }
+}
+
+OG.shape.component.Cell.prototype.onPasteShape = function (copied, pasted) {
+
+}
+
+OG.shape.component.Cell.prototype.onDrawLabel = function (text) {
+ var me = this;
+ if (me.data && me.data.dataTable && me.data.dataTable.type == 'column') {
+ var cellView = me.data.dataTable;
+ var tableId = cellView.tableId;
+ var table = me.currentCanvas.getElementById(tableId);
+ if (table) {
+ var existColumn = table.shape.getColumnByField(cellView.column);
+ existColumn.title = text;
+ table.shape.draw(true);
+ }
+ }
+}
+OG.shape.component.Cell.prototype.createContextMenu = function () {
+ var me = this;
+
+ function guid() {
+ function s4() {
+ return Math.floor((1 + Math.random()) * 0x10000)
+ .toString(16)
+ .substring(1);
+ }
+
+ return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
+ s4() + '-' + s4() + s4() + s4();
+ }
+
+ //칼럼인 경우 행 추가 가능하다.
+ if (me.data && me.data.dataTable && me.data.dataTable.type == 'column') {
+ var cellView = me.data.dataTable;
+ var tableId = cellView.tableId;
+ var table = me.currentCanvas.getElementById(tableId);
+ if (table) {
+ this.contextMenu = {
+ 'left': {
+ name: '오른쪽 열 추가', callback: function () {
+ var existColumn = table.shape.getColumnByField(cellView.column);
+ table.shape.addColumn({
+ data: guid(),
+ title: '',
+ defaultContent: '',
+ renderer: existColumn.renderer,
+ columnEditable: true
+ }, cellView.cellIndex + 1);
+ }
+ },
+ 'right': {
+ name: '왼쪽 열 추가', callback: function () {
+ var existColumn = table.shape.getColumnByField(cellView.column);
+ table.shape.addColumn({
+ data: guid(),
+ title: '',
+ defaultContent: '',
+ renderer: existColumn.renderer,
+ columnEditable: true
+ }, cellView.cellIndex);
+ }
+ },
+ 'remove': {
+ name: '열 삭제', callback: function () {
+ table.shape.removeColumn(cellView.cellIndex);
+ }
+ },
+ 'cut': {
+ name: '잘라내기', callback: function () {
+ table.shape.cutColumn = cellView.column;
+ }
+ }
+ };
+ if (table.shape.cutColumn) {
+ this.contextMenu['paste'] = {
+ name: '붙여넣기', callback: function () {
+ var cutColumn = table.shape.cutColumn;
+ table.shape.cutColumn = null;
+ table.shape.cutAndPaste(cutColumn, cellView.column);
+ }
+ }
+ }
+
+ return this.contextMenu;
+ }
+ } else {
+ return {};
+ }
+};
+
+
+/**
+ * 도형의 Style 과 Shape 정보를 통해 캔버스에 렌더링 기능을 정의한 인터페이스
+ *
+ * @class
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ * @requires OG.shape.*
+ *
+ * @param {HTMLElement|String} container 컨테이너 DOM element or ID
+ * @param {Number[]} containerSize 컨테이너 Width, Height
+ * @param {String} backgroundColor 캔버스 배경색
+ * @param {String} backgroundImage 캔버스 배경이미지
+ * @param {Object} config Configuration
+ * @author Seungpil Park
+ */
+OG.renderer.IRenderer = function (container, containerSize, backgroundColor, backgroundImage, config) {
+ this._CONFIG = null;
+ this._PAPER = null;
+ this._ROOT_GROUP = null;
+ this._ETC_GROUP = null;
+ this._ID_PREFIX = Math.round(Math.random() * 10000);
+ this._LAST_ID = 0;
+ this._ELE_MAP = new OG.HashMap();
+};
+
+OG.renderer.IRenderer.prototype = {
+
+ /**
+ * ID를 generate 한다.
+ *
+ * @return {String} ID
+ * @private
+ */
+ _genId: function () {
+ var id = "OG_" + this._ID_PREFIX + "_" + this._LAST_ID;
+ this._LAST_ID++;
+ return id;
+ },
+
+ /**
+ * 시작좌표, 끝좌표를 연결하는 베지어 곡선의 콘트롤 포인트를 반환한다.
+ *
+ * @param {Number[]} from 시작좌표
+ * @param {Number[]} to 끝좌표
+ * @param {String} fromDirection 방향(E,W,S,N)
+ * @param {String} toDirection 방향(E,W,S,N)
+ * @return {Number[][]} [시작좌표, 콘트롤포인트1, 콘트롤포인트2, 끝좌표]
+ * @private
+ */
+ _bezierCurve: function (from, to, fromDirection, toDirection) {
+ var coefficient = 100, direction1 = [1, 0], direction2 = [-1, 0],
+ distance, d1, d2, bezierPoints = [];
+
+ distance = Math.sqrt(Math.pow(from[0] - to[0], 2) + Math.pow(from[1] - to[1], 2));
+ if (distance < coefficient) {
+ coefficient = distance / 2;
+ }
+
+ switch (fromDirection.toLowerCase()) {
+ case "e":
+ direction1 = [1, 0];
+ break;
+ case "w":
+ direction1 = [-1, 0];
+ break;
+ case "s":
+ direction1 = [0, 1];
+ break;
+ case "n":
+ direction1 = [0, -1];
+ break;
+ default:
+ direction1 = [1, 0];
+ break;
+ }
+
+ switch (toDirection.toLowerCase()) {
+ case "e":
+ direction2 = [1, 0];
+ break;
+ case "w":
+ direction2 = [-1, 0];
+ break;
+ case "s":
+ direction2 = [0, 1];
+ break;
+ case "n":
+ direction2 = [0, -1];
+ break;
+ default:
+ direction2 = [-1, 0];
+ break;
+ }
+
+ // Calculating the direction vectors d1 and d2
+ d1 = [direction1[0] * coefficient, direction1[1] * coefficient];
+ d2 = [direction2[0] * coefficient, direction2[1] * coefficient];
+
+ // Bezier Curve Poinsts(from, control_point1, control_point2, to)
+ bezierPoints[0] = from;
+ bezierPoints[1] = [from[0] + d1[0], from[1] + d1[1]];
+ bezierPoints[2] = [to[0] + d2[0], to[1] + d2[1]];
+ bezierPoints[3] = to;
+
+ return bezierPoints;
+ },
+
+ /**
+ * Edge Direction 을 보정한다.
+ *
+ * @param {Number[]} from 시작위치
+ * @param {Number[]} to 끝위치
+ * @return {String[]} edge-direction 보정된 edge-direction
+ * @private
+ */
+ _adjustEdgeDirection: function (from, to) {
+ var fromDrct, toDrct;
+ var fromXY = {x: from[0], y: from[1]}, toXY = {x: to[0], y: to[1]};
+
+ if (fromXY.x <= toXY.x && fromXY.y <= toXY.y) {
+ if (Math.abs(toXY.x - fromXY.x) > Math.abs(toXY.y - fromXY.y)) {
+ fromDrct = "e";
+ toDrct = "w";
+ } else {
+ fromDrct = "s";
+ toDrct = "n";
+ }
+ } else if (fromXY.x <= toXY.x && fromXY.y > toXY.y) {
+ if (Math.abs(toXY.x - fromXY.x) > Math.abs(toXY.y - fromXY.y)) {
+ fromDrct = "e";
+ toDrct = "w";
+ } else {
+ fromDrct = "n";
+ toDrct = "s";
+ }
+ } else if (fromXY.x > toXY.x && fromXY.y <= toXY.y) {
+ if (Math.abs(toXY.x - fromXY.x) > Math.abs(toXY.y - fromXY.y)) {
+ fromDrct = "w";
+ toDrct = "e";
+ } else {
+ fromDrct = "s";
+ toDrct = "n";
+ }
+ } else if (fromXY.x > toXY.x && fromXY.y > toXY.y) {
+ if (Math.abs(toXY.x - fromXY.x) > Math.abs(toXY.y - fromXY.y)) {
+ fromDrct = "w";
+ toDrct = "e";
+ } else {
+ fromDrct = "n";
+ toDrct = "s";
+ }
+ }
+
+ return [fromDrct, toDrct];
+ },
+
+ /**
+ * 터미널로부터 부모 Shape element 를 찾아 반환한다.
+ *
+ * @param {Element|String} terminal 터미널 Element or ID
+ * @return {Element} Shape element
+ * @private
+ */
+ _getShapeFromTerminal: function (terminal) {
+ var element;
+ if (terminal) {
+ var shapeId = terminal.substring(0, terminal.indexOf(OG.Constants.TERMINAL));
+ element = this.getElementById(shapeId);
+ }
+ return element;
+ },
+
+ /**
+ * Shape 을 캔버스에 위치 및 사이즈 지정하여 드로잉한다.
+ *
+ * @example
+ * renderer.drawShape([100, 100], new OG.CircleShape(), [50, 50], {stroke:'red'});
+ *
+ * @param {Number[]} position 드로잉할 위치 좌표(중앙 기준)
+ * @param {OG.shape.IShape} shape Shape
+ * @param {Number[]} size Shape Width, Height
+ * @param {OG.geometry.Style|Object} style 스타일
+ * @param {String} id Element ID 지정
+ * @return {Element} Group DOM Element with geometry
+ */
+ drawShape: function (position, shape, size, style, id) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * Geometry 를 캔버스에 드로잉한다.
+ *
+ * @param {OG.geometry.Geometry} geometry 기하 객체
+ * @param {OG.geometry.Style|Object} style 스타일
+ * @return {Element} Group DOM Element with geometry
+ */
+ drawGeom: function (geometry, style, id) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * Text 를 캔버스에 위치 및 사이즈 지정하여 드로잉한다.
+ * (스타일 'text-anchor': 'start' or 'middle' or 'end' 에 따라 위치 기준이 다름)
+ *
+ * @example
+ * renderer.drawText([100, 100], 'Hello', null, {'text-anchor':'start'});
+ *
+ * @param {Number[]} position 드로잉할 위치 좌표(스타일 'text-anchor': 'start' or 'middle' or 'end' 에 따라 기준이 다름)
+ * @param {String} text 텍스트
+ * @param {Number[]} size Text Width, Height, Angle
+ * @param {OG.geometry.Style|Object} style 스타일
+ * @param {String} id Element ID 지정
+ * @return {Element} DOM Element
+ */
+ drawText: function (position, text, size, style, id) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * Image 를 캔버스에 위치 및 사이즈 지정하여 드로잉한다.
+ *
+ * @example
+ * renderer.drawImage([100, 100], 'img.jpg', [50, 50]);
+ *
+ * @param {Number[]} position 드로잉할 위치 좌표(좌상단 기준)
+ * @param {String} imgSrc 이미지경로
+ * @param {Number[]} size Image Width, Height, Angle
+ * @param {OG.geometry.Style|Object} style 스타일
+ * @param {String} id Element ID 지정
+ * @return {Element} DOM Element
+ */
+ drawImage: function (position, imgSrc, size, style, id) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * 라인을 캔버스에 드로잉한다.
+ *
+ * @param {OG.geometry.Line} line 라인
+ * @param {OG.geometry.Style|Object} style 스타일
+ * @param {String} id Element ID 지정
+ * @param {Boolean} isSelf 셀프 연결 여부
+ * @return {Element} Group DOM Element with geometry
+ */
+ drawEdge: function (line, style, id, isSelf) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * Shape 의 Label 을 캔버스에 위치 및 사이즈 지정하여 드로잉한다.
+ *
+ * @param {Element|String} shapeElement Shape DOM element or ID
+ * @param {String} text 텍스트
+ * @param {Object} style 스타일
+ * @return {Element} DOM Element
+ */
+ drawLabel: function (shapeElement, text, style) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * Edge 의 from, to Label 을 캔버스에 위치 및 사이즈 지정하여 드로잉한다.
+ *
+ * @param {Element|String} shapeElement Shape DOM element or ID
+ * @param {String} text 텍스트
+ * @param {String} type 유형(FROM or TO)
+ * @return {Element} DOM Element
+ */
+ drawEdgeLabel: function (shapeElement, text, type) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * Element 에 저장된 geom, angle, image, text 정보로 shape 을 redraw 한다.
+ *
+ * @param {Element} element Shape 엘리먼트
+ * @param {String[]} excludeEdgeId redraw 제외할 Edge ID
+ */
+ redrawShape: function (element, excludeEdgeId) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * Shape 의 연결된 Edge 를 redraw 한다.(이동 또는 리사이즈시)
+ *
+ * @param {Element} element
+ */
+ redrawConnectedEdge: function (element) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * 두개의 터미널을 연결하고, 속성정보에 추가한다.
+ * @param {Element|Number[]} fromTerminal 시작점 (fromTerminal)
+ * @param {Element|Number[]} toTerminal 끝점 (toTerminal)
+ * @param {Element} edge Edge Shape
+ * @param {OG.geometry.Style|Object} style 스타일
+ * @param {String} label Label
+ * @param {Boolean} preventTrigger 이벤트 트리거 발생 막기
+ * @returns {Element} 연결된 Edge 엘리먼트
+ */
+ connect: function (fromTerminal, toTerminal, edge, style, label, preventTrigger) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * 연결속성정보를 삭제한다. Edge 인 경우는 라인만 삭제하고, 일반 Shape 인 경우는 연결된 모든 Edge 를 삭제한다.
+ *
+ * @param {Element} element
+ */
+ disconnect: function (element) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * ID에 해당하는 Element 의 Edge 연결시 Drop Over 가이드를 드로잉한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ */
+ drawDropOverGuide: function (element) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * ID에 해당하는 Element 의 Move & Resize 용 가이드를 드로잉한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @return {Object}
+ */
+ drawGuide: function (element) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * ID에 해당하는 Element 의 Move & Resize 용 가이드를 제거한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ */
+ removeGuide: function (element) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * 모든 Move & Resize 용 가이드를 제거한다.
+ */
+ removeAllGuide: function () {
+ throw new OG.NotImplementedException();
+ },
+
+
+ /**
+ * ID에 해당하는 Element 의 Connect Guide 를 제거한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ */
+ removeConnectGuide: function () {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * 캔버스의 모든 Connect Guide 를 제거한다.
+ *
+ */
+ removeAllConnectGuide: function () {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * ID에 해당하는 Element 이외의 모든 Connect Guide 를 제거한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ */
+ removeOtherConnectGuide: function () {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * 캔버스의 가상선을 삭제한다.
+ */
+ removeAllVirtualEdge: function () {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * ID에 해당하는 Edge Element 의 Move & Resize 용 가이드를 드로잉한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @return {Object}
+ */
+ drawEdgeGuide: function (element) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * Rectangle 모양의 마우스 드래그 선택 박스 영역을 드로잉한다.
+ *
+ * @param {Number[]} position 드로잉할 위치 좌표(좌상단)
+ * @param {Number[]} size Text Width, Height, Angle
+ * @param {OG.geometry.Style|Object} style 스타일
+ * @return {Element} DOM Element
+ */
+ drawRubberBand: function (position, size, style) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * Rectangle 모양의 마우스 드래그 선택 박스 영역을 제거한다.
+ *
+ * @param {Element} root first, rubberBand 정보를 저장한 엘리먼트
+ */
+ removeRubberBand: function (root) {
+ throw new OG.NotImplementedException();
+ },
+
+
+ /**
+ * ID에 해당하는 Element 의 Draggable 가이드를 드로잉한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @return {Element}
+ * @override
+ */
+ drawDraggableGuide: function (element) {
+ throw new OG.NotImplementedException();
+ },
+ /**
+ * ID에 해당하는 Element 의 Collapse 가이드를 드로잉한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @return {Element}
+ */
+ drawCollapseGuide: function (element) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * ID에 해당하는 Element 의 Collapse 가이드를 제거한다.
+ *
+ * @param {Element} element
+ */
+ removeCollapseGuide: function (element) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * 주어진 Shape 들을 그룹핑한다.
+ *
+ * @param {Element[]} elements
+ * @return {Element} Group Shape Element
+ */
+ group: function (elements) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * 주어진 그룹들을 그룹해제한다.
+ *
+ * @param {Element[]} groupElements
+ * @return {Element[]} ungrouped Elements
+ */
+ ungroup: function (groupElements) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * 주어진 Shape 들을 그룹에 추가한다.
+ *
+ * @param {Element} groupElement
+ * @param {Element[]} elements
+ */
+ addToGroup: function (groupElement, elements, eventOffset) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * 주어진 Shape 이 그룹인 경우 collapse 한다.
+ *
+ * @param {Element} element
+ */
+ collapse: function (element) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * 주어진 Shape 이 그룹인 경우 expand 한다.
+ *
+ * @param {Element} element
+ */
+ expand: function (element) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * 드로잉된 모든 오브젝트를 클리어한다.
+ */
+ clear: function () {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * Shape 을 캔버스에서 관련된 모두를 삭제한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ */
+ removeShape: function (element) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * ID에 해당하는 Element 를 캔버스에서 제거한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ */
+ remove: function (element) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * 하위 엘리먼트만 제거한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ */
+ removeChild: function (element) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * 랜더러 캔버스 Root Element 를 반환한다.
+ *
+ * @return {Element} Element
+ */
+ getRootElement: function () {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * 랜더러 캔버스 Root Group Element 를 반환한다.
+ *
+ * @return {Element} Element
+ */
+ getRootGroup: function () {
+ return this._ROOT_GROUP.node;
+ },
+
+ /**
+ * 주어진 지점을 포함하는 Top Element 를 반환한다.
+ *
+ * @param {Number[]} position 위치 좌표
+ * @return {Element} Element
+ */
+ getElementByPoint: function (position) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * 주어진 Boundary Box 영역에 포함되는 Shape(GEOM, TEXT, IMAGE) Element 를 반환한다.
+ * 모든 vertices를 포함한 엘리먼트를 반환한다.
+ *
+ * @param {OG.geometry.Envelope} envelope Boundary Box 영역
+ * @return {Element[]} Element
+ */
+ getElementsByBBox: function (envelope) {
+ var elements = [];
+ $(this.getRootElement()).find("[_type=" + OG.Constants.NODE_TYPE.SHAPE + "]").each(function (index, element) {
+ if (element.shape.geom && envelope.isContainsAll(element.shape.geom.getVertices())) {
+ elements.push(element);
+ }
+ });
+
+ return elements;
+ },
+ /**
+ * 엘리먼트에 속성값을 설정한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @param {Object} attribute 속성값
+ */
+ setAttr: function (element, attribute) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * 엘리먼트 속성값을 반환한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @param {String} attrName 속성이름
+ * @return {Object} attribute 속성값
+ */
+ getAttr: function (element, attrName) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * Shape 의 스타일을 변경한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @param {Object} style 스타일
+ */
+ setShapeStyle: function (element, style) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * ID에 해당하는 Element 를 최상단 레이어로 이동한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ */
+ toFront: function (element) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * ID에 해당하는 Element 를 최하단 레이어로 이동한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ */
+ toBack: function (element) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * ID에 해당하는 Element 를 앞으로 한단계 이동한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ */
+ bringForward: function (element) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * ID에 해당하는 Element 를 뒤로 한단계 이동한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ */
+ sendBackward: function (element) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * 랜더러 캔버스의 사이즈(Width, Height)를 반환한다.
+ *
+ * @return {Number[]} Canvas Width, Height
+ */
+ getCanvasSize: function () {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * 랜더러 캔버스의 사이즈(Width, Height)를 변경한다.
+ *
+ * @param {Number[]} size Canvas Width, Height
+ */
+ setCanvasSize: function (size) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * 랜더러 캔버스의 사이즈(Width, Height)를 실제 존재하는 Shape 의 영역에 맞게 변경한다.
+ *
+ * @param {Number[]} minSize Canvas 최소 Width, Height
+ * @param {Boolean} fitScale 주어진 minSize 에 맞게 fit 여부(Default:false)
+ */
+ fitCanvasSize: function (minSize, fitScale) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * 새로운 View Box 영역을 설정한다. (ZoomIn & ZoomOut 가능)
+ *
+ * @param {Number[]} position 위치 좌표(좌상단 기준)
+ * @param {Number[]} size Canvas Width, Height
+ * @param {Boolean} isFit Fit 여부
+ */
+ setViewBox: function (position, size, isFit) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * Scale 을 반환한다. (리얼 사이즈 : Scale = 1)
+ *
+ * @return {Number} 스케일값
+ */
+ getScale: function () {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * Scale 을 설정한다. (리얼 사이즈 : Scale = 1)
+ *
+ * @param {Number} scale 스케일값
+ */
+ setScale: function (scale) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * ID에 해당하는 Element 를 캔버스에서 show 한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ */
+ show: function (element) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * ID에 해당하는 Element 를 캔버스에서 hide 한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ */
+ hide: function (element) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * Source Element 를 Target Element 아래에 append 한다.
+ *
+ * @param {Element|String} srcElement Element 또는 ID
+ * @param {Element|String} targetElement Element 또는 ID
+ * @return {Element} Source Element
+ */
+ appendChild: function (srcElement, targetElement) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * Source Element 를 Target Element 이후에 insert 한다.
+ *
+ * @param {Element|String} srcElement Element 또는 ID
+ * @param {Element|String} targetElement Element 또는 ID
+ * @return {Element} Source Element
+ */
+ insertAfter: function (srcElement, targetElement) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * Source Element 를 Target Element 이전에 insert 한다.
+ *
+ * @param {Element|String} srcElement Element 또는 ID
+ * @param {Element|String} targetElement Element 또는 ID
+ * @return {Element} Source Element
+ */
+ insertBefore: function (srcElement, targetElement) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * 해당 Element 를 가로, 세로 Offset 만큼 이동한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @param {Number[]} offset [가로, 세로]
+ * @return {Element} Element
+ */
+ move: function (element, offset) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * 주어진 중심좌표로 해당 Element 를 이동한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @param {Number[]} position [x, y]
+ * @return {Element} Element
+ */
+ moveCentroid: function (element, position) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * 중심 좌표를 기준으로 주어진 각도 만큼 회전한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @param {Number} angle 각도
+ * @return {Element} Element
+ */
+ rotate: function (element, angle) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * 상, 하, 좌, 우 외곽선을 이동한 만큼 리사이즈 한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @param {Number[]} offset [상, 하, 좌, 우] 각 방향으로 + 값
+ * @return {Element} Element
+ */
+ resize: function (element, offset) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * 중심좌표는 고정한 채 Bounding Box 의 width, height 를 리사이즈 한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @param {Number[]} size [Width, Height]
+ * @return {Element} Element
+ */
+ resizeBox: function (element, size) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * 노드 Element 를 복사한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @return {Element} Element
+ */
+ clone: function (element) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * ID로 Node Element 를 반환한다.
+ *
+ * @param {String} id
+ * @return {Element} Element
+ */
+ getElementById: function (id) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * Shape 타입에 해당하는 Node Element 들을 반환한다.
+ *
+ * @param {String} shapeType Shape 타입(GEOM, HTML, IMAGE, EDGE, GROUP), Null 이면 모든 타입
+ * @param {String} excludeType 제외 할 타입
+ * @return {Element[]} Element's Array
+ */
+ getElementsByType: function (shapeType, excludeType) {
+ var root = this.getRootGroup();
+ if (shapeType && excludeType) {
+ return $(root).find("[_type=SHAPE][_shape=" + shapeType + "][_shape!=" + excludeType + "]");
+ } else if (shapeType) {
+ return $(root).find("[_type=SHAPE][_shape=" + shapeType + "]");
+ } else if (excludeType) {
+ return $(root).find("[_type=SHAPE][_shape!=" + excludeType + "]");
+ } else {
+ return $(root).find("[_type=SHAPE]");
+ }
+ },
+
+ /**
+ * 해당 엘리먼트의 BoundingBox 영역 정보를 반환한다.
+ *
+ * @param {Element|String} element
+ * @return {Object} {width, height, x, y, x2, y2}
+ */
+ getBBox: function (element) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * 부모노드기준으로 캔버스 루트 엘리먼트의 BoundingBox 영역 정보를 반환한다.
+ *
+ * @return {Object} {width, height, x, y, x2, y2}
+ */
+ getRootBBox: function () {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * 부모노드기준으로 캔버스 루트 엘리먼트의 실제 Shape 이 차지하는 BoundingBox 영역 정보를 반환한다.
+ *
+ * @return {Object} {width, height, x, y, x2, y2}
+ */
+ getRealRootBBox: function () {
+ var minX = Number.MAX_VALUE, minY = Number.MAX_VALUE, maxX = Number.MIN_VALUE, maxY = Number.MIN_VALUE,
+ shapeElements = this.getElementsByType(), shape, envelope, upperLeft, lowerRight, i,
+ rootBBox = {
+ width: 0,
+ height: 0,
+ x: 0,
+ y: 0,
+ x2: 0,
+ y2: 0
+ };
+
+ for (var i = 0, leni = shapeElements.length; i < leni; i++) {
+ shape = shapeElements[i].shape;
+ if (shape && shape.geom) {
+ envelope = shape.geom.getBoundary();
+ upperLeft = envelope.getUpperLeft();
+ lowerRight = envelope.getLowerRight();
+
+ minX = minX > upperLeft.x ? upperLeft.x : minX;
+ minY = minY > upperLeft.y ? upperLeft.y : minY;
+ maxX = maxX < lowerRight.x ? lowerRight.x : maxX;
+ maxY = maxY < lowerRight.y ? lowerRight.y : maxY;
+
+ rootBBox = {
+ width: maxX - minX,
+ height: maxY - minY,
+ x: minX,
+ y: minY,
+ x2: maxX,
+ y2: maxY
+ };
+ }
+ }
+
+ return rootBBox;
+ },
+
+ /**
+ * 캔버스의 컨테이너 DOM element 를 반환한다.
+ *
+ * @return {Element} 컨테이너
+ */
+ getContainer: function () {
+ throw new OG.NotImplementedException();
+ },
+
+
+ /**
+ * SVG 인지 여부를 반환한다.
+ *
+ * @return {Boolean} svg 여부
+ */
+ isSVG: function () {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * VML 인지 여부를 반환한다.
+ *
+ * @return {Boolean} vml 여부
+ */
+ isVML: function () {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * 두 도형 사이의 연결된 Edge 를 반환한다.
+ * @param elements
+ * @returns {Element} edge
+ */
+ getRelatedEdgeFromShapes: function (elements) {
+ var edge;
+ var fromElement = elements[0];
+ var toElement = elements[1];
+ if (!fromElement || !toElement) {
+ return null;
+ }
+ var prevShapeId, nextShapeId;
+ var prevEdges = this.getPrevEdges(fromElement);
+ var nextEdges = this.getNextEdges(fromElement);
+ for (var i = 0, leni = prevEdges.length; i < leni; i++) {
+ prevShapeId = $(prevEdges[i]).attr('_from');
+ if (prevShapeId) {
+ prevShapeId = prevShapeId.substring(0, prevShapeId.indexOf(OG.Constants.TERMINAL));
+ if (prevShapeId == toElement.id) {
+ edge = prevEdges[i];
+ }
+ }
+ }
+ for (var i = 0, leni = nextEdges.length; i < leni; i++) {
+ nextShapeId = $(nextEdges[i]).attr('_to');
+ if (nextShapeId) {
+ nextShapeId = nextShapeId.substring(0, nextShapeId.indexOf(OG.Constants.TERMINAL));
+ if (nextShapeId == toElement.id) {
+ edge = nextEdges[i];
+ }
+ }
+ }
+ return edge;
+ },
+ /**
+ * 연결된 이전 Edge Element 들을 반환한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @return {Element[]} Previous Element's Array
+ */
+ getPrevEdges: function (element) {
+ var prevEdgeIds = $(element).attr('_fromedge'),
+ edgeArray = [],
+ edgeIds, edge, i;
+
+ if (prevEdgeIds) {
+ edgeIds = prevEdgeIds.split(',');
+ for (var i = 0, leni = edgeIds.length; i < leni; i++) {
+ edge = this.getElementById(edgeIds[i]);
+ if (edge) {
+ edgeArray.push(edge);
+ }
+ }
+ }
+
+ return edgeArray;
+ },
+
+ /**
+ * 연결된 이후 Edge Element 들을 반환한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @return {Element[]} Previous Element's Array
+ */
+ getNextEdges: function (element) {
+ var nextEdgeIds = $(element).attr('_toedge'),
+ edgeArray = [],
+ edgeIds, edge, i;
+
+ if (nextEdgeIds) {
+ edgeIds = nextEdgeIds.split(',');
+ for (var i = 0, leni = edgeIds.length; i < leni; i++) {
+ edge = this.getElementById(edgeIds[i]);
+ if (edge) {
+ edgeArray.push(edge);
+ }
+ }
+ }
+
+ return edgeArray;
+ },
+
+ /**
+ * 연결된 이전 노드 Element 들을 반환한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @return {Element[]} Previous Element's Array
+ */
+ getPrevShapes: function (element) {
+ var prevEdges = this.getPrevEdges(element),
+ shapeArray = [],
+ prevShapeId, shape, i;
+
+ for (var i = 0, leni = prevEdges.length; i < leni; i++) {
+ prevShapeId = $(prevEdges[i]).attr('_from');
+ if (prevShapeId) {
+ prevShapeId = prevShapeId.substring(0, prevShapeId.indexOf(OG.Constants.TERMINAL));
+ shape = this.getElementById(prevShapeId);
+ if (shape) {
+ shapeArray.push(shape);
+ }
+ }
+ }
+
+ return shapeArray;
+ },
+
+ /**
+ * 연결된 이전 노드 Element ID들을 반환한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @return {String[]} Previous Element Id's Array
+ */
+ getPrevShapeIds: function (element) {
+ var prevEdges = this.getPrevEdges(element),
+ shapeArray = [],
+ prevShapeId, i;
+
+ for (var i = 0, leni = prevEdges.length; i < leni; i++) {
+ prevShapeId = $(prevEdges[i]).attr('_from');
+ if (prevShapeId) {
+ prevShapeId = prevShapeId.substring(0, prevShapeId.indexOf(OG.Constants.TERMINAL));
+ shapeArray.push(prevShapeId);
+ }
+ }
+ return shapeArray;
+ },
+
+ /**
+ * 연결된 이후 노드 Element 들을 반환한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @return {Element[]} Previous Element's Array
+ */
+ getNextShapes: function (element) {
+ var nextEdges = this.getNextEdges(element),
+ shapeArray = [],
+ nextShapeId, shape, i;
+
+ for (var i = 0, leni = nextEdges.length; i < leni; i++) {
+ nextShapeId = $(nextEdges[i]).attr('_to');
+ if (nextShapeId) {
+ nextShapeId = nextShapeId.substring(0, nextShapeId.indexOf(OG.Constants.TERMINAL));
+ shape = this.getElementById(nextShapeId);
+ if (shape) {
+ shapeArray.push(shape);
+ }
+ }
+ }
+
+ return shapeArray;
+ },
+
+ /**
+ * 연결된 이후 노드 Element ID들을 반환한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @return {String[]} Previous Element Id's Array
+ */
+ getNextShapeIds: function (element) {
+ var nextEdges = this.getNextEdges(element),
+ shapeArray = [],
+ nextShapeId, i;
+
+ for (var i = 0, leni = nextEdges.length; i < leni; i++) {
+ nextShapeId = $(nextEdges[i]).attr('_to');
+ if (nextShapeId) {
+ nextShapeId = nextShapeId.substring(0, nextShapeId.indexOf(OG.Constants.TERMINAL));
+ shapeArray.push(nextShapeId);
+ }
+ }
+
+ return shapeArray;
+ },
+
+ /**
+ * Node 엘리먼트의 커넥트 가이드 엘리먼트를 반환한다.
+ *
+ * @param {Element} Element 엘리먼트
+ * @return {Array} Array Element
+ */
+ getConnectGuideElements: function (element) {
+ throw new OG.NotImplementedException();
+ },
+
+ /**
+ * 최상위 그룹 엘리먼트인지 반환한다.
+ *
+ * @param {Element} Element 엘리먼트
+ * @return {boolean} true false
+ */
+ isTopGroup: function (element) {
+ var parent = element.parentElement;
+ if (!parent) {
+ parent = element.parentNode;
+ }
+ if (!element || !parent) {
+ return false;
+ }
+ if (!element.shape instanceof OG.shape.GroupShape) {
+ return false;
+ }
+
+ if (parent.id === this.getRootGroup().id) {
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * 부모 엘리먼트를 반환한다. 부모가 루트일때는 반환하지 않는다.
+ *
+ * @param {Element} element 엘리먼트
+ * @return {Element} element 엘리먼트
+ */
+ getParent: function (element) {
+ var parent = element.parentElement;
+ if (!parent) {
+ parent = element.parentNode;
+ }
+ if (!element || !parent) {
+ return null;
+ }
+ if (parent.id === this.getRootGroup().id) {
+ return null;
+ }
+ return parent;
+ },
+
+ /**
+ * 그룹의 하위 엘리먼트를 반환한다.
+ *
+ * @param {Element} element 엘리먼트
+ * @returns {Array} Elements
+ */
+ getChilds: function (element) {
+ var childShapes = [];
+ if (!element || OG.Util.isIE() ? !element.childNodes : !element.children) {
+ return childShapes;
+ }
+ $.each(OG.Util.isIE() ? element.childNodes : element.children, function (index, child) {
+ if ($(child).attr("_type") === OG.Constants.NODE_TYPE.SHAPE) {
+ childShapes.push(child);
+ }
+ });
+ return childShapes;
+ },
+
+ /**
+ * 그룹 Shape 인지 반환한다. RootGroup 일 경우는 제외.
+ *
+ * @param {Element} element 엘리먼트
+ * @return {boolean} true false
+ */
+ isGroup: function (element) {
+ var parent = element.parentElement;
+ if (!parent) {
+ parent = element.parentNode;
+ }
+ if (!element || !parent) {
+ return false;
+ }
+ if (element.id === this.getRootGroup().id) {
+ return false;
+ }
+ if (element.shape instanceof OG.shape.GroupShape) {
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * 캔버스의 모든 Shape 들을 리턴
+ *
+ * @return {Array} Elements
+ */
+ getAllShapes: function () {
+ var elements = [];
+ $(this.getRootElement()).find("[_type=" + OG.Constants.NODE_TYPE.SHAPE + "]").each(function (index, element) {
+ elements.push(element);
+ });
+ return elements;
+ },
+ /**
+ * 캔버스의 모든 Edge를 리턴
+ *
+ * @return {Array} Edge Elements
+ */
+ getAllEdges: function () {
+ var edges = [];
+ var elements = this.getAllShapes();
+ $.each(elements, function (index, element) {
+ if ($(element).attr("_shape") === OG.Constants.SHAPE_TYPE.EDGE) {
+ edges.push(element);
+ }
+ });
+ return edges;
+ },
+ /**
+ * 캔버스의 모든 Edge 가 아닌 shpaes 를 리턴
+ *
+ * @return {Array} Edge Elements
+ */
+ getAllNotEdges: function () {
+ var shpaes = [];
+ var elements = this.getAllShapes();
+ $.each(elements, function (index, element) {
+ if ($(element).attr("_shape") !== OG.Constants.SHAPE_TYPE.EDGE) {
+ shpaes.push(element);
+ }
+ })
+ return shpaes;
+ },
+ /**
+ * Edge 여부를 판단.
+ *
+ * @return {boolean} true false
+ */
+ isEdge: function (element) {
+ return $(element).attr("_shape") === OG.Constants.SHAPE_TYPE.EDGE;
+ },
+ /**
+ * Shape 여부를 판단.
+ *
+ * @return {boolean} true false
+ */
+ isShape: function (element) {
+ return $(element).attr("_type") === OG.Constants.NODE_TYPE.SHAPE;
+ },
+ /**
+ * 캔버스의 히스토리를 초기화한다.
+ */
+ initHistory: function () {
+ throw new OG.NotImplementedException();
+ },
+ /**
+ * 캔버스에 히스토리를 추가한다.
+ */
+ addHistory: function () {
+ throw new OG.NotImplementedException();
+ },
+ /**
+ * 캔버스의 Undo
+ */
+ undo: function () {
+ throw new OG.NotImplementedException();
+ },
+ /**
+ * 캔버스의 Redo
+ */
+ redo: function () {
+ throw new OG.NotImplementedException();
+ }
+};
+OG.renderer.IRenderer.prototype.constructor = OG.renderer.IRenderer;
+/**
+ * Raphael 라이브러리를 이용하여 구현한 랜더러 캔버스 클래스
+ * - 노드에 추가되는 속성 : _type, _shape, _selected, _from, _to, _fromedge, _toedge
+ * - 노드에 저장되는 값 : shape : { geom, angle, image, text }, data : 커스텀 Object
+ *
+ * @class
+ * @extends OG.renderer.IRenderer
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ * @requires OG.shape.*
+ * @requires raphael-2.1.0
+ *
+ * @param {HTMLElement|String} container 컨테이너 DOM element or ID
+ * @param {Number[]} containerSize 컨테이너 Width, Height
+ * @param {String} backgroundColor 캔버스 배경색
+ * @param {String} backgroundImage 캔버스 배경이미지
+ * @param {Object} config Configuration
+ * @author Seungpil Park
+ */
+OG.renderer.RaphaelRenderer = function (container, containerSize, backgroundColor, backgroundImage, config) {
+ OG.renderer.RaphaelRenderer.superclass.call(this, arguments);
+
+ this._CONFIG = config;
+ this._PAPER = new Raphael(container, containerSize ? containerSize[0] : null, containerSize ? containerSize[1] : null);
+
+ // 최상위 그룹 엘리먼트 초기화
+ this._ROOT_GROUP = this._add(this._PAPER.group(), null, OG.Constants.NODE_TYPE.ROOT);
+ this._ETC_GROUP = this._add(this._PAPER.group(), null, OG.Constants.NODE_TYPE.ETC);
+ this._PAPER.id = "OG_" + this._ID_PREFIX;
+ this._PAPER.canvas.id = "OG_" + this._ID_PREFIX;
+ this._CANVAS_COLOR = backgroundColor || this._CONFIG.CANVAS_BACKGROUND;
+
+ $(this._PAPER.canvas).css({
+ "background-color": this._CANVAS_COLOR,
+ "user-select": "none",
+ "-o-user-select": "none",
+ "-moz-user-select": "none",
+ "-khtml-user-select": "none",
+ "-webkit-user-select": "none"
+ });
+ if (backgroundImage) {
+ $(this._PAPER.canvas).css({"background-image": backgroundImage});
+ }
+
+ // container 에 keydown 이벤트 가능하도록
+ $(this._PAPER.canvas.parentNode).attr("tabindex", "0");
+ $(this._PAPER.canvas.parentNode).css({"outline": "none"});
+
+ // container 의 position 을 static 인 경우 offset 이 깨지므로 relative 로 보정
+ if ($(this._PAPER.canvas.parentNode).css('position') === 'static') {
+ $(this._PAPER.canvas.parentNode).css({
+ position: 'relative',
+ left: '0',
+ top: '0'
+ });
+ }
+};
+OG.renderer.RaphaelRenderer.prototype = new OG.renderer.IRenderer();
+OG.renderer.RaphaelRenderer.superclass = OG.renderer.IRenderer;
+OG.renderer.RaphaelRenderer.prototype.constructor = OG.renderer.RaphaelRenderer;
+OG.RaphaelRenderer = OG.renderer.RaphaelRenderer;
+
+/**
+ * ID를 발급하고 ID:rElement 해쉬맵에 추가한다.
+ *
+ * @param {Raphael.Element} rElement 라파엘 엘리먼트
+ * @param {String} id 지정ID
+ * @param {String} nodeType Node 유형(ROOT, SHAPE ...)
+ * @param {String} shapeType Shape 유형(GEOM, TEXT, IMAGE, EDGE, GROUP ...)
+ * @return {Raphael.Element} rElement 라파엘 엘리먼트
+ * @private
+ */
+OG.renderer.RaphaelRenderer.prototype._add = function (rElement, id, nodeType, shapeType) {
+ rElement.id = id || this._genId();
+ rElement.node.id = rElement.id;
+ rElement.node.raphaelid = rElement.id;
+ if (nodeType) {
+ $(rElement.node).attr("_type", nodeType);
+ }
+ if (shapeType) {
+ $(rElement.node).attr("_shape", shapeType);
+ }
+ this._ELE_MAP.put(rElement.id, rElement);
+
+ return rElement;
+};
+
+OG.renderer.RaphaelRenderer.prototype.setCanvas = function (canvas) {
+ this._CANVAS = canvas;
+};
+
+/**
+ * 라파엘 엘리먼트를 하위 엘리먼트 포함하여 제거한다.
+ *
+ * @param {Raphael.Element} rElement 라파엘 엘리먼트
+ * @private
+ */
+OG.renderer.RaphaelRenderer.prototype._remove = function (rElement) {
+ var childNodes, i;
+ if (rElement) {
+ childNodes = rElement.node.childNodes;
+ for (i = childNodes.length - 1; i >= 0; i--) {
+ this._remove(this._getREleById(childNodes[i].id));
+ }
+ this._ELE_MAP.remove(rElement.id);
+ rElement.remove();
+ }
+};
+
+/**
+ * 하위 엘리먼트만 제거한다.
+ *
+ * @param {Raphael.Element} rElement 라파엘 엘리먼트
+ * @private
+ */
+OG.renderer.RaphaelRenderer.prototype._removeChild = function (rElement) {
+ var childNodes, i;
+ if (rElement) {
+ childNodes = rElement.node.childNodes;
+ for (i = childNodes.length - 1; i >= 0; i--) {
+ if (childNodes[i].tagName == 'svg') {
+ childNodes[i].parentNode.removeChild(childNodes[i]);
+ } else {
+ this._remove(this._getREleById(childNodes[i].id));
+ }
+ }
+ }
+};
+
+/**
+ * ID에 해당하는 RaphaelElement 를 반환한다.
+ *
+ * @param {String} id ID
+ * @return {Raphael.Element} RaphaelElement
+ * @private
+ */
+OG.renderer.RaphaelRenderer.prototype._getREleById = function (id) {
+ return this._ELE_MAP.get(id);
+};
+
+OG.renderer.RaphaelRenderer.prototype._drawSubShape = function (groupElement) {
+ //그룹 엘리먼트에 createSubShape 메소드를 확인한다.
+ var me = this, subShapeNodes, subShapeNode, width, height,
+ left, top, right, bottom, align, verticalAlign,
+ subVertices, subStyle, subShape, subShapeId, tempNode, zIndex,
+ boundary, bW, bH, bT, bL, tempNodes = [];
+ if (!groupElement.shape.createSubShape) {
+ return;
+ }
+
+ subShapeNodes = groupElement.shape.createSubShape();
+ if (!subShapeNodes || !subShapeNodes.length) {
+ return;
+ }
+ for (var i = 0, leni = subShapeNodes.length; i < leni; i++) {
+ subShapeNode = subShapeNodes[i];
+ width = subShapeNode.width;
+ height = subShapeNode.height;
+ left = subShapeNode.left;
+ top = subShapeNode.top;
+ right = subShapeNode.right;
+ bottom = subShapeNode.bottom;
+ align = subShapeNode.align;
+ zIndex = subShapeNode['z-index'];
+ verticalAlign = subShapeNode['vertical-align'];
+ subVertices = subShapeNode.vertices ? JSON.parse(JSON.stringify(subShapeNode.vertices)) : null;
+ subStyle = subShapeNode.style ? subShapeNode.style : {};
+ subShape = subShapeNode.shape;
+
+ //지정된 subShape 이 없다면 리턴한다.
+ if (!subShape) {
+ continue;
+ }
+
+ //subShapeId 를 구한다.
+ subShapeId = groupElement.id + '_sub_' + i;
+
+ //groupElement 의 바운더리에 따라 위치값들을 정한다.
+ boundary = me.getBoundary(groupElement);
+ bW = boundary.getWidth();
+ bH = boundary.getHeight();
+ bL = boundary.getUpperLeft().x;
+ bT = boundary.getUpperLeft().y;
+
+ var getLength = function (standard, value) {
+ var length;
+
+ //값이 없고, 0 이 아닐경우
+ if (!value && value != 0) {
+ length = undefined;
+ }
+ //숫자 형태인 경우
+ else if (typeof value == 'number') {
+ length = value;
+ } else if (typeof value == 'string') {
+ //픽셀인 경우
+ if (value.indexOf('px') != -1) {
+ length = parseFloat(value.replace('px', ''));
+ }
+ //퍼센테이지 경우
+ else if (value.indexOf('%') != -1) {
+ value = parseFloat(value.replace('%', ''));
+ length = standard * (value / 100);
+ }
+ //그외에는 숫자취급
+ else {
+ length = parseFloat(value);
+ }
+ }
+ return length;
+ };
+
+ //Edge 가 아닌 일반 도형의 위치값을 구한다.
+ width = getLength(bW, width);
+ height = getLength(bH, height);
+ left = getLength(bW, left) + bL;
+ right = bL + bW - getLength(bW, right) - width;
+ top = getLength(bH, top) + bT;
+ bottom = bT + bH - getLength(bH, bottom) - height;
+
+ //right 가 있다면 left 보다 우선하고, bottom 이 있다면 top 보다 우선한다.
+ //최종 위치 계산은 top 과 left 로 한다.
+ if (!right && right != 0) {
+
+ } else {
+ left = right;
+ }
+ if (!bottom && bottom != 0) {
+
+ } else {
+ top = bottom;
+ }
+
+ //align 이나 vertice-algin 값이 있을 경우 left 와 top 값을 오버라이드 한다.
+ if (align == 'left') {
+ left = bL;
+ } else if (align == 'center') {
+ left = bL + (bW / 2) - (width / 2);
+ } else if (align == 'right') {
+ left = bL + bW - width;
+ }
+
+ if (verticalAlign == 'top') {
+ top = bT;
+ } else if (verticalAlign == 'middle') {
+ top = bT + (bH / 2) - (height / 2);
+ } else if (verticalAlign == 'bottom') {
+ top = bT + bH - height;
+ }
+
+ //Edge 인 도형의 위치값을 구한다. subVertices 의 (left,right),(top,bottom) 기준값으로 정한다.
+ if (subShape instanceof OG.shape.EdgeShape) {
+ if (subVertices && subVertices.length) {
+ for (var v = 0, lenv = subVertices.length; v < lenv; v++) {
+ //x 축이 left 기준인 경우
+ if (typeof subVertices[v][0] == 'string' && subVertices[v][0].indexOf('left') != -1) {
+ subVertices[v][0] = subVertices[v][0].replace('left', '');
+ subVertices[v][0] = getLength(bW, subVertices[v][0]) + bL;
+ }
+ //x 축이 right 기준인 경우
+ else if (typeof subVertices[v][0] == 'string' && subVertices[v][0].indexOf('right') != -1) {
+ subVertices[v][0] = subVertices[v][0].replace('right', '');
+ subVertices[v][0] = bL + bW - getLength(bW, subVertices[v][0]);
+ }
+ //그 외의 경우는 left 처리
+ else {
+ subVertices[v][0] = getLength(bW, subVertices[v][0]) + bL;
+ }
+
+ //y 축이 top 기준인 경우
+ if (typeof subVertices[v][1] == 'string' && subVertices[v][1].indexOf('top') != -1) {
+ subVertices[v][1] = subVertices[v][1].replace('top', '');
+ subVertices[v][1] = getLength(bH, subVertices[v][1]) + bT;
+ }
+ //y 축이 bottom 기준인 경우
+ else if (typeof subVertices[v][1] == 'string' && subVertices[v][1].indexOf('bottom') != -1) {
+ subVertices[v][1] = subVertices[v][1].replace('bottom', '');
+ subVertices[v][1] = bT + bH - getLength(bH, subVertices[v][1]);
+ }
+ //그 외의 경우는 top 처리
+ else {
+ subVertices[v][1] = getLength(bH, subVertices[v][1]) + bT;
+ }
+ }
+ } else {
+ subVertices = [[bL, bT], [bL + bW, bT]];
+ }
+ subShape.geom = new OG.PolyLine(subVertices);
+ }
+
+ //노드 복사를 위한 가상의 그룹노드
+ if (subShape instanceof OG.shape.EdgeShape) {
+ tempNode = me.drawShape(null, subShape, null, subStyle, subShapeId, true);
+ } else {
+ tempNode = me.drawShape([left + width / 2, top + height / 2], subShape, [width, height], subStyle, subShapeId, true);
+ }
+
+ //z-index 에 따라 tempNodes 에 인서트한다.
+ //zIndex 가 없거나 0 이면 0 이다.
+ if (!zIndex || zIndex == 0) {
+ zIndex = 0;
+ }
+ tempNodes.push({
+ index: zIndex,
+ node: tempNode
+ });
+ }
+
+ //tempNodes 를 인덱스에 따라 소팅한다.
+ tempNodes.sort(
+ function (a, b) {
+ return a['index'] - b['index']
+ }
+ );
+
+ var index, node, standardChild;
+ for (var i = 0, leni = tempNodes.length; i < leni; i++) {
+ //groupElement 내부의 가장 첫 자식을 기준으로 삼는다.
+ standardChild = groupElement.firstChild;
+ index = tempNodes[i].index;
+ node = $(tempNodes[i].node);
+ $(node).children().each(function (childIndex, child) {
+ $(child).removeAttr('_type');
+ $(child).removeAttr('_shape');
+ $(child).attr('_index', index);
+
+ //0 보다 큰 인덱스는 groupElement 에 순서대로 인서트한다.
+ if (index >= 0) {
+ groupElement.appendChild(child);
+ }
+
+ else {
+ //기준이 없다면 순서대로 인서트한다.
+ if (!standardChild) {
+ groupElement.appendChild(child);
+ }
+ //기준이 있다면 기준 앞에 인서트한다.
+ else {
+ groupElement.insertBefore(child, standardChild);
+ }
+ }
+ });
+
+ //가상의 그룹노드를 삭제한다.
+ me._remove(me._getREleById($(node).attr('id')));
+ }
+};
+
+/**
+ * Geometry 를 캔버스에 드로잉한다.(Recursive)
+ *
+ * @param {Element} groupElement Group DOM Element
+ * @param {OG.geometry.Geometry} geometry 기하 객체
+ * @param {OG.geometry.Style|Object} style 스타일
+ * @param {Object} parentStyle Geometry Collection 인 경우 상위 Geometry 스타일
+ * @return {Element}
+ * @private
+ */
+OG.renderer.RaphaelRenderer.prototype._drawGeometry = function (groupElement, geometry, style, parentStyle, isEdge) {
+ //이곳에, 패턴에 관한 데이터를 추가해야 한다.
+ //패턴에 따라 주어진 vertices 를 따라 패턴을 추가해준다.
+ //패턴 두께. 패턴 내용물. 패턴 특이점. => 패턴의 일정거리까지는 무엇. 일정거리까지는 무엇. 디폴트는 무엇.
+ //패턴이 있다면 패턴 이외의 실제 라인은 투명처리.
+ //OG.Constants.ORIGINAL_NODE
+
+ var me = this, i = 0, pathStr = "", vertices, element, geomObj, _style = {}, connectGuideElement;
+ var svg = me.getRootElement();
+ var getRoundedPath = function (rectangle, radius) {
+ var rectObj, rectVert, offset1, offset2, angle, array = [],
+ getRoundedOffset = function (coord, dist, deg) {
+ var theta = Math.PI / 180 * deg;
+ return new OG.geometry.Coordinate(
+ OG.Util.round(coord.x + dist * Math.cos(theta)),
+ OG.Util.round(coord.y + dist * Math.sin(theta))
+ );
+ };
+
+ rectObj = OG.JSON.decode(rectangle.toString());
+ rectVert = rectangle.getVertices();
+ angle = rectObj.angle;
+
+ offset1 = getRoundedOffset(rectVert[0], radius, 90 + angle);
+ offset2 = getRoundedOffset(rectVert[0], radius, angle);
+ array = array.concat(["M", offset1.x, offset1.y, "Q", rectVert[0].x, rectVert[0].y, offset2.x, offset2.y]);
+
+ offset1 = getRoundedOffset(rectVert[1], radius, 180 + angle);
+ offset2 = getRoundedOffset(rectVert[1], radius, 90 + angle);
+ array = array.concat(["L", offset1.x, offset1.y, "Q", rectVert[1].x, rectVert[1].y, offset2.x, offset2.y]);
+
+ offset1 = getRoundedOffset(rectVert[2], radius, 270 + angle);
+ offset2 = getRoundedOffset(rectVert[2], radius, 180 + angle);
+ array = array.concat(["L", offset1.x, offset1.y, "Q", rectVert[2].x, rectVert[2].y, offset2.x, offset2.y]);
+
+ offset1 = getRoundedOffset(rectVert[3], radius, angle);
+ offset2 = getRoundedOffset(rectVert[3], radius, 270 + angle);
+ array = array.concat(["L", offset1.x, offset1.y, "Q", rectVert[3].x, rectVert[3].y, offset2.x, offset2.y, "Z"]);
+
+ return array.toString();
+ }, getConnectGuideStyle = function () {
+ return me._CONFIG.DEFAULT_STYLE.CONNECT_GUIDE_EVENT_AREA
+ }, setConnectGuideAttr = function (ele) {
+ ele.attr(getConnectGuideStyle());
+ };
+
+ if (parentStyle) {
+ OG.Util.apply(_style, (style instanceof OG.geometry.Style) ? style.map : style || {},
+ OG.Util.apply({}, geometry.style.map, OG.Util.apply({}, parentStyle, me._CONFIG.DEFAULT_STYLE.GEOM)));
+ } else {
+ OG.Util.apply(_style, (style instanceof OG.geometry.Style) ? style.map : style || {},
+ OG.Util.apply({}, geometry.style.map, me._CONFIG.DEFAULT_STYLE.GEOM));
+ }
+
+ var multi = _style['multi'];
+ var rootMarker = _style['marker'];
+ var rootPattern = _style['pattern'];
+ var rootAnimation = _style['animation'];
+ var marker, pattern;
+
+
+ //기초 선분의 vertices 를 중심으로, top,from,to 에 따른 가상 선분을 그린다.
+ var drawMulti = function (vertices) {
+ var multiData, top, from, to, nodeId, multiStyle;
+
+ /**
+ * nodePath 스트링으로부터 distance 표현식 만큼의 거리를 반환한다.
+ * distance 는 start,center,end, percentage, number, end- 형태로 올 수 있다.
+ * @param nodePath 선분 노드 패스
+ * @param distance 거리 표현식
+ * @returns {Number} 선분 길이 대비 거리
+ */
+ var getSubDistance = function (nodePath, distance) {
+ var totalLenth = Raphael.getTotalLength(nodePath);
+ var length;
+ //넘버 형태일 경우
+ if (typeof distance == 'number') {
+ length = distance;
+ } else if (typeof distance == 'string') {
+ //픽셀일 경우
+ if (distance.indexOf('px') != -1) {
+ length = parseInt(distance.replace('px', ''));
+ }
+ //퍼센테이지 일 경우
+ if (distance.indexOf('%') != -1) {
+ distance = parseInt(distance.replace('%', ''));
+ length = totalLenth * (distance / 100);
+ }
+ else if (distance == 'start') {
+ length = 0;
+ }
+ else if (distance == 'center') {
+ length = totalLenth / 2;
+ }
+ else if (distance == 'end') {
+ length = totalLenth;
+ }
+ else if (distance.indexOf('end-') != -1) {
+ length = totalLenth - getSubDistance(nodePath, distance.replace('end-', ''));
+ }
+ else {
+ distance = parseInt(distance);
+ length = distance;
+ }
+ }
+ return length;
+ };
+
+ for (var m = 0, lenm = multi.length; m < lenm; m++) {
+ multiData = multi[m];
+ top = multiData['top'];
+ from = multiData['from'];
+ to = multiData['to'];
+
+ multiStyle = OG.Util.apply(JSON.parse(JSON.stringify(_style)), multiData['style']);
+ if (!top || !from || !to) {
+ if (top != 0) {
+ continue;
+ }
+ }
+ //노드 아이디는 도형아이디 + 멀티 선분의 인덱스이다.
+ nodeId = groupElement.id + m;
+
+ //top 만큼 평행한 라인을 구한다.
+ var pathStr;
+ var newVertices = [];
+ if (top == 0) {
+ newVertices = vertices;
+ } else {
+ newVertices = geometry.getParallelPath(vertices, top);
+ }
+ for (var l = 0, lenl = newVertices.length; l < lenl; l++) {
+ if (l === 0) {
+ pathStr = "M" + newVertices[l].x + " " + newVertices[l].y;
+ } else {
+ pathStr += "L" + newVertices[l].x + " " + newVertices[l].y;
+ }
+ }
+
+ //getSubDistance
+ var subPath = Raphael.getSubpath(pathStr, getSubDistance(pathStr, from), getSubDistance(pathStr, to));
+ var path = me._PAPER.path(subPath);
+
+ //마커 정보가 있을 경우는 arrow 스타일을 제거해주도록 한다.
+ if (multiStyle['marker']) {
+ delete multiStyle['arrow-end'];
+ delete multiStyle['arrow-start'];
+ }
+
+ path.attr(multiStyle);
+ me._add(path);
+ groupElement.appendChild(path.node);
+
+ if (multiStyle['marker']) {
+ drawMarker(path, multiStyle, subPath, m);
+ }
+ if (multiStyle['pattern']) {
+ drawPattern(path, multiStyle, subPath, m);
+ }
+ if (multiStyle['animation']) {
+ drawAnimation(path, multiStyle);
+ }
+ }
+ };
+
+ var drawPattern = function (rElement, nodeStyle, nodePath, nodeIndex) {
+ var patternShapeId = nodeStyle['pattern']['id'];
+ var thickness = nodeStyle['pattern']['thickness'];
+ var unitWidth = nodeStyle['pattern']['unit-width'];
+ var unitHeight = nodeStyle['pattern']['unit-height'];
+ var patternWidth = nodeStyle['pattern']['pattern-width'];
+ var patternHeight = nodeStyle['pattern']['pattern-height'];
+ var patternTransform = nodeStyle['pattern']['patternTransform'];
+ var patternStyle = nodeStyle['pattern']['style'] ? nodeStyle['pattern']['style'] : {};
+
+ patternWidth = patternWidth ? patternWidth : 20;
+ patternHeight = patternHeight ? patternHeight : 20;
+ unitWidth = unitWidth ? unitWidth : patternWidth;
+ unitHeight = unitHeight ? unitHeight : patternHeight;
+ thickness = thickness ? thickness : 20;
+
+ //지정한 마커 shape 이 없다면 리턴한다.
+ var patternShape;
+ eval('patternShape = ' + patternShapeId);
+ if (!patternShape) {
+ return;
+ }
+ //Def 에 들어갈 마커 id 를 구한다.
+ var split = patternShapeId.split('.');
+ var patternId = groupElement.id + nodeIndex + split[split.length - 1];
+
+ //기존 def 에 존재하는 마커 삭제
+ var existMarkerDef = $(svg).find('#' + patternId);
+ existMarkerDef.remove();
+
+ patternShape = eval('new ' + patternShapeId + '()');
+ var geometry = patternShape.createPattern();
+
+ // 좌상단으로 이동 및 크기 조정
+ geometry.moveCentroid([unitWidth / 2, unitHeight / 2]);
+ geometry.resizeBox(unitWidth, unitHeight);
+
+ //패턴 스타일링. geometry < nodeOverrideStyle < patternStyle
+ var nodeOverrideStyle = {
+ 'stroke': nodeStyle['stroke']
+ };
+ OG.Util.apply(geometry.style.map, nodeOverrideStyle);
+ OG.Util.apply(geometry.style.map, patternStyle);
+
+ //노드 복사를 위한 가상의 그룹노드
+ var tempNode = me.drawGeom(geometry, null, OG.Constants.PATTERN_TEMP_NODE);
+ var cloneNode = $(tempNode).clone().wrapAll("");
+
+ //가상의 그룹노드를 삭제한다.
+ me._remove(me._getREleById(tempNode.id));
+
+ cloneNode.removeAttr('_type');
+ cloneNode.removeAttr('_shape');
+ cloneNode.removeAttr('id');
+ cloneNode.children().each(function () {
+ $(this).removeAttr('_type');
+ $(this).removeAttr('_shape');
+ $(this).removeAttr('id');
+ $(this).removeAttr('marker-end');
+ $(this).removeAttr('marker-start');
+ $(this).removeAttr('marker-mid');
+ });
+
+ var data = {
+ id: patternId,
+ x: 0,
+ y: 0,
+ width: patternWidth,
+ height: patternHeight,
+ patternUnits: 'userSpaceOnUse'
+ };
+ if (patternTransform) {
+ data.patternTransform = patternTransform;
+ }
+
+ var el = document.createElementNS('http://www.w3.org/2000/svg', 'pattern');
+ el.appendChild(cloneNode.get(0));
+ for (var k in data) {
+ el.setAttribute(k, data[k]);
+ }
+
+ $(svg).find('defs').get(0).appendChild(el);
+
+ //edge 일 경우 thickness 두께 만큼 새로운 패스를 생성한다.
+ if (isEdge) {
+ if (!thickness || thickness == 0) {
+ return;
+ }
+ //thickness +- 만큼 평행한 라인을 구한다.
+ var pathStr;
+ var topVertices = geometry.getParallelPath(vertices, thickness);
+ var bottomVertices = geometry.getParallelPath(vertices, -thickness);
+
+ //bottomVertices 의 순서를 리버스한다.
+ bottomVertices = bottomVertices.reverse();
+
+ //두 vertices 를 결합하여 Rectangle 형태의 polyline 을 생성한다.
+ var newVertices = topVertices.concat(bottomVertices);
+
+ for (var l = 0, lenl = newVertices.length; l < lenl; l++) {
+ if (l === 0) {
+ pathStr = "M" + newVertices[l].x + " " + newVertices[l].y;
+ } else {
+ pathStr += "L" + newVertices[l].x + " " + newVertices[l].y;
+ }
+ }
+ var path = me._PAPER.path(pathStr);
+
+ //새 패스에 적용할 스타일은 nodeStyle 의 fill-opacity 이다.
+ //만일 없다면 1로 적용한다.
+ var newPathStyle = {
+ 'fill-opacity': nodeStyle['fill-opacity'] ? nodeStyle['fill-opacity'] : 1,
+ 'stroke': 'none'
+ };
+ path.attr(newPathStyle);
+ me._add(path);
+ groupElement.appendChild(path.node);
+ $(path.node).attr('fill', 'url(#' + patternId + ')');
+ }
+ //edge 가 아닐경우 fill 에 바로 마커를 입힌다.
+ else {
+ $(rElement.node).attr('fill', 'url(#' + patternId + ')');
+ }
+ };
+
+ /**
+ * Path 선분에 마커를 적용한다.
+ * @param rElement 라파엘 엘리먼트
+ * @param nodeStyle 패스 스타일
+ * @param nodePath 패스 스트링
+ * @param nodeIndex 그룹 엘리먼트(g) 내부의 엘리먼트 인덱스
+ */
+ var drawMarker = function (rElement, nodeStyle, nodePath, nodeIndex) {
+ var makerData = {};
+ for (var key in nodeStyle['marker']) {
+ if (key != 'start' && key != 'end' && key != 'mid') {
+ continue;
+ }
+ var markerShapeId = nodeStyle['marker'][key]['id'];
+ var size = nodeStyle['marker'][key]['size'];
+ var ref = nodeStyle['marker'][key]['ref'];
+ var makerStyle = nodeStyle['marker'][key]['style'] ? nodeStyle['marker'][key]['style'] : {};
+
+ //지정한 마커 shape 이 없다면 리턴한다.
+ var makerShape;
+ eval('makerShape = ' + markerShapeId);
+ if (!makerShape) {
+ continue;
+ }
+ //Def 에 들어갈 마커 id 를 구한다.
+ var split = markerShapeId.split('.');
+ var markerId = groupElement.id + nodeIndex + key + split[split.length - 1];
+
+ //기존 def 에 존재하는 마커 삭제
+ var existMarkerDef = $(svg).find('#' + markerId);
+ existMarkerDef.remove();
+
+ makerShape = eval('new ' + markerShapeId + '()');
+ var geometry = makerShape.createMarker();
+
+ // 좌상단으로 이동 및 크기 조정
+ geometry.moveCentroid([size[0] / 2, size[1] / 2]);
+ geometry.resizeBox(size[0], size[1]);
+
+ //ref 값이 없을 시 값 보정
+ if (!ref) {
+ ref = [0, 0];
+ if (key == 'start') {
+ ref[0] = size[0];
+ ref[1] = size[1] / 2;
+ } else if (key == 'end') {
+ ref[0] = 0;
+ ref[1] = size[1] / 2;
+ } else if (key == 'mid') {
+ ref[0] = size[0] / 2;
+ ref[1] = size[1] / 2;
+ }
+ }
+
+ //마커 스타일링. geometry < nodeOverrideStyle < makerStyle
+ var nodeOverrideStyle = {
+ 'stroke': nodeStyle['stroke'],
+ 'fill': nodeStyle['stroke']
+ };
+ OG.Util.apply(geometry.style.map, nodeOverrideStyle);
+ OG.Util.apply(geometry.style.map, makerStyle);
+
+ //노드 복사를 위한 가상의 그룹노드
+ var tempNode = me.drawGeom(geometry, null, OG.Constants.MARKER_TEMP_NODE);
+ var cloneNode = $(tempNode).clone().wrapAll("");
+
+ //가상의 그룹노드를 삭제한다.
+ me._remove(me._getREleById(tempNode.id));
+
+ //시작 키일때는 g 태그를 180도 회전시킨다.
+ if (key == 'start') {
+ cloneNode.attr('transform', 'rotate(180,' + size[0] / 2 + ',' + size[1] / 2 + ')');
+ }
+
+ cloneNode.removeAttr('_type');
+ cloneNode.removeAttr('_shape');
+ cloneNode.removeAttr('id');
+ cloneNode.children().each(function () {
+ $(this).removeAttr('_type');
+ $(this).removeAttr('_shape');
+ $(this).removeAttr('id');
+ $(this).removeAttr('marker-end');
+ $(this).removeAttr('marker-start');
+ $(this).removeAttr('marker-mid');
+ });
+
+ var data = {
+ id: markerId,
+ refX: ref[0],
+ refY: ref[1],
+ markerWidth: size[0],
+ markerHeight: size[1],
+ orient: 'auto'
+ };
+ var el = document.createElementNS('http://www.w3.org/2000/svg', 'marker');
+ el.appendChild(cloneNode.get(0));
+ for (var k in data) {
+ el.setAttribute(k, data[k]);
+ }
+
+ $(svg).find('defs').get(0).appendChild(el);
+ makerData[key] = data;
+ }
+
+ //기존 노드는 투명처리한다.
+ rElement.attr('stroke-opacity', '0');
+
+ //기존 노드의 패스를 마커의 영역만큼 뺀 새로운 패스를 생성한다.
+ var totalLenth = Raphael.getTotalLength(nodePath);
+ var from, to;
+ if (makerData['start'] && makerData['end']) {
+ from = makerData['start']['markerWidth'] * 1.5;
+ to = totalLenth - makerData['end']['markerWidth'] * 1.5;
+ }
+ else if (makerData['start'] && !makerData['end']) {
+ from = makerData['start']['markerWidth'] * 1.5;
+ to = totalLenth;
+ }
+ else if (!makerData['start'] && makerData['end']) {
+ from = 0;
+ to = totalLenth - makerData['end']['markerWidth'] * 1.5;
+ } else {
+ from = 0;
+ to = totalLenth;
+ }
+
+ var subPath = Raphael.getSubpath(nodePath, from, to);
+ var virtualNode = me._PAPER.path(subPath);
+ virtualNode.attr(nodeStyle);
+
+ //virtualNode 에 마커 스타일 적용
+ for (var key in makerData) {
+ $(virtualNode.node).attr('marker-' + key, 'url(#' + makerData[key].id + ')');
+ }
+
+ me._add(virtualNode);
+ groupElement.appendChild(virtualNode.node);
+ };
+
+ var drawAnimation = function (rElement, nodeStyle) {
+ var animationData = nodeStyle['animation'];
+ var animationRepeat = nodeStyle['animation-repeat'];
+ var maxDuration = 0;
+ var monitorAnimationIndex;
+ var delay, ms;
+ for (var i = 0, leni = animationData.length; i < leni; i++) {
+ ms = animationData[i].ms ? animationData[i].ms : 0;
+ delay = animationData[i].delay ? animationData[i].delay : 0;
+ if (maxDuration < ms + delay) {
+ maxDuration = ms + delay;
+ monitorAnimationIndex = i;
+ }
+ }
+
+ var startAnimation = function () {
+ for (var i = 0, leni = animationData.length; i < leni; i++) {
+ var ani;
+ if (i == monitorAnimationIndex && animationRepeat) {
+ ani = Raphael.animation(animationData[i].to, animationData[i].ms, startAnimation);
+ rElement.attr(animationData[i].start).animate(ani.delay(animationData[i].delay));
+ } else {
+ ani = Raphael.animation(animationData[i].to, animationData[i].ms);
+ rElement.attr(animationData[i].start).animate(ani.delay(animationData[i].delay));
+ }
+ }
+ };
+ startAnimation();
+ };
+
+ geometry.style.map = _style;
+
+ // 타입에 따라 드로잉
+ switch (geometry.TYPE) {
+ case OG.Constants.GEOM_TYPE.POINT:
+ element = this._PAPER.circle(geometry.coordinate.x, geometry.coordinate.y, 0.5);
+ element.attr(_style);
+
+ //패턴정보가 있을 경우
+ if (rootPattern) {
+ drawPattern(element, _style, null, 0);
+ }
+
+ // connectGuideElement = this._PAPER.circle(geometry.coordinate.x, geometry.coordinate.y, 0.5);
+ // setConnectGuideAttr(connectGuideElement);
+ break;
+
+ case OG.Constants.GEOM_TYPE.LINE:
+ case OG.Constants.GEOM_TYPE.POLYLINE:
+ case OG.Constants.GEOM_TYPE.POLYGON:
+ pathStr = "";
+ vertices = geometry.getVertices();
+ for (var i = 0, leni = vertices.length; i < leni; i++) {
+ if (i === 0) {
+ pathStr = "M" + vertices[i].x + " " + vertices[i].y;
+ } else {
+ pathStr += "L" + vertices[i].x + " " + vertices[i].y;
+ }
+ }
+
+ if (isEdge) {
+ //연결선이면서 마커정보가 있거나 멀티라인인 경우는 arrow 스타일을 제거하도록 한다.
+ if (rootMarker || multi) {
+ delete _style['arrow-end'];
+ delete _style['arrow-start'];
+ }
+ element = this._PAPER.path(pathStr);
+ element.attr(_style);
+
+ //멀티 라인 정보가 있을 경우
+ if (multi) {
+ drawMulti(vertices);
+ element.attr('stroke-opacity', '0');
+ }
+ //멀티 라인 정보가 없고, 마커정보가 있을 경우
+ else if (!multi && rootMarker) {
+ drawMarker(element, _style, pathStr, 0);
+ }
+ //멀티 라인 정보가 없고, 패턴정보가 있을 경우
+ else if (!multi && rootPattern) {
+ drawPattern(element, _style, pathStr, 0);
+ }
+ //애니메이션 정보가 있을 경우
+ else if (!multi && rootAnimation) {
+ drawAnimation(element, _style);
+ }
+
+ connectGuideElement = this._PAPER.path(pathStr);
+ setConnectGuideAttr(connectGuideElement);
+
+ } else {
+ element = this._PAPER.path(pathStr);
+ element.attr(_style);
+
+ //패턴정보가 있을 경우
+ if (rootPattern) {
+ drawPattern(element, _style, pathStr, 0);
+ }
+
+ //애니메이션 정보가 있을 경우
+ if (rootAnimation) {
+ drawAnimation(element, _style);
+ }
+ }
+
+ break;
+ case OG.Constants.GEOM_TYPE.RECTANGLE:
+ if ((_style.r || 0) === 0) {
+ pathStr = "";
+ vertices = geometry.getVertices();
+ for (var i = 0, leni = vertices.length; i < leni; i++) {
+ if (i === 0) {
+ pathStr = "M" + vertices[i].x + " " + vertices[i].y;
+ } else {
+ pathStr += "L" + vertices[i].x + " " + vertices[i].y;
+ }
+ }
+ } else {
+ pathStr = getRoundedPath(geometry, _style.r || 0);
+ }
+
+ element = this._PAPER.path(pathStr);
+ element.attr(_style);
+
+ //패턴정보가 있을 경우
+ if (rootPattern) {
+ drawPattern(element, _style, pathStr, 0);
+ }
+ //애니메이션 정보가 있을 경우
+ if (rootAnimation) {
+ drawAnimation(element, _style);
+ }
+
+ break;
+
+ case OG.Constants.GEOM_TYPE.CIRCLE:
+ geomObj = OG.JSON.decode(geometry.toString());
+ if (geomObj.type === OG.Constants.GEOM_NAME[OG.Constants.GEOM_TYPE.CIRCLE]) {
+ element = this._PAPER.circle(geomObj.center[0], geomObj.center[1], geomObj.radius);
+ //connectGuideElement = this._PAPER.circle(geomObj.center[0], geomObj.center[1], geomObj.radius);
+ } else if (geomObj.type === OG.Constants.GEOM_NAME[OG.Constants.GEOM_TYPE.ELLIPSE]) {
+ if (geomObj.angle === 0) {
+ element = this._PAPER.ellipse(geomObj.center[0], geomObj.center[1], geomObj.radiusX, geomObj.radiusY);
+ } else {
+ pathStr = "";
+ vertices = geometry.getControlPoints();
+ pathStr = "M" + vertices[1].x + " " + vertices[1].y + "A" + geomObj.radiusX + " " + geomObj.radiusY
+ + " " + geomObj.angle + " 1 0 " + vertices[5].x + " " + vertices[5].y;
+ pathStr += "M" + vertices[1].x + " " + vertices[1].y + "A" + geomObj.radiusX + " " + geomObj.radiusY
+ + " " + geomObj.angle + " 1 1 " + vertices[5].x + " " + vertices[5].y;
+ element = this._PAPER.path(pathStr);
+ }
+ }
+ element.attr(_style);
+
+ //패턴정보가 있을 경우
+ if (rootPattern) {
+ drawPattern(element, _style, pathStr, 0);
+ }
+
+ //애니메이션 정보가 있을 경우
+ if (rootAnimation) {
+ drawAnimation(element, _style);
+ }
+
+ break;
+
+ case OG.Constants.GEOM_TYPE.ELLIPSE:
+ geomObj = OG.JSON.decode(geometry.toString());
+ if (geomObj.angle === 0) {
+ element = this._PAPER.ellipse(geomObj.center[0], geomObj.center[1], geomObj.radiusX, geomObj.radiusY);
+ } else {
+ pathStr = "";
+ vertices = geometry.getControlPoints();
+ pathStr = "M" + vertices[1].x + " " + vertices[1].y + "A" + geomObj.radiusX + " " + geomObj.radiusY
+ + " " + geomObj.angle + " 1 0 " + vertices[5].x + " " + vertices[5].y;
+ pathStr += "M" + vertices[1].x + " " + vertices[1].y + "A" + geomObj.radiusX + " " + geomObj.radiusY
+ + " " + geomObj.angle + " 1 1 " + vertices[5].x + " " + vertices[5].y;
+ element = this._PAPER.path(pathStr);
+ }
+ element.attr(_style);
+ //패턴정보가 있을 경우
+ if (rootPattern) {
+ drawPattern(element, _style, pathStr, 0);
+ }
+
+ //애니메이션 정보가 있을 경우
+ if (rootAnimation) {
+ drawAnimation(element, _style);
+ }
+ break;
+
+ case OG.Constants.GEOM_TYPE.CURVE:
+ pathStr = "";
+ vertices = geometry.getControlPoints();
+ for (var i = 0, leni = vertices.length; i < leni; i++) {
+ if (i === 0) {
+ pathStr = "M" + vertices[i].x + " " + vertices[i].y;
+ } else if (i === 1) {
+ pathStr += "R" + vertices[i].x + " " + vertices[i].y;
+ } else {
+ pathStr += " " + vertices[i].x + " " + vertices[i].y;
+ }
+ }
+ element = this._PAPER.path(pathStr);
+ element.attr(_style);
+ //패턴정보가 있을 경우
+ if (rootPattern) {
+ drawPattern(element, _style, pathStr, 0);
+ }
+
+ //애니메이션 정보가 있을 경우
+ if (rootAnimation) {
+ drawAnimation(element, _style);
+ }
+
+ if (isEdge) {
+ connectGuideElement = this._PAPER.path(pathStr);
+ setConnectGuideAttr(connectGuideElement);
+ }
+ break;
+
+ case OG.Constants.GEOM_TYPE.BEZIER_CURVE:
+ pathStr = "";
+ vertices = geometry.getControlPoints();
+ for (var i = 0, leni = vertices.length; i < leni; i++) {
+ if (i === 0) {
+ pathStr = "M" + vertices[i].x + " " + vertices[i].y;
+ } else if (i === 1) {
+ pathStr += "C" + vertices[i].x + " " + vertices[i].y;
+ } else {
+ pathStr += " " + vertices[i].x + " " + vertices[i].y;
+ }
+ }
+ element = this._PAPER.path(pathStr);
+ element.attr(_style);
+
+ //패턴정보가 있을 경우
+ if (rootPattern) {
+ drawPattern(element, _style, pathStr, 0);
+ }
+
+ //애니메이션 정보가 있을 경우
+ if (rootAnimation) {
+ drawAnimation(element, _style);
+ }
+
+ if (isEdge) {
+ connectGuideElement = this._PAPER.path(pathStr);
+ setConnectGuideAttr(connectGuideElement);
+ }
+ break;
+
+ case OG.Constants.GEOM_TYPE.COLLECTION:
+ for (var i = 0, leni = geometry.geometries.length; i < leni; i++) {
+ // recursive call
+ this._drawGeometry(groupElement, geometry.geometries[i], geometry.geometries[i].style, geometry.style.map);
+ }
+ break;
+ }
+
+ if (element) {
+ this._add(element);
+
+ groupElement.appendChild(element.node);
+ $(element.node).attr('name', OG.Constants.ORIGINAL_NODE);
+ if (connectGuideElement) {
+ this._add(connectGuideElement);
+ groupElement.appendChild(connectGuideElement.node);
+ $('#' + connectGuideElement.node.id).attr('name', OG.Constants.CONNECT_GUIDE_EVENT_AREA.NAME);
+ }
+
+ return element.node;
+ } else {
+ return groupElement;
+ }
+};
+
+/**
+ * Shape 의 Label 을 캔버스에 위치 및 사이즈 지정하여 드로잉한다.
+ *
+ * @param {Number[]} position 드로잉할 위치 좌표(중앙 기준)
+ * @param {String} text 텍스트
+ * @param {Number[]} size Text Width, Height, Angle
+ * @param {OG.geometry.Style|Object} style 스타일
+ * @param {String} id Element ID 지정
+ * @param {Boolean} isEdge 라인여부(라인인 경우 라벨이 가려지지 않도록)
+ * @return {Element} DOM Element
+ * @private
+ */
+OG.renderer.RaphaelRenderer.prototype._drawLabel = function (position, text, size, style, id, isEdge) {
+ var me = this, LABEL_PADDING = me._CONFIG.LABEL_PADDING,
+ width = size ? size[0] - LABEL_PADDING * 2 : null,
+ height = size ? size[1] - LABEL_PADDING * 2 : null,
+ angle = size ? size[2] || 0 : 0,
+ group, element, rect, _style = {}, text_anchor, geom,
+ bBox, left, top, x, y;
+ OG.Util.apply(_style, (style instanceof OG.geometry.Style) ? style.map : style || {}, me._CONFIG.DEFAULT_STYLE.TEXT);
+
+ // ID 지정된 경우 존재하면 하위 노드 제거
+ if (id === 0 || id) {
+ group = this._getREleById(id);
+ if (group) {
+ this._removeChild(group);
+ } else {
+ group = this._PAPER.group();
+ this._add(group, id);
+ }
+ } else {
+ group = this._PAPER.group();
+ this._add(group, id);
+ }
+
+ // text-anchor 리셋
+ text_anchor = _style["text-anchor"] || 'middle';
+ _style["text-anchor"] = 'middle';
+
+ //라벨 width 스타일 적용
+ size[0] = style.map['label-width'] ? style.map['label-width'] : size[0];
+
+ //라벨 최대,최소 적용
+ if (me._CONFIG.LABEL_MIN_SIZE && size[0]) {
+ if (size[0] < me._CONFIG.LABEL_MIN_SIZE) {
+ size[0] = me._CONFIG.LABEL_MIN_SIZE;
+ }
+ }
+ if (me._CONFIG.LABEL_MAX_SIZE && size[0]) {
+ if (size[0] > me._CONFIG.LABEL_MAX_SIZE) {
+ size[0] = me._CONFIG.LABEL_MAX_SIZE;
+ }
+ }
+
+ //라벨 draw
+ element = this._PAPER.text(position[0], position[1], text, size);
+ element.attr(_style);
+
+ // real size
+ bBox = element.getBBox();
+
+ // calculate width, height, left, top
+ width = width ? (width > bBox.width ? width : bBox.width) : bBox.width;
+ height = height ? (height > bBox.height ? height : bBox.height) : bBox.height;
+ left = OG.Util.round(position[0] - width / 2);
+ top = OG.Util.round(position[1] - height / 2);
+
+ // Boundary Box
+ geom = new OG.Rectangle([left, top], width, height);
+
+ if (_style["label-direction"] === 'vertical') {
+ // Text Horizontal Align
+ switch (text_anchor) {
+ case "start":
+ y = geom.getBoundary().getLowerCenter().y;
+ break;
+ case "end":
+ y = geom.getBoundary().getUpperCenter().y;
+ break;
+ case "middle":
+ y = geom.getBoundary().getCentroid().y;
+ break;
+ default:
+ y = geom.getBoundary().getCentroid().y;
+ break;
+ }
+
+ // Text Vertical Align
+ switch (_style["vertical-align"]) {
+ case "top":
+ x = OG.Util.round(geom.getBoundary().getLeftCenter().x + bBox.height / 2);
+ break;
+ case "bottom":
+ x = OG.Util.round(geom.getBoundary().getRightCenter().x - bBox.height / 2);
+ break;
+ case "middle":
+ x = geom.getBoundary().getCentroid().x;
+ break;
+ default:
+ x = geom.getBoundary().getCentroid().x;
+ break;
+ }
+
+ angle = -90;
+ } else {
+ // Text Horizontal Align
+ switch (text_anchor) {
+ case "start":
+ x = geom.getBoundary().getLeftCenter().x;
+ break;
+ case "end":
+ x = geom.getBoundary().getRightCenter().x;
+ break;
+ case "middle":
+ x = geom.getBoundary().getCentroid().x;
+ break;
+ default:
+ x = geom.getBoundary().getCentroid().x;
+ break;
+ }
+
+ // Text Vertical Align
+ switch (_style["vertical-align"]) {
+ case "top":
+ y = OG.Util.round(geom.getBoundary().getUpperCenter().y + bBox.height / 2);
+ break;
+ case "bottom":
+ y = OG.Util.round(geom.getBoundary().getLowerCenter().y - bBox.height / 2);
+ break;
+ case "middle":
+ y = geom.getBoundary().getCentroid().y;
+ break;
+ default:
+ y = geom.getBoundary().getCentroid().y;
+ break;
+ }
+ }
+
+ // text align, font-color, font-size 적용
+ element.attr({
+ x: x,
+ y: y,
+ stroke: "none",
+ fill: _style["font-color"] || me._CONFIG.DEFAULT_STYLE.LABEL["font-color"],
+ "font-size": _style["font-size"] || me._CONFIG.DEFAULT_STYLE.LABEL["font-size"],
+ "fill-opacity": 1
+ });
+
+ if (text != "") {
+ $(element.node).css({
+ "background-color": _style["fill"] || me._CONFIG.DEFAULT_STYLE.LABEL["fill"]
+ });
+ }
+
+ $(element.node).css({
+ "cursor": "move"
+ });
+
+ // angle 적용
+ if (angle || _style["label-angle"]) {
+ if (angle === 0) {
+ angle = parseInt(_style["label-angle"], 10);
+ }
+ element.rotate(angle);
+ }
+
+ // text-anchor 적용
+ element.attr({
+ 'text-anchor': text_anchor
+ });
+
+ // 라인인 경우 overwrap 용 rectangle
+ if (isEdge && text) {
+ // real size
+ bBox = element.getBBox();
+
+ rect = this._PAPER.rect(bBox.x - LABEL_PADDING / 2, bBox.y - LABEL_PADDING / 2,
+ bBox.width + LABEL_PADDING, bBox.height + LABEL_PADDING);
+ var rectFill = this._CANVAS_COLOR;
+ if (rectFill == 'transparent') {
+ rectFill = 'white';
+ }
+ rect.attr({stroke: "none", fill: rectFill, 'fill-opacity': 1});
+ this._add(rect);
+ group.node.appendChild(rect.node);
+ }
+
+ // Add to group
+ this._add(element, id + "FO");
+ group.node.appendChild(element.node);
+
+ return group.node;
+};
+
+/**
+ * Shape 을 캔버스에 위치 및 사이즈 지정하여 드로잉한다.
+ *
+ * @example
+ * renderer.drawShape([100, 100], new OG.CircleShape(), [50, 50], {stroke:'red'});
+ *
+ * @param {Number[]} position 드로잉할 위치 좌표(중앙 기준)
+ * @param {OG.shape.IShape} shape Shape
+ * @param {Number[]} size Shape Width, Height
+ * @param {OG.geometry.Style|Object} style 스타일
+ * @param {String} id Element ID 지정
+ * @param {Boolean} preventEvent 이벤트 방지
+ * @return {Element} Group DOM Element with geometry
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.drawShape = function (position, shape, size, style, id, preventEvent, preventDropEvent) {
+ var width = size ? size[0] : 100,
+ height = size ? size[1] : 100,
+ groupNode, geometry, text, image, html, xml,
+ me = this;
+
+ //현재 캔버스 등록
+ shape.currentCanvas = this._CANVAS;
+
+ if (shape instanceof OG.shape.GeomShape) {
+ geometry = shape.createShape();
+
+ // 좌상단으로 이동 및 크기 조정
+ geometry.moveCentroid(position);
+ // console.log(width, height)
+ geometry.resizeBox(width, height);
+ if (size && size[2]) {
+ geometry.rotate(size[2]);
+ shape.angle = size[2];
+ }
+ groupNode = this.drawGeom(geometry, style, id);
+ shape.geom = groupNode.geom;
+
+ } else if (shape instanceof OG.shape.TextShape) {
+ text = shape.createShape();
+
+ groupNode = this.drawText(position, text, size, style, id);
+ shape.text = groupNode.text;
+ shape.angle = groupNode.angle;
+ shape.geom = groupNode.geom;
+ } else if (shape instanceof OG.shape.ImageShape) {
+ image = shape.createShape();
+
+ groupNode = this.drawImage(position, image, size, style, id);
+ shape.image = groupNode.image;
+ shape.angle = groupNode.angle;
+ shape.geom = groupNode.geom;
+ } else if (shape instanceof OG.shape.SvgShape) {
+ xml = shape.createShape();
+
+ groupNode = this.drawSvg(position, xml, size, style, id);
+ shape.xml = groupNode.xml;
+ shape.angle = groupNode.angle;
+ shape.geom = groupNode.geom;
+ } else if (shape instanceof OG.shape.HtmlShape) {
+ html = shape.createShape();
+
+ groupNode = this.drawHtml(position, html, size, style, id);
+ shape.html = groupNode.html;
+ shape.angle = groupNode.angle;
+ shape.geom = groupNode.geom;
+ } else if (shape instanceof OG.shape.EdgeShape) {
+ geometry = shape.geom || shape.createShape();
+
+ groupNode = this.drawEdge(geometry, style, id);
+ shape.geom = groupNode.geom;
+ } else if (shape instanceof OG.shape.GroupShape) {
+ geometry = shape.createShape();
+
+ // 좌상단으로 이동 및 크기 조정
+ geometry.moveCentroid(position);
+ geometry.resizeBox(width, height);
+ if (size && size[2]) {
+ geometry.rotate(size[2]);
+ shape.angle = size[2];
+ }
+
+ groupNode = this.drawGroup(geometry, style, id);
+ shape.geom = groupNode.geom;
+ }
+
+ if (shape.geom) {
+ groupNode.shape = shape;
+ } else {
+ groupNode.shape.label = shape.label;
+ }
+
+ //shape 부가기능 덮어쓰기
+ if (groupNode.shape.geom) {
+ shape.geom = groupNode.shape.geom;
+ groupNode.shape = shape;
+ }
+
+ groupNode.shapeStyle = (style instanceof OG.geometry.Style) ? style.map : style;
+ $(groupNode).attr("_shape_id", shape.SHAPE_ID);
+
+ // 서브 shape 드로잉
+ me._drawSubShape(groupNode);
+
+ // Draw Label
+ if (!(shape instanceof OG.shape.TextShape)) {
+ this.drawLabel(groupNode);
+
+ if (shape instanceof OG.shape.EdgeShape) {
+ this.drawEdgeLabel(groupNode, null, 'FROM');
+ this.drawEdgeLabel(groupNode, null, 'TO');
+ }
+ }
+ if (groupNode.geom) {
+ if (OG.Util.isIE7()) {
+ groupNode.removeAttribute("geom");
+ } else {
+ delete groupNode.geom;
+ }
+ }
+ if (groupNode.text) {
+ if (OG.Util.isIE7()) {
+ groupNode.removeAttribute("text");
+ } else {
+ delete groupNode.text;
+ }
+ }
+ if (groupNode.image) {
+ if (OG.Util.isIE7()) {
+ groupNode.removeAttribute("image");
+ } else {
+ delete groupNode.image;
+ }
+ }
+ if (groupNode.angle) {
+ if (OG.Util.isIE7()) {
+ groupNode.removeAttribute("angle");
+ } else {
+ delete groupNode.angle;
+ }
+ }
+
+ //TODO 이 구간에서 성능저하가 있으므로 신규 shape 이며 그룹위에 떨어졌을 경우 바로 그룹처리 하는 로직을 잠시 보류.
+ //신규 shape 이면 그룹위에 그려졌을 경우 그룹처리
+ // var setGroup = function () {
+ // var frontGroup = me.getFrontForBoundary(me.getBoundary(groupNode));
+ //
+ // if (!frontGroup) {
+ // return;
+ // }
+ // //draw 대상이 Edge 이면 리턴.
+ // if (me.isEdge(groupNode)) {
+ // return;
+ // }
+ // //draw 대상이 Lane 인 경우 리턴.
+ // if (me.isLane(groupNode)) {
+ // return;
+ // }
+ // //그룹이 Lane 인 경우 RootLane 으로 변경
+ // if (me.isLane(frontGroup)) {
+ // frontGroup = me.getRootLane(frontGroup);
+ // }
+ // if (!me._CONFIG.GROUP_DROPABLE || !frontGroup.shape.GROUP_DROPABLE) {
+ // return;
+ // }
+ //
+ // //자신일 경우 반응하지 않는다.
+ // if (frontGroup.id === groupNode.id) {
+ // return;
+ // }
+ // frontGroup.appendChild(groupNode);
+ // };
+ // if (!id) {
+ // setGroup();
+ // }
+
+ //신규 Lane 또는 Pool 이 그려졌을 경우 처리
+ // if (!id && (me.isLane(groupNode) || me.isPool(groupNode))) {
+ // me.putInnerShapeToPool(groupNode);
+ // }
+
+ //shape 에 현재 엘리먼트 등록
+ shape.currentElement = groupNode;
+
+ // drawShape event fire
+ if (!preventEvent) {
+ shape.onDrawShape();
+ $(this._PAPER.canvas).trigger('drawShape', [groupNode]);
+ }
+
+ if (me._CONFIG.POOL_DROP_EVENT && !id && !preventDropEvent && me.isTopGroup(groupNode) && (me.isLane(groupNode) || me.isPool(groupNode))) {
+ me.setDropablePool(groupNode);
+ }
+
+ return groupNode;
+};
+
+/**
+ * Geometry 를 캔버스에 드로잉한다.
+ *
+ * @param {OG.geometry.Geometry} geometry 기하 객체
+ * @param {OG.geometry.Style|Object} style 스타일
+ * @return {Element} Group DOM Element with geometry
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.drawGeom = function (geometry, style, id) {
+ var me = this, group, _style = {};
+
+ OG.Util.apply(_style, (style instanceof OG.geometry.Style) ? style.map : style || {});
+
+ // ID 지정된 경우 존재하면 하위 노드 제거
+ if (id === 0 || id) {
+ group = this._getREleById(id);
+ if (group) {
+ this._removeChild(group);
+ } else {
+ group = this._PAPER.group();
+ this._add(group, id, OG.Constants.NODE_TYPE.SHAPE, OG.Constants.SHAPE_TYPE.GEOM);
+ this._ROOT_GROUP.node.appendChild(group.node);
+ }
+ } else {
+ group = this._PAPER.group();
+ this._add(group, id, OG.Constants.NODE_TYPE.SHAPE, OG.Constants.SHAPE_TYPE.GEOM);
+ this._ROOT_GROUP.node.appendChild(group.node);
+ }
+ // Draw geometry
+ this._drawGeometry(group.node, geometry, _style);
+ group.node.geom = geometry;
+ group.attr(me._CONFIG.DEFAULT_STYLE.SHAPE);
+
+ if (group.node.shape) {
+ group.node.shape.geom = geometry;
+
+ if (group.node.geom) {
+ if (OG.Util.isIE7()) {
+ group.node.removeAttribute("geom");
+ } else {
+ delete group.node.geom;
+ }
+ }
+ }
+
+ return group.node;
+};
+
+/**
+ * Text 를 캔버스에 위치 및 사이즈 지정하여 드로잉한다.
+ *
+ * @param {Number[]} position 드로잉할 위치 좌표(중앙 기준)
+ * @param {String} text 텍스트
+ * @param {Number[]} size Text Width, Height, Angle
+ * @param {OG.geometry.Style|Object} style 스타일
+ * @param {String} id Element ID 지정
+ * @return {Element} DOM Element
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.drawText = function (position, text, size, style, id) {
+ var me = this, width = size ? size[0] : null,
+ height = size ? size[1] : null,
+ angle = size ? size[2] || 0 : 0,
+ group, element, _style = {}, geom,
+ bBox, left, top, x, y;
+ OG.Util.apply(_style, (style instanceof OG.geometry.Style) ? style.map : style || {}, me._CONFIG.DEFAULT_STYLE.TEXT);
+
+ // ID 지정된 경우 존재하면 하위 노드 제거
+ if (id === 0 || id) {
+ group = this._getREleById(id);
+ if (group) {
+ this._removeChild(group);
+ } else {
+ group = this._PAPER.group();
+ this._add(group, id, OG.Constants.NODE_TYPE.SHAPE, OG.Constants.SHAPE_TYPE.TEXT);
+ this._ROOT_GROUP.node.appendChild(group.node);
+ }
+ } else {
+ group = this._PAPER.group();
+ this._add(group, id, OG.Constants.NODE_TYPE.SHAPE, OG.Constants.SHAPE_TYPE.TEXT);
+ this._ROOT_GROUP.node.appendChild(group.node);
+ }
+
+ // Draw text
+ element = this._PAPER.text(position[0], position[1], text, size);
+ element.attr(_style);
+
+ // real size
+ bBox = element.getBBox();
+
+ // calculate width, height, left, top
+ width = width ? (width > bBox.width ? width : bBox.width) : bBox.width;
+ height = height ? (height > bBox.height ? height : bBox.height) : bBox.height;
+ left = OG.Util.round(position[0] - width / 2);
+ top = OG.Util.round(position[1] - height / 2);
+
+ // Boundary Box
+ geom = new OG.Rectangle([left, top], width, height);
+ geom.style.map = _style;
+
+ // Text Horizontal Align
+ switch (_style["text-anchor"]) {
+ case "start":
+ x = geom.getBoundary().getLeftCenter().x;
+ break;
+ case "end":
+ x = geom.getBoundary().getRightCenter().x;
+ break;
+ case "middle":
+ x = geom.getBoundary().getCentroid().x;
+ break;
+ default:
+ x = geom.getBoundary().getCentroid().x;
+ break;
+ }
+
+ // Text Vertical Align
+ switch (_style["vertical-align"]) {
+ case "top":
+ y = OG.Util.round(geom.getBoundary().getUpperCenter().y + bBox.height / 2);
+ break;
+ case "bottom":
+ y = OG.Util.round(geom.getBoundary().getLowerCenter().y - bBox.height / 2);
+ break;
+ case "middle":
+ y = geom.getBoundary().getCentroid().y;
+ break;
+ default:
+ y = geom.getBoundary().getCentroid().y;
+ break;
+ }
+
+ // text align 적용
+ element.attr({x: x, y: y});
+
+ // font-color, font-size 적용
+ element.attr({
+ stroke: "none",
+ fill: _style["font-color"] || me._CONFIG.DEFAULT_STYLE.LABEL["font-color"],
+ "font-size": _style["font-size"] || me._CONFIG.DEFAULT_STYLE.LABEL["font-size"]
+ });
+
+ // angle 적용
+ if (angle) {
+ element.rotate(angle);
+ }
+
+ // Add to group
+ this._add(element);
+ group.node.appendChild(element.node);
+ group.node.text = text;
+ group.node.angle = angle;
+ group.node.geom = geom;
+ group.attr(me._CONFIG.DEFAULT_STYLE.SHAPE);
+
+ if (group.node.shape) {
+ group.node.shape.text = text;
+ group.node.shape.angle = angle;
+ group.node.shape.geom = geom;
+
+ if (group.node.text) {
+ if (OG.Util.isIE7()) {
+ group.node.removeAttribute("text");
+ } else {
+ delete group.node.text;
+ }
+ }
+ if (group.node.angle) {
+ if (OG.Util.isIE7()) {
+ group.node.removeAttribute("angle");
+ } else {
+ delete group.node.angle;
+ }
+ }
+ if (group.node.geom) {
+ if (OG.Util.isIE7()) {
+ group.node.removeAttribute("geom");
+ } else {
+ delete group.node.geom;
+ }
+ }
+ }
+
+ return group.node;
+};
+
+/**
+ * 임베드 HTML String 을 캔버스에 위치 및 사이즈 지정하여 드로잉한다.
+ *
+ * @param {Number[]} position 드로잉할 위치 좌표(중앙 기준)
+ * @param {String} html 임베드 HTML String
+ * @param {Number[]} size Image Width, Height, Angle
+ * @param {OG.geometry.Style|Object} style 스타일
+ * @param {String} id Element ID 지정
+ * @return {Element} DOM Element
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.drawHtml = function (position, html, size, style, id) {
+ var me = this, width = size ? size[0] : null,
+ height = size ? size[1] : null,
+ angle = size ? size[2] || 0 : 0,
+ group, element, _style = {}, bBox, geom, left, top;
+ OG.Util.apply(_style, (style instanceof OG.geometry.Style) ? style.map : style || {}, me._CONFIG.DEFAULT_STYLE.HTML);
+
+ // ID 지정된 경우 존재하면 하위 노드 제거
+ if (id === 0 || id) {
+ group = this._getREleById(id);
+ if (group) {
+ this._removeChild(group);
+ } else {
+ group = this._PAPER.group();
+ this._add(group, id, OG.Constants.NODE_TYPE.SHAPE, OG.Constants.SHAPE_TYPE.HTML);
+ this._ROOT_GROUP.node.appendChild(group.node);
+ }
+ } else {
+ group = this._PAPER.group();
+ this._add(group, id, OG.Constants.NODE_TYPE.SHAPE, OG.Constants.SHAPE_TYPE.HTML);
+ this._ROOT_GROUP.node.appendChild(group.node);
+ }
+
+ // Draw foreign object
+ element = this._PAPER.foreignObject(html, position[0], position[1], width, height);
+ element.attr(_style);
+
+ // real size
+ bBox = element.getBBox();
+
+ // calculate width, height, left, top
+ width = width || bBox.width;
+ height = height || bBox.height;
+ left = OG.Util.round(position[0] - width / 2);
+ top = OG.Util.round(position[1] - height / 2);
+
+ // text align 적용
+ element.attr({x: left, y: top});
+
+ geom = new OG.Rectangle([left, top], width, height);
+ if (angle) {
+ element.rotate(angle);
+ }
+ geom.style.map = _style;
+
+ // Add to group
+ this._add(element);
+ group.node.appendChild(element.node);
+ group.node.html = html;
+ group.node.angle = angle;
+ group.node.geom = geom;
+ group.attr(me._CONFIG.DEFAULT_STYLE.SHAPE);
+
+ if (group.node.shape) {
+ group.node.shape.html = html;
+ group.node.shape.angle = angle;
+ group.node.shape.geom = geom;
+
+ if (group.node.html) {
+ if (OG.Util.isIE7()) {
+ group.node.removeAttribute("html");
+ } else {
+ delete group.node.html;
+ }
+ }
+ if (group.node.angle) {
+ if (OG.Util.isIE7()) {
+ group.node.removeAttribute("angle");
+ } else {
+ delete group.node.angle;
+ }
+ }
+ if (group.node.geom) {
+ if (OG.Util.isIE7()) {
+ group.node.removeAttribute("geom");
+ } else {
+ delete group.node.geom;
+ }
+ }
+ }
+
+ return group.node;
+};
+
+/**
+ * Image 를 캔버스에 위치 및 사이즈 지정하여 드로잉한다.
+ *
+ * @param {Number[]} position 드로잉할 위치 좌표(중앙 기준)
+ * @param {String} imgSrc 이미지경로
+ * @param {Number[]} size Image Width, Height, Angle
+ * @param {OG.geometry.Style|Object} style 스타일
+ * @param {String} id Element ID 지정
+ * @return {Element} DOM Element
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.drawImage = function (position, imgSrc, size, style, id) {
+ var me = this, width = size ? size[0] : null,
+ height = size ? size[1] : null,
+ angle = size ? size[2] || 0 : 0,
+ group, element, _style = {}, bBox, geom, left, top;
+ OG.Util.apply(_style, (style instanceof OG.geometry.Style) ? style.map : style || {}, me._CONFIG.DEFAULT_STYLE.IMAGE);
+
+ // ID 지정된 경우 존재하면 하위 노드 제거
+ if (id === 0 || id) {
+ group = this._getREleById(id);
+ if (group) {
+ this._removeChild(group);
+ } else {
+ group = this._PAPER.group();
+ this._add(group, id, OG.Constants.NODE_TYPE.SHAPE, OG.Constants.SHAPE_TYPE.IMAGE);
+ this._ROOT_GROUP.node.appendChild(group.node);
+ }
+ } else {
+ group = this._PAPER.group();
+ this._add(group, id, OG.Constants.NODE_TYPE.SHAPE, OG.Constants.SHAPE_TYPE.IMAGE);
+ this._ROOT_GROUP.node.appendChild(group.node);
+ }
+
+ // Draw image
+ element = this._PAPER.image(imgSrc, position[0], position[1], width, height);
+ element.attr(_style);
+
+ // real size
+ bBox = element.getBBox();
+
+ // calculate width, height, left, top
+ width = width || bBox.width;
+ height = height || bBox.height;
+ left = OG.Util.round(position[0] - width / 2);
+ top = OG.Util.round(position[1] - height / 2);
+
+ // text align 적용
+ element.attr({x: left, y: top});
+
+ geom = new OG.Rectangle([left, top], width, height);
+ if (angle) {
+ element.rotate(angle);
+ }
+ geom.style.map = _style;
+
+ // Add to group
+ this._add(element);
+ group.node.appendChild(element.node);
+ group.node.image = imgSrc;
+ group.node.angle = angle;
+ group.node.geom = geom;
+ group.attr(me._CONFIG.DEFAULT_STYLE.SHAPE);
+
+ if (group.node.shape) {
+ group.node.shape.image = imgSrc;
+ group.node.shape.angle = angle;
+ group.node.shape.geom = geom;
+
+ if (group.node.image) {
+ if (OG.Util.isIE7()) {
+ group.node.removeAttribute("image");
+ } else {
+ delete group.node.image;
+ }
+ }
+ if (group.node.angle) {
+ if (OG.Util.isIE7()) {
+ group.node.removeAttribute("angle");
+ } else {
+ delete group.node.angle;
+ }
+ }
+ if (group.node.geom) {
+ if (OG.Util.isIE7()) {
+ group.node.removeAttribute("geom");
+ } else {
+ delete group.node.geom;
+ }
+ }
+ }
+
+ return group.node;
+};
+
+/**
+ * Svg 를 캔버스에 위치 및 사이즈 지정하여 드로잉한다.
+ *
+ * @param {Number[]} position 드로잉할 위치 좌표(중앙 기준)
+ * @param {String} xml 드로잉할 xml
+ * @param {Number[]} size Image Width, Height, Angle
+ * @param {OG.geometry.Style|Object} style 스타일
+ * @param {String} id Element ID 지정
+ * @return {Element} DOM Element
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.drawSvg = function (position, xml, size, style, id) {
+ var me = this, width = size ? size[0] : null,
+ height = size ? size[1] : null,
+ angle = size ? size[2] || 0 : 0,
+ group, element, _style = {}, bBox, geom, left, top;
+ OG.Util.apply(_style, (style instanceof OG.geometry.Style) ? style.map : style || {}, me._CONFIG.DEFAULT_STYLE.SVG);
+
+ // ID 지정된 경우 존재하면 하위 노드 제거
+ if (id === 0 || id) {
+ group = this._getREleById(id);
+ if (group) {
+ this._removeChild(group);
+ } else {
+ group = this._PAPER.group();
+ this._add(group, id, OG.Constants.NODE_TYPE.SHAPE, OG.Constants.SHAPE_TYPE.SVG);
+ this._ROOT_GROUP.node.appendChild(group.node);
+ }
+ } else {
+ group = this._PAPER.group();
+ this._add(group, id, OG.Constants.NODE_TYPE.SHAPE, OG.Constants.SHAPE_TYPE.SVG);
+ this._ROOT_GROUP.node.appendChild(group.node);
+ }
+
+ // Draw xml
+ element = $(xml); //this._PAPER.image(imgSrc, position[0], position[1], width, height);
+ for (var key in _style) {
+ element.attr(key, _style[key]);
+ }
+
+ left = OG.Util.round(position[0] - width / 2);
+ top = OG.Util.round(position[1] - height / 2);
+
+ // text align 적용
+ element.attr('x', left + 'px');
+ element.attr('y', top + 'px');
+ element.attr('width', width + 'px');
+ element.attr('height', height + 'px');
+
+ geom = new OG.Rectangle([left, top], width, height);
+ if (angle) {
+ element.rotate(angle);
+ }
+ geom.style.map = _style;
+
+ $(group.node).append(element);
+ group.node.xml = xml;
+ group.node.angle = angle;
+ group.node.geom = geom;
+ group.attr(me._CONFIG.DEFAULT_STYLE.SHAPE);
+
+ if (group.node.shape) {
+ group.node.shape.xml = xml;
+ group.node.shape.angle = angle;
+ group.node.shape.geom = geom;
+
+ if (group.node.image) {
+ if (OG.Util.isIE7()) {
+ group.node.removeAttribute("image");
+ } else {
+ delete group.node.image;
+ }
+ }
+ if (group.node.angle) {
+ if (OG.Util.isIE7()) {
+ group.node.removeAttribute("angle");
+ } else {
+ delete group.node.angle;
+ }
+ }
+ if (group.node.geom) {
+ if (OG.Util.isIE7()) {
+ group.node.removeAttribute("geom");
+ } else {
+ delete group.node.geom;
+ }
+ }
+ }
+
+ return group.node;
+};
+
+/**
+ * 사용자가 지정한 변곡점을 반환
+ * - 조건:
+ * 오직 4개의 점을 가지고있는 다각선에서
+ * 변곡점이 자동으로 그려진 점의 위치가 아닐 때
+ *
+ * - issue: 사용자가 지정한 변곡점(?)은 유지되어야 함
+ **/
+OG.renderer.RaphaelRenderer.prototype._getPointOfInflectionFromEdge = function (line) {
+ var offset = null, _startV, _endV, _centerV, _vertices, _ax, _ay;
+ if (line instanceof OG.geometry.PolyLine) {
+ _vertices = line.getVertices();
+ //한개의 변곡점 일 때 처리하도록 함
+ if (_vertices.length != 4) {
+ return offset;
+ }
+
+ _startV = _vertices[0];
+ _endV = _vertices[_vertices.length - 1];
+ _centerV = _vertices[1];
+
+ offset = {
+ "x": _centerV["x"] - _startV["x"]
+ , "y": _centerV["y"] - _startV["y"]
+ };
+
+ _ax = OG.Util.round((_endV["x"] - _startV["x"]) / 2);
+ _ay = OG.Util.round((_endV["y"] - _startV["y"]) / 2);
+
+ offset["x"] = (offset["x"] == _ax) ? 0 : offset["x"];
+ offset["y"] = (offset["y"] == _ay) ? 0 : offset["y"];
+ }
+
+ return offset;
+};
+
+/**
+ * 라인을 캔버스에 드로잉한다.
+ * OG.geometry.Line 타입인 경우 EdgeType 에 따라 Path 를 자동으로 계산하며,
+ * OG.geometry.PolyLine 인 경우는 주어진 Path 그대로 drawing 한다.
+ *
+ * @param {OG.geometry.Line|OG.geometry.PolyLine} line 또는 polyLine
+ * @param {OG.geometry.Style|Object} style 스타일
+ * @param {String} id Element ID 지정
+ * @param {Boolean} isSelf 셀프 연결 여부
+ * @return {Element} Group DOM Element with geometry
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.drawEdge = function (line, style, id, isSelf, pointOfInflection) {
+ var me = this, group, _style = {},
+ vertices = line.getVertices(),
+ from = vertices[0], to = vertices[vertices.length - 1],
+ points = [], edge, edge_direction;
+
+ OG.Util.apply(_style, (style instanceof OG.geometry.Style) ? style.map : style || {},
+ OG.Util.apply({}, line.style.map, me._CONFIG.DEFAULT_STYLE.EDGE));
+
+ // ID 지정된 경우 존재하면 하위 노드 제거
+ if (id === 0 || id) {
+ group = this._getREleById(id);
+ if (group) {
+ this._removeEdgeChild(group);
+ } else {
+ group = this._PAPER.group();
+ this._add(group, id, OG.Constants.NODE_TYPE.SHAPE, OG.Constants.SHAPE_TYPE.EDGE);
+ this._ROOT_GROUP.node.appendChild(group.node);
+ }
+ } else {
+ group = this._PAPER.group();
+ this._add(group, id, OG.Constants.NODE_TYPE.SHAPE, OG.Constants.SHAPE_TYPE.EDGE);
+ this._ROOT_GROUP.node.appendChild(group.node);
+ }
+
+ if (isSelf) {
+ points = [
+ [from.x, from.y - me._CONFIG.GUIDE_RECT_SIZE / 2],
+ [from.x + me._CONFIG.GUIDE_RECT_SIZE * 2, from.y - me._CONFIG.GUIDE_RECT_SIZE],
+ [from.x + me._CONFIG.GUIDE_RECT_SIZE * 2, from.y + me._CONFIG.GUIDE_RECT_SIZE],
+ [from.x, from.y + me._CONFIG.GUIDE_RECT_SIZE / 2]
+ ];
+ } else if (line instanceof OG.geometry.Line) {
+ // edgeType
+ switch (_style["edge-type"].toLowerCase()) {
+ case OG.Constants.EDGE_TYPE.STRAIGHT:
+ points = [from, to];
+ break;
+ case OG.Constants.EDGE_TYPE.PLAIN:
+ edge_direction = this._adjustEdgeDirection([from.x, from.y], [to.x, to.y]);
+ points = [from, to];
+ break;
+ case OG.Constants.EDGE_TYPE.BEZIER:
+
+ edge_direction = this._adjustEdgeDirection([from.x, from.y], [to.x, to.y]);
+ points = this._bezierCurve([from.x, from.y], [to.x, to.y], edge_direction[0], edge_direction[1]);
+ break;
+ }
+ } else if (line instanceof OG.geometry.Curve) {
+ points = line.getControlPoints();
+ } else if (line instanceof OG.geometry.BezierCurve) {
+ points = line.getControlPoints();
+ } else {
+ points = vertices;
+ }
+
+ // Draw geometry
+ if (isSelf) {
+ edge = new OG.Curve(points);
+ } else if (line instanceof OG.geometry.Curve) {
+ edge = new OG.Curve(points);
+ } else if (line instanceof OG.geometry.BezierCurve) {
+ edge = new OG.BezierCurve(points);
+ } else {
+ if (_style["edge-type"].toLowerCase() === OG.Constants.EDGE_TYPE.BEZIER) {
+ edge = new OG.BezierCurve(points);
+ } else {
+ edge = new OG.PolyLine(points);
+ }
+ }
+
+ // draw Edge
+ this._drawGeometry(group.node, edge, _style, null, true);
+
+ group.node.geom = edge;
+ group.attr(me._CONFIG.DEFAULT_STYLE.SHAPE);
+
+ if (group.node.shape) {
+ group.node.shape.geom = edge;
+
+ if (group.node.geom) {
+ if (OG.Util.isIE7()) {
+ group.node.removeAttribute("geom");
+ } else {
+ delete group.node.geom;
+ }
+ }
+ }
+
+ return group.node;
+};
+
+OG.renderer.RaphaelRenderer.prototype.drawGroup = function (geometry, style, id) {
+ var me = this, group, geomElement, _style = {}, childNodes, i, boundary, titleLine, group_hidden, _tempStyle = {},
+ geom_shadow;
+
+ OG.Util.apply(_style, (style instanceof OG.geometry.Style) ? style.map : style || {});
+
+ // ID 지정된 경우 존재하면 하위 노드 제거, 하위에 Shape 은 삭제하지 않도록
+ if (id === 0 || id) {
+ group = this._getREleById(id);
+ if (group) {
+ childNodes = group.node.childNodes;
+ for (i = childNodes.length - 1; i >= 0; i--) {
+ if ($(childNodes[i]).attr("_type") !== OG.Constants.NODE_TYPE.SHAPE) {
+ this._remove(this._getREleById(childNodes[i].id));
+ }
+ }
+ } else {
+ group = this._PAPER.group();
+ this._add(group, id, OG.Constants.NODE_TYPE.SHAPE, OG.Constants.SHAPE_TYPE.GROUP);
+ this._ROOT_GROUP.node.appendChild(group.node);
+ }
+ } else {
+ group = this._PAPER.group();
+ this._add(group, id, OG.Constants.NODE_TYPE.SHAPE, OG.Constants.SHAPE_TYPE.GROUP);
+ this._ROOT_GROUP.node.appendChild(group.node);
+ }
+
+ // Draw geometry
+ geomElement = this._drawGeometry(group.node, geometry, _style);
+ group.node.geom = geometry;
+ group.attr(me._CONFIG.DEFAULT_STYLE.SHAPE);
+
+ // // Draw Hidden Shadow
+ boundary = geometry.getBoundary();
+ group_hidden = new OG.geometry.Rectangle(boundary.getUpperLeft()
+ , (boundary.getUpperRight().x - boundary.getUpperLeft().x)
+ , (boundary.getLowerLeft().y - boundary.getUpperLeft().y));
+
+ // 타이틀 라인 Drawing
+ OG.Util.apply(_tempStyle, geometry.style.map, _style);
+ if (_tempStyle['label-direction'] && _tempStyle['vertical-align'] === 'top') {
+ if (_tempStyle['label-direction'] === 'vertical') {
+ if (_tempStyle['title-size']) {
+ titleLine = new OG.geometry.Line(
+ [boundary.getUpperLeft().x + _tempStyle['title-size'], boundary.getUpperLeft().y],
+ [boundary.getLowerLeft().x + _tempStyle['title-size'], boundary.getLowerLeft().y]
+ );
+ group_hidden = new OG.geometry.Rectangle(boundary.getUpperLeft(), _tempStyle['title-size'], (boundary.getLowerLeft().y - boundary.getUpperLeft().y));
+ } else {
+ titleLine = new OG.geometry.Line(
+ [boundary.getUpperLeft().x + 20, boundary.getUpperLeft().y],
+ [boundary.getLowerLeft().x + 20, boundary.getLowerLeft().y]
+ );
+ group_hidden = new OG.geometry.Rectangle(boundary.getUpperLeft(), 20, (boundary.getLowerLeft().y - boundary.getUpperLeft().y));
+ }
+ } else {
+ if (_tempStyle['title-size']) {
+ titleLine = new OG.geometry.Line(
+ [boundary.getUpperLeft().x, boundary.getUpperLeft().y + _tempStyle['title-size']],
+ [boundary.getUpperRight().x, boundary.getUpperRight().y + _tempStyle['title-size']]
+ );
+ group_hidden = new OG.geometry.Rectangle(boundary.getUpperLeft(), (boundary.getUpperRight().x - boundary.getUpperLeft().x), _tempStyle['title-size']);
+ } else {
+ titleLine = new OG.geometry.Line(
+ [boundary.getUpperLeft().x, boundary.getUpperLeft().y + 20],
+ [boundary.getUpperRight().x, boundary.getUpperRight().y + 20]
+ );
+ group_hidden = new OG.geometry.Rectangle(boundary.getUpperLeft(), (boundary.getUpperRight().x - boundary.getUpperLeft().x), 20);
+ }
+ }
+ this._drawGeometry(group.node, titleLine, _style);
+
+ //label-fill 어트리뷰트 적용
+ var groupHiddenStyle = JSON.parse(JSON.stringify(me._CONFIG.DEFAULT_STYLE.GROUP_HIDDEN));
+ if (_tempStyle['label-fill']) {
+ groupHiddenStyle['fill'] = _tempStyle['label-fill'];
+ }
+ if (_tempStyle['label-fill-opacity']) {
+ groupHiddenStyle['fill-opacity'] = _tempStyle['label-fill-opacity'];
+ }
+ this._drawGeometry(group.node, group_hidden, groupHiddenStyle);
+ }
+
+ // 위치조정
+ if (geomElement.id !== group.node.firstChild.id) {
+ group.node.insertBefore(geomElement, group.node.firstChild);
+ }
+
+ if (group.node.shape) {
+ if (!group.node.shape.isCollapsed || group.node.shape.isCollapsed === false) {
+ group.node.shape.geom = geometry;
+ }
+
+ if (group.node.geom) {
+ if (OG.Util.isIE7()) {
+ group.node.removeAttribute("geom");
+ } else {
+ delete group.node.geom;
+ }
+ }
+ }
+ return group.node;
+};
+
+/**
+ * Shape 의 Label 을 캔버스에 위치 및 사이즈 지정하여 드로잉한다.
+ *
+ * @param {Element|String} shapeElement Shape DOM element or ID
+ * @param {String} text 텍스트
+ * @param {OG.geometry.Style|Object} style 스타일
+ * @return {Element} DOM Element
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.drawLabel = function (shapeElement, text, style) {
+ var rElement = this._getREleById(OG.Util.isElement(shapeElement) ? shapeElement.id : shapeElement),
+ element, labelElement, envelope, _style = {}, size, beforeText, beforeEvent, position,
+ /**
+ * 라인(꺽은선)의 중심위치를 반환한다.
+ *
+ * @param {Element} element Edge 엘리먼트
+ * @return {OG.Coordinate}
+ */
+ getCenterOfEdge = function (element) {
+ var vertices, from, to, lineLength, distance = 0, i, intersectArray;
+
+ if (element.shape.geom.style.get("edge-type") === OG.Constants.EDGE_TYPE.BEZIER) {
+ vertices = element.shape.geom.getControlPoints();
+ from = vertices[0];
+ to = vertices[vertices.length - 1];
+ return new OG.geometry.Coordinate(OG.Util.round((from.x + to.x) / 2), OG.Util.round((from.y + to.y) / 2));
+ } else {
+ // Edge Shape 인 경우 라인의 중간 지점 찾기
+ vertices = element.shape.geom.getVertices();
+ lineLength = element.shape.geom.getLength();
+
+ for (var i = 0, leni = vertices.length - 1; i < leni; i++) {
+ distance += vertices[i].distance(vertices[i + 1]);
+ if (distance > lineLength / 2) {
+ intersectArray = element.shape.geom.intersectCircleToLine(
+ vertices[i + 1], distance - lineLength / 2, vertices[i + 1], vertices[i]
+ );
+ break;
+ }
+ }
+ return intersectArray[0];
+ }
+ };
+
+ OG.Util.apply(_style, (style instanceof OG.geometry.Style) ? style.map : style || {});
+
+ if (rElement && rElement.node.shape) {
+ text = OG.Util.trim(text);
+ element = rElement.node;
+ envelope = element.shape.geom.getBoundary();
+ beforeText = element.shape.label;
+
+ // beforeLabelChange event fire
+ if (text !== undefined && text !== beforeText) {
+ var onBeforeLabelChange = element.shape.onBeforeLabelChange(text, beforeText);
+ if (typeof onBeforeLabelChange == 'boolean' && !onBeforeLabelChange) {
+ return false;
+ }
+ beforeEvent = jQuery.Event("beforeLabelChange", {
+ element: element,
+ afterText: text,
+ beforeText: beforeText
+ });
+
+ $(this._PAPER.canvas).trigger(beforeEvent);
+ if (beforeEvent.isPropagationStopped()) {
+ return false;
+ }
+ text = beforeEvent.afterText;
+ }
+ OG.Util.apply(element.shape.geom.style.map, _style);
+ element.shape.label = text === undefined ? element.shape.label : text;
+
+ if (element.shape.label !== undefined) {
+
+ // label-position 에 따라 위치 조정
+ switch (element.shape.geom.style.get("label-position")) {
+ case "left":
+ position = [envelope.getCentroid().x - envelope.getWidth(), envelope.getCentroid().y];
+ break;
+ case "right":
+ position = [envelope.getCentroid().x + envelope.getWidth(), envelope.getCentroid().y];
+ break;
+ case "top":
+ position = [envelope.getCentroid().x, envelope.getCentroid().y - envelope.getHeight()];
+ break;
+ case "bottom":
+ position = [envelope.getCentroid().x, envelope.getCentroid().y + envelope.getHeight()];
+ break;
+ default:
+ position = [envelope.getCentroid().x, envelope.getCentroid().y];
+ break;
+ }
+ size = [envelope.getWidth(), envelope.getHeight()];
+
+ if (element.shape instanceof OG.shape.EdgeShape) {
+ var centerOfEdge = getCenterOfEdge(element);
+ position = [centerOfEdge.x, centerOfEdge.y];
+ }
+
+ labelElement = this._drawLabel(
+ position,
+ element.shape.label,
+ size,
+ element.shape.geom.style,
+ element.id + OG.Constants.LABEL_SUFFIX,
+ element.shape instanceof OG.shape.EdgeShape
+ );
+ element.appendChild(labelElement);
+
+ // drawLabel event fire
+ if (text !== undefined) {
+ element.shape.onDrawLabel(text);
+ $(this._PAPER.canvas).trigger('drawLabel', [element, text]);
+
+ }
+
+ if (text !== undefined && beforeText !== undefined && text !== beforeText) {
+ // labelChanged event fire
+ element.shape.onLabelChanged(text, beforeText);
+ // console.log("onLabelChanged",text,beforeText);
+ $(this._PAPER.canvas).trigger('labelChanged', [element, text, beforeText]);
+ }
+ }
+ }
+ // console.log("labelElement",labelElement);
+ return labelElement;
+};
+
+/**
+ * Edge 의 from, to Label 을 캔버스에 위치 및 사이즈 지정하여 드로잉한다.
+ *
+ * @param {Element|String} shapeElement Shape DOM element or ID
+ * @param {String} text 텍스트
+ * @param {String} type 유형(FROM or TO)
+ * @return {Element} DOM Element
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.drawEdgeLabel = function (shapeElement, text, type) {
+ var me = this, rElement = this._getREleById(OG.Util.isElement(shapeElement) ? shapeElement.id : shapeElement),
+ element, vertices, labelElement, position, edgeLabel, suffix;
+
+ if (rElement && rElement.node.shape) {
+ text = OG.Util.trim(text);
+ element = rElement.node;
+
+ if (element.shape instanceof OG.shape.EdgeShape) {
+ vertices = element.shape.geom.getVertices();
+ if (type === 'FROM') {
+ position = [vertices[0].x, vertices[0].y + me._CONFIG.FROMTO_LABEL_OFFSET_TOP];
+ element.shape.fromLabel = text || element.shape.fromLabel;
+ edgeLabel = element.shape.fromLabel;
+ suffix = OG.Constants.FROM_LABEL_SUFFIX;
+ } else {
+ position = [vertices[vertices.length - 1].x, vertices[vertices.length - 1].y + me._CONFIG.FROMTO_LABEL_OFFSET_TOP];
+ element.shape.toLabel = text || element.shape.toLabel;
+ edgeLabel = element.shape.toLabel;
+ suffix = OG.Constants.TO_LABEL_SUFFIX;
+ }
+
+ if (edgeLabel) {
+ labelElement = this._drawLabel(
+ position,
+ edgeLabel,
+ [0, 0],
+ element.shape.geom.style,
+ element.id + suffix,
+ false
+ );
+ element.appendChild(labelElement);
+ }
+ }
+ }
+
+ return labelElement;
+};
+
+/**
+ * Element 에 저장된 geom, angle, image, text 정보로 shape 을 redraw 한다.
+ *
+ * @param {Element} element Shape 엘리먼트
+ * @param {String[]} excludeEdgeId redraw 제외할 Edge ID
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.redrawShape = function (element, excludeEdgeId, inclusion) {
+ var me = this, envelope, center, width, height, upperLeft;
+
+ var redrawChildConnectedEdge = function (_collapseRootElement, _element) {
+ var edgeIdArray, fromEdge, toEdge, _childNodes = _element.childNodes, otherShape, i, j, isNeedToRedraw;
+ for (i = _childNodes.length - 1; i >= 0; i--) {
+
+ if (!me.isShape(_element)) {
+ return;
+ }
+ redrawChildConnectedEdge(_collapseRootElement, _childNodes[i]);
+
+ isNeedToRedraw = false;
+ edgeIdArray = $(_childNodes[i]).attr("_fromedge");
+ if (edgeIdArray) {
+ edgeIdArray = edgeIdArray.split(",");
+ for (var j = 0, lenj = edgeIdArray.length; j < lenj; j++) {
+ fromEdge = me.getElementById(edgeIdArray[j]);
+ if (fromEdge) {
+ otherShape = me._getShapeFromTerminal($(fromEdge).attr("_from"));
+
+ // otherShape 이 같은 collapse 범위내에 있는지 체크
+ if ($(otherShape).parents("#" + _collapseRootElement.id).length === 0) {
+ isNeedToRedraw = true;
+ }
+ }
+ }
+ }
+
+ edgeIdArray = $(_childNodes[i]).attr("_toedge");
+ if (edgeIdArray) {
+ edgeIdArray = edgeIdArray.split(",");
+ for (var j = 0, lenj = edgeIdArray.length; j < lenj; j++) {
+ toEdge = me.getElementById(edgeIdArray[j]);
+ if (toEdge) {
+ otherShape = me._getShapeFromTerminal($(toEdge).attr("_to"));
+
+ // otherShape 이 같은 collapse 범위내에 있는지 체크
+ if ($(otherShape).parents("#" + _collapseRootElement.id).length === 0) {
+ isNeedToRedraw = true;
+ }
+ }
+ }
+ }
+
+ // group 영역 밖의 연결된 otherShape 이 있는 경우 redrawConnectedEdge
+ if (isNeedToRedraw === true) {
+ me.redrawConnectedEdge(_childNodes[i], excludeEdgeId);
+ }
+ }
+ };
+
+ if (element && element.shape.geom) {
+ if (element.shape.redrawShape) {
+ element.shape.redrawShape();
+ }
+
+ switch ($(element).attr("_shape")) {
+ case OG.Constants.SHAPE_TYPE.GEOM:
+ element = this.drawGeom(element.shape.geom, {}, element.id);
+ this.redrawConnectedEdge(element, excludeEdgeId);
+ this.drawLabel(element);
+ break;
+ case OG.Constants.SHAPE_TYPE.TEXT:
+ envelope = element.shape.geom.getBoundary();
+ center = envelope.getCentroid();
+ width = envelope.getWidth();
+ height = envelope.getHeight();
+ element = this.drawText([center.x, center.y], element.shape.text,
+ [width, height, element.shape.angle], element.shape.geom.style, element.id);
+ this.redrawConnectedEdge(element, excludeEdgeId);
+ break;
+ case OG.Constants.SHAPE_TYPE.SVG:
+ envelope = element.shape.geom.getBoundary();
+ center = envelope.getCentroid();
+ width = envelope.getWidth();
+ height = envelope.getHeight();
+ element = this.drawSvg([center.x, center.y], element.shape.xml,
+ [width, height, element.shape.angle], element.shape.geom.style, element.id);
+ this.redrawConnectedEdge(element, excludeEdgeId);
+ this.drawLabel(element);
+ break;
+ case OG.Constants.SHAPE_TYPE.IMAGE:
+ envelope = element.shape.geom.getBoundary();
+ center = envelope.getCentroid();
+ width = envelope.getWidth();
+ height = envelope.getHeight();
+ element = this.drawImage([center.x, center.y], element.shape.image,
+ [width, height, element.shape.angle], element.shape.geom.style, element.id);
+ this.redrawConnectedEdge(element, excludeEdgeId);
+ this.drawLabel(element);
+ break;
+ case OG.Constants.SHAPE_TYPE.HTML:
+ envelope = element.shape.geom.getBoundary();
+ center = envelope.getCentroid();
+ width = envelope.getWidth();
+ height = envelope.getHeight();
+ element = this.drawHtml([center.x, center.y], element.shape.html,
+ [width, height, element.shape.angle], element.shape.geom.style, element.id);
+ this.redrawConnectedEdge(element, excludeEdgeId);
+ this.drawLabel(element);
+ break;
+ case OG.Constants.SHAPE_TYPE.EDGE:
+ element = this.drawEdge(element.shape.geom, element.shape.geom.style, element.id);
+ this.drawLabel(element);
+ this.drawEdgeLabel(element, null, 'FROM');
+ this.drawEdgeLabel(element, null, 'TO');
+ break;
+ case OG.Constants.SHAPE_TYPE.GROUP:
+ if (element.shape.isCollapsed === true) {
+ envelope = element.shape.geom.getBoundary();
+ upperLeft = envelope.getUpperLeft();
+ element = this.drawGroup(new OG.geometry.Rectangle(
+ upperLeft, me._CONFIG.COLLAPSE_SIZE * 3, me._CONFIG.COLLAPSE_SIZE * 2),
+ element.shape.geom.style, element.id);
+ redrawChildConnectedEdge(element, element);
+ this.redrawConnectedEdge(element, excludeEdgeId);
+ } else {
+ element = this.drawGroup(element.shape.geom, element.shape.geom.style, element.id);
+ this.redrawConnectedEdge(element, excludeEdgeId);
+ this.drawLabel(element);
+ }
+ break;
+ }
+
+ //서브 도형 그리기
+ me._drawSubShape(element);
+
+ //shape 에 현재 캔버스,엘리먼트 등록
+ element.shape.currentElement = element;
+ element.shape.currentCanvas = this._CANVAS;
+ }
+
+ // redrawShape event fire
+ element.shape.onRedrawShape();
+ $(this._PAPER.canvas).trigger('redrawShape', [element]);
+
+ return element;
+};
+
+/**
+ * Shape 의 연결된 Edge 를 redraw 한다.(이동 또는 리사이즈시)
+ *
+ * @param {Element} element
+ * @param {String[]} excludeEdgeId redraw 제외할 Edge ID
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.redrawConnectedEdge = function (element, excludeEdgeId) {
+ var me = this, edgeId,
+ rightAngleCalibration = function (edgeId) {
+ //리드로우 하기 전에 shape과의 연결점과 연결점 이전의 라인이 수평 또는 수직인지 살피고
+ //만약 그렇다면 수평 또는 수직의 형태를 그대로 보전하는 작업을 한다.
+ var rEdge = me._getREleById(edgeId);
+ var vertices, from, to, edge, geometry, fromXY, toXY;
+
+ if (!rEdge) {
+ return edgeId;
+ }
+
+ edge = rEdge.node;
+ geometry = rEdge.node.shape.geom;
+
+ if (!geometry) {
+ return edgeId;
+ }
+
+ vertices = geometry.getVertices();
+ from = $(edge).attr("_from");
+ to = $(edge).attr("_to");
+
+ //두 점만으로 이루어진 엣지일 경우는 해당하지 않는다.
+ if (vertices.length <= 2) {
+ return edgeId;
+ }
+
+ if (from) {
+ fromXY = me._getPositionFromTerminal(from);
+ }
+
+ if (to) {
+ toXY = me._getPositionFromTerminal(to);
+ }
+
+ if (from) {
+ if (vertices[0].x === vertices[1].x) {
+ vertices[1].x = fromXY.x;
+ }
+ if (vertices[0].y === vertices[1].y) {
+ vertices[1].y = fromXY.y;
+ }
+ }
+
+ if (to) {
+ if (vertices[vertices.length - 1].x === vertices[vertices.length - 2].x) {
+ vertices[vertices.length - 2].x = toXY.x;
+ }
+ if (vertices[vertices.length - 1].y === vertices[vertices.length - 2].y) {
+ vertices[vertices.length - 2].y = toXY.y;
+ }
+ }
+ edge.shape.geom.setVertices(vertices);
+ return edge;
+ };
+
+ var edgeToReconnect;
+ edgeId = $(element).attr("_fromedge");
+ if (edgeId) {
+ $.each(edgeId.split(","), function (idx, item) {
+ if (!excludeEdgeId || excludeEdgeId.toString().indexOf(item) < 0) {
+ edgeToReconnect = rightAngleCalibration(item);
+ me.connect($(edgeToReconnect).attr('_from'), $(edgeToReconnect).attr('_to'), edgeToReconnect, null, null, true);
+ }
+ });
+ }
+
+ edgeId = $(element).attr("_toedge");
+ if (edgeId) {
+ $.each(edgeId.split(","), function (idx, item) {
+ if (!excludeEdgeId || excludeEdgeId.toString().indexOf(item) < 0) {
+ edgeToReconnect = rightAngleCalibration(item);
+ me.connect($(edgeToReconnect).attr('_from'), $(edgeToReconnect).attr('_to'), edgeToReconnect, null, null, true);
+ }
+ });
+ }
+};
+
+
+/**
+ * 연결된 터미널의 vertices 를 초기화하여 재연결한다.
+ *
+ * @param {Element} edge Edge Shape
+ * @return {Element} 연결된 Edge 엘리먼트
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.reconnect = function (edge) {
+
+ var rEdge = this._getREleById(OG.Util.isElement(edge) ? edge.id : edge);
+ if (rEdge) {
+ edge = rEdge.node;
+ } else {
+ return null;
+ }
+
+ var from, to;
+ var me = this, fromShape, toShape, fromXY, toXY,
+ isSelf;
+
+ from = $(edge).attr("_from");
+ to = $(edge).attr("_to");
+ if (from) {
+ fromShape = this._getShapeFromTerminal(from);
+ }
+ if (to) {
+ toShape = this._getShapeFromTerminal(to);
+ }
+ if (!fromShape || !toShape) {
+ return edge;
+ }
+
+ // 연결 포지션 초기화
+ from = this.createDefaultTerminalString(fromShape);
+ to = this.createDefaultTerminalString(toShape);
+
+ fromXY = this._getPositionFromTerminal(from);
+ toXY = this._getPositionFromTerminal(to);
+
+ // 연결 노드 정보 설정
+ $(edge).attr("_from", from);
+ $(edge).attr("_to", to);
+
+
+ var geometry = edge.shape.geom;
+ var vertices = geometry.getVertices();
+ var newVertieces = [vertices[0], vertices[vertices.length - 1]];
+
+ newVertieces[0].x = fromXY.x;
+ newVertieces[0].y = fromXY.y;
+
+ newVertieces[1].x = toXY.x;
+ newVertieces[1].y = toXY.y;
+
+
+ // 라인 드로잉
+ edge = this.drawEdge(new OG.PolyLine(newVertieces), edge.shape.geom.style, edge ? edge.id : null);
+
+ me.trimConnectInnerVertice(edge);
+ me.trimConnectIntersection(edge);
+ me.trimEdge(edge);
+ me.checkBridgeEdge(edge);
+
+ return edge;
+};
+
+/**
+ * 두개의 터미널을 연결하고, 속성정보에 추가한다.
+ * @param {Element|Number[]} fromTerminal 시작점 (fromTerminal)
+ * @param {Element|Number[]} toTerminal 끝점 (toTerminal)
+ * @param {Element} edge Edge Shape
+ * @param {OG.geometry.Style|Object} style 스타일
+ * @param {String} label Label
+ * @param {Boolean} preventTrigger 이벤트 트리거 발생 막기
+ * @returns {Element} 연결된 Edge 엘리먼트
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.connect = function (fromTerminal, toTerminal, edge, style, label, preventTrigger) {
+ var isEssensia;
+ var rEdge = this._getREleById(OG.Util.isElement(edge) ? edge.id : edge);
+ if (rEdge) {
+ edge = rEdge.node;
+ } else {
+ return null;
+ }
+
+ //if label null, convert undefined
+ label = label ? label : undefined;
+ var me = this, _style = {}, fromShape, toShape, fromXY, toXY,
+ isSelf, beforeEvent,
+ addAttrValues = function (element, name, value) {
+ var attrValue = $(element).attr(name),
+ array = attrValue ? attrValue.split(",") : [],
+ newArray = [];
+ $.each(array, function (idx, item) {
+ if (item !== value) {
+ newArray.push(item);
+ }
+ });
+ newArray.push(value);
+
+ $(element).attr(name, newArray.toString());
+ return element;
+ };
+
+ // edge 의 style 도 검색하여 존재한다면 style 에 set
+ if (edge.shape.geom.style instanceof OG.geometry.Style) {
+ style = edge.shape.geom.style;
+ }
+ OG.Util.apply(_style, (style instanceof OG.geometry.Style) ? style.map : style || {}, me._CONFIG.DEFAULT_STYLE.EDGE);
+
+
+ if (!fromTerminal) {
+ fromTerminal = $(edge).attr("_from");
+ }
+ if (!toTerminal) {
+ toTerminal = $(edge).attr("_to");
+ }
+
+ if (fromTerminal) {
+ fromShape = this._getShapeFromTerminal(fromTerminal);
+ fromXY = this._getPositionFromTerminal(fromTerminal);
+ }
+
+ if (toTerminal) {
+ toShape = this._getShapeFromTerminal(toTerminal);
+ toXY = this._getPositionFromTerminal(toTerminal);
+ }
+
+ //셀프 커넥션 처리
+ isSelf = fromShape && toShape && fromShape.id === toShape.id;
+ if (isSelf) {
+ fromXY = toXY = fromShape.shape.geom.getBoundary().getRightCenter();
+ }
+
+ if (fromShape && toShape) {
+ if (fromShape.attributes._shape_id.value == "OG.shape.bpmn.Value_Chain" || toShape.attributes._shape_id.value == "OG.shape.bpmn.Value_Chain") {
+ _style["arrow-end"] = "none";
+ } else if (fromShape.attributes._shape_id.value == "OG.shape.bpmn.Value_Chain_Module" || toShape.attributes._shape_id.value == "OG.shape.bpmn.Value_Chain_Module") {
+ _style["arrow-end"] = "none";
+ } else if (fromShape.attributes._shape_id.value == "OG.shape.bpmn.A_HumanTask" || toShape.attributes._shape_id.value == "OG.shape.bpmn.A_HumanTask") {
+ _style["edge-type"] = "plain";
+ _style["arrow-start"] = "none";
+ _style["arrow-end"] = "block-wide-long";
+ }
+
+ var onBeforeConnectShapeFrom = fromShape.shape.onBeforeConnectShape(edge, fromShape, toShape);
+ if (typeof onBeforeConnectShapeFrom == 'boolean' && !onBeforeConnectShapeFrom) {
+ this.remove(edge);
+ return null;
+ }
+ var onBeforeConnectShapeTo = toShape.shape.onBeforeConnectShape(edge, fromShape, toShape);
+ if (typeof onBeforeConnectShapeTo == 'boolean' && !onBeforeConnectShapeTo) {
+ this.remove(edge);
+ return null;
+ }
+ beforeEvent = jQuery.Event("beforeConnectShape", {edge: edge, fromShape: fromShape, toShape: toShape});
+ $(this._PAPER.canvas).trigger(beforeEvent);
+ if (beforeEvent.isPropagationStopped()) {
+ this.remove(edge);
+ return null;
+ }
+ }
+
+ var geometry = edge.shape.geom;
+ var vertices = geometry.getVertices();
+
+ if (fromTerminal) {
+ vertices[0].x = fromXY.x
+ vertices[0].y = fromXY.y
+ }
+
+ if (toTerminal) {
+ vertices[vertices.length - 1].x = toXY.x
+ vertices[vertices.length - 1].y = toXY.y
+ }
+
+ // 라인 드로잉
+ if (fromShape) {
+ isEssensia = $(fromShape).attr("_shape_id").indexOf('OG.shape.essencia') !== -1;
+ }
+ if (!isEssensia) {
+ // 디폴트 스타일이 정해져 있지 않다면 화살표로 그린다.
+ if (typeof style == 'undefined' || style == null || style.length == 0 || style == '') {
+ edge.shape.geom.style.map['arrow-start'] = 'none';
+ edge.shape.geom.style.map['arrow-end'] = 'block';
+ }
+ edge = this.drawEdge(new OG.PolyLine(vertices), edge.shape.geom.style, edge ? edge.id : null, isSelf);
+ }
+ if (isEssensia) {
+ edge.shape.geom.style.map['arrow-start'] = 'diamond';
+ edge.shape.geom.style.map['arrow-end'] = 'none';
+ edge = this.drawEdge(new OG.PolyLine(vertices), edge.shape.geom.style, edge ? edge.id : null, isSelf);
+ }
+
+ // Draw Label
+ this.drawLabel(edge, label);
+ this.drawEdgeLabel(edge, null, 'FROM');
+ this.drawEdgeLabel(edge, null, 'TO');
+
+
+ // 이전 연결속성정보 삭제
+ this.disconnect(edge);
+
+ // 연결 노드 정보 설정
+ if (fromTerminal) {
+ $(edge).attr("_from", fromTerminal);
+ addAttrValues(fromShape, "_toedge", edge.id);
+ }
+ if (toTerminal) {
+ $(edge).attr("_to", toTerminal);
+ addAttrValues(toShape, "_fromedge", edge.id);
+ }
+
+ me.trimConnectInnerVertice(edge);
+ me.trimConnectIntersection(edge);
+ me.trimEdge(edge);
+ me.checkBridgeEdge(edge);
+
+ if (fromShape && toShape) {
+ // connectShape event fire
+ if (!preventTrigger) {
+ fromShape.shape.onConnectShape(edge, fromShape, toShape);
+ toShape.shape.onConnectShape(edge, fromShape, toShape);
+ $(this._PAPER.canvas).trigger('connectShape', [edge, fromShape, toShape]);
+ }
+ }
+
+ return edge;
+};
+
+/**
+ * 주어진 도형을 신규 아이디로 변경한다.
+ * @param element
+ * @param id
+ */
+OG.renderer.RaphaelRenderer.prototype.updateId = function (element, id) {
+ var me = this;
+ var replaceTerminalId = function (edge, oldId, id) {
+ var terminal, newTerminal;
+ terminal = $(edge).attr('_from');
+ if (terminal && terminal.length > 0) {
+ var split = terminal.split('_TERMINAL_');
+ if (split[0] == oldId) {
+ newTerminal = id + '_TERMINAL_' + split[1];
+ $(edge).attr('_from', newTerminal);
+ }
+ }
+ terminal = $(edge).attr('_to');
+ if (terminal && terminal.length > 0) {
+ var split = terminal.split('_TERMINAL_');
+ if (split[0] == oldId) {
+ newTerminal = id + '_TERMINAL_' + split[1];
+ $(edge).attr('_to', newTerminal);
+ }
+ }
+ };
+ var rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element);
+ if (!rElement) {
+ return;
+ }
+
+ var oldId = rElement.id;
+ //Edge 인 경우 연결된 도형이 있을 경우, _toedge, _fromedge 를 변경한다.
+ if (rElement.node.shape instanceof OG.EdgeShape) {
+ var relatedElementsFromEdge = me._CANVAS.getRelatedElementsFromEdge(rElement.node);
+ if (relatedElementsFromEdge.from) {
+ var toedge = $(relatedElementsFromEdge.from).attr('_toedge');
+ var array = toedge.split(",");
+ $.each(array, function (idx, item) {
+ if (item == oldId) {
+ array[idx] = id;
+ }
+ });
+ $(relatedElementsFromEdge.from).attr('_toedge', array.toString());
+ }
+ if (relatedElementsFromEdge.to) {
+ var fromedge = $(relatedElementsFromEdge.to).attr('_fromedge');
+ var array = fromedge.split(",");
+ $.each(array, function (idx, item) {
+ if (item == oldId) {
+ array[idx] = id;
+ }
+ });
+ $(relatedElementsFromEdge.to).attr('_fromedge', array.toString());
+ }
+ }
+ //Edge 가 아니고 연결된 Edge 가 있을경우, Edge 의 터미널을 변경한다.
+ else {
+ var prevEdges = me.getPrevEdges(rElement.node);
+ var nextEdges = me.getNextEdges(rElement.node);
+ for (var i = 0, leni = prevEdges.length; i < leni; i++) {
+ replaceTerminalId(prevEdges[i], oldId, id);
+ }
+ for (var i = 0, leni = nextEdges.length; i < leni; i++) {
+ replaceTerminalId(nextEdges[i], oldId, id);
+ }
+ }
+
+ rElement.node.id = id;
+ rElement.id = id;
+ this._ELE_MAP.put(id, rElement);
+ this._ELE_MAP.remove(oldId);
+ return rElement.node;
+};
+
+/**
+ * 단방향 연결속성정보를 삭제한다. Edge 인 경우에만 해당한다.
+ *
+ * @param {Element} element
+ * @param {String} connectDirection 연결방향 'from' or 'to'
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.disconnectOneWay = function (element, connectDirection) {
+ var me = this, fromShape, toShape,
+ removeAttrValue = function (element, name, value) {
+ var attrValue = $(element).attr(name),
+ array = attrValue ? attrValue.split(",") : [],
+ newArray = [];
+ $.each(array, function (idx, item) {
+ if (item !== value) {
+ newArray.push(item);
+ }
+ });
+
+ $(element).attr(name, newArray.toString());
+ return element;
+ };
+
+ var isEdge = $(element).attr("_shape") === OG.Constants.SHAPE_TYPE.EDGE;
+ if (!element || !isEdge) {
+ return;
+ }
+
+ // Edge 인 경우 연결된 Shape 의 연결 속성 정보를 삭제
+ var fromTerminal = $(element).attr("_from");
+ var toTerminal = $(element).attr("_to");
+
+ if (fromTerminal && connectDirection === 'from') {
+ fromShape = this._getShapeFromTerminal(fromTerminal);
+ removeAttrValue(fromShape, "_toedge", element.id);
+ $(element).removeAttr("_from");
+ }
+
+ if (toTerminal && connectDirection === 'to') {
+ toShape = this._getShapeFromTerminal(toTerminal);
+ removeAttrValue(toShape, "_fromedge", element.id);
+ $(element).removeAttr("_to");
+ }
+
+ // disconnectShape event fire
+ if (fromShape && toShape) {
+ fromShape.shape.onDisconnectShape(element, fromShape, toShape);
+ toShape.shape.onDisconnectShape(element, fromShape, toShape);
+ $(this._PAPER.canvas).trigger('disconnectShape', [element, fromShape, toShape]);
+ }
+};
+
+/**
+ * 연결속성정보를 삭제한다. Edge 인 경우는 연결 속성정보만 삭제하고, 일반 Shape 인 경우는 연결된 모든 Edge 를 삭제한다.
+ *
+ * @param {Element} element
+ * @param {Boolean} preventEvent
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.disconnect = function (element, preventEvent) {
+ var me = this, fromTerminalId, toTerminalId, fromShape, toShape, fromEdgeId, toEdgeId, fromEdge, toEdge,
+ removeAttrValue = function (element, name, value) {
+ var attrValue = $(element).attr(name),
+ array = attrValue ? attrValue.split(",") : [],
+ newArray = [];
+ $.each(array, function (idx, item) {
+ if (item !== value) {
+ newArray.push(item);
+ }
+ });
+
+ $(element).attr(name, newArray.toString());
+ return element;
+ };
+
+ if (element) {
+ if ($(element).attr("_shape") === OG.Constants.SHAPE_TYPE.EDGE) {
+ // Edge 인 경우 연결된 Shape 의 연결 속성 정보를 삭제
+ var fromTerminal = $(element).attr("_from");
+ var toTerminal = $(element).attr("_to");
+
+ if (fromTerminal) {
+ fromShape = this._getShapeFromTerminal(fromTerminal);
+ removeAttrValue(fromShape, "_toedge", element.id);
+ $(element).removeAttr("_from");
+ }
+
+ if (toTerminal) {
+ toShape = this._getShapeFromTerminal(toTerminal);
+ removeAttrValue(toShape, "_fromedge", element.id);
+ $(element).removeAttr("_to");
+ }
+
+ // disconnectShape event fire
+ if (fromShape && toShape) {
+ fromShape.shape.onDisconnectShape(element, fromShape, toShape);
+ toShape.shape.onDisconnectShape(element, fromShape, toShape);
+ if (!preventEvent) {
+ $(this._PAPER.canvas).trigger('disconnectShape', [element, fromShape, toShape]);
+ }
+ }
+ } else {
+ // 일반 Shape 인 경우 연결된 모든 Edge 와 속성 정보를 삭제
+ fromEdgeId = $(element).attr("_fromedge");
+ toEdgeId = $(element).attr("_toedge");
+
+ if (fromEdgeId) {
+ $.each(fromEdgeId.split(","), function (idx, item) {
+ fromEdge = me.getElementById(item);
+
+ fromTerminalId = $(fromEdge).attr("_from");
+ if (fromTerminalId) {
+ fromShape = me._getShapeFromTerminal(fromTerminalId);
+ removeAttrValue(fromShape, "_toedge", item);
+ }
+
+ // disconnectShape event fire
+ if (fromShape && element) {
+ fromShape.shape.onDisconnectShape(fromEdge, fromShape, element);
+ element.shape.onDisconnectShape(fromEdge, fromShape, element);
+ if (!preventEvent) {
+ $(me._PAPER.canvas).trigger('disconnectShape', [fromEdge, fromShape, element]);
+ }
+ }
+ me.removeShape(fromEdge, preventEvent);
+ });
+ }
+
+ if (toEdgeId) {
+ $.each(toEdgeId.split(","), function (idx, item) {
+ toEdge = me.getElementById(item);
+
+ toTerminalId = $(toEdge).attr("_to");
+ if (toTerminalId) {
+ toShape = me._getShapeFromTerminal(toTerminalId);
+ removeAttrValue(toShape, "_fromedge", item);
+ }
+
+ // disconnectShape event fire
+ if (element && toShape) {
+ element.shape.onDisconnectShape(toEdge, element, toShape);
+ toShape.shape.onDisconnectShape(toEdge, element, toShape);
+ if (!preventEvent) {
+ $(me._PAPER.canvas).trigger('disconnectShape', [toEdge, element, toShape]);
+ }
+ }
+
+ me.removeShape(toEdge, preventEvent);
+ });
+ }
+ }
+ }
+};
+
+/**
+ * ID에 해당하는 Element 의 Drop Over 가이드를 드로잉한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.drawDropOverGuide = function (element) {
+ var me = this, rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element),
+ geometry = rElement ? rElement.node.shape.geom : null,
+ envelope, _upperLeft, _bBoxRect,
+ _size = me._CONFIG.GUIDE_RECT_SIZE / 2,
+ _hSize = _size / 2;
+
+ if (rElement && geometry && $(element).attr("_shape") !== OG.Constants.SHAPE_TYPE.EDGE && !this._getREleById(rElement.id + OG.Constants.DROP_OVER_BBOX_SUFFIX)) {
+ envelope = geometry.getBoundary();
+ _upperLeft = envelope.getUpperLeft();
+
+ // guide line 랜더링
+ _bBoxRect = this._PAPER.rect(_upperLeft.x - _hSize, _upperLeft.y - _hSize, envelope.getWidth() + _size, envelope.getHeight() + _size);
+ _bBoxRect.attr(OG.Util.apply({'stroke-width': _size}, me._CONFIG.DEFAULT_STYLE.DROP_OVER_BBOX));
+ this._add(_bBoxRect, rElement.id + OG.Constants.DROP_OVER_BBOX_SUFFIX);
+
+ // layer 위치 조정
+ _bBoxRect.insertAfter(rElement);
+ }
+};
+
+OG.renderer.RaphaelRenderer.prototype.drawGuide = function (element) {
+ var me = this, rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element),
+ geometry = rElement ? rElement.node.shape.geom : null,
+ envelope,
+ group, guide,
+ _bBoxRect, _line, _linePath1, _linePath2, _rect,
+ _upperLeft, _upperRight, _lowerLeft, _lowerRight, _leftCenter, _upperCenter, _rightCenter, _lowerCenter,
+ _ulRect, _urRect, _lwlRect, _lwrRect, _lcRect, _ucRect, _rcRect, _lwcRect,
+ _size = me._CONFIG.GUIDE_RECT_SIZE, _hSize = OG.Util.round(_size / 2),
+ _ctrlSize = me._CONFIG.GUIDE_LINE_SIZE,
+ _ctrlMargin = me._CONFIG.GUIDE_LINE_MARGIN,
+ _trash, _rotate, isEdge, isEssensia, controllers = [], isLane,
+ _qUpper, _qLow, _qBisector, _qThirds;
+
+ var bboxStyle = element.shape.GUIDE_BBOX ? element.shape.GUIDE_BBOX : me._CONFIG.DEFAULT_STYLE.GUIDE_BBOX;
+
+ var _isConnectable = rElement && me._CANVAS._HANDLER._isConnectable(element.shape);
+ var _isConnectCloneable = rElement && me._CANVAS._HANDLER._isConnectCloneable(element.shape);
+ var _isDeletable = rElement && me._CANVAS._HANDLER._isDeletable(element.shape);
+ var _isResizable = rElement && me._CANVAS._HANDLER._isResizable(element.shape);
+
+
+ var createLinePath = function (x, y, marginTop, diff) {
+ var marginTop = marginTop ? marginTop : 0;
+ var from = [x, (y + _ctrlSize - diff) + marginTop];
+ var to = [x + _ctrlSize, y + marginTop];
+ var path = 'M' + from[0] + ' ' + from[1] + 'L' + to[0] + ' ' + to[1];
+ return path;
+ };
+
+ var createTextLinePath = function (x, y) {
+ var from = [x, (y + 4)];
+ var to = [x + _ctrlSize, (y + 4)];
+ var path = 'M' + from[0] + ' ' + from[1] + 'L' + to[0] + ' ' + to[1];
+ return path;
+ };
+
+ isEssensia = $(element).attr("_shape_id").indexOf('OG.shape.essencia') === -1 ? false : true;
+ isEdge = $(element).attr("_shape") === OG.Constants.SHAPE_TYPE.EDGE;
+
+ if (element.shape instanceof OG.shape.HorizontalLaneShape
+ || element.shape instanceof OG.shape.VerticalLaneShape) {
+ isLane = true;
+ }
+
+ if (!rElement) {
+ return null;
+ }
+
+ if (!geometry) {
+ return null;
+ }
+
+ envelope = geometry.getBoundary();
+ _upperLeft = envelope.getUpperLeft();
+ _upperRight = envelope.getUpperRight();
+ _lowerLeft = envelope.getLowerLeft();
+ _lowerRight = envelope.getLowerRight();
+ _leftCenter = envelope.getLeftCenter();
+ _upperCenter = envelope.getUpperCenter();
+ _rightCenter = envelope.getRightCenter();
+ _lowerCenter = envelope.getLowerCenter();
+
+
+ function _drawGroup() {
+ // group
+ group = me._getREleById(rElement.id + OG.Constants.GUIDE_SUFFIX.GUIDE);
+ if (group) {
+ me._remove(group);
+ me._remove(me._getREleById(rElement.id + OG.Constants.GUIDE_SUFFIX.BBOX));
+ }
+
+ group = me._PAPER.group();
+ guide = {
+ group: group.node
+ };
+ me._add(group, rElement.id + OG.Constants.GUIDE_SUFFIX.GUIDE);
+ }
+
+ function _drawBbox() {
+ if (!isEdge) {
+ _bBoxRect = me._PAPER.rect(_upperLeft.x, _upperLeft.y, envelope.getWidth(), envelope.getHeight());
+ }
+ if (isEdge) {
+ _bBoxRect = me._PAPER.rect(_upperLeft.x - 10, _upperLeft.y - 10, envelope.getWidth() + 20, envelope.getHeight() + 20);
+ }
+ _bBoxRect.attr(bboxStyle);
+ me._add(_bBoxRect, rElement.id + OG.Constants.GUIDE_SUFFIX.BBOX);
+ guide.bBox = _bBoxRect.node;
+ }
+
+ function _redrawBbox() {
+ me._remove(me._getREleById(rElement.id + OG.Constants.GUIDE_SUFFIX.BBOX));
+ if (!isEdge) {
+ _bBoxRect = me._PAPER.rect(_upperLeft.x, _upperLeft.y, envelope.getWidth(), envelope.getHeight());
+ }
+ if (isEdge) {
+ _bBoxRect = me._PAPER.rect(_upperLeft.x - 10, _upperLeft.y - 10, envelope.getWidth() + 20, envelope.getHeight() + 20);
+ }
+ _bBoxRect.attr(bboxStyle);
+ me._add(_bBoxRect, rElement.id + OG.Constants.GUIDE_SUFFIX.BBOX);
+ }
+
+ function _drawGuide() {
+ if (!_isResizable) {
+ return;
+ }
+ _ulRect = me._PAPER.rect(_upperLeft.x - _hSize, _upperLeft.y - _hSize, _size, _size);
+ _urRect = me._PAPER.rect(_upperRight.x - _hSize, _upperRight.y - _hSize, _size, _size);
+ _lwlRect = me._PAPER.rect(_lowerLeft.x - _hSize, _lowerLeft.y - _hSize, _size, _size);
+ _lwrRect = me._PAPER.rect(_lowerRight.x - _hSize, _lowerRight.y - _hSize, _size, _size);
+ _lcRect = me._PAPER.rect(_leftCenter.x - _hSize, _leftCenter.y - _hSize, _size, _size);
+ _ucRect = me._PAPER.rect(_upperCenter.x - _hSize, _upperCenter.y - _hSize, _size, _size);
+ _rcRect = me._PAPER.rect(_rightCenter.x - _hSize, _rightCenter.y - _hSize, _size, _size);
+ _lwcRect = me._PAPER.rect(_lowerCenter.x - _hSize, _lowerCenter.y - _hSize, _size, _size);
+
+ _ulRect.attr(me._CONFIG.DEFAULT_STYLE.GUIDE_UL);
+ _urRect.attr(me._CONFIG.DEFAULT_STYLE.GUIDE_UR);
+ _lwlRect.attr(me._CONFIG.DEFAULT_STYLE.GUIDE_LL);
+ _lwrRect.attr(me._CONFIG.DEFAULT_STYLE.GUIDE_LR);
+ _lcRect.attr(me._CONFIG.DEFAULT_STYLE.GUIDE_LC);
+ _ucRect.attr(me._CONFIG.DEFAULT_STYLE.GUIDE_UC);
+ _rcRect.attr(me._CONFIG.DEFAULT_STYLE.GUIDE_RC);
+ _lwcRect.attr(me._CONFIG.DEFAULT_STYLE.GUIDE_LWC);
+
+ group.appendChild(_ulRect);
+ group.appendChild(_urRect);
+ group.appendChild(_lwlRect);
+ group.appendChild(_lwrRect);
+ group.appendChild(_lcRect);
+ group.appendChild(_ucRect);
+ group.appendChild(_rcRect);
+ group.appendChild(_lwcRect);
+
+ me._add(_ulRect, rElement.id + OG.Constants.GUIDE_SUFFIX.UL);
+ me._add(_urRect, rElement.id + OG.Constants.GUIDE_SUFFIX.UR);
+ me._add(_lwlRect, rElement.id + OG.Constants.GUIDE_SUFFIX.LWL);
+ me._add(_lwrRect, rElement.id + OG.Constants.GUIDE_SUFFIX.LWR);
+ me._add(_lcRect, rElement.id + OG.Constants.GUIDE_SUFFIX.LC);
+ me._add(_ucRect, rElement.id + OG.Constants.GUIDE_SUFFIX.UC);
+ me._add(_rcRect, rElement.id + OG.Constants.GUIDE_SUFFIX.RC);
+ me._add(_lwcRect, rElement.id + OG.Constants.GUIDE_SUFFIX.LWC);
+
+ guide.ul = _ulRect.node;
+ guide.ur = _urRect.node;
+ guide.lwl = _lwlRect.node;
+ guide.lwr = _lwrRect.node;
+ guide.lc = _lcRect.node;
+ guide.uc = _ucRect.node;
+ guide.rc = _rcRect.node;
+ guide.lwc = _lwcRect.node;
+ }
+
+ function _redrawGuide() {
+ if (!_isResizable) {
+ return;
+ }
+ _ulRect = me._getREleById(rElement.id + OG.Constants.GUIDE_SUFFIX.UL);
+ _urRect = me._getREleById(rElement.id + OG.Constants.GUIDE_SUFFIX.UR);
+ _lwlRect = me._getREleById(rElement.id + OG.Constants.GUIDE_SUFFIX.LWL);
+ _lwrRect = me._getREleById(rElement.id + OG.Constants.GUIDE_SUFFIX.LWR);
+ _lcRect = me._getREleById(rElement.id + OG.Constants.GUIDE_SUFFIX.LC);
+ _ucRect = me._getREleById(rElement.id + OG.Constants.GUIDE_SUFFIX.UC);
+ _rcRect = me._getREleById(rElement.id + OG.Constants.GUIDE_SUFFIX.RC);
+ _lwcRect = me._getREleById(rElement.id + OG.Constants.GUIDE_SUFFIX.LWC);
+
+ _ulRect.attr({x: _upperLeft.x - _hSize, y: _upperLeft.y - _hSize});
+ _urRect.attr({x: _upperRight.x - _hSize, y: _upperRight.y - _hSize});
+ _lwlRect.attr({x: _lowerLeft.x - _hSize, y: _lowerLeft.y - _hSize});
+ _lwrRect.attr({x: _lowerRight.x - _hSize, y: _lowerRight.y - _hSize});
+ _lcRect.attr({x: _leftCenter.x - _hSize, y: _leftCenter.y - _hSize});
+ _ucRect.attr({x: _upperCenter.x - _hSize, y: _upperCenter.y - _hSize});
+ _rcRect.attr({x: _rightCenter.x - _hSize, y: _rightCenter.y - _hSize});
+ _lwcRect.attr({x: _lowerCenter.x - _hSize, y: _lowerCenter.y - _hSize});
+ }
+
+ function _drawController(i, controller) {
+ var _image = me._PAPER.image(me._CONFIG.IMAGE_BASE + controller.image, 0, 0, _ctrlSize, _ctrlSize);
+ _image.attr(me._CONFIG.DEFAULT_STYLE.GUIDE_LINE_AREA);
+ group.appendChild(_image);
+ me._add(_image, rElement.id + OG.Constants.GUIDE_SUFFIX.CONTROLLER + i);
+ guide._image = _image.node;
+
+ if (controller.action) {
+ $(_image.node).click(function (event) {
+ //mousedown 이후에 지속하도록.
+ setTimeout(function () {
+ controller.action(event, element);
+ }, 10);
+ })
+ }
+ else if (controller.create) {
+ if (!guide.rect) {
+ guide.rect = [];
+ }
+ guide.rect.push({
+ node: _image.node,
+ shape: controller.create.shape,
+ width: controller.create.width,
+ height: controller.create.height,
+ style: controller.create.style
+ });
+ }
+ controllers.push(_image);
+ }
+
+ function _redrawController(i, controller) {
+ var _image = me._getREleById(rElement.id + OG.Constants.GUIDE_SUFFIX.CONTROLLER + i);
+ controllers.push(_image);
+ }
+
+ function _drawTrash() {
+ if (!_isDeletable) {
+ return;
+ }
+ _trash = me._PAPER.image(me._CONFIG.IMAGE_BASE + 'trash.png', 0, 0, _ctrlSize, _ctrlSize);
+ _trash.attr(me._CONFIG.DEFAULT_STYLE.GUIDE_LINE_AREA);
+ group.appendChild(_trash);
+ me._add(_trash, rElement.id + OG.Constants.GUIDE_SUFFIX.TRASH);
+ guide.trash = _trash.node;
+
+ $(_trash.node).click(function () {
+ if (me.isLane(element)) {
+ me.removeLaneShape(element);
+ me.addHistory();
+ } else {
+ me.removeShape(element);
+ me.addHistory();
+ }
+ })
+ controllers.push(_trash);
+ }
+
+ function _redrawTrash() {
+ if (!_isDeletable) {
+ return;
+ }
+ _trash = me._getREleById(rElement.id + OG.Constants.GUIDE_SUFFIX.TRASH);
+ controllers.push(_trash);
+ }
+
+ function _drawRotate(angle) {
+ if (!_isDeletable) {
+ return;
+ }
+ _rotate = me._PAPER.image(me._CONFIG.IMAGE_BASE + 'rotate.png', 0, 0, _ctrlSize, _ctrlSize);
+ _rotate.attr(me._CONFIG.DEFAULT_STYLE.GUIDE_LINE_AREA);
+ group.appendChild(_rotate);
+ me._add(_rotate, rElement.id + OG.Constants.GUIDE_SUFFIX.ROTATE);
+ guide.trash = _rotate.node;
+
+
+ $(_rotate.node).click(function () {
+ if (me.isLane(element)) {
+ me.removeLaneShape(element);
+ me.addHistory();
+ } else {
+ //Rotate 클릭 구분
+ if (rElement.attrs.cursor == 'move') {
+ me.rotate(element, 330)
+ rElement.attrs.cursor = 'default'
+ } else if (rElement.attrs.cursor == 'default') {
+ me.rotate(element, 0)
+ rElement.attrs.cursor = 'move'
+
+ }
+ me.addHistory();
+ }
+ })
+ controllers.push(_rotate);
+ }
+
+ function _redrawRotate() {
+ if (!_isDeletable) {
+ return;
+ }
+ _rotate = me._getREleById(rElement.id + OG.Constants.GUIDE_SUFFIX.ROTATE);
+ controllers.push(_rotate);
+ }
+
+ function _drawLine() {
+ if (!_isConnectable) {
+ return;
+ }
+ _line = me._PAPER.rect(_upperRight.x + _ctrlMargin, _upperRight.y, _ctrlSize, _ctrlSize);
+ if (me._CONFIG.GUIDE_CONTROL_LINE_NUM == 2) {
+ _linePath1 = me._PAPER.path(createLinePath(0, 0, 0, 8));
+ _linePath2 = me._PAPER.path(createLinePath(0, 0, 8, 8));
+ } else {
+ _linePath1 = me._PAPER.path(createLinePath(0, 0, 0, 4));
+ }
+ _line.attr(me._CONFIG.DEFAULT_STYLE.GUIDE_LINE_AREA);
+
+ if (!isEssensia) {
+ _linePath1.attr(me._CONFIG.DEFAULT_STYLE.GUIDE_LINE);
+ if (_linePath2) {
+ _linePath2.attr(me._CONFIG.DEFAULT_STYLE.GUIDE_LINE);
+ }
+ }
+ if (isEssensia) {
+ _linePath1.attr(me._CONFIG.DEFAULT_STYLE.GUIDE_LINE_ESSENSIA);
+ if (_linePath2) {
+ _linePath2.attr(me._CONFIG.DEFAULT_STYLE.GUIDE_LINE_ESSENSIA);
+ }
+ }
+
+ group.appendChild(_linePath1);
+ group.appendChild(_line);
+
+ me._add(_line, rElement.id + OG.Constants.GUIDE_SUFFIX.LINE);
+ me._add(_linePath1, rElement.id + OG.Constants.GUIDE_SUFFIX.LINE + '1');
+
+
+ if (_linePath2) {
+ _linePath2.attr({'stroke-dasharray': '-'});
+ group.appendChild(_linePath2);
+ me._add(_linePath2, rElement.id + OG.Constants.GUIDE_SUFFIX.LINE + '2');
+ }
+
+ if (!guide.line) {
+ guide.line = [];
+ }
+ guide.line.push({
+ node: _line.node,
+ text: ''
+ });
+ controllers.push(_line);
+ }
+
+ function _redrawLine() {
+ if (!_isConnectable) {
+ return;
+ }
+ _line = me._getREleById(rElement.id + OG.Constants.GUIDE_SUFFIX.LINE);
+ _linePath1 = me._getREleById(rElement.id + OG.Constants.GUIDE_SUFFIX.LINE + '1');
+ _linePath2 = me._getREleById(rElement.id + OG.Constants.GUIDE_SUFFIX.LINE + '2');
+ controllers.push(_line);
+ }
+
+ function _drawTextLine(i, text) {
+ if (!_isConnectable) {
+ return;
+ }
+ var displayText;
+ var shapeId;
+ var shapeLabel;
+ //텍스트 형태가 스트링일 경우, 디폴트 선도형은 OG.EdgeShape
+ if (typeof text == 'string') {
+ displayText = text;
+ shapeLabel = text;
+ shapeId = 'OG.EdgeShape';
+ }
+ //오브젝트 형태일 경우 text 파라미터와 shape 파라미터를 얻는다.
+ else {
+ displayText = text.text;
+ shapeLabel = text.label;
+ shapeId = text.shape;
+ }
+ var minText = displayText;
+ if (displayText.length > 3) {
+ minText = displayText.substring(0, 3) + '..';
+ }
+ _line = me._PAPER.rect(_upperRight.x + _ctrlMargin, _upperRight.y, _ctrlSize, _ctrlSize);
+ _linePath1 = me._PAPER.path(createTextLinePath(0, 0));
+ _linePath2 = me._PAPER.text(0, 0, minText, _ctrlSize);
+ _line.attr(me._CONFIG.DEFAULT_STYLE.GUIDE_LINE_AREA);
+
+ if (!isEssensia) {
+ _linePath1.attr(me._CONFIG.DEFAULT_STYLE.GUIDE_LINE);
+ }
+ if (isEssensia) {
+ _linePath1.attr(me._CONFIG.DEFAULT_STYLE.GUIDE_LINE_ESSENSIA);
+ }
+
+ group.appendChild(_linePath1);
+ group.appendChild(_linePath2);
+ group.appendChild(_line);
+
+ me._add(_line, rElement.id + OG.Constants.GUIDE_SUFFIX.LINE_TEXT + i);
+ me._add(_linePath1, rElement.id + OG.Constants.GUIDE_SUFFIX.LINE_TEXT + i + '1');
+ me._add(_linePath2, rElement.id + OG.Constants.GUIDE_SUFFIX.LINE_TEXT + i + '2');
+
+ if (!guide.line) {
+ guide.line = [];
+ }
+ guide.line.push({
+ node: _line.node,
+ text: displayText,
+ label: shapeLabel ? shapeLabel : '',
+ shape: shapeId
+ });
+ controllers.push(_line);
+ }
+
+ function _redrawTextLine(i, text) {
+ if (!_isConnectable) {
+ return;
+ }
+ _line = me._getREleById(rElement.id + OG.Constants.GUIDE_SUFFIX.LINE_TEXT + i);
+ _linePath1 = me._getREleById(rElement.id + OG.Constants.GUIDE_SUFFIX.LINE_TEXT + i + '1');
+ _linePath2 = me._getREleById(rElement.id + OG.Constants.GUIDE_SUFFIX.LINE_TEXT + i + '2');
+ controllers.push(_line);
+ }
+
+ function _drawRect() {
+ if (!_isConnectCloneable) {
+ return;
+ }
+ _rect = me._PAPER.rect(_upperRight.x + _ctrlMargin, _upperRight.y, _ctrlSize, _ctrlSize);
+ _rect.attr(me._CONFIG.DEFAULT_STYLE.GUIDE_RECT_AREA);
+
+ group.appendChild(_rect);
+
+ me._add(_rect, rElement.id + OG.Constants.GUIDE_SUFFIX.RECT);
+
+ if (!guide.rect) {
+ guide.rect = [];
+ }
+ guide.rect.push({
+ node: _rect.node
+ });
+ controllers.push(_rect);
+ }
+
+ function _redrawRect() {
+ if (!_isConnectCloneable) {
+ return;
+ }
+ _rect = me._getREleById(rElement.id + OG.Constants.GUIDE_SUFFIX.RECT);
+ controllers.push(_rect);
+ }
+
+ function _drawLaneQuarter(divideCount) {
+ _qUpper = me._PAPER.image(me._CONFIG.IMAGE_BASE + "quarter-upper.png", 0, 0, _ctrlSize, _ctrlSize);
+ _qUpper.attr(me._CONFIG.DEFAULT_STYLE.GUIDE_LINE_AREA);
+ group.appendChild(_qUpper);
+ me._add(_qUpper, rElement.id + OG.Constants.GUIDE_SUFFIX.QUARTER_UPPER);
+ guide.qUpper = _qUpper.node;
+ $(_qUpper.node).click(function () {
+ me.divideLane(element, OG.Constants.GUIDE_SUFFIX.QUARTER_UPPER);
+ });
+
+ _qBisector = me._PAPER.image(me._CONFIG.IMAGE_BASE + "quarter-bisector.png", 0, 0, _ctrlSize, _ctrlSize);
+ _qBisector.attr(me._CONFIG.DEFAULT_STYLE.GUIDE_LINE_AREA);
+ group.appendChild(_qBisector);
+ me._add(_qBisector, rElement.id + OG.Constants.GUIDE_SUFFIX.QUARTER_BISECTOR);
+ guide.qBisector = _qBisector.node;
+ $(_qBisector.node).click(function () {
+ me.divideLane(element, OG.Constants.GUIDE_SUFFIX.QUARTER_BISECTOR);
+ });
+
+ _qThirds = me._PAPER.image(me._CONFIG.IMAGE_BASE + "quarter-thirds.png", 0, 0, _ctrlSize, _ctrlSize);
+ _qThirds.attr(me._CONFIG.DEFAULT_STYLE.GUIDE_LINE_AREA);
+ group.appendChild(_qThirds);
+ me._add(_qThirds, rElement.id + OG.Constants.GUIDE_SUFFIX.QUARTER_THIRDS);
+ guide.qThirds = _qThirds.node;
+ $(_qThirds.node).click(function () {
+ me.divideLane(element, OG.Constants.GUIDE_SUFFIX.QUARTER_THIRDS);
+ });
+
+ _qLow = me._PAPER.image(me._CONFIG.IMAGE_BASE + "quarter-low.png", 0, 0, _ctrlSize, _ctrlSize);
+ _qLow.attr(me._CONFIG.DEFAULT_STYLE.GUIDE_LINE_AREA);
+ group.appendChild(_qLow);
+ me._add(_qLow, rElement.id + OG.Constants.GUIDE_SUFFIX.QUARTER_LOW);
+ guide.qLow = _qLow.node;
+ $(_qLow.node).click(function () {
+ me.divideLane(element, OG.Constants.GUIDE_SUFFIX.QUARTER_LOW);
+ });
+
+ if (divideCount === 0) {
+ _hide(_qBisector);
+ _hide(_qThirds);
+
+ controllers.push(_qUpper);
+ controllers.push(_qLow);
+ }
+ if (divideCount === 1) {
+ _hide(_qThirds);
+
+ controllers.push(_qUpper);
+ controllers.push(_qBisector);
+ controllers.push(_qLow);
+ }
+
+ if (divideCount === 2) {
+ controllers.push(_qUpper);
+ controllers.push(_qBisector);
+ controllers.push(_qThirds);
+ controllers.push(_qLow);
+ }
+ }
+
+ function _redrawLaneQuarter(divideCount) {
+ _qUpper = me._getREleById(rElement.id + OG.Constants.GUIDE_SUFFIX.QUARTER_UPPER);
+ _qBisector = me._getREleById(rElement.id + OG.Constants.GUIDE_SUFFIX.QUARTER_BISECTOR);
+ _qThirds = me._getREleById(rElement.id + OG.Constants.GUIDE_SUFFIX.QUARTER_THIRDS);
+ _qLow = me._getREleById(rElement.id + OG.Constants.GUIDE_SUFFIX.QUARTER_LOW);
+
+ if (divideCount === 0) {
+ _hide(_qBisector);
+ _hide(_qThirds);
+ controllers.push(_qUpper);
+ controllers.push(_qLow);
+ }
+
+ if (divideCount === 1) {
+ _hide(_qThirds);
+ controllers.push(_qUpper);
+ controllers.push(_qBisector);
+ controllers.push(_qLow);
+ }
+
+ if (divideCount === 2) {
+ controllers.push(_qUpper);
+ controllers.push(_qBisector);
+ controllers.push(_qThirds);
+ controllers.push(_qLow);
+ }
+
+ $.each(controllers, function (idx, controller) {
+ _show(controller);
+ })
+ }
+
+ function _hide(controller) {
+ //일시적으로 숨기고 캔버스 외각처리.
+ controller.attr({opacity: '0', x: -100, y: -100});
+ }
+
+ function _show(controller) {
+ controller.attr({opacity: '1'});
+ }
+
+ //화면에 보여질 컨트롤러들을 정렬한다.
+ function _setControllerPosition() {
+ var maxIconPerLine = 4, x, y, divide, rest;
+
+ $.each(controllers, function (index, controller) {
+ divide = parseInt(index / maxIconPerLine);
+ rest = parseInt(index % maxIconPerLine);
+ x = _upperRight.x + ((divide + 1) * (_ctrlMargin + _ctrlSize) - _ctrlSize);
+ y = _upperRight.y + (rest * (_ctrlMargin + _ctrlSize));
+
+ if (!controller) return;
+
+ controller.attr({x: x, y: y});
+
+
+ //라인일경우 하위 라인까지 함께 재배치
+ if (controller.id === rElement.id + OG.Constants.GUIDE_SUFFIX.LINE) {
+ if (me._CONFIG.GUIDE_CONTROL_LINE_NUM == 2) {
+ me._getREleById(controller.id + '1').attr({'path': createLinePath(x, y, 0, 8)});
+ me._getREleById(controller.id + '2').attr({'path': createLinePath(x, y, 8, 8)});
+ } else {
+ me._getREleById(controller.id + '1').attr({'path': createLinePath(x, y, 0, 4)});
+ }
+ }
+
+ //텍스트 라인일경우 하위 라인까지 함께 재배치
+ if (controller.id.indexOf(rElement.id + OG.Constants.GUIDE_SUFFIX.LINE_TEXT) != -1) {
+ var index = controller.id.replace(rElement.id + OG.Constants.GUIDE_SUFFIX.LINE_TEXT, '');
+ me._getREleById(controller.id + '1').attr({'path': createTextLinePath(x, y)});
+ me._getREleById(controller.id + '2').attr({x: x + 10, y: y + 16});
+ }
+ });
+ }
+
+ var textList = me.getTextListInController(element);
+ if (!textList) {
+ textList = [];
+ }
+
+ var shapeControllers = [];
+ if (element.shape && element.shape.createController) {
+ shapeControllers = element.shape.createController();
+ }
+
+ //기존에 가이드가 있을 경우
+ if (this._getREleById(rElement.id + OG.Constants.GUIDE_SUFFIX.GUIDE)) {
+ if (!isEdge) {
+ _redrawBbox();
+ _redrawGuide();
+
+ if (isLane) {
+ _redrawLaneQuarter(me.enableDivideCount(element));
+ if (shapeControllers.length) {
+ $.each(shapeControllers, function (i, controller) {
+ _redrawController(i, controller);
+ })
+ }
+ }
+ if (!isLane) {
+ _redrawRect();
+ if (shapeControllers.length) {
+ $.each(shapeControllers, function (i, controller) {
+ _redrawController(i, controller);
+ })
+ }
+ if (textList.length) {
+ $.each(textList, function (i, text) {
+ _redrawTextLine(i, text);
+ })
+ } else {
+ _redrawLine();
+ }
+ }
+ // _redrawTrash();
+ _redrawRotate();
+ }
+ if (isEdge) {
+ _redrawBbox();
+ // _redrawTrash();
+ }
+
+ _setControllerPosition();
+ return null;
+ }
+ //기존에 가이드가 없을 경우
+ else {
+ if (isEdge) {
+ _drawGroup();
+ _drawBbox();
+ // _drawTrash();
+ // _drawRotate();
+ }
+ if (!isEdge) {
+ _drawGroup();
+ _drawBbox();
+ _drawGuide();
+
+ if (isLane) {
+ _drawLaneQuarter(me.enableDivideCount(element));
+ if (shapeControllers.length) {
+ $.each(shapeControllers, function (i, controller) {
+ _drawController(i, controller);
+ })
+ }
+ }
+ if (!isLane) {
+ _drawRect();
+ if (shapeControllers.length) {
+ $.each(shapeControllers, function (i, controller) {
+ _drawController(i, controller);
+ })
+ }
+ if (textList.length) {
+ $.each(textList, function (i, text) {
+ _drawTextLine(i, text);
+ })
+ } else {
+ _drawLine();
+ }
+ }
+ _drawRotate();
+ }
+ _setControllerPosition();
+
+ // layer 위치 조정
+ if (_bBoxRect) {
+ _bBoxRect.insertBefore(rElement);
+ }
+ if (group) {
+ me.getRootGroup().appendChild(group.node);
+ }
+ // selected 속성값 설정
+ $(rElement.node).attr("_selected", "true");
+ return guide;
+ }
+};
+
+/**
+ * ID에 해당하는 Element 의 Stick 용 가이드를 드로잉한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @param {Object} position
+ */
+OG.renderer.RaphaelRenderer.prototype.drawStickGuide = function (position) {
+ if (!this._CONFIG.STICK_GUIDE) {
+ return;
+ }
+ var me = this, path, pathX, pathY;
+
+ if (!position) {
+ return;
+ }
+ if (position.x) {
+ pathX = position.x * me._CONFIG.SCALE;
+ this.removeStickGuide('vertical');
+ path = this._PAPER.path("M" + pathX + ",0L" + pathX + ",10000");
+ this._stickGuideX = path;
+ }
+ if (position.y) {
+ pathY = position.y * me._CONFIG.SCALE;
+ this.removeStickGuide('horizontal');
+ path = this._PAPER.path("M0," + pathY + "L10000," + pathY);
+ this._stickGuideY = path;
+ }
+ if (path) {
+ path.attr("stroke-width", "2");
+ path.attr("stroke", "#FFCC50");
+ path.attr("opacity", "0.7");
+ }
+};
+
+OG.renderer.RaphaelRenderer.prototype.removeStickGuide = function (direction) {
+ if (!this._CONFIG.STICK_GUIDE) {
+ return;
+ }
+ if (!direction) {
+ return;
+ }
+ if (direction === 'vertical') {
+ if (this._stickGuideX) {
+ this._remove(this._stickGuideX);
+ this._stickGuideX = null;
+ }
+ }
+ if (direction === 'horizontal') {
+ if (this._stickGuideY) {
+ this._remove(this._stickGuideY);
+ this._stickGuideY = null;
+ }
+ }
+};
+
+OG.renderer.RaphaelRenderer.prototype.removeAllStickGuide = function () {
+ if (!this._CONFIG.STICK_GUIDE) {
+ return;
+ }
+ this.removeStickGuide('vertical');
+ this.removeStickGuide('horizontal');
+};
+
+/**
+ * ID에 해당하는 Element 의 Move & Resize 용 가이드를 제거한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.removeGuide = function (element) {
+
+ var rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element),
+ guide, bBox;
+ if (rElement) {
+ guide = this._getREleById(rElement.id + OG.Constants.GUIDE_SUFFIX.GUIDE);
+ bBox = this._getREleById(rElement.id + OG.Constants.GUIDE_SUFFIX.BBOX);
+
+ rElement.node.removeAttribute("_selected");
+ this._remove(guide);
+ this._remove(bBox);
+ this.removeAllStickGuide();
+ }
+};
+
+/**
+ * 모든 Move & Resize 용 가이드를 제거한다.
+ *
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.removeAllGuide = function () {
+ var me = this;
+ $(me.getRootElement()).find("[_type=" + OG.Constants.NODE_TYPE.SHAPE + "][_selected=true]").each(function (index, item) {
+ if (OG.Util.isElement(item) && item.id) {
+ me.removeGuide(item);
+ }
+ });
+};
+
+/**
+ * ID에 해당하는 Edge Element 의 Move & Resize 용 가이드를 드로잉한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @return {Object}
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.drawEdgeGuide = function (element) {
+ return null;
+
+ var me = this, rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element),
+ geometry = rElement ? rElement.node.shape.geom : null,
+ vertices, isSelf,
+ group, guide, pathStr,
+ _bBoxLine, _fromRect, _toRect, _controlRect, controlNode = [],
+ _size = me._CONFIG.GUIDE_RECT_SIZE, _hSize = OG.Util.round(_size / 2), _style = {},
+ i;
+
+ var bboxStyle = element.shape.GUIDE_BBOX ? element.shape.GUIDE_BBOX : me._CONFIG.DEFAULT_STYLE.GUIDE_BBOX;
+
+ if (rElement && geometry) {
+ OG.Util.apply(_style, geometry.style.map, me._CONFIG.DEFAULT_STYLE.EDGE);
+
+ vertices = _style["edge-type"] === OG.Constants.EDGE_TYPE.BEZIER ? geometry.getControlPoints() : geometry.getVertices();
+
+ isSelf = $(element).attr("_from") && $(element).attr("_to") && $(element).attr("_from") === $(element).attr("_to");
+
+ if (this._getREleById(rElement.id + OG.Constants.GUIDE_SUFFIX.GUIDE)) {
+ // 가이드가 이미 존재하는 경우에는 bBoxLine 만 삭제후 새로 draw 하고 나머지 guide 는 Update 한다.
+ // bBoxLine remove -> redraw
+ this._remove(this._getREleById(rElement.id + OG.Constants.GUIDE_SUFFIX.BBOX));
+ pathStr = "";
+ if (_style["edge-type"] === OG.Constants.EDGE_TYPE.BEZIER) {
+ for (var i = 0, leni = vertices.length; i < leni; i++) {
+ if (i === 0) {
+ pathStr = "M" + vertices[i].x + " " + vertices[i].y;
+ } else if (i === 1) {
+ pathStr += "C" + vertices[i].x + " " + vertices[i].y;
+ } else {
+ pathStr += " " + vertices[i].x + " " + vertices[i].y;
+ }
+ }
+ } else {
+ for (var i = 0, leni = vertices.length; i < leni; i++) {
+ if (i === 0) {
+ pathStr = "M" + vertices[i].x + " " + vertices[i].y;
+ } else {
+ pathStr += "L" + vertices[i].x + " " + vertices[i].y;
+ }
+ }
+ }
+
+ _bBoxLine = this._PAPER.path(pathStr);
+ _bBoxLine.attr(bboxStyle);
+ this._add(_bBoxLine, rElement.id + OG.Constants.GUIDE_SUFFIX.BBOX);
+ _bBoxLine.insertBefore(rElement);
+
+ // 시작지점 가이드 Update
+ _fromRect = this._getREleById(rElement.id + OG.Constants.GUIDE_SUFFIX.FROM);
+ _fromRect.attr({x: vertices[0].x - _hSize, y: vertices[0].y - _hSize});
+
+ // 종료지점 가이드 Update
+ _toRect = this._getREleById(rElement.id + OG.Constants.GUIDE_SUFFIX.TO);
+ _toRect.attr({x: vertices[vertices.length - 1].x - _hSize, y: vertices[vertices.length - 1].y - _hSize});
+
+ // 콘트롤 가이드 Update
+ if (!isSelf && _style["edge-type"] !== OG.Constants.EDGE_TYPE.BEZIER) {
+ for (var i = 1, leni = vertices.length - 2; i < leni; i++) {
+ if (vertices[i].x === vertices[i + 1].x) {
+ _controlRect = this._getREleById(rElement.id + OG.Constants.GUIDE_SUFFIX.CTL_H + i);
+ if (_controlRect) {
+ _controlRect.attr({
+ x: vertices[i].x - _hSize,
+ y: OG.Util.round((vertices[i].y + vertices[i + 1].y) / 2) - _hSize
+ });
+ }
+ } else {
+ _controlRect = this._getREleById(rElement.id + OG.Constants.GUIDE_SUFFIX.CTL_V + i);
+ if (_controlRect) {
+ _controlRect.attr({
+ x: OG.Util.round((vertices[i].x + vertices[i + 1].x) / 2) - _hSize,
+ y: vertices[i].y - _hSize
+ });
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ // group
+ group = this._getREleById(rElement.id + OG.Constants.GUIDE_SUFFIX.GUIDE);
+ if (group) {
+ this._remove(group);
+ this._remove(this._getREleById(rElement.id + OG.Constants.GUIDE_SUFFIX.BBOX));
+ }
+ group = this._PAPER.group();
+
+ // 쉐도우 가이드
+ pathStr = "";
+ if (_style["edge-type"] === OG.Constants.EDGE_TYPE.BEZIER) {
+ for (var i = 0, leni = vertices.length; i < leni; i++) {
+ if (i === 0) {
+ pathStr = "M" + vertices[i].x + " " + vertices[i].y;
+ } else if (i === 1) {
+ pathStr += "C" + vertices[i].x + " " + vertices[i].y;
+ } else {
+ pathStr += " " + vertices[i].x + " " + vertices[i].y;
+ }
+ }
+ } else {
+ for (var i = 0, leni = vertices.length; i < leni; i++) {
+ if (i === 0) {
+ pathStr = "M" + vertices[i].x + " " + vertices[i].y;
+ } else {
+ pathStr += "L" + vertices[i].x + " " + vertices[i].y;
+ }
+ }
+ }
+ _bBoxLine = this._PAPER.path(pathStr);
+ _bBoxLine.attr(bboxStyle);
+
+ // 시작지점 가이드
+ _fromRect = this._PAPER.rect(vertices[0].x - _hSize, vertices[0].y - _hSize, _size, _size);
+ _fromRect.attr(me._CONFIG.DEFAULT_STYLE.GUIDE_FROM);
+ group.appendChild(_fromRect);
+ this._add(_fromRect, rElement.id + OG.Constants.GUIDE_SUFFIX.FROM);
+
+ // 종료지점 가이드
+ _toRect = this._PAPER.rect(vertices[vertices.length - 1].x - _hSize, vertices[vertices.length - 1].y - _hSize, _size, _size);
+ _toRect.attr(me._CONFIG.DEFAULT_STYLE.GUIDE_TO);
+ group.appendChild(_toRect);
+ this._add(_toRect, rElement.id + OG.Constants.GUIDE_SUFFIX.TO);
+
+ // 콘트롤 가이드
+ if (!isSelf && _style["edge-type"] !== OG.Constants.EDGE_TYPE.BEZIER) {
+ for (var i = 1, leni = vertices.length - 2; i < leni; i++) {
+ if (vertices[i].x === vertices[i + 1].x) {
+ _controlRect = this._PAPER.rect(vertices[i].x - _hSize,
+ OG.Util.round((vertices[i].y + vertices[i + 1].y) / 2) - _hSize, _size, _size);
+ _controlRect.attr(me._CONFIG.DEFAULT_STYLE.GUIDE_CTL_H);
+ this._add(_controlRect, rElement.id + OG.Constants.GUIDE_SUFFIX.CTL_H + i);
+ } else {
+ _controlRect = this._PAPER.rect(OG.Util.round((vertices[i].x + vertices[i + 1].x) / 2) - _hSize,
+ vertices[i].y - _hSize, _size, _size);
+ _controlRect.attr(me._CONFIG.DEFAULT_STYLE.GUIDE_CTL_V);
+ this._add(_controlRect, rElement.id + OG.Constants.GUIDE_SUFFIX.CTL_V + i);
+ }
+ group.appendChild(_controlRect);
+ controlNode.push(_controlRect.node);
+ }
+ }
+ this._add(_bBoxLine, rElement.id + OG.Constants.GUIDE_SUFFIX.BBOX);
+ this._add(group, rElement.id + OG.Constants.GUIDE_SUFFIX.GUIDE);
+
+ // guide 정의
+ guide = {
+ bBox: _bBoxLine.node,
+ group: group.node,
+ from: _fromRect.node,
+ to: _toRect.node,
+ controls: controlNode
+ };
+
+ // layer 위치 조정
+ _bBoxLine.insertBefore(rElement);
+ group.insertAfter(rElement);
+
+ // selected 속성값 설정
+ $(rElement.node).attr("_selected", "true");
+
+ return guide;
+ }
+
+ return null;
+};
+
+/**
+ * Rectangle 모양의 마우스 드래그 선택 박스 영역을 드로잉한다.
+ *
+ * @param {Number[]} position 드로잉할 위치 좌표(좌상단)
+ * @param {Number[]} size Text Width, Height, Angle
+ * @param {OG.geometry.Style|Object} style 스타일
+ * @return {Element} DOM Element
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.drawRubberBand = function (position, size, style) {
+ var me = this, x = position ? position[0] : 0,
+ y = position ? position[1] : 0,
+ width = size ? size[0] : 0,
+ height = size ? size[1] : 0,
+ rect = this._getREleById(OG.Constants.RUBBER_BAND_ID),
+ _style = {};
+ if (rect) {
+ rect.attr({
+ x: x,
+ y: y,
+ width: Math.abs(width),
+ height: Math.abs(height)
+ });
+ return rect;
+ }
+ OG.Util.apply(_style, (style instanceof OG.geometry.Style) ? style.map : style || {}, me._CONFIG.DEFAULT_STYLE.RUBBER_BAND);
+ rect = this._PAPER.rect(x, y, width, height).attr(_style);
+ this._add(rect, OG.Constants.RUBBER_BAND_ID);
+ this._ETC_GROUP.node.appendChild(rect.node);
+
+ return rect.node;
+};
+
+/**
+ * Rectangle 모양의 마우스 드래그 선택 박스 영역을 제거한다.
+ *
+ * @param {Element} root first, rubberBand 정보를 저장한 엘리먼트
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.removeRubberBand = function (root) {
+ this.setAttr(OG.Constants.RUBBER_BAND_ID, {x: 0, y: 0, width: 0, height: 0});
+ $(root).removeData("dragBox_first");
+ $(root).removeData("rubberBand");
+};
+
+/**
+ * ID에 해당하는 Element 의 Collapse 가이드를 제거한다.
+ *
+ * @param {Element} element
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.removeCollapseGuide = function (element) {
+ var rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element),
+ _bBoxRect, _rect;
+
+ if (rElement) {
+ _bBoxRect = this._getREleById(rElement.id + OG.Constants.COLLAPSE_BBOX_SUFFIX);
+ if (_bBoxRect) {
+ this._remove(_bBoxRect);
+ }
+ _rect = this._getREleById(rElement.id + OG.Constants.COLLAPSE_SUFFIX);
+ if (_rect) {
+ this._remove(_rect);
+ }
+ }
+};
+
+/**
+ * 주어진 Shape 들을 그룹핑한다.
+ *
+ * @param {Element[]} elements
+ * @return {Element} Group Shape Element
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.group = function (elements) {
+ var groupShapeEle, geometryArray = [], geometryCollection, envelope, position, shape, size, i;
+
+ if (elements && elements.length > 1) {
+ // 그룹핑할 Shape 의 전체 영역 계산
+ for (var i = 0, leni = elements.length; i < leni; i++) {
+ geometryArray.push(elements[i].shape.geom);
+ }
+ geometryCollection = new OG.GeometryCollection(geometryArray);
+ envelope = geometryCollection.getBoundary();
+
+ // 위치 및 사이즈 설정
+ position = [envelope.getCentroid().x, envelope.getCentroid().y];
+ shape = new OG.GroupShape();
+ size = [envelope.getWidth(), envelope.getHeight()];
+
+ // draw group
+ groupShapeEle = this.drawShape(position, shape, size);
+
+ // append child
+ for (var i = 0, leni = elements.length; i < leni; i++) {
+ groupShapeEle.appendChild(elements[i]);
+ }
+
+ // group event fire
+ for (var c = 0, lenc = elements.length; c < lenc; c++) {
+ elements[c].shape.onGroup(groupShapeEle);
+ }
+ $(this._PAPER.canvas).trigger('group', [groupShapeEle]);
+ }
+
+ return groupShapeEle;
+};
+
+/**
+ * 주어진 그룹들을 그룹해제한다.
+ *
+ * @param {Element[]} groupElements
+ * @return {Element[]} ungrouped Elements
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.ungroup = function (groupElements) {
+ var ungroupElements = [], children, i, j;
+ if (groupElements && groupElements.length > 0) {
+ for (var i = 0, leni = groupElements.length; i < leni; i++) {
+ children = $(groupElements[i]).children("[_type='" + OG.Constants.NODE_TYPE.SHAPE + "']");
+ for (var j = 0, lenj = children.length; j < lenj; j++) {
+ groupElements[i].parentNode.appendChild(children[j]);
+ ungroupElements.push(children[j]);
+ }
+ this.removeShape(groupElements[i]);
+ }
+
+ // ungroup event fire
+ for (var c = 0, lenc = ungroupElements.length; c < lenc; c++) {
+ ungroupElements[c].shape.onUnGroup();
+ }
+ $(this._PAPER.canvas).trigger('ungroup', [ungroupElements]);
+ }
+
+ return ungroupElements;
+};
+
+/**
+ * 주어진 Shape 들을 그룹에 추가한다.
+ *
+ * @param {Element} groupElement
+ * @param {Element[]} elements
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.addToGroup = function (groupElement, elements, eventOffset) {
+ for (var i = 0, leni = elements.length; i < leni; i++) {
+ groupElement.appendChild(elements[i]);
+ elements[i].shape.onAddedToGroup(groupElement, elements[i], eventOffset);
+ }
+ if (groupElement.shape && groupElement.shape.onAddToGroup) {
+ groupElement.shape.onAddToGroup(groupElement, elements, eventOffset);
+ }
+};
+
+/**
+ * 드로잉된 모든 오브젝트를 클리어한다.
+ *
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.clear = function () {
+ this._PAPER.clear();
+ this._ELE_MAP.clear();
+ this._ID_PREFIX = Math.round(Math.random() * 10000);
+ this._LAST_ID = 0;
+ this._ROOT_GROUP = this._add(this._PAPER.group(), null, OG.Constants.NODE_TYPE.ROOT);
+ this._ETC_GROUP = this._add(this._PAPER.group(), null, OG.Constants.NODE_TYPE.ETC);
+};
+
+OG.renderer.RaphaelRenderer.prototype.alignLeft = function () {
+ var minX = 0, me = this;
+ $(me.getRootElement()).find("[_selected=true]").each(function (idx, item) {
+ if (item.shape.TYPE == 'EDGE')
+ return;
+
+ if (minX == 0) {
+ minX = item.shape.geom.boundary._leftCenter.x;
+ } else {
+ minX = minX > item.shape.geom.boundary._leftCenter.x ? item.shape.geom.boundary._leftCenter.x : minX;
+ }
+ });
+ $(me.getRootElement()).find("[_selected=true]").each(function (idx, item) {
+ if (item.shape.TYPE == 'EDGE')
+ return;
+
+ me.move(item, [minX - item.shape.geom.boundary._leftCenter.x, 0]);
+ me.drawGuide(item);
+ });
+
+ me._CANVAS._HANDLER.selectShapes(me._CANVAS._HANDLER._getSelectedElement());
+};
+
+OG.renderer.RaphaelRenderer.prototype.alignRight = function () {
+ var maxX = 0, me = this;
+ $(me.getRootElement()).find("[_selected=true]").each(function (idx, item) {
+ if (item.shape.TYPE == 'EDGE')
+ return;
+
+ if (maxX == 0) {
+ maxX = item.shape.geom.boundary._rightCenter.x;
+ } else {
+ maxX = maxX < item.shape.geom.boundary._rightCenter.x ? item.shape.geom.boundary._rightCenter.x : maxX;
+ }
+ });
+
+ $(me.getRootElement()).find("[_selected=true]").each(function (idx, item) {
+ if (item.shape.TYPE == 'EDGE')
+ return;
+
+ me.move(item, [maxX - item.shape.geom.boundary._rightCenter.x, 0]);
+ me.drawGuide(item);
+ });
+
+ me._CANVAS._HANDLER.selectShapes(me._CANVAS._HANDLER._getSelectedElement());
+};
+
+OG.renderer.RaphaelRenderer.prototype.alignBottom = function () {
+ var maxY = 0, me = this;
+ $(me.getRootElement()).find("[_selected=true]").each(function (idx, item) {
+ if (item.shape.TYPE == 'EDGE')
+ return;
+
+ if (maxY == 0) {
+ maxY = item.shape.geom.boundary._leftCenter.y;
+ } else {
+ maxY = maxY < item.shape.geom.boundary._leftCenter.y ? item.shape.geom.boundary._leftCenter.y : maxY;
+ }
+ });
+
+ $(me.getRootElement()).find("[_selected=true]").each(function (idx, item) {
+ if (item.shape.TYPE == 'EDGE')
+ return;
+
+ me.move(item, [0, maxY - item.shape.geom.boundary._leftCenter.y]);
+ me.drawGuide(item);
+ });
+
+ me._CANVAS._HANDLER.selectShapes(me._CANVAS._HANDLER._getSelectedElement());
+};
+
+OG.renderer.RaphaelRenderer.prototype.alignTop = function () {
+ var minY = 0, me = this;
+ $(me.getRootElement()).find("[_selected=true]").each(function (idx, item) {
+ if (item.shape.TYPE == 'EDGE')
+ return;
+
+ if (minY == 0) {
+ minY = item.shape.geom.boundary._rightCenter.y;
+ } else {
+ minY = minY > item.shape.geom.boundary._rightCenter.y ? item.shape.geom.boundary._rightCenter.y : minY;
+ }
+ });
+
+ $(me.getRootElement()).find("[_selected=true]").each(function (idx, item) {
+ if (item.shape.TYPE == 'EDGE')
+ return;
+
+ me.move(item, [0, minY - item.shape.geom.boundary._rightCenter.y]);
+ me.drawGuide(item);
+ });
+
+ me._CANVAS._HANDLER.selectShapes(me._CANVAS._HANDLER._getSelectedElement());
+};
+
+/**
+ * Shape 을 캔버스에서 관련된 모두를 삭제한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.removeShape = function (element, preventEvent) {
+ var rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element),
+ childNodes, beforeEvent, i, removedElement;
+ childNodes = rElement.node.childNodes;
+
+ beforeEvent = jQuery.Event("beforeRemoveShape", {element: rElement.node});
+
+ if (!preventEvent) {
+ if (element.shape) {
+ var onBeforeRemoveShape = element.shape.onBeforeRemoveShape();
+ if (typeof onBeforeRemoveShape == 'boolean' && !onBeforeRemoveShape) {
+ return false;
+ }
+ }
+ $(this._PAPER.canvas).trigger(beforeEvent);
+ if (beforeEvent.isPropagationStopped()) {
+ return false;
+ }
+ }
+
+ this.removeAllConnectGuide();
+
+ for (i = childNodes.length - 1; i >= 0; i--) {
+ if (childNodes[i].tagName == 'svg') {
+ childNodes[i].parentNode.removeChild(childNodes[i]);
+ } else if ($(childNodes[i]).attr("_type") === OG.Constants.NODE_TYPE.SHAPE) {
+ this.removeShape(childNodes[i], preventEvent);
+ }
+ }
+
+ this.disconnect(rElement.node, preventEvent);
+ this.removeGuide(rElement.node);
+ this.removeCollapseGuide(rElement.node);
+
+ removedElement = OG.Util.clone(rElement.node);
+
+ if (!preventEvent) {
+ if (element.shape) {
+ element.shape.onRemoveShape();
+ }
+ }
+ this.remove(rElement.node);
+
+ // removeShape event fire
+ if (!preventEvent) {
+ $(this._PAPER.canvas).trigger('removeShape', [removedElement]);
+ }
+};
+
+/**
+ * ID에 해당하는 Element 를 캔버스에서 제거한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.remove = function (element) {
+ var id = OG.Util.isElement(element) ? element.id : element,
+ rElement = this._getREleById(id);
+ this._remove(rElement);
+};
+
+/**
+ * 하위 엘리먼트만 제거한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.removeChild = function (element) {
+ var id = OG.Util.isElement(element) ? element.id : element,
+ rElement = this._getREleById(id);
+ this._removeChild(rElement);
+};
+
+/**
+ * 랜더러 캔버스 Root Element 를 반환한다.
+ *
+ * @return {Element} Element
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.getRootElement = function () {
+ return this._PAPER.canvas;
+};
+
+/**
+ * 주어진 지점을 포함하는 Top Element 를 반환한다.
+ *
+ * @param {Number[]} position 위치 좌표
+ * @return {Element} Element
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.getElementByPoint = function (position) {
+ var element = this._PAPER.getElementByPoint(position[0], position[1]);
+ return element ? element.node.parentNode : null;
+};
+
+
+/**
+ * 엘리먼트에 속성값을 설정한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @param {Object} attribute 속성값
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.setAttr = function (element, attribute) {
+ var rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element);
+ if (rElement) {
+ rElement.attr(attribute);
+ }
+};
+
+/**
+ * 엘리먼트 속성값을 반환한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @param {String} attrName 속성이름
+ * @return {Object} attribute 속성값
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.getAttr = function (element, attrName) {
+ var rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element);
+ if (rElement) {
+ return rElement.attr(attrName);
+ }
+ return null;
+};
+
+/**
+ * Shape 의 스타일을 변경한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @param {Object} style 스타일
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.setShapeStyle = function (element, style) {
+ var rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element);
+ if (rElement && element.shape && element.shape.geom) {
+ OG.Util.apply(element.shape.geom.style.map, style || {});
+ element.shapeStyle = element.shapeStyle || {};
+ OG.Util.apply(element.shapeStyle, style || {});
+ this.redrawShape(element);
+ }
+};
+
+/**
+ * Shape 의 선 연결 커스텀 컨트롤러를 설정한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @param {Array} textList 텍스트 리스트
+ * @override
+ */
+
+OG.renderer.RaphaelRenderer.prototype.setTextListInController = function (element, textList) {
+ var rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element);
+ if (rElement && element.shape && element.shape.geom) {
+ element.shape.textList = textList;
+ }
+};
+
+/**
+ * Shape 의 선 연결 커스텀 컨트롤러를 가져온다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @override
+ */
+
+OG.renderer.RaphaelRenderer.prototype.getTextListInController = function (element) {
+ var rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element);
+ if (rElement && element.shape && element.shape.geom) {
+ return element.shape.textList;
+ }
+};
+
+/**
+ * ID에 해당하는 Element 를 최상단 레이어로 이동한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.toFront = function (element) {
+ var rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element);
+ if (rElement) {
+ rElement.toFront();
+ }
+};
+
+/**
+ * ID에 해당하는 Element 를 최하단 레이어로 이동한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.toBack = function (element) {
+ var rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element);
+ if (rElement) {
+ rElement.toBack();
+ }
+};
+
+/**
+ * ID에 해당하는 Element 를 앞으로 한단계 이동한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.bringForward = function (element) {
+ var rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element);
+ if (!rElement) {
+ return;
+ }
+ element = rElement.node;
+ var me = this, root = $(me.getRootGroup());
+ if (me.isLane(element)) {
+ element = me._RENDERER.getRootLane(element);
+ }
+ var length = $(element).prevAll().length;
+ root[0].insertBefore(element, OG.Util.isIE() ? root[0].childNodes[length + 1] : root[0].children[length + 1]);
+};
+
+/**
+ * ID에 해당하는 Element 를 뒤로 한단계 이동한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.sendBackward = function (element) {
+ var rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element);
+ if (!rElement) {
+ return;
+ }
+ element = rElement.node;
+ var me = this, root = $(me.getRootGroup());
+ if (me.isLane(element)) {
+ element = me.getRootLane(element);
+ }
+ var length = $(element).prevAll().length;
+ var depth = length - 2;
+ if (depth < 0) {
+ depth = 0;
+ }
+ root[0].insertBefore(element, OG.Util.isIE() ? root[0].childNodes[depth] : root[0].children[depth]);
+};
+
+/**
+ * 랜더러 캔버스의 사이즈(Width, Height)를 반환한다.
+ *
+ * @return {Number[]} Canvas Width, Height
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.getCanvasSize = function () {
+ return [this._PAPER.width, this._PAPER.height];
+};
+
+/**
+ * 랜더러 캔버스의 사이즈(Width, Height)를 변경한다.
+ *
+ * @param {Number[]} size Canvas Width, Height
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.setCanvasSize = function (size) {
+ this._PAPER.setSize(size[0], size[1]);
+};
+
+/**
+ * 랜더러 캔버스의 사이즈(Width, Height)를 실제 존재하는 Shape 의 영역에 맞게 변경한다.
+ *
+ * @param {Number[]} minSize Canvas 최소 Width, Height
+ * @param {Boolean} fitScale 주어진 minSize 에 맞게 fit 여부(Default:false)
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.fitCanvasSize = function (minSize, fitScale) {
+ var me = this, realRootBBox = this.getRealRootBBox(), offsetX, offsetY, scale = 1,
+ width = realRootBBox.width + me._CONFIG.FIT_CANVAS_PADDING * 2,
+ height = realRootBBox.height + me._CONFIG.FIT_CANVAS_PADDING * 2;
+ if (realRootBBox.width !== 0 && realRootBBox.height !== 0) {
+ offsetX = realRootBBox.x > me._CONFIG.FIT_CANVAS_PADDING ?
+ -1 * (realRootBBox.x - me._CONFIG.FIT_CANVAS_PADDING) : me._CONFIG.FIT_CANVAS_PADDING - realRootBBox.x;
+ offsetY = realRootBBox.y > me._CONFIG.FIT_CANVAS_PADDING ?
+ -1 * (realRootBBox.y - me._CONFIG.FIT_CANVAS_PADDING) : me._CONFIG.FIT_CANVAS_PADDING - realRootBBox.y;
+
+ this.move(this.getRootGroup(), [offsetX, offsetY]);
+ this.removeAllGuide();
+
+ if (minSize && minSize.length === 2) {
+ if (OG.Util.isDefined(fitScale) && fitScale === true) {
+ scale = minSize[0] / width > minSize[1] / height ? minSize[1] / height : minSize[0] / width;
+ }
+
+ width = width < minSize[0] ? minSize[0] : width;
+ height = height < minSize[1] ? minSize[1] : height;
+ }
+ this.setScale(OG.Util.roundPrecision(scale, 1));
+ this.setCanvasSize([width, height]);
+ }
+};
+
+/**
+ * 새로운 View Box 영역을 설정한다. (ZoomIn & ZoomOut 가능)
+ *
+ * @param {Number[]} position 위치 좌표(좌상단 기준)
+ * @param {Number[]} size Canvas Width, Height
+ * @param {Boolean} isFit Fit 여부
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.setViewBox = function (position, size, isFit) {
+ this._PAPER.setViewBox(position[0], position[1], size[0], size[1], isFit);
+};
+
+/**
+ * Scale 을 반환한다. (리얼 사이즈 : Scale = 1)
+ *
+ * @return {Number} 스케일값
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.getScale = function (scale) {
+ var me = this;
+ return me._CONFIG.SCALE;
+};
+
+/**
+ * Scale 을 설정한다. (리얼 사이즈 : Scale = 1)
+ *
+ * @param {Number} scale 스케일값
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.setScale = function (scale) {
+ var me = this;
+ if (me._CONFIG.SCALE_MIN <= scale && scale <= me._CONFIG.SCALE_MAX) {
+ if (this.isVML()) {
+ // TODO : VML 인 경우 처리
+ $(this._ROOT_GROUP.node).css({
+ 'width': 21600 / scale,
+ 'height': 21600 / scale
+ });
+
+ $(this._ROOT_GROUP.node).find('[_type=SHAPE]').each(function (idx, item) {
+ $(item).css({
+ 'width': 21600,
+ 'height': 21600
+ });
+ });
+ } else {
+ $(this._ROOT_GROUP.node).attr('transform', 'scale(' + scale + ')');
+ $(this._ETC_GROUP.node).attr('transform', 'scale(' + scale + ')');
+ }
+
+ this._PAPER.setSize(
+ OG.Util.roundGrid(this._PAPER.width / me._CONFIG.SCALE * scale, me._CONFIG.MOVE_SNAP_SIZE),
+ OG.Util.roundGrid(this._PAPER.height / me._CONFIG.SCALE * scale, me._CONFIG.MOVE_SNAP_SIZE)
+ );
+
+ me._CONFIG.SCALE = scale;
+
+ me._CANVAS.updateBackDoor();
+ }
+};
+
+/**
+ * ID에 해당하는 Element 를 캔버스에서 show 한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.show = function (element) {
+ var rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element);
+ if (rElement) {
+ rElement.show();
+ }
+};
+
+/**
+ * ID에 해당하는 Element 를 캔버스에서 hide 한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.hide = function (element) {
+ var rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element);
+ if (rElement) {
+ rElement.hide();
+ }
+};
+
+/**
+ * Source Element 를 Target Element 아래에 append 한다.
+ *
+ * @param {Element|String} srcElement Element 또는 ID
+ * @param {Element|String} targetElement Element 또는 ID
+ * @return {Element} Source Element
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.appendChild = function (srcElement, targetElement) {
+ var srcRElement = this._getREleById(OG.Util.isElement(srcElement) ? srcElement.id : srcElement),
+ targetRElement = this._getREleById(OG.Util.isElement(targetElement) ? targetElement.id : targetElement);
+
+ targetRElement.appendChild(srcRElement);
+
+ return srcRElement;
+};
+
+/**
+ * Source Element 를 Target Element 이후에 insert 한다.
+ *
+ * @param {Element|String} srcElement Element 또는 ID
+ * @param {Element|String} targetElement Element 또는 ID
+ * @return {Element} Source Element
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.insertAfter = function (srcElement, targetElement) {
+ var srcRElement = this._getREleById(OG.Util.isElement(srcElement) ? srcElement.id : srcElement),
+ targetRElement = this._getREleById(OG.Util.isElement(targetElement) ? targetElement.id : targetElement);
+
+ srcRElement.insertAfter(targetRElement);
+
+ return srcRElement;
+};
+
+/**
+ * Source Element 를 Target Element 이전에 insert 한다.
+ *
+ * @param {Element|String} srcElement Element 또는 ID
+ * @param {Element|String} targetElement Element 또는 ID
+ * @return {Element} Source Element
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.insertBefore = function (srcElement, targetElement) {
+ var srcRElement = this._getREleById(OG.Util.isElement(srcElement) ? srcElement.id : srcElement),
+ targetRElement = this._getREleById(OG.Util.isElement(targetElement) ? targetElement.id : targetElement);
+
+ srcRElement.insertBefore(targetRElement);
+
+ return srcRElement;
+};
+
+/**
+ * 해당 Element 를 가로, 세로 Offset 만큼 이동한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @param {Number[]} offset [가로, 세로]
+ * @param {String[]} excludeEdgeId redraw 제외할 Edge ID
+ * @return {Element} Element
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.move = function (element, offset, excludeEdgeId) {
+ var me = this, rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element),
+ type = rElement ? rElement.node.getAttribute("_type") : null,
+ geometry;
+
+ this.removeCollapseGuide(element);
+ if (rElement && type) {
+ $(rElement.node).children("[_type=" + OG.Constants.NODE_TYPE.SHAPE + "][_shape=EDGE]").each(function (idx, item) {
+ // recursive
+ me.move(item, offset, excludeEdgeId);
+ });
+ $(rElement.node).children("[_type=" + OG.Constants.NODE_TYPE.SHAPE + "][_shape!=EDGE]").each(function (idx, item) {
+ // recursive
+ me.move(item, offset, excludeEdgeId);
+ });
+
+ if (type !== OG.Constants.NODE_TYPE.ROOT && rElement.node.shape) {
+ geometry = rElement.node.shape.geom;
+ geometry.move(offset[0], offset[1]);
+
+ this.redrawShape(rElement.node, excludeEdgeId);
+
+ // moveShape event fire
+ rElement.node.shape.onMoveShape(offset);
+ $(this._PAPER.canvas).trigger('moveShape', [rElement.node, offset]);
+
+ return rElement.node;
+ } else {
+ return element;
+ }
+ } else if (rElement) {
+ rElement.transform("...t" + offset[0] + "," + offset[1]);
+
+ // moveShape event fire
+ rElement.node.shape.onMoveShape(offset);
+ $(this._PAPER.canvas).trigger('moveShape', [rElement.node, offset]);
+
+ return rElement.node;
+ }
+
+ return null;
+};
+
+/**
+ * 주어진 중심좌표로 해당 Element 를 이동한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @param {Number[]} position [x, y]
+ * @return {Element} Element
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.moveCentroid = function (element, position) {
+ var rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element),
+ geometry = rElement ? rElement.node.shape.geom : null,
+ bBox, center = {};
+
+ if (rElement && geometry) {
+ center = geometry.getCentroid();
+
+ return this.move(element, [position[0] - center.x, position[1] - center.y]);
+ } else if (rElement) {
+ bBox = rElement.getBBox();
+ center.x = bBox.x + OG.Util.round(bBox.width / 2);
+ center.y = bBox.y + OG.Util.round(bBox.height / 2);
+
+ return this.move(element, [position[0] - center.x, position[1] - center.y]);
+ }
+ this.removeCollapseGuide(element);
+
+ return null;
+};
+
+/**
+ * 중심 좌표를 기준으로 주어진 각도 만큼 회전한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @param {Number} angle 각도
+ * @return {Element} Element
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.rotate = function (element, angle) {
+ var rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element),
+ type = rElement ? rElement.node.getAttribute("_shape") : null,
+ geometry = rElement ? rElement.node.shape.geom : null,
+ shape, envelope, center, width, height;
+
+ if (rElement && type && geometry) {
+ if (type === OG.Constants.SHAPE_TYPE.IMAGE ||
+ type === OG.Constants.SHAPE_TYPE.TEXT ||
+ type === OG.Constants.SHAPE_TYPE.HTML ||
+ type === OG.Constants.SHAPE_TYPE.SVG) {
+ shape = rElement.node.shape.clone();
+ envelope = geometry.getBoundary();
+ center = envelope.getCentroid();
+ width = envelope.getWidth();
+ height = envelope.getHeight();
+ this.drawShape([center.x, center.y], shape, [width, height, angle], rElement.node.shapeStyle, rElement.node.id);
+ }
+ else {
+ if (rElement.node.shape.angle) {
+ geometry.rotate(-1 * rElement.node.shape.angle);
+ }
+ geometry.rotate(angle);
+ rElement.node.shape.angle = angle;
+
+ this.redrawShape(rElement.node);
+ }
+ // rotateShape event fire
+ rElement.node.shape.onRotateShape(angle);
+ $(this._PAPER.canvas).trigger('rotateShape', [rElement.node, angle]);
+ // console.log(rElement.node)
+ return rElement.node;
+
+ } else if (rElement) {
+
+ rElement.rotate(angle);
+ // rotateShape event fire
+ rElement.node.shape.onRotateShape(angle);
+ $(this._PAPER.canvas).trigger('rotateShape', [rElement.node, angle]);
+ return rElement.node;
+ }
+
+ return null;
+};
+
+/**
+ * 상, 하, 좌, 우 외곽선을 이동한 만큼 리사이즈 한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @param {Number[]} offset [상, 하, 좌, 우] 각 방향으로 + 값
+ * @param preventEvent
+ * @return {Element} Element
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.resize = function (element, offset, preventEvent) {
+ var rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element),
+ type = rElement ? rElement.node.getAttribute("_shape") : null,
+ geometry = rElement ? rElement.node.shape.geom : null,
+ bBox, offsetX, offsetY, width, height, hRate, vRate;
+
+ this.removeCollapseGuide(element);
+
+ if (rElement && type && geometry) {
+ geometry.resize(offset[0], offset[1], offset[2], offset[3]);
+
+ this.redrawShape(rElement.node);
+
+ // resizeShape event fire
+ if (!preventEvent) {
+ rElement.node.shape.onResize(offset);
+ $(this._PAPER.canvas).trigger('resizeShape', [rElement.node, offset]);
+ }
+
+ return rElement.node;
+ } else if (rElement) {
+ bBox = rElement.getBBox();
+
+ offsetX = offset[2] + offset[3];
+ offsetY = offset[0] + offset[1];
+ width = bBox.width + offsetX;
+ height = bBox.height + offsetY;
+ hRate = bBox.width === 0 ? 1 : width / bBox.width;
+ vRate = bBox.height === 0 ? 1 : height / bBox.height;
+
+ rElement.transform("...t" + (-1 * offset[2]) + "," + (-1 * offset[0]));
+ rElement.transform("...s" + hRate + "," + vRate + "," + bBox.x + "," + bBox.y);
+
+ // resizeShape event fire
+ if (!preventEvent) {
+ rElement.node.shape.onResize(offset);
+ $(this._PAPER.canvas).trigger('resizeShape', [rElement.node, offset]);
+ }
+
+ return rElement.node;
+ }
+
+ return null;
+};
+
+/**
+ * 중심좌표는 고정한 채 Bounding Box 의 width, height 를 리사이즈 한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @param {Number[]} size [Width, Height]
+ * @param preventEvent
+ * @return {Element} Element
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.resizeBox = function (element, size, preventEvent) {
+ var rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element),
+ geometry = rElement ? rElement.node.shape.geom : null,
+ boundary, bBox, offsetWidth, offsetHeight;
+
+ this.removeCollapseGuide(element);
+ if (rElement && geometry) {
+ boundary = geometry.getBoundary();
+ offsetWidth = OG.Util.round((size[0] - boundary.getWidth()) / 2);
+ offsetHeight = OG.Util.round((size[1] - boundary.getHeight()) / 2);
+
+ return this.resize(element, [offsetHeight, offsetHeight, offsetWidth, offsetWidth], preventEvent);
+ } else if (rElement) {
+ bBox = rElement.getBBox();
+ offsetWidth = OG.Util.round((size[0] - bBox.width) / 2);
+ offsetHeight = OG.Util.round((size[1] - bBox.height) / 2);
+
+ return this.resize(element, [offsetHeight, offsetHeight, offsetWidth, offsetWidth], preventEvent);
+ }
+
+ return null;
+};
+
+/**
+ * 노드 Element 를 복사한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @return {Element} Element
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.clone = function (element) {
+ // TODO : 오류 - group 인 경우 clone 처리 필요
+ var rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element), newElement;
+ newElement = rElement.clone();
+ this._add(newElement);
+ this._ROOT_GROUP.node.appendChild(newElement.node);
+
+ return newElement.node;
+};
+
+/**
+ * ID로 Node Element 를 반환한다.
+ *
+ * @param {String} id
+ * @return {Element} Element
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.getElementById = function (id) {
+ var rElement = this._getREleById(id);
+ return rElement ? rElement.node : null;
+};
+
+/**
+ * 해당 엘리먼트의 BoundingBox 영역 정보를 반환한다.
+ *
+ * @param {Element|String} element
+ * @return {Object} {width, height, x, y, x2, y2}
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.getBBox = function (element) {
+ var rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element);
+ return rElement.getBBox();
+};
+
+/**
+ * 부모노드기준으로 캔버스 루트 엘리먼트의 BoundingBox 영역 정보를 반환한다.
+ *
+ * @return {Object} {width, height, x, y, x2, y2}
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.getRootBBox = function () {
+ var container = this._PAPER.canvas.parentNode,
+ width = OG.Util.isFirefox() ? this._PAPER.canvas.width.baseVal.value : this._PAPER.canvas.scrollWidth,
+ height = OG.Util.isFirefox() ? this._PAPER.canvas.height.baseVal.value : this._PAPER.canvas.scrollHeight,
+ x = container.offsetLeft,
+ y = container.offsetTop;
+
+ return {
+ width: width,
+ height: height,
+ x: x,
+ y: y,
+ x2: x + width,
+ y2: y + height
+ };
+};
+
+/**
+ * 캔버스의 컨테이너 DOM element 를 반환한다.
+ *
+ * @return {Element} 컨테이너
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.getContainer = function () {
+ return this._PAPER.canvas.parentNode;
+};
+
+/**
+ * SVG 인지 여부를 반환한다.
+ *
+ * @return {Boolean} svg 여부
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.isSVG = function () {
+ return Raphael.svg;
+};
+
+/**
+ * VML 인지 여부를 반환한다.
+ *
+ * @return {Boolean} vml 여부
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.isVML = function () {
+ return Raphael.vml;
+};
+
+/**
+ * Node 엘리먼트의 커넥트 가이드 영역 엘리먼트를 반환한다.
+ *
+ * @param {Element} Element 엘리먼트
+ * @return {Array} Array Element
+ */
+OG.renderer.RaphaelRenderer.prototype.getConnectGuideElements = function (element) {
+ var childNodes, i, connectGuideElements = [];
+ var rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element);
+ if (rElement) {
+ childNodes = rElement.node.childNodes;
+ for (i = childNodes.length - 1; i >= 0; i--) {
+ if ($('#' + childNodes[i].id) && $('#' + childNodes[i].id).attr('name') == OG.Constants.CONNECT_GUIDE_EVENT_AREA.NAME) {
+ connectGuideElements.push(childNodes[i]);
+ }
+ }
+ }
+ return connectGuideElements;
+};
+
+/**
+ * Node 엘리먼트의 커넥트 가이드 영역을 제외한 엘리먼트를 반환한다.
+ *
+ * @param {Element} Element 엘리먼트
+ * @return {Array} Array Element
+ */
+OG.renderer.RaphaelRenderer.prototype.getNotConnectGuideElements = function (element) {
+ var childNodes, i, notConnectGuideElements = [];
+ var rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element);
+ if (rElement) {
+ childNodes = rElement.node.childNodes;
+ for (i = childNodes.length - 1; i >= 0; i--) {
+ if ($('#' + childNodes[i].id) && $('#' + childNodes[i].id).attr('name') !== OG.Constants.CONNECT_GUIDE_EVENT_AREA.NAME) {
+ notConnectGuideElements.push(childNodes[i]);
+ }
+ }
+ }
+ return notConnectGuideElements;
+};
+
+
+OG.renderer.RaphaelRenderer.prototype.drawConnectGuide = function (element) {
+ var me = this, rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element),
+ geometry = rElement ? rElement.node.shape.geom : null,
+ envelope,
+ guide,
+ _connectBoxRect,
+ _upperLeft,
+ vertualBoundary,
+ _size = me._CONFIG.GUIDE_RECT_SIZE;
+
+ var spotCircleStyle = me._CONFIG.DEFAULT_STYLE.CONNECT_GUIDE_SPOT_CIRCLE;
+ var spotRectStyle = me._CONFIG.DEFAULT_STYLE.CONNECT_GUIDE_SPOT_RECT;
+
+ var drawEdgeSpot = function () {
+ var vertices = geometry.getVertices();
+ var spots = [];
+
+ //변곡점 스팟
+ for (var i = 0, leni = vertices.length; i < leni; i++) {
+ var spot = me._PAPER.circle(vertices[i].x, vertices[i].y, spotCircleStyle.r);
+ spot.attr(spotCircleStyle);
+ me._add(spot);
+ rElement.appendChild(spot);
+ $(spot.node).data('index', i);
+ $(spot.node).data('vertice', vertices[i]);
+ $(spot.node).data('parent', rElement);
+ $(spot.node).data('type', OG.Constants.CONNECT_GUIDE_SUFFIX.SPOT_CIRCLE);
+ $(spot.node).attr('name', OG.Constants.CONNECT_GUIDE_SUFFIX.SPOT);
+ me.toFront(spot);
+ spots.push(spot.node);
+ }
+ //직각이동 스팟
+ $.each(vertices, function (index, vertice) {
+ if (index > 0) {
+ var isRightAngle = geometry.isRightAngleBetweenPoints(vertices[index - 1], vertices[index]);
+ if (isRightAngle.flag) {
+ var middleSpotPoint = {
+ x: (vertices[index - 1].x + vertices[index].x) / 2,
+ y: (vertices[index - 1].y + vertices[index].y) / 2
+ }
+ if (isRightAngle.type === 'vertical') {
+ var lineLenght = vertices[index - 1].y - vertices[index].y;
+ if (Math.abs(lineLenght) < 50) {
+ return;
+ }
+
+ var width = spotRectStyle.w;
+ var height = spotRectStyle.h;
+ vertualBoundary = {
+ x: middleSpotPoint.x - (height / 2),
+ y: middleSpotPoint.y - (width / 2),
+ width: height,
+ height: width
+ };
+ spotRectStyle.cursor = 'ew-resize';
+ } else {
+ var lineLenght = vertices[index - 1].x - vertices[index].x;
+ if (Math.abs(lineLenght) < 50) {
+ return;
+ }
+
+ var width = spotRectStyle.w;
+ var height = spotRectStyle.h;
+ vertualBoundary = {
+ x: middleSpotPoint.x - (width / 2),
+ y: middleSpotPoint.y - (height / 2),
+ width: width,
+ height: height
+ }
+ spotRectStyle.cursor = 'ns-resize';
+ }
+
+ var spot = me._PAPER.rect(vertualBoundary.x, vertualBoundary.y, vertualBoundary.width, vertualBoundary.height);
+ spot.attr(spotRectStyle);
+ me._add(spot);
+ rElement.appendChild(spot);
+ $(spot.node).data('prev', index - 1);
+ $(spot.node).data('next', index);
+ $(spot.node).data('parent', rElement);
+ $(spot.node).data('vertice', middleSpotPoint);
+ $(spot.node).attr('name', OG.Constants.CONNECT_GUIDE_SUFFIX.SPOT);
+ $(spot.node).data('type', OG.Constants.CONNECT_GUIDE_SUFFIX.SPOT_RECT);
+ $(spot.node).data('direction', isRightAngle.type);
+ me.toFront(spot);
+ spots.push(spot.node);
+ }
+ }
+ });
+
+ return spots;
+ };
+
+ // 스팟이 이미 존재하는 경우에는 가이드만 새로 만든다.
+ if (me.getSpots(element).length > 0) {
+ return null;
+ }
+
+ // 선택할수 없는 엘리먼트일경우 패스.
+ if (!me._CANVAS._HANDLER._isSelectable(element.shape)) {
+ return null;
+ }
+
+ if (rElement && geometry) {
+ envelope = geometry.getBoundary();
+ _upperLeft = envelope.getUpperLeft();
+
+ var expendBoundary = {
+ x: _upperLeft.x - me._CONFIG.DEFAULT_STYLE.CONNECT_GUIDE_BBOX_EXPEND,
+ y: _upperLeft.y - me._CONFIG.DEFAULT_STYLE.CONNECT_GUIDE_BBOX_EXPEND,
+ width: envelope.getWidth() + me._CONFIG.DEFAULT_STYLE.CONNECT_GUIDE_BBOX_EXPEND * 2,
+ height: envelope.getHeight() + me._CONFIG.DEFAULT_STYLE.CONNECT_GUIDE_BBOX_EXPEND * 2
+ };
+
+ this._remove(this._getREleById(rElement.id + OG.Constants.CONNECT_GUIDE_SUFFIX.BBOX));
+ _connectBoxRect = this._PAPER.rect(expendBoundary.x, expendBoundary.y, expendBoundary.width, expendBoundary.height);
+ _connectBoxRect.attr(me._CONFIG.DEFAULT_STYLE.CONNECT_GUIDE_BBOX);
+ this._add(_connectBoxRect, rElement.id + OG.Constants.CONNECT_GUIDE_SUFFIX.BBOX);
+
+ if ($(element).attr("_shape") === OG.Constants.SHAPE_TYPE.EDGE) {
+ var spots = drawEdgeSpot();
+ // guide 정의
+ guide = {
+ bBox: _connectBoxRect.node,
+ spots: spots
+ };
+ } else {
+ // guide 정의
+ guide = {
+ bBox: _connectBoxRect.node
+ };
+ }
+ // layer 위치 조정
+ _connectBoxRect.insertBefore(rElement);
+
+ return guide;
+ }
+ return null;
+};
+
+
+/**
+ * ID에 해당하는 Element 의 Connect Guide 를 제거한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ */
+OG.renderer.RaphaelRenderer.prototype.removeConnectGuide = function (element) {
+ var me = this;
+ var rElement = me._getREleById(OG.Util.isElement(element) ? element.id : element);
+ if (!rElement) {
+ return;
+ }
+ var bBox = me._getREleById(rElement.id + OG.Constants.CONNECT_GUIDE_SUFFIX.BBOX);
+ $(me.getSpots(element)).each(function (index, spot) {
+ me._remove(me._getREleById(spot.id));
+ })
+ me._remove(bBox);
+};
+
+/**
+ * 캔버스의 모든 Connect Guide 를 제거한다.
+ *
+ */
+OG.renderer.RaphaelRenderer.prototype.removeAllConnectGuide = function () {
+
+ var me = this;
+ $(me.getRootElement()).find("[_type=" + OG.Constants.NODE_TYPE.SHAPE + "]").each(function (index, item) {
+ if (OG.Util.isElement(item) && item.id) {
+ me.removeConnectGuide(item);
+ me.removeVirtualSpot(item);
+ }
+ });
+};
+
+/**
+ * ID에 해당하는 Element 이외의 모든 Connect Guide 를 제거한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ */
+OG.renderer.RaphaelRenderer.prototype.removeOtherConnectGuide = function (element) {
+
+ var me = this;
+ $(me.getRootElement()).find("[_type=" + OG.Constants.NODE_TYPE.SHAPE + "]").each(function (index, item) {
+ if (OG.Util.isElement(item) && item.id && element.id !== item.id) {
+ me.removeConnectGuide(item);
+ me.removeVirtualSpot(item);
+ }
+ });
+};
+
+
+/**
+ * Element 내부의 Spot 들을 반환한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @return {Array} Spot Element Array
+ */
+OG.renderer.RaphaelRenderer.prototype.getSpots = function (element) {
+ var rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element),
+ list = [];
+
+ if (rElement) {
+ $('#' + rElement.id).find('[name=' + OG.Constants.CONNECT_GUIDE_SUFFIX.SPOT + ']').each(function (index, spot) {
+ list.push(spot);
+ });
+ }
+ return list;
+}
+
+/**
+ * Element 내부의 변곡점 Spot 들만 반환한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @return {Array} Spot Element Array
+ */
+OG.renderer.RaphaelRenderer.prototype.getCircleSpots = function (element) {
+ var rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element),
+ list = [];
+
+ if (rElement) {
+ $('#' + rElement.id).find('[name=' + OG.Constants.CONNECT_GUIDE_SUFFIX.SPOT + ']').each(function (index, spot) {
+ if ($(spot).data('type') === OG.Constants.CONNECT_GUIDE_SUFFIX.SPOT_CIRCLE) {
+ list.push(spot);
+ }
+ });
+ }
+ return list;
+}
+
+
+/**
+ * 주어진 좌표와 가장 Edge Element의 가장 가까운 거리에 가상 변곡점 스팟을 생성한다.
+ *
+ * @param {Number} x 이벤트의 캔버스 기준 x 좌표
+ * @param {Number} x 이벤트의 캔버스 기준 y 좌표
+ * @param {Element|String} element Element 또는 ID
+ * @return {Element} Spot Element
+ */
+OG.renderer.RaphaelRenderer.prototype.createVirtualSpot = function (x, y, element) {
+ var me = this, rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element),
+ geometry = rElement ? rElement.node.shape.geom : null,
+ vertices,
+ minDistanceLine = [],
+ minDistance = 0,
+ minDistanceIndex = [],
+ spots,
+ enableDrawDistance,
+ coordinate,
+ spotCircleStyle,
+ virtualSpot
+
+
+ //기존 가상스팟은 삭제한다.
+ me.removeVirtualSpot(element);
+
+ //엣지가 아닐경우 스킵
+ if ($(element).attr("_shape") !== OG.Constants.SHAPE_TYPE.EDGE) {
+ return null;
+ }
+
+ if (rElement && geometry) {
+ vertices = geometry.getVertices();
+ minDistanceLine = [];
+ minDistanceIndex = [];
+ minDistance = 0;
+ $.each(vertices, function (index, vertice) {
+ if (index > 0) {
+ var distance = geometry.distanceToLine([x, y], [vertices[index - 1], vertices[index]]);
+ if (index == 1) {
+ minDistance = distance;
+ minDistanceLine = [vertices[index - 1], vertices[index]];
+ minDistanceIndex = [index - 1, index];
+ } else {
+ if (distance < minDistance) {
+ minDistance = distance;
+ minDistanceLine = [vertices[index - 1], vertices[index]];
+ minDistanceIndex = [index - 1, index];
+ }
+ }
+ }
+ });
+
+ coordinate = geometry.intersectPointToLine([x, y], minDistanceLine);
+
+ spotCircleStyle = me._CONFIG.DEFAULT_STYLE.CONNECT_GUIDE_SPOT_CIRCLE;
+
+ //가상 변곡점 스팟의 생성조건
+ //조건1 : 가상 스팟 생성시 고정 스팟의 바운더리 영역과 겹치지 않아야 한다.
+
+ //구현계산:
+ //가상점의 중심과 고정스팟의 중심의 거리가,
+ //고정스팟이 원일경우 : 고정스팟 반지름 + 가상스팟 반지름보다 커야 생성가능하다.
+ //고정스팟이 사각형일경우 : 고정스팟의 긴 변 + 가상스팟 반지름보다 커야 생성가능하다.
+ spots = me.getSpots(element);
+ enableDrawDistance = true;
+ $.each(spots, function (index, spot) {
+ var type = $(spot).data('type');
+ var center = $(spot).data('vertice');
+
+ var coordinateToSpotDistance =
+ Math.sqrt(
+ Math.pow((coordinate.x - center.x), 2) + Math.pow((coordinate.y - center.y), 2)
+ );
+
+ if (type === OG.Constants.CONNECT_GUIDE_SUFFIX.SPOT_CIRCLE) {
+ var preventArea = spotCircleStyle.r +
+ parseInt($(spot).attr('r'));
+ if (preventArea >= coordinateToSpotDistance) {
+ enableDrawDistance = false;
+ }
+ }
+ if (type === OG.Constants.CONNECT_GUIDE_SUFFIX.SPOT_RECT) {
+ var longSide = $(spot).attr('width');
+ if ($(spot).attr('height') > $(spot).attr('width')) {
+ longSide = $(spot).attr('height');
+ }
+ var preventArea = spotCircleStyle.r +
+ parseInt(longSide);
+ if (preventArea >= coordinateToSpotDistance) {
+ enableDrawDistance = false;
+ }
+ }
+ })
+ if (!enableDrawDistance) {
+ return null;
+ }
+
+ virtualSpot = me._PAPER.circle(coordinate.x, coordinate.y, spotCircleStyle.r);
+ virtualSpot.attr(spotCircleStyle);
+ me._add(virtualSpot);
+ rElement.appendChild(virtualSpot);
+
+ $(virtualSpot.node).data('prev', minDistanceIndex[0]);
+ $(virtualSpot.node).data('next', minDistanceIndex[1]);
+ $(virtualSpot.node).data('vertice', coordinate);
+ $(virtualSpot.node).data('parent', rElement);
+ $(virtualSpot.node).data('type', OG.Constants.CONNECT_GUIDE_SUFFIX.SPOT_CIRCLE);
+ $(virtualSpot.node).attr('name', OG.Constants.CONNECT_GUIDE_SUFFIX.VIRTUAL_SPOT);
+ me.toFront(virtualSpot);
+ return virtualSpot.node;
+ }
+ return null;
+}
+
+/**
+ * Element 내부의 가상 변곡점 스팟을 반환한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @return {Element} Spot Element
+ */
+OG.renderer.RaphaelRenderer.prototype.getVirtualSpot = function (element) {
+ var rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element),
+ id, spot;
+
+ if (rElement) {
+ spot = $('#' + rElement.id).find('[name=' + OG.Constants.CONNECT_GUIDE_SUFFIX.VIRTUAL_SPOT + ']');
+ id = spot.attr('id');
+ }
+ return this.getElementById(id);
+}
+
+/**
+ * Element 내부의 가상 변곡점 스팟을 삭제한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @return {Element} Spot Element
+ */
+OG.renderer.RaphaelRenderer.prototype.removeVirtualSpot = function (element) {
+ var rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element),
+ id, spot;
+
+ if (rElement) {
+ spot = $('#' + rElement.id).find('[name=' + OG.Constants.CONNECT_GUIDE_SUFFIX.VIRTUAL_SPOT + ']');
+ id = spot.attr('id');
+ }
+ return this.remove(id);
+}
+
+/**
+ * Element 내부의 Spot 중 선택한 스팟을 제외하고 모두 삭제하고, 가이드라인도 삭제한다.
+ *
+ * @param {Element|String} 선택한 spot Element 또는 ID
+ */
+OG.renderer.RaphaelRenderer.prototype.selectSpot = function (spot) {
+ var me = this;
+ var spotRElement = this._getREleById(OG.Util.isElement(spot) ? spot.id : spot);
+ if (spotRElement) {
+ var parentRElement = $(spotRElement.node).data('parent');
+
+ this._remove(this._getREleById(parentRElement.id + OG.Constants.CONNECT_GUIDE_SUFFIX.BBOX));
+
+ $(me.getSpots(parentRElement.node)).each(function (index, _spot) {
+ if (_spot.id !== spotRElement.node.id) {
+ me.remove(_spot);
+ }
+ });
+ }
+};
+
+/**
+ * Edge의 하위 엘리먼트들을 제거한다.
+ *
+ * @param {Raphael.Element} rElement 라파엘 엘리먼트
+ * @private
+ */
+OG.renderer.RaphaelRenderer.prototype._removeEdgeChild = function (rElement) {
+ var childNodes, i;
+ if (rElement) {
+ childNodes = rElement.node.childNodes;
+ for (i = childNodes.length - 1; i >= 0; i--) {
+ //스팟은 삭제하지 않는다.
+ var skipRemove = false;
+ if ($(childNodes[i]).attr('name') === OG.Constants.CONNECT_GUIDE_SUFFIX.VIRTUAL_SPOT) {
+ skipRemove = true;
+ }
+ if ($(childNodes[i]).attr('name') === OG.Constants.CONNECT_GUIDE_SUFFIX.SPOT) {
+ skipRemove = true;
+ }
+ if (!skipRemove) {
+ this._remove(this._getREleById(childNodes[i].id));
+ }
+ }
+ }
+};
+
+/**
+ * 하위 엘리먼트들을 반환한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @return {Array} Array Element
+ */
+OG.renderer.RaphaelRenderer.prototype.getChildNodes = function (element) {
+ var rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element);
+ var childNodes, i;
+ if (rElement) {
+ childNodes = rElement.node.childNodes;
+ }
+ if (!childNodes) {
+ return [];
+ } else {
+ return childNodes;
+ }
+};
+
+/**
+ * Edge Element 내부의 패스중 나열된 두 꼭지점이 매우 짧은 선일 경우 하나의 꼭지점으로 정리한다.
+ * Edge Element 내부의 패스중 나열된 세 꼭지점이 평행에 가까울 경우 하나의 선분으로 정리한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ */
+OG.renderer.RaphaelRenderer.prototype.trimEdge = function (element) {
+ var me = this, rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element),
+ geometry = rElement ? rElement.node.shape.geom : null;
+
+ var vertices = geometry.getVertices();
+ var orgVerticesLength = vertices.length;
+
+ //Edge Element 내부의 패스중 나열된 세 꼭지점이 평행에 가까울 경우 하나의 선분으로 정리한다.
+ for (var i = vertices.length; i--;) {
+ if (i < vertices.length - 1 && vertices[i - 1]) {
+ var angleBetweenThreePoints =
+ geometry.angleBetweenThreePoints(vertices[i + 1], vertices[i], vertices[i - 1]);
+
+ if (vertices[i + 1].x === vertices[i].x && vertices[i + 1].y === vertices[i].y) {
+ vertices.splice(i, 1);
+ }
+ else if (vertices[i].x === vertices[i - 1].x && vertices[i].y === vertices[i - 1].y) {
+ vertices.splice(i, 1);
+ }
+
+ else if (angleBetweenThreePoints >= me._CONFIG.TRIM_EDGE_ANGLE_SIZE) {
+ vertices.splice(i, 1);
+ }
+ }
+ }
+ if (orgVerticesLength !== vertices.length) {
+ element.shape.geom.setVertices(vertices);
+ element = me.drawEdge(new OG.PolyLine(vertices), element.shape.geom.style, element.id);
+ me.drawLabel(element);
+ me.drawEdgeLabel(element, null, 'FROM');
+ me.drawEdgeLabel(element, null, 'TO');
+ }
+}
+
+/**
+ * Edge Element의 연결 정보가 있을 경우 연결대상과 꼭지점의 다중 중복을 정리한다.
+ * 다중 중복 정리 후 Edge 의 모양이 직선인 경우 새로운 plain 을 제작한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @return {Element} element
+ */
+OG.renderer.RaphaelRenderer.prototype.trimConnectInnerVertice = function (element) {
+ var me = this, rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element),
+ geometry = rElement ? rElement.node.shape.geom : null,
+ from, to, fromShape, toShape, fromXY, toXY;
+
+ var vertices = geometry.getVertices();
+ from = $(element).attr("_from");
+ to = $(element).attr("_to");
+
+ if (from) {
+ fromShape = this._getShapeFromTerminal(from);
+ fromXY = this._getPositionFromTerminal(from);
+ }
+
+ if (to) {
+ toShape = this._getShapeFromTerminal(to);
+ toXY = this._getPositionFromTerminal(to);
+ }
+
+ //Edge Element의 연결 정보가 있을 경우 연결대상과 꼭지점의 다중 중복을 정리한다.
+ var startVertice;
+ var startVerticeIdx;
+ var firstExternalVertice;
+ var firstExternalVerticeIdx;
+ var lastExcludeVertice;
+ var lastExcludeVerticeIdx;
+ var caculateExternalVerticeLine = function () {
+ if (firstExternalVertice) {
+ if (firstExternalVerticeIdx > 0 && firstExternalVerticeIdx < vertices.length - 1) {
+ var angleBetweenPoints = geometry.isRightAngleBetweenPoints(firstExternalVertice, lastExcludeVertice);
+ if (angleBetweenPoints.flag) {
+ if (angleBetweenPoints.type === 'horizontal') {
+ vertices[firstExternalVerticeIdx].y = vertices[startVerticeIdx].y;
+ }
+ if (angleBetweenPoints.type === 'vertical') {
+ vertices[firstExternalVerticeIdx].x = vertices[startVerticeIdx].x;
+ }
+ }
+ }
+ if (startVerticeIdx === 0) {
+ vertices.splice(startVerticeIdx + 1, firstExternalVerticeIdx - (startVerticeIdx + 1));
+ }
+ if (startVerticeIdx === vertices.length - 1) {
+ vertices.splice(firstExternalVerticeIdx + 1, startVerticeIdx - (firstExternalVerticeIdx + 1));
+ }
+ }
+ }
+ if (fromShape) {
+ for (var i = 0, leni = vertices.length; i < leni; i++) {
+ if (i == 0) {
+ startVertice = vertices[i];
+ startVerticeIdx = i;
+ continue;
+ }
+ var containsPoint = fromShape.shape.geom.isContainsPoint(vertices[i]);
+ if (containsPoint) {
+ if (i == (vertices.length - 1)) {
+ firstExternalVertice = vertices[i];
+ firstExternalVerticeIdx = i;
+ lastExcludeVertice = vertices[i - 1];
+ lastExcludeVerticeIdx = i - 1;
+ } else {
+ firstExternalVertice = vertices[i + 1];
+ firstExternalVerticeIdx = i + 1;
+ lastExcludeVertice = vertices[i];
+ lastExcludeVerticeIdx = i;
+ }
+ }
+ }
+ caculateExternalVerticeLine();
+ startVertice = null;
+ startVerticeIdx = null;
+ firstExternalVertice = null;
+ firstExternalVerticeIdx = null;
+ lastExcludeVertice = null;
+ lastExcludeVerticeIdx = null;
+ }
+ if (toShape) {
+ for (var i = vertices.length - 1; i >= 0; i--) {
+ if (i == vertices.length - 1) {
+ startVertice = vertices[i];
+ startVerticeIdx = i;
+ continue;
+ }
+ var containsPoint = toShape.shape.geom.isContainsPoint(vertices[i]);
+ if (containsPoint) {
+ if (i == 0) {
+ firstExternalVertice = vertices[i];
+ firstExternalVerticeIdx = i;
+ lastExcludeVertice = vertices[i + 1];
+ lastExcludeVerticeIdx = i + 1;
+ } else {
+ firstExternalVertice = vertices[i - 1];
+ firstExternalVerticeIdx = i - 1;
+ lastExcludeVertice = vertices[i];
+ lastExcludeVerticeIdx = i;
+ }
+ }
+ }
+ caculateExternalVerticeLine();
+ }
+
+ //다중 중복 정리 후 Edge 의 모양이 직선인 경우 새로운 plain 을 제작한다.
+ if (vertices.length === 2) {
+ element = me.drawEdge(new OG.Line(vertices[0], vertices[1]), element.shape.geom.style, element.id);
+ element = me.trimEdgeDirection(element, fromShape, toShape);
+ } else {
+ element.shape.geom.setVertices(vertices);
+ element = me.drawEdge(new OG.PolyLine(vertices), element.shape.geom.style, element.id);
+ }
+
+ me.drawLabel(element);
+ me.drawEdgeLabel(element, null, 'FROM');
+ me.drawEdgeLabel(element, null, 'TO');
+
+ return element;
+}
+
+/**
+ * Edge Element의 연결 정보가 있을 경우 선분과 연결대상의 연결점을 자연스럽게 한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @return {Element} element
+ */
+OG.renderer.RaphaelRenderer.prototype.trimConnectIntersection = function (element) {
+ var me = this, rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element),
+ geometry = rElement ? rElement.node.shape.geom : null,
+ from, to, fromShape, toShape, fromXY, toXY, shortestIntersection;
+
+ var vertices = geometry.getVertices();
+ from = $(element).attr("_from");
+ to = $(element).attr("_to");
+
+ if (from) {
+ fromShape = this._getShapeFromTerminal(from);
+ fromXY = this._getPositionFromTerminal(from);
+ }
+
+ if (to) {
+ toShape = this._getShapeFromTerminal(to);
+ toXY = this._getPositionFromTerminal(to);
+ }
+ //Edge Element의 연결 정보가 있을 경우 선분과 연결대상의 연결점을 자연스럽게 한다.
+ if (from) {
+ shortestIntersection =
+ fromShape.shape.geom.shortestIntersectToLine([vertices[1], [fromXY.x, fromXY.y]]);
+
+ if (shortestIntersection) {
+ vertices[0].x = shortestIntersection.x
+ vertices[0].y = shortestIntersection.y
+ } else {
+ vertices[0].x = fromXY.x
+ vertices[0].y = fromXY.y
+ }
+ }
+
+ if (to) {
+ shortestIntersection =
+ toShape.shape.geom.shortestIntersectToLine([vertices[vertices.length - 2], [toXY.x, toXY.y]]);
+
+ if (shortestIntersection) {
+ vertices[vertices.length - 1].x = shortestIntersection.x
+ vertices[vertices.length - 1].y = shortestIntersection.y
+ } else {
+ vertices[vertices.length - 1].x = toXY.x
+ vertices[vertices.length - 1].y = toXY.y
+ }
+ }
+
+ element.shape.geom.setVertices(vertices);
+ element = me.drawEdge(new OG.PolyLine(vertices), element.shape.geom.style, element.id);
+ me.drawLabel(element);
+ me.drawEdgeLabel(element, null, 'FROM');
+ me.drawEdgeLabel(element, null, 'TO');
+
+ return element;
+}
+
+/**
+ * ID에 해당하는 Element 의 바운더리 영역을 리턴한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @return {OG.geometry.Envelope} Envelope 영역
+ */
+OG.renderer.RaphaelRenderer.prototype.getBoundary = function (element) {
+ var rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element),
+ envelope;
+
+ if (rElement && rElement.node && rElement.node.shape && rElement.node.shape.geom) {
+ envelope = rElement.node.shape.geom.getBoundary();
+ }
+
+ return envelope;
+};
+
+/**
+ * Element 에 하이라이트 속성을 부여한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @param {Object} highlight HIGHLIGHT 속성 집합.
+ */
+OG.renderer.RaphaelRenderer.prototype.setHighlight = function (element, highlight) {
+ var rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element);
+ var me = this;
+ if (rElement) {
+ element = rElement.node;
+ var childNodes = me.getNotConnectGuideElements(element);
+ $.each(childNodes, function (idx, childNode) {
+ var orgAttrGroup = {};
+ for (var key in highlight) {
+ var orgAttr = me.getAttr(childNode, key);
+ if (orgAttr) {
+ orgAttrGroup[key] = orgAttr;
+ }
+ }
+ $(childNode).data('orgAttrGroup', orgAttrGroup);
+ me.setAttr(childNode, highlight);
+ });
+ }
+}
+
+/**
+ * Element 에 하이라이트 속성을 제거한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @param {Object} highlight HIGHLIGHT 속성 집합.
+ */
+OG.renderer.RaphaelRenderer.prototype.removeHighlight = function (element, highlight) {
+ var rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element);
+ var me = this;
+ if (rElement) {
+ element = rElement.node;
+ var childNodes = me.getNotConnectGuideElements(element);
+ $.each(childNodes, function (idx, childNode) {
+ var orgAttrGroup = $(childNode).data('orgAttrGroup');
+ if (!orgAttrGroup) {
+ return;
+ }
+ for (var key in highlight) {
+ var orgAttr = orgAttrGroup[key];
+ if (!orgAttr) {
+ orgAttrGroup[key] = null;
+ }
+ }
+ $(childNode).removeData('orgAttrGroup');
+ me.setAttr(childNode, orgAttrGroup);
+ });
+ }
+}
+
+/**
+ * 터미널 문자열을 생성한다.
+ *
+ * @param {Element|String} Element Element 또는 ID
+ * @param {Array} point 연결 좌표정보 [x,y]
+ *
+ * @return {String} terminal 터미널 문자열
+ */
+OG.renderer.RaphaelRenderer.prototype.createTerminalString = function (element, point) {
+
+ var terminal;
+ var rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element);
+
+ if (rElement) {
+ var percentageDistance = rElement.node.shape.geom.getPercentageDistanceFromPoint(point);
+ if (percentageDistance) {
+ terminal = rElement.node.id + OG.Constants.TERMINAL + '_' + percentageDistance.px + '_' + percentageDistance.py;
+ }
+ }
+ return terminal;
+}
+
+/**
+ * 디폴트 터미널 문자열을 생성한다.
+ *
+ * @param {Element|String} Element Element 또는 ID
+ *
+ * @return {String} terminal 터미널 문자열
+ */
+OG.renderer.RaphaelRenderer.prototype.createDefaultTerminalString = function (element) {
+
+ var terminal;
+ var rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element);
+
+ if (rElement) {
+ var percentageDistance = {px: 50, py: 50};
+ if (percentageDistance) {
+ terminal = rElement.node.id + OG.Constants.TERMINAL + '_' + percentageDistance.px + '_' + percentageDistance.py;
+ }
+ }
+ return terminal;
+};
+
+/**
+ * 터미널로부터 부모 Shape element 로의 퍼센테이지 좌표를 반환한다.
+ *
+ * @param {Element|String} terminal 터미널 Element or ID
+ * @return {Array} [px,py]
+ * @private
+ */
+OG.renderer.RaphaelRenderer.prototype._getPercentageFromTerminal = function (terminal) {
+ var pXpY;
+ if (terminal) {
+ var shapeId = terminal.substring(0, terminal.indexOf(OG.Constants.TERMINAL));
+ var replace = terminal.replace(shapeId + OG.Constants.TERMINAL + '_', '');
+ var split = replace.split('_');
+ pXpY = [parseInt(split[0]), parseInt(split[1])]
+ }
+ return pXpY;
+}
+/**
+ * 터미널로부터 좌표를 반환한다.
+ *
+ * @param {Element|String} terminal 터미널 Element or ID
+ * @return {OG.geometry.Coordinate} 좌표
+ * @private
+ */
+OG.renderer.RaphaelRenderer.prototype._getPositionFromTerminal = function (terminal) {
+ var me = this;
+ var xy, pXpY;
+ var percentageDistance = {px: 50, py: 50};
+ if (terminal) {
+ var shapeId = terminal.substring(0, terminal.indexOf(OG.Constants.TERMINAL));
+ var replace = terminal.replace(shapeId + OG.Constants.TERMINAL + '_', '');
+ pXpY = replace.split('_');
+ var rElement = this._getREleById(shapeId);
+ if (rElement) {
+ xy = rElement.node.shape.geom.getPointFromPercentageDistance(pXpY);
+ if (!xy || isNaN(xy.x) || isNaN(xy.y)) {
+ xy = rElement.node.shape.geom.getPointFromPercentageDistance(
+ [percentageDistance.px, percentageDistance.py]
+ );
+ }
+ xy.x = me._CONFIG.DRAG_GRIDABLE ? OG.Util.roundGrid(xy.x, me._CONFIG.MOVE_SNAP_SIZE / 2) : xy.x;
+ xy.y = me._CONFIG.DRAG_GRIDABLE ? OG.Util.roundGrid(xy.y, me._CONFIG.MOVE_SNAP_SIZE / 2) : xy.y;
+ }
+ }
+ return xy;
+}
+
+/**
+ * 캔버스의 Edge 들을 항상 최상단으로 이동시킨다.
+ *
+ */
+OG.renderer.RaphaelRenderer.prototype.toFrontEdges = function () {
+ //Edge는 항상 toFront
+ var me = this;
+ var root = me.getRootGroup();
+ var edges = me.getAllEdges();
+
+ for (var i = 0, leni = edges.length; i < leni; i++) {
+ root.removeChild(edges[i]);
+ root.appendChild(edges[i]);
+ }
+}
+
+/**
+ * 캔버스의 Edge 들의 가이드를 제거한다.
+ */
+OG.renderer.RaphaelRenderer.prototype.removeAllEdgeGuide = function () {
+ var me = this;
+ var edges = me.getAllEdges();
+ $.each(edges, function (index, edge) {
+ me.removeGuide(edge);
+ })
+}
+
+/**
+ * 주어진 좌표와 선택된 Element 사이에 가상 연결선을 생성한다.
+ *
+ * @param {Number} x 이벤트의 캔버스 기준 x 좌표
+ * @param {Number} x 이벤트의 캔버스 기준 y 좌표
+ * @param {Element|String} targetEle Element 또는 ID
+ * @return {Element} Edge Element
+ */
+OG.renderer.RaphaelRenderer.prototype.createVirtualEdge = function (x, y, targetEle) {
+ var me = this, rElement = this._getREleById(OG.Util.isElement(targetEle) ? targetEle.id : targetEle),
+ geometry = rElement ? rElement.node.shape.geom : null,
+ virtualEdge,
+ virtualEdgeStyle = me._CONFIG.DEFAULT_STYLE.GUIDE_VIRTUAL_EDGE,
+ root = me.getRootElement();
+
+ //엣지 일경우 스킵
+ if ($(targetEle).attr("_shape") === OG.Constants.SHAPE_TYPE.EDGE) {
+ return null;
+ }
+
+ //기존 가상선은 삭제한다.
+ me.removeAllVirtualEdge();
+
+ if (rElement && geometry) {
+ var boundary = me.getBoundary(targetEle);
+ var width = boundary.getWidth();
+ var height = boundary.getHeight();
+ var upperLeft = boundary.getUpperLeft();
+ var start = [Math.round(upperLeft.x + (width / 2)), Math.round(upperLeft.y + (height / 2))];
+ var end = [x - 3, y - 3]
+
+ virtualEdge = me.drawEdge(new OG.PolyLine([start, end]), virtualEdgeStyle, OG.Constants.GUIDE_SUFFIX.LINE_VIRTUAL_EDGE);
+
+ $(virtualEdge).data('targetEle', targetEle);
+ return virtualEdge;
+ }
+ return null;
+}
+
+/**
+ * 캔버스의 가상 연결선을 업데이트한다.
+ *
+ * @param {Number} x 이벤트의 캔버스 기준 x 좌표
+ * @param {Number} x 이벤트의 캔버스 기준 y 좌표
+ */
+OG.renderer.RaphaelRenderer.prototype.updateVirtualEdge = function (x, y) {
+ var me = this, rElement,
+ geometry,
+ virtualEdge, targetEle,
+ virtualEdgeStyle = me._CONFIG.DEFAULT_STYLE.GUIDE_VIRTUAL_EDGE;
+
+
+ var caculateNoneEventAreaPoint = function (startP, targetP) {
+ var eventProtectLength = 5;
+ var _x, _y, _a, _sa;
+ _a = ((startP[1] - targetP[1]) * -1) / (startP[0] - targetP[0]);
+ _sa = Math.pow(_a, 2);
+ _x = Math.sqrt(Math.pow(eventProtectLength, 2) / (_sa + 1));
+ _y = Math.sqrt(Math.pow(eventProtectLength, 2) - Math.pow(_x, 2));
+
+ var fixedTagetP = {
+ x: targetP[0],
+ y: targetP[1]
+ };
+ if (targetP[0] > startP[0]) {
+ fixedTagetP.x = targetP[0] - _x;
+ }
+ if (targetP[0] < startP[0]) {
+ fixedTagetP.x = targetP[0] + _x;
+ }
+
+ if (targetP[1] > startP[1]) {
+ fixedTagetP.y = targetP[1] - _y;
+ }
+ if (targetP[1] < startP[1]) {
+ fixedTagetP.y = targetP[1] + _y;
+ }
+ return [fixedTagetP.x, fixedTagetP.y];
+ }
+
+ virtualEdge = me.getElementById(OG.Constants.GUIDE_SUFFIX.LINE_VIRTUAL_EDGE);
+
+ if (virtualEdge) {
+ targetEle = $(virtualEdge).data('targetEle');
+ }
+
+ if (targetEle) {
+ rElement = this._getREleById(OG.Util.isElement(targetEle) ? targetEle.id : targetEle);
+ geometry = rElement ? rElement.node.shape.geom : null;
+ }
+
+ if (rElement && geometry) {
+ var boundary = me.getBoundary(targetEle);
+ var width = boundary.getWidth();
+ var height = boundary.getHeight();
+ var upperLeft = boundary.getUpperLeft();
+ var start = [Math.round(upperLeft.x + (width / 2)), Math.round(upperLeft.y + (height / 2))];
+ var end = [x, y];
+ var fixedEnd = caculateNoneEventAreaPoint(start, end);
+
+ me.drawEdge(new OG.PolyLine([start, fixedEnd]), virtualEdgeStyle, OG.Constants.GUIDE_SUFFIX.LINE_VIRTUAL_EDGE);
+ }
+};
+
+/**
+ * 캔버스의 가상선의 타겟 엘리먼트를 구한다.
+ *
+ * @param {Number} x 이벤트의 캔버스 기준 x 좌표
+ * @param {Number} x 이벤트의 캔버스 기준 y 좌표
+ */
+OG.renderer.RaphaelRenderer.prototype.getTargetfromVirtualEdge = function () {
+ var me = this, virtualEdge, targetEle;
+
+ virtualEdge = me.getElementById(OG.Constants.GUIDE_SUFFIX.LINE_VIRTUAL_EDGE);
+
+ if (virtualEdge) {
+ targetEle = $(virtualEdge).data('targetEle');
+ }
+
+ if (targetEle) {
+ return targetEle;
+ }
+
+ return null;
+};
+
+/**
+ * 캔버스의 가상선을 삭제한다.
+ */
+OG.renderer.RaphaelRenderer.prototype.removeAllVirtualEdge = function () {
+ $(this.getRootGroup()).data(OG.Constants.GUIDE_SUFFIX.LINE_CONNECT_MODE, false);
+ $(this.getRootGroup()).data(OG.Constants.GUIDE_SUFFIX.RECT_CONNECT_MODE, false);
+ return this.remove(OG.Constants.GUIDE_SUFFIX.LINE_VIRTUAL_EDGE);
+};
+
+/**
+ * 캔버스의 히스토리를 초기화한다.
+ *
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.initHistory = function () {
+ var me = this;
+ me._CONFIG.HISTORY_INDEX = 0;
+ me._CONFIG.HISTORY[me._CANVAS.toJSON()];
+}
+
+/**
+ * 캔버스에 히스토리를 추가한다.
+ *
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.addHistory = function () {
+ $(this._PAPER.canvas).trigger('addHistory');
+ if (this._CONFIG.AUTO_HISTORY && !this._CONFIG.FAST_LOADING) {
+ var me = this;
+ var history = me._CONFIG.HISTORY;
+ var historySize = me._CONFIG.HISTORY_SIZE;
+ var historyIndex = me._CONFIG.HISTORY_INDEX;
+
+ if (history.length == 0) {
+ historyIndex = 0;
+ history.push(me._CANVAS.toJSON());
+
+ } else {
+ if (historySize <= history.length) {
+ history.splice(0, 1);
+ historyIndex = historyIndex - 1;
+ }
+ history.splice(historyIndex + 1);
+ history.push(me._CANVAS.toJSON());
+ historyIndex = history.length - 1;
+ }
+
+ me._CONFIG.HISTORY = history;
+ me._CONFIG.HISTORY_INDEX = historyIndex;
+
+
+ //캔버스가 서버로부터 받은 데이터를 적용시키는 과정이 아닐 경우 브로드캐스트 수행.
+ if (me._CANVAS.getRemotable()) {
+ if (!me._CANVAS.getRemoteDuring()) {
+ OG.RemoteHandler.broadCastCanvas(me._CANVAS, function (canvas) {
+
+ });
+ }
+ }
+
+ //슬라이더가 있을경우 슬라이더 업데이트
+ me._CANVAS.updateSlider();
+ } else if (this._CONFIG.AUTO_SLIDER_UPDATE && !this._CONFIG.FAST_LOADING) {
+ this._CANVAS.updateSlider();
+ }
+};
+
+/**
+ * 캔버스의 Undo
+ *
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.undo = function () {
+ var me = this;
+ var history = me._CONFIG.HISTORY;
+ var historyIndex = me._CONFIG.HISTORY_INDEX;
+
+ if (historyIndex >= 0) {
+ historyIndex = historyIndex - 1;
+ me._CANVAS.loadJSON(history[historyIndex]);
+ }
+ me._CONFIG.HISTORY_INDEX = historyIndex;
+ $(this._PAPER.canvas).trigger('undo');
+};
+
+/**
+ * 캔버스의 Redo
+ *
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.redo = function () {
+ var me = this;
+ var history = me._CONFIG.HISTORY;
+ var historyIndex = me._CONFIG.HISTORY_INDEX;
+
+ if (historyIndex < history.length - 1) {
+ historyIndex = historyIndex + 1;
+ me._CANVAS.loadJSON(history[historyIndex]);
+ }
+ me._CONFIG.HISTORY_INDEX = historyIndex;
+ $(this._PAPER.canvas).trigger('redo');
+};
+
+/**
+ * 도형의 Lane 타입 여부를 판별한다.
+ *
+ * @param {Element|String} Element Element 또는 ID
+ * @return {boolean} true false
+ */
+OG.renderer.RaphaelRenderer.prototype.isLane = function (element) {
+ var rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element);
+ if (!rElement) {
+ return false;
+ }
+ if (rElement.node.shape instanceof OG.shape.HorizontalLaneShape
+ || rElement.node.shape instanceof OG.shape.VerticalLaneShape) {
+ return true;
+ }
+ return false;
+};
+
+/**
+ * 도형의 Pool 타입 여부를 판별한다.
+ *
+ * @param {Element|String} Element Element 또는 ID
+ * @return {boolean} true false
+ */
+OG.renderer.RaphaelRenderer.prototype.isPool = function (element) {
+ var rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element);
+ if (!rElement) {
+ return false;
+ }
+ if (rElement.node.shape instanceof OG.shape.HorizontalPoolShape
+ || rElement.node.shape instanceof OG.shape.VerticalPoolShape) {
+ return true;
+ }
+ return false;
+};
+/**
+ * 도형의 ScopeActivity 타입 여부를 판별한다.
+ *
+ * @param {Element|String} Element Element 또는 ID
+ * @return {boolean} true false
+ */
+OG.renderer.RaphaelRenderer.prototype.isScopeActivity = function (element) {
+ var rElement = this._getREleById(OG.Util.isElement(element) ? element.id : element);
+ if (!rElement) {
+ return false;
+ }
+ if (rElement.node.shape instanceof OG.shape.bpmn.ScopeActivity) {
+ return true;
+ }
+ return false;
+};
+
+/**
+ * 도형의 HorizontalLaneShape 타입 여부를 판별한다.
+ *
+ * @param {Element|String} Element Element 또는 ID
+ * @return {boolean} true false
+ */
+OG.renderer.RaphaelRenderer.prototype.isHorizontalLane = function (element) {
+ return element.shape instanceof OG.shape.HorizontalLaneShape;
+};
+
+/**
+ * 도형의 VerticalLaneShape 타입 여부를 판별한다.
+ *
+ * @param {Element|String} Element Element 또는 ID
+ * @return {boolean} true false
+ */
+OG.renderer.RaphaelRenderer.prototype.isVerticalLane = function (element) {
+ return element.shape instanceof OG.shape.VerticalLaneShape;
+};
+
+/**
+ * 도형의 HorizontalPoolShape 타입 여부를 판별한다.
+ *
+ * @param {Element|String} Element Element 또는 ID
+ * @return {boolean} true false
+ */
+OG.renderer.RaphaelRenderer.prototype.isHorizontalPool = function (element) {
+ return element.shape instanceof OG.shape.HorizontalPoolShape;
+};
+
+/**
+ * 도형의 VerticalPoolShape 타입 여부를 판별한다.
+ *
+ * @param {Element|String} Element Element 또는 ID
+ * @return {boolean} true false
+ */
+OG.renderer.RaphaelRenderer.prototype.isVerticalPool = function (element) {
+ return element.shape instanceof OG.shape.VerticalPoolShape;
+};
+
+/**
+ * Lane 타입 도형 하위의 Lane 타입들을 리턴한다.
+ *
+ * @param {Element|String} Element Element 또는 ID
+ * @return {Array} childsLanes
+ */
+OG.renderer.RaphaelRenderer.prototype.getChildLane = function (element) {
+ var childsLanes = [], me = this;
+ var rElement = me._getREleById(OG.Util.isElement(element) ? element.id : element);
+ if (!rElement) {
+ return childsLanes;
+ }
+ element = rElement.node;
+ var childs = me.getChilds(element);
+ $.each(childs, function (index, child) {
+ if (me.isLane(child)) {
+ childsLanes.push(child);
+ }
+ })
+ return childsLanes;
+};
+
+/**
+ * Lane 타입이 내부적으로 분기가 가능한 수를 리턴한다.
+ *
+ * @param {Element|String} Element Element 또는 ID
+ * @return {Number} 0,1,2
+ */
+OG.renderer.RaphaelRenderer.prototype.enableDivideCount = function (element) {
+ var me = this;
+ var rElement = me._getREleById(OG.Util.isElement(element) ? element.id : element);
+ var geometry = rElement ? rElement.node.shape.geom : null;
+ if (!rElement || !geometry) {
+ return 0;
+ }
+ element = rElement.node;
+ if (!me.isLane(element)) {
+ return 0;
+ }
+
+ //하위 Lane 이 있다면 분기할 수 없다.
+ if (me.getChildLane(element).length) {
+ return 0;
+ }
+
+ var minSize = me._CONFIG.LANE_MIN_SIZE;
+ var boundary = geometry.getBoundary();
+ var height = boundary.getHeight();
+ var width = boundary.getWidth();
+ if (this.isHorizontalLane(element)) {
+ if (height > (minSize * 3)) {
+ return 2;
+ }
+ if (height > (minSize * 2)) {
+ return 1;
+ }
+ }
+ if (this.isVerticalLane(element)) {
+ if (width > (minSize * 3)) {
+ return 2;
+ }
+ if (width > (minSize * 2)) {
+ return 1;
+ }
+ }
+ return 0;
+};
+
+/**
+ * Lane,Pool 의 타이틀 영역을 제외한 boundary 를 리턴한다.
+ *
+ * @param {Element|String} Element Element 또는 ID
+ * @param {OG.geometry.Envelope} boundary
+ */
+OG.renderer.RaphaelRenderer.prototype.getExceptTitleLaneArea = function (element) {
+ var me = this;
+ var rElement = me._getREleById(OG.Util.isElement(element) ? element.id : element);
+ var geometry = rElement ? rElement.node.shape.geom : null;
+
+ if (!rElement || !geometry) {
+ return null;
+ }
+
+ var boundary = geometry.getBoundary();
+ var style = geometry.style.map;
+ var titleSize = style['title-size'] ? style['title-size'] : 20;
+ var upperLeft = boundary.getUpperLeft();
+ var width = boundary.getWidth();
+ var height = boundary.getHeight();
+ var newUpperLeft, newWidth, newHeight;
+
+ if (!me.isLane(element) && !me.isPool(element)) {
+ return boundary;
+ }
+
+ if (me.isHorizontalLane(element) || me.isHorizontalPool(element)) {
+ newUpperLeft = new OG.geometry.Coordinate(upperLeft.x + titleSize, upperLeft.y);
+ newWidth = width - titleSize;
+ newHeight = height;
+ }
+
+ if (me.isVerticalLane(element) || me.isVerticalPool(element)) {
+ newUpperLeft = new OG.geometry.Coordinate(upperLeft.x, upperLeft.y + titleSize);
+ newWidth = width;
+ newHeight = height - titleSize;
+ }
+
+ if (newUpperLeft) {
+ return new OG.geometry.Envelope(newUpperLeft, newWidth, newHeight);
+ }
+
+ //타이틀 라벨이 없을 경우 바운더리 리턴.
+ return boundary;
+};
+
+/**
+ * Lane 을 분기한다.
+ *
+ * @param {Element|String} Element Element 또는 ID
+ * @param {String} quarterOrder 분기 명령 QUARTER_UPPER | QUARTER_LOW | QUARTER_BISECTOR | QUARTER_THIRDS
+ */
+OG.renderer.RaphaelRenderer.prototype.divideLane = function (element, quarterOrder) {
+ var divedLanes = [];
+ var me = this;
+ var rElement = me._getREleById(OG.Util.isElement(element) ? element.id : element);
+ var geometry = rElement ? rElement.node.shape.geom : null;
+ if (!rElement || !geometry) {
+ return;
+ }
+ element = rElement.node;
+
+ if (!me.isLane(element)) {
+ return;
+ }
+
+ var isUpper = quarterOrder === OG.Constants.GUIDE_SUFFIX.QUARTER_UPPER;
+ var isLow = quarterOrder === OG.Constants.GUIDE_SUFFIX.QUARTER_LOW;
+ var isBisector = quarterOrder === OG.Constants.GUIDE_SUFFIX.QUARTER_BISECTOR;
+ var isThirds = quarterOrder === OG.Constants.GUIDE_SUFFIX.QUARTER_THIRDS;
+
+ //내부 분기일경우
+ //1. 내 영역에서 타이틀 영역을 뺀 것이 분기가능한 바운더리 영역이다.
+ //2. 2등분 또는 3등분 만큼 새로운 lane 을 만들어 영역안에 위치시키고 부모 lane 에 인서트한다.
+ if (isBisector || isThirds) {
+ var quarterLength = isBisector ? 2 : 3;
+ var targetArea = me.getExceptTitleLaneArea(element);
+ var targetUpperLeft = targetArea.getUpperLeft();
+ for (var i = 0; i < quarterLength; i++) {
+ if (me.isHorizontalLane(element)) {
+ var _width = parseInt(targetArea.getWidth());
+ var _height = parseInt(targetArea.getHeight() / quarterLength);
+ var x = targetUpperLeft.x;
+ var y = targetUpperLeft.y + (_height * i);
+ if (i === quarterLength - 1) {
+ _height = _height + (targetArea.getHeight() % quarterLength);
+ }
+ var newLane = me._CANVAS.drawShape([x + (_width / 2), y + (_height / 2)], new OG.HorizontalLaneShape(), [_width, _height], null, null, element.id, true, true);
+ divedLanes.push(newLane);
+ }
+
+ if (me.isVerticalLane(element)) {
+ var _width = parseInt(targetArea.getWidth() / quarterLength);
+ var _height = parseInt(targetArea.getHeight());
+ var x = targetUpperLeft.x + (_width * i);
+ var y = targetUpperLeft.y;
+ if (i === quarterLength - 1) {
+ _width = _width + (targetArea.getWidth() % quarterLength);
+ }
+ var newLane = me._CANVAS.drawShape([x + (_width / 2), y + (_height / 2)], new OG.VerticalLaneShape(), [_width, _height], null, null, element.id, true, true);
+ divedLanes.push(newLane);
+ }
+ me.fitLaneOrder(element);
+ }
+ }
+
+ //외부 확장일 경우
+ //1.최상위 lane 일 경우
+ // 1) 하위 lane 이 없으면 하위 lane 을 추가하고 기준으로 삼는다.
+ // 2) 하위 lane 이 있으면 upper,low 에 따른 기준을 골라 삼는다.
+
+ //2.최상위 lane 이 아닐경우
+ // 1) 기준에서 upper, low에 따라 신규 lane 을 생성한다.
+ // 2) 신규 lane 은 디폴트 규격
+ // 3) 신규 lane 을 기준 대상 부모 lane 에 추가한다.
+ if (isUpper || isLow) {
+ var standardLane;
+
+ //최상위 lane 일 경우
+ if (me.isTopGroup(element)) {
+ var childLane = me.getChildLane(element);
+
+ //하위 lane 이 없으면 하위 lane 을 추가하고 기준으로 삼는다.
+ if (!childLane.length) {
+ var targetArea = me.getExceptTitleLaneArea(element);
+ var targetUpperLeft = targetArea.getUpperLeft();
+ var _width = targetArea.getWidth();
+ var _height = targetArea.getHeight();
+ var x = targetUpperLeft.x;
+ var y = targetUpperLeft.y;
+ var shape = me.isHorizontalLane(element) ? new OG.HorizontalLaneShape() : new OG.VerticalLaneShape();
+ standardLane = me._CANVAS.drawShape([x + (_width / 2), y + (_height / 2)], shape, [_width, _height], null, null, element.id, true, true);
+ divedLanes.push(standardLane);
+ }
+
+ //하위 lane 이 있으면 upper,low 에 따른 기준을 골라 삼는다.
+ else {
+ $.each(childLane, function (idx, child) {
+ if (!standardLane) {
+ standardLane = child;
+ }
+ var childUpperLeft = child.shape.geom.getBoundary().getUpperLeft();
+ var standardUpperLeft = standardLane.shape.geom.getBoundary().getUpperLeft();
+ if (me.isHorizontalLane(element) && isUpper) {
+ //y가 가장 높은것 (좌표상 작은수치)
+ if (childUpperLeft.y < standardUpperLeft.y) {
+ standardLane = child;
+ }
+ }
+ if (me.isHorizontalLane(element) && isLow) {
+ //y 가 가장 낮은것 (좌표상 높은수치)
+ if (childUpperLeft.y > standardUpperLeft.y) {
+ standardLane = child;
+ }
+ }
+ if (me.isVerticalLane(element) && isUpper) {
+ //x 가 가장 높은것
+ if (childUpperLeft.x > standardUpperLeft.x) {
+ standardLane = child;
+ }
+ }
+ if (me.isVerticalLane(element) && isLow) {
+ //x 가 가장 낮은것
+ if (childUpperLeft.x < standardUpperLeft.x) {
+ standardLane = child;
+ }
+ }
+ });
+ }
+ } else {
+ standardLane = element;
+ }
+
+ //기준이 정해졌다면, 기준에서 upper, low에 따라 신규 lane 을 생성한다.
+ //확장 방향에 따라 영향을 받는 lane 들의 위치를 재조정한다.
+ if (standardLane) {
+ var parent = me.getParent(standardLane);
+ var boundary = standardLane.shape.geom.getBoundary();
+ var defaultSize = me._CONFIG.LANE_DEFAULT_SIZE;
+ var _width;
+ var _height;
+ var shape;
+ var x = boundary.getUpperLeft().x;
+ var y = boundary.getUpperLeft().y;
+ var moveOffset = [];
+
+ if (me.isHorizontalLane(element)) {
+ shape = new OG.HorizontalLaneShape();
+ _width = boundary.getWidth();
+ _height = defaultSize;
+ }
+ if (me.isVerticalLane(element)) {
+ shape = new OG.VerticalLaneShape();
+ _width = defaultSize;
+ _height = boundary.getHeight();
+ }
+
+ if (me.isHorizontalLane(element) && isUpper) {
+ x = boundary.getUpperLeft().x;
+ y = boundary.getUpperLeft().y - _height;
+ moveOffset = [0, (_height * -1)];
+ }
+ if (me.isHorizontalLane(element) && isLow) {
+ x = boundary.getLowerLeft().x;
+ y = boundary.getLowerLeft().y;
+ moveOffset = [0, _height];
+ }
+ if (me.isVerticalLane(element) && isUpper) {
+ x = boundary.getUpperRight().x;
+ y = boundary.getUpperRight().y;
+ moveOffset = [_width, 0];
+ }
+ if (me.isVerticalLane(element) && isLow) {
+ x = boundary.getUpperLeft().x - _width;
+ y = boundary.getUpperLeft().y;
+ moveOffset = [(_width * -1), 0];
+ }
+
+ var lanesToMove = [];
+ var baseLanes = me.getBaseLanes(standardLane);
+ var indexOfLane = me.getIndexOfLane(standardLane);
+ $.each(baseLanes, function (index, baseLane) {
+ if (isUpper) {
+ if (index < indexOfLane) {
+ lanesToMove.push(baseLane);
+ }
+ }
+ if (isLow) {
+ if (index > indexOfLane) {
+ lanesToMove.push(baseLane);
+ }
+ }
+ });
+ var newLane = me._CANVAS.drawShape([x + (_width / 2), y + (_height / 2)], shape, [_width, _height], null, null, parent.id, true, true);
+ divedLanes.push(newLane);
+ $.each(lanesToMove, function (index, laneToMove) {
+ me.move(laneToMove, moveOffset);
+ });
+ me.reEstablishLane(standardLane);
+ me.fitLaneOrder(standardLane);
+ }
+ }
+
+ if (divedLanes.length) {
+ for (var i = 0, leni = divedLanes.length; i < leni; i++) {
+ //생성된 lane 의 부모에 대해 첫번째 자식으로 들어감으로써 lane 에 속한 다른 도형의 인덱스들을 방해하지 않는다.
+ //divedLanes[i].parentElement.insertBefore(divedLanes[i], divedLanes[i].parentElement.firstChild);
+
+ $(this._PAPER.canvas).trigger('divideLane', divedLanes[i]);
+ }
+ }
+
+ me.offDropablePool();
+
+ return divedLanes;
+};
+
+/**
+ * Lane 의 최상의 Lane 으로부터 모든 Base Lane 들을 반환한다.
+ * Base Lane 은 자식 Lane 을 가지지 않는 Lane 을 뜻함.
+ * 반환하는 Array 는 좌표상의 값을 기준으로 정렬되어 있는 상태이다.
+ *
+ * @param {Element|String} Element Element 또는 ID
+ * @return {Array} childBaseLanes
+ */
+OG.renderer.RaphaelRenderer.prototype.getBaseLanes = function (element) {
+ var me = this;
+ var rElement = me._getREleById(OG.Util.isElement(element) ? element.id : element);
+ var rootLane;
+ var baseLanes = [];
+ var tempLanes = [];
+ var sortable = [];
+
+ function chooseBaseLane(lane) {
+ var _childLanes = me.getChildLane(lane);
+ if (_childLanes.length) {
+ for (var i = 0, leni = _childLanes.length; i < leni; i++) {
+ chooseBaseLane(_childLanes[i]);
+ }
+ } else {
+ tempLanes.push(lane);
+ }
+ }
+
+ if (!rElement) {
+ return baseLanes;
+ }
+ element = rElement.node;
+ rootLane = me.getRootLane(element);
+
+ if (!rootLane) {
+ return baseLanes;
+ }
+
+ chooseBaseLane(rootLane);
+
+ var isHorizontal = me.isHorizontalLane(rootLane);
+ var isVertical = me.isVerticalLane(rootLane);
+
+ $.each(tempLanes, function (index, tempLane) {
+ var upperLeft = tempLane.shape.geom.getBoundary().getUpperLeft();
+ if (isHorizontal) {
+ sortable.push([tempLane, upperLeft.y]);
+ }
+ if (isVertical) {
+ sortable.push([tempLane, upperLeft.x]);
+ }
+ })
+
+ //수직배열: y값이 작은값부터 정렬한다.
+ if (isHorizontal) {
+ sortable.sort(function (a, b) {
+ return a[1] - b[1]
+ })
+ }
+ //수평배열: x값이 큰 값부터 정렬한다.
+ if (isVertical) {
+ sortable.sort(function (a, b) {
+ return b[1] - a[1]
+ })
+ }
+ $.each(sortable, function (index, sort) {
+ baseLanes.push(sort[0]);
+ })
+ return baseLanes;
+};
+
+/**
+ * Lane 의 최상위 Lane 을 반환한다.
+ *
+ * @param {Element|String} Element Element 또는 ID
+ * @return {Element} Lane Element
+ */
+OG.renderer.RaphaelRenderer.prototype.getRootLane = function (element) {
+ var me = this;
+ var rootLane;
+ var parent;
+ var rElement = me._getREleById(OG.Util.isElement(element) ? element.id : element);
+ if (!rElement) {
+ return null;
+ }
+ element = rElement.node;
+
+ if (!me.isLane(element)) {
+ return null;
+ }
+
+ while (!rootLane) {
+ if (!parent) {
+ parent = element;
+ }
+ if (me.getRootGroup().id === parent.id) {
+ rootLane = null;
+ break;
+ }
+ if (me.isTopGroup(parent)) {
+ rootLane = parent;
+ } else {
+ parent = me.getParent(parent);
+ }
+ }
+ return rootLane;
+};
+
+/**
+ * Lane 의 BaseLane 으로부터 자신의 순서를 구한다.
+ *
+ * @param {Element|String} Element Element 또는 ID
+ * @return {Number} index
+ */
+OG.renderer.RaphaelRenderer.prototype.getIndexOfLane = function (element) {
+ var me = this;
+ var rElement = me._getREleById(OG.Util.isElement(element) ? element.id : element);
+ if (!rElement) {
+ return null;
+ }
+ element = rElement.node;
+
+ if (!me.isLane(element)) {
+ return null;
+ }
+
+ var index = -1;
+ var baseLanes = me.getBaseLanes(element);
+ $.each(baseLanes, function (idx, baseLane) {
+ if (element.id === baseLane.id) {
+ index = idx;
+ }
+ })
+ if (index < 0) {
+ throw new Error("Lane Element has no index.");
+ } else {
+ return index;
+ }
+};
+
+/**
+ * Lane 의 최상위 Lane 으로부터 Depth를 구한다.
+ *
+ * @param {Element|String} Element Element 또는 ID
+ * @return {Number} depth
+ */
+OG.renderer.RaphaelRenderer.prototype.getDepthOfLane = function (element) {
+ var me = this;
+ var rElement = me._getREleById(OG.Util.isElement(element) ? element.id : element);
+ if (!rElement) {
+ return null;
+ }
+ if (!me.isLane(element)) {
+ return null;
+ }
+
+ var lengthToRoot = 0;
+
+ function cacualateDepth(lane) {
+ if (me.isLane(lane) && !me.isTopGroup(lane)) {
+ lengthToRoot++;
+ cacualateDepth(me.getParent(lane));
+ }
+ }
+
+ cacualateDepth(element);
+
+ return lengthToRoot;
+};
+
+/**
+ * Lane 의 BaseLane 영역을 기준으로 전체 Lane 의 구조를 재정립한다.
+ *
+ * @param {Element|String} Element Element 또는 ID
+ */
+OG.renderer.RaphaelRenderer.prototype.reEstablishLane = function (element) {
+ var me = this;
+ var rElement = me._getREleById(OG.Util.isElement(element) ? element.id : element);
+ if (!rElement) {
+ return null;
+ }
+ element = rElement.node;
+
+ if (!me.isLane(element)) {
+ return null;
+ }
+ var baseLanes = me.getBaseLanes(element);
+
+ //베이스라인으로만 이루어진 부모 Lane 집합을 구한다.
+ var estableishTargets = [];
+ var parents = {};
+ $.each(baseLanes, function (index, baseLane) {
+ //key, value 집합으로 추린다.
+ var parent = me.getParent(baseLane);
+ if (parent && parent.id) {
+ parents[parent.id] = parent;
+ }
+ });
+ for (var key in parents) {
+ var hasNoChild = true;
+ var childLanes = me.getChildLane(parents[key]);
+ $.each(childLanes, function (index, childLane) {
+ var childOfchildLanes = me.getChildLane(childLane);
+ if (childOfchildLanes && childOfchildLanes.length) {
+ hasNoChild = false;
+ }
+ });
+ if (hasNoChild) {
+ estableishTargets.push(parents[key]);
+ }
+ }
+
+ function establishLanes(lanes) {
+ //재정립할 대상이 더이상 없을 경우 루프를 종료한다.
+ if (!lanes.length) {
+ return;
+ }
+ //가상의 Lane 트리 구조를 생각했을 때 depth 가 높은 순으로 정렬
+ var sortable = [];
+ $.each(lanes, function (index, lane) {
+ var depth = me.getDepthOfLane(lane);
+ sortable.push([lane, depth]);
+ })
+ sortable.sort(function (a, b) {
+ return b[1] - a[1];
+ })
+
+ //정렬된 재정립 대상마다 childLane의 영역을 기준으로 자신의 영역을 수정한다.
+ $.each(sortable, function (index, sorted) {
+ var lane = sorted[0];
+ var envelope = me.getBoundaryOfElements(me.getChildLane(lane));
+ var style = lane.shape.geom.style.map;
+ var titleSize = style['title-size'] ? style['title-size'] : 20;
+ var left, right, upper, lower;
+ upper = envelope.getUpperLeft().y;
+ lower = envelope.getLowerRight().y;
+ left = envelope.getUpperLeft().x;
+ right = envelope.getLowerRight().x;
+ if (me.isHorizontalLane(lane)) {
+ left = left - titleSize;
+ }
+ if (me.isVerticalLane(lane)) {
+ upper = upper - titleSize;
+ }
+
+ var boundary = lane.shape.geom.getBoundary();
+
+ upper = boundary.getUpperCenter().y - upper;
+ lower = lower - boundary.getLowerCenter().y;
+ left = boundary.getLeftCenter().x - left;
+ right = right - boundary.getRightCenter().x;
+
+ me.resize(lane, [upper, lower, left, right]);
+ })
+
+ //대상마다 부모를 선정하여 다음 루프를 위한 estableishTargets를 다시 수집 한다.
+ var newxtEstableishTargets = [];
+ var nextParents = {};
+ $.each(lanes, function (index, lane) {
+ var depth = me.getDepthOfLane(lane);
+ //depth 가 0은 루트이므로 다음수집대상에 포함되지 않는다.
+ if (depth > 0) {
+ var parent = me.getParent(lane);
+ if (parent && parent.id) {
+ nextParents[parent.id] = parent;
+ }
+ }
+ })
+ for (var key in nextParents) {
+ newxtEstableishTargets.push(nextParents[key]);
+ }
+ establishLanes(newxtEstableishTargets);
+ }
+
+ establishLanes(estableishTargets);
+};
+
+/**
+ * 주어진 Shape 들의 바운더리 영역을 반환한다.
+ *
+ * @param {Element[]} elements
+ * @return {OG.geometry.Envelope} Envelope 영역
+ * @override
+ */
+OG.renderer.RaphaelRenderer.prototype.getBoundaryOfElements = function (elements) {
+ var geometryArray = [], geometryCollection, envelope, i;
+
+ if (elements && elements.length > 0) {
+ for (var i = 0, leni = elements.length; i < leni; i++) {
+
+ geometryArray.push(elements[i].shape.geom);
+ }
+ geometryCollection = new OG.GeometryCollection(geometryArray);
+ envelope = geometryCollection.getBoundary();
+ }
+
+ return envelope;
+};
+
+/**
+ * Lane 의 baseLane 들 중
+ * Lane의 주어진 direction 과 BaseLane 의 주어진 direction 이 가장 가까운 BaseLane 의 인덱스를 반환한다.
+ *
+ * @param {Element|String} Element Element 또는 ID
+ * @param {String} direction
+ * @return {Number} index
+ */
+OG.renderer.RaphaelRenderer.prototype.getNearestBaseLaneIndexAsDirection = function (element, direction) {
+ var me = this;
+ var rElement = me._getREleById(OG.Util.isElement(element) ? element.id : element);
+ if (!rElement || !direction) {
+ return null;
+ }
+ if (!me.isLane(element)) {
+ return null;
+ }
+
+ var orgValue;
+ var index;
+ var nearestValue;
+ var baseLanes = me.getBaseLanes(element);
+ var orgEnvelope = me.getBoundary(element);
+ var orgLeft, orgRight, orgUpper, orgLow;
+ orgUpper = orgEnvelope.getUpperLeft().y;
+ orgLow = orgEnvelope.getLowerRight().y;
+ orgLeft = orgEnvelope.getUpperLeft().x;
+ orgRight = orgEnvelope.getLowerRight().x;
+
+ $.each(baseLanes, function (idx, baseLane) {
+ var envelope = me.getBoundary(baseLane);
+ var left, right, upper, low, compareValue;
+ upper = envelope.getUpperLeft().y;
+ low = envelope.getLowerRight().y;
+ left = envelope.getUpperLeft().x;
+ right = envelope.getLowerRight().x;
+ if (direction === 'upper') {
+ compareValue = upper;
+ orgValue = orgUpper;
+ }
+ if (direction === 'low') {
+ compareValue = low;
+ orgValue = orgLow;
+ }
+ if (direction === 'left') {
+ compareValue = left;
+ orgValue = orgLeft;
+ }
+ if (direction === 'right') {
+ compareValue = right;
+ orgValue = orgRight;
+ }
+ if (!nearestValue && nearestValue !== 0) {
+ nearestValue = Math.abs(compareValue - orgValue);
+ index = idx;
+ }
+ if (nearestValue > Math.abs(compareValue - orgValue)) {
+ nearestValue = Math.abs(compareValue - orgValue);
+ index = idx;
+ }
+ });
+
+ return index;
+};
+
+/**
+ * Group 의 내부 도형들의 Boundary를 반환한다.
+ * Lane 이면 최상위 Lane의 내부 도형들의 Boundary를 반환한다.
+ *
+ * @param {Element|String} Element Element 또는 ID
+ * @return {OG.geometry.Envelope} Envelope 영역
+ */
+OG.renderer.RaphaelRenderer.prototype.getBoundaryOfInnerShapesGroup = function (element) {
+ var me = this;
+ var innerShapes = [];
+ var rElement = me._getREleById(OG.Util.isElement(element) ? element.id : element);
+ if (!rElement) {
+ return null;
+ }
+ element = rElement.node;
+
+ var group;
+ var childsShapes = [];
+ if (me.isLane(element)) {
+ childsShapes = me.getInnerShapesOfLane(element);
+ } else {
+ childsShapes = me.getChilds(element);
+ }
+
+ $.each(childsShapes, function (idx, childsShape) {
+ if (!me.isLane(childsShape)) {
+ innerShapes.push(childsShape);
+ }
+ });
+ if (!innerShapes.length) {
+ return null;
+ }
+ return me.getBoundaryOfElements(innerShapes)
+};
+
+
+/**
+ * Lane 의 BaseLane 중 길이가 가장 작은 Lane 을 반환한다.
+ *
+ * @param {Element|String} Element Element 또는 ID
+ * @param {Element} baseLane
+ */
+OG.renderer.RaphaelRenderer.prototype.getSmallestBaseLane = function (element) {
+ var me = this;
+ var rElement = me._getREleById(OG.Util.isElement(element) ? element.id : element);
+
+ if (!rElement) {
+ return null;
+ }
+
+ element = rElement.node;
+
+ if (!me.isLane(element)) {
+ return null;
+ }
+
+ var baseLanes = me.getBaseLanes(element);
+ var smallestValue;
+ var smallestLane;
+ $.each(baseLanes, function (index, baseLane) {
+ var compareValue;
+ var boundary = me.getBoundary(baseLane);
+ if (me.isHorizontalLane(baseLane)) {
+ compareValue = boundary.getWidth();
+ }
+ if (me.isVerticalLane(baseLane)) {
+ compareValue = boundary.getHeight();
+ }
+ if (compareValue) {
+ if (!smallestLane) {
+ smallestLane = baseLane;
+ smallestValue = compareValue;
+ }
+ if (compareValue < smallestValue) {
+ smallestLane = baseLane;
+ smallestValue = compareValue;
+ }
+ }
+ });
+
+ return smallestLane;
+};
+/**
+ * Lane 을 리사이즈한다.
+ *
+ * @param {Element|String} Element Element 또는 ID
+ * @param {Number[]} offset [상, 하, 좌, 우] 각 방향으로 + 값
+ */
+OG.renderer.RaphaelRenderer.prototype.resizeLane = function (element, offset) {
+ var me = this;
+ var rElement = me._getREleById(OG.Util.isElement(element) ? element.id : element);
+
+ if (!rElement) {
+ return;
+ }
+ element = rElement.node;
+
+ if (!me.isLane(element)) {
+ return;
+ }
+
+ var resizeChildLane = function (parentLane, resizeOffset) {
+ me.resize(parentLane, resizeOffset);
+
+ var childLanes = me.getChildLane(parentLane);
+ $.each(childLanes, function (index, childLane) {
+ resizeChildLane(childLane, resizeOffset);
+ })
+ }
+
+ var du = offset[0], dlw = offset[1], dl = offset[2], dr = offset[3];
+ var rootLane = me.getRootLane(element);
+ var baseLanes = me.getBaseLanes(element);
+
+ //1.baseLane 이 리사이즈 된 경우 -> 주변 baseLane 리사이즈
+ //2.baseLane 이 아닌 것이 리사이즈 된 경우
+ // -> 리사이즈 증분값으로부터 디렉션을 추적
+ // -> 실질적으로 리사이징 된 베이스 라인을 선정
+ // -> 주변 baseLane 리사이즈
+ var myIndex;
+ if (me.getChildLane(element).length) {
+ if (me.isHorizontalLane(element)) {
+ if (du) {
+ myIndex = me.getNearestBaseLaneIndexAsDirection(element, 'upper');
+ }
+ if (dlw) {
+ myIndex = me.getNearestBaseLaneIndexAsDirection(element, 'low');
+ }
+ }
+ if (me.isVerticalLane(element)) {
+ if (dl) {
+ myIndex = me.getNearestBaseLaneIndexAsDirection(element, 'left');
+ }
+ if (dr) {
+ myIndex = me.getNearestBaseLaneIndexAsDirection(element, 'right');
+ }
+ }
+ } else {
+ myIndex = me.getIndexOfLane(element);
+ }
+
+ if (me.isHorizontalLane(element)) {
+
+ //HorizontalLane 인 경우는 dl, dr 값이 변경되었을 경우 모든 Lane 에 적용.
+ resizeChildLane(rootLane, [0, 0, dl, dr]);
+
+ //HorizontalLane 인 경우는 du,dlw 값이 변경되었을 경우만 주변 Lane 정리와 자신을 변경.
+ if (du || dlw) {
+ me.resize(baseLanes[myIndex], [du, dlw, 0, 0]);
+ if (myIndex > 0) {
+ me.resize(baseLanes[myIndex - 1], [0, (du * -1), 0, 0]);
+ }
+ if (myIndex < baseLanes.length - 1) {
+ me.resize(baseLanes[myIndex + 1], [(dlw * -1), 0, 0, 0]);
+ }
+ }
+ }
+ if (me.isVerticalLane(element)) {
+
+ //VerticalLane 인 경우는 du, dlw 값이 변경되었을 경우 모든 Lane 에 적용.
+ resizeChildLane(rootLane, [du, dlw, 0, 0]);
+
+ //VerticalLane 인 경우는 dl, dr 값이 변경되었을 경우만 주변 Lane 정리와 자신을 변경.
+ if (dl || dr) {
+ me.resize(baseLanes[myIndex], [0, 0, dl, dr]);
+ if (myIndex > 0) {
+ me.resize(baseLanes[myIndex - 1], [0, 0, (dr * -1), 0]);
+ }
+ if (myIndex < baseLanes.length - 1) {
+ me.resize(baseLanes[myIndex + 1], [0, 0, 0, (dl * -1)]);
+ }
+ }
+ }
+
+ //최종적으로 변경된 baseLane들을 기준으로 전체Lane을 재정립한다.
+ me.reEstablishLane(element);
+};
+
+/**
+ * Lane 을 삭제한다.
+ *
+ * @param {Element|String} Element Element 또는 ID
+ */
+OG.renderer.RaphaelRenderer.prototype.removeLaneShape = function (element) {
+ var me = this;
+ var rElement = me._getREleById(OG.Util.isElement(element) ? element.id : element);
+
+ if (!rElement) {
+ return;
+ }
+ element = rElement.node;
+
+ if (!me.isLane(element)) {
+ me.removeShape(element);
+ }
+
+ if (me.isTopGroup(element)) {
+ me.removeShape(element);
+ return;
+ }
+
+ var baseLanes = me.getBaseLanes(element);
+ var rootLane = me.getRootLane(element);
+ var parentLane = me.getParent(element);
+ var sameSectorLanes = me.getChildLane(parentLane);
+
+ //부모Lane 의 하나있는 childLane 을 삭제할 경우
+ if (sameSectorLanes.length === 1) {
+ me.removeShape(element);
+ me.reEstablishLane(rootLane);
+ return;
+ }
+
+ //부모Lane 에 childLane 이 다수일 경우
+ if (me.isHorizontalLane(element)) {
+ var neighborTopBase;
+ var neighborLowBase;
+ var topBaseIndex = me.getNearestBaseLaneIndexAsDirection(element, 'upper');
+ var lowBaseIndex = me.getNearestBaseLaneIndexAsDirection(element, 'low');
+ var lostSize = me.getBoundary(element).getHeight();
+ if (topBaseIndex > 0) {
+ neighborTopBase = baseLanes[topBaseIndex - 1];
+ }
+ if (lowBaseIndex < baseLanes.length - 1) {
+ neighborLowBase = baseLanes[lowBaseIndex + 1];
+ }
+ if (me.getParent(element))
+ if (neighborTopBase && neighborLowBase) {
+ me.resize(neighborTopBase, [0, lostSize / 2, 0, 0]);
+ me.resize(neighborLowBase, [lostSize / 2, 0, 0, 0]);
+ }
+ if (neighborTopBase && !neighborLowBase) {
+ me.resize(neighborTopBase, [0, lostSize, 0, 0]);
+ }
+ if (!neighborTopBase && neighborLowBase) {
+ me.resize(neighborLowBase, [lostSize, 0, 0, 0]);
+ }
+ me.removeShape(element);
+ me.reEstablishLane(rootLane);
+ }
+
+ if (me.isVerticalLane(element)) {
+ var neighborRightBase;
+ var neighborLeftBase;
+ var rightBaseIndex = me.getNearestBaseLaneIndexAsDirection(element, 'right');
+ var leftBaseIndex = me.getNearestBaseLaneIndexAsDirection(element, 'left');
+ var lostSize = me.getBoundary(element).getWidth();
+ if (rightBaseIndex > 0) {
+ neighborRightBase = baseLanes[rightBaseIndex - 1];
+ }
+ if (leftBaseIndex < baseLanes.length - 1) {
+ neighborLeftBase = baseLanes[leftBaseIndex + 1];
+ }
+ if (me.getParent(element))
+ if (neighborRightBase && neighborLeftBase) {
+ me.resize(neighborRightBase, [0, 0, lostSize / 2, 0]);
+ me.resize(neighborLeftBase, [0, 0, 0, lostSize / 2]);
+ }
+ if (neighborRightBase && !neighborLeftBase) {
+ me.resize(neighborRightBase, [0, 0, lostSize, 0]);
+ }
+ if (!neighborRightBase && neighborLeftBase) {
+ me.resize(neighborLeftBase, [0, 0, 0, lostSize]);
+ }
+ me.removeShape(element);
+ me.reEstablishLane(rootLane);
+ }
+};
+
+/**
+ * Lane 내부 도형들을 구한다.
+ *
+ * @param {Element|String} Element Element 또는 ID
+ */
+OG.renderer.RaphaelRenderer.prototype.getInnerShapesOfLane = function (element) {
+ var me = this;
+ var innerShapes = [];
+ var rElement = me._getREleById(OG.Util.isElement(element) ? element.id : element);
+ if (!rElement) {
+ return innerShapes;
+ }
+ element = rElement.node;
+
+ function getInnerShapes(lane) {
+ var childsShapes = me.getChilds(lane);
+ $.each(childsShapes, function (idx, childsShape) {
+ if (me.isLane(childsShape)) {
+ getInnerShapes(childsShape);
+ }
+ if (!me.isLane(childsShape)) {
+ innerShapes.push(childsShape);
+ }
+ })
+ }
+
+ var rootLane = me.getRootLane(element);
+ getInnerShapes(rootLane);
+
+ return innerShapes;
+};
+
+/**
+ * Lane 의 내부 도형들을 앞으로 이동시킨다.
+ *
+ * @param {Element|String} Element Element 또는 ID
+ */
+OG.renderer.RaphaelRenderer.prototype.fitLaneOrder = function (element) {
+ var me = this;
+ var rElement = me._getREleById(OG.Util.isElement(element) ? element.id : element);
+
+ if (!rElement) {
+ return;
+ }
+ element = rElement.node;
+
+ if (!me.isLane(element)) {
+ return;
+ }
+
+ var rootLane = me.getRootLane(element);
+ var innerShapesOfLane = me.getInnerShapesOfLane(element);
+
+ $.each(innerShapesOfLane, function (index, innerShape) {
+ rootLane.appendChild(innerShape);
+ });
+};
+
+/**
+ * Shape 가 소속된 의 최상위 그룹 앨리먼트를 반환한다.
+ * 그룹이 소속이 아닌 앨리먼트는 자신을 반환한다.
+ *
+ * @param {Element|String} Element 또는 ID
+ * @return {Element} Element
+ */
+OG.renderer.RaphaelRenderer.prototype.getRootGroupOfShape = function (element) {
+ var me = this;
+ var rootGroup;
+ var parent;
+ var rElement = me._getREleById(OG.Util.isElement(element) ? element.id : element);
+ if (!rElement) {
+ return null;
+ }
+ element = rElement.node;
+
+ if (!me.getParent(element)) {
+ return element;
+ }
+
+ while (!rootGroup) {
+ if (!parent) {
+ parent = element;
+ }
+ if (me.getRootGroup().id === parent.id) {
+ rootGroup = null;
+ break;
+ }
+ if (me.isTopGroup(parent)) {
+ rootGroup = parent;
+ } else {
+ parent = me.getParent(parent);
+ }
+ }
+ return rootGroup;
+};
+/**
+ * Edge 가 Gourp 사이를 넘어가는 경우 스타일에 변화를 준다.
+ *
+ * @param {Element|String} Element 또는 ID
+ */
+OG.renderer.RaphaelRenderer.prototype.checkBridgeEdge = function (element) {
+ if (!this._CONFIG.CHECK_BRIDGE_EDGE) {
+ return;
+ }
+ var me = this, fromStyleChangable, toStyleChangable;
+ var rElement = me._getREleById(OG.Util.isElement(element) ? element.id : element);
+
+ if (!rElement) {
+ return;
+ }
+ element = rElement.node;
+ var isEdge = $(element).attr("_shape") === OG.Constants.SHAPE_TYPE.EDGE;
+ if (!isEdge) {
+ return;
+ }
+
+ var from = $(element).attr("_from");
+ var to = $(element).attr("_to");
+ var fromShape, toShape, fromRoot, toRoot;
+ if (from) {
+ fromShape = this._getShapeFromTerminal(from);
+ fromRoot = me.getRootGroupOfShape(fromShape);
+ fromStyleChangable = me._CANVAS._HANDLER._isConnectStyleChangable(fromShape.shape);
+ }
+ if (to) {
+ toShape = this._getShapeFromTerminal(to);
+ toRoot = me.getRootGroupOfShape(toShape);
+ toStyleChangable = me._CANVAS._HANDLER._isConnectStyleChangable(toShape.shape);
+ }
+
+ if (fromShape && toShape && fromRoot && toRoot) {
+ //양쪽 모두 루트 캔버스 하위의 엘리먼트라면 기본 처리한다.
+ if (fromRoot.id === fromShape.id && toShape.id === toRoot.id) {
+ me.setShapeStyle(element, {
+ 'arrow-start': 'none',
+ "stroke-dasharray": ''
+ });
+ return;
+ }
+ //양쪽 모두 Lane 으로 부터 파생되었으면 기본 처리한다.
+ if (me.isLane(fromRoot) && me.isLane(toRoot)) {
+ me.setShapeStyle(element, {
+ 'arrow-start': 'none',
+ "stroke-dasharray": ''
+ });
+ return;
+ }
+
+ //둘중 한쪽이 스타일 변경 방지 처리가 되어있으면 기본 처리한다.
+ if (!fromStyleChangable || !toStyleChangable) {
+ me.setShapeStyle(element, {
+ 'arrow-start': 'none',
+ "stroke-dasharray": ''
+ });
+ return;
+ }
+
+ //양쪽 루트그룹의 아이디가 틀리면 대쉬어래이 처리
+ if (fromRoot.id !== toRoot.id) {
+ me.setShapeStyle(element, {
+ "arrow-start": "open_oval",
+ "stroke-dasharray": '- '
+ });
+ return;
+ }
+ }
+
+ me.setShapeStyle(element, {
+ 'arrow-start': 'none',
+ "stroke-dasharray": ''
+ });
+};
+/**
+ * 모든 Edge 를 checkBridgeEdge
+ */
+OG.renderer.RaphaelRenderer.prototype.checkAllBridgeEdge = function () {
+ var me = this;
+ var edges = me.getAllEdges();
+ $.each(edges, function (index, edge) {
+ me.checkBridgeEdge(edge);
+ });
+};
+
+/**
+ * Group 내부의 모든 shape 을 리턴한다.
+ *
+ * @param {Element|String} Element Element 또는 ID
+ */
+OG.renderer.RaphaelRenderer.prototype.getInnerShapesOfGroup = function (element) {
+ var me = this;
+ var innerShapes = [];
+ var rElement = me._getREleById(OG.Util.isElement(element) ? element.id : element);
+ if (!rElement) {
+ return innerShapes;
+ }
+ element = rElement.node;
+
+ var elements = [];
+ $(element).find("[_type=" + OG.Constants.NODE_TYPE.SHAPE + "]").each(function (index, element) {
+ elements.push(element);
+ });
+ return elements;
+};
+
+/**
+ * 주어진 좌표를 포함하는 Elemnt 중 가장 Front 에 위치한 Element 를 반환한다.
+ *
+ * @param {Number[]} point 좌표값
+ * @return {Element} Element
+ */
+OG.renderer.RaphaelRenderer.prototype.getFrontForCoordinate = function (point) {
+ var me = this, envelope, mostFrontElement;
+
+ function getFront(group) {
+ var frontElement;
+ var childs = me.getChilds(group);
+ if (!childs.length) {
+ return;
+ }
+ $.each(childs, function (index, child) {
+ if (me.isEdge(child)) {
+ return;
+ }
+ envelope = me.getBoundary(child);
+ if (envelope.isContains(point)) {
+ frontElement = child;
+ }
+ })
+ if (frontElement) {
+ mostFrontElement = frontElement;
+ getFront(frontElement);
+ }
+ }
+
+ getFront(me.getRootGroup());
+ return mostFrontElement;
+};
+
+/**
+ * Boundary 를 포함하는 가장 Front 에 위치한 Group Element 를 반환한다.
+ *
+ * @param {OG.geometry.Envelope} boundary 영역
+ * @return {Element} Element
+ */
+OG.renderer.RaphaelRenderer.prototype.getFrontForBoundary = function (boundary) {
+ var me = this, envelope, mostFrontElement = null;
+ if (!boundary) {
+ return mostFrontElement;
+ }
+
+ function getFront(group) {
+ var frontElement;
+ var childs = me.getChilds(group);
+ if (!childs.length) {
+ return;
+ }
+ $.each(childs, function (index, child) {
+
+ if (me.isEdge(child)) {
+ return;
+ }
+ if (!me.isGroup(child)) {
+ return;
+ }
+
+ if (child.shape instanceof OG.shape.bpmn.A_Task) {
+ return;
+ }
+
+ //바운더리가 일치하는 경우 반응하지 않는다.
+ envelope = me.getBoundary(child);
+ if (boundary.getUpperLeft().x === envelope.getUpperLeft().x &&
+ boundary.getUpperLeft().y === envelope.getUpperLeft().y &&
+ boundary.getWidth() === envelope.getWidth() &&
+ boundary.getHeight() === envelope.getHeight()) {
+ return;
+ }
+
+ if (envelope.isContainsAll(boundary.getVertices())) {
+ frontElement = child;
+ }
+ });
+ if (frontElement) {
+ mostFrontElement = frontElement;
+ getFront(frontElement);
+ }
+ }
+
+ getFront(me.getRootGroup());
+ return mostFrontElement;
+};
+
+
+/**
+ * 신규 Edge 의 vertices 를 연결대상 도형에 따라 설정한다
+ *
+ * @param {Element|String} Edge Element 또는 ID
+ * @param {Element|String} FromShape Element 또는 ID
+ * @param {Element|String} ToShape Element 또는 ID
+ * @return {Element} Edge Element
+ */
+OG.renderer.RaphaelRenderer.prototype.trimEdgeDirection = function (edge, fromShape, toShape) {
+
+ //세 도형 중 하나라도 누락되면 수행하지 않는다.
+ if (!edge || !fromShape || !toShape) {
+ return edge;
+ }
+ var rEdge = this._getREleById(OG.Util.isElement(edge) ? edge.id : edge);
+ var rFromShape = this._getREleById(OG.Util.isElement(fromShape) ? fromShape.id : fromShape);
+ var rToShape = this._getREleById(OG.Util.isElement(toShape) ? toShape.id : toShape);
+ if (!rEdge || !rFromShape || !rToShape) {
+ return edge;
+ }
+ edge = rEdge.node;
+ fromShape = rFromShape.node;
+ toShape = rToShape.node;
+
+
+ //plain 선형이 아닌경우 수행하지 않는다.
+ var edgeType = edge.shape.geom.style.map['edge-type'];
+ if (edgeType && edgeType != 'plain') {
+ return edge;
+ }
+
+ var me = this;
+ var points = [];
+ var vertices = edge.shape.geom.getVertices();
+ var fromP = vertices[0];
+ var toP = vertices[vertices.length - 1];
+
+ var fromBoundary = me.getBoundary(fromShape);
+ var toBoundary = me.getBoundary(toShape);
+ var fLeft = fromBoundary.getLeftCenter().x;
+ var fRight = fromBoundary.getRightCenter().x;
+
+ var tLeft = toBoundary.getLeftCenter().x;
+ var tRight = toBoundary.getRightCenter().x;
+
+ if (tLeft > fRight) {
+ points.push([fromP.x, fromP.y]);
+ points.push([fRight + ((tLeft - fRight) / 2), fromP.y]);
+ points.push([fRight + ((tLeft - fRight) / 2), toP.y]);
+ points.push([toP.x, toP.y]);
+
+ } else if (fLeft > tRight) {
+ points.push([fromP.x, fromP.y]);
+ points.push([fLeft + ((tRight - fLeft) / 2), fromP.y]);
+ points.push([fLeft + ((tRight - fLeft) / 2), toP.y]);
+ points.push([toP.x, toP.y]);
+
+ } else {
+ points.push([fromP.x, fromP.y]);
+ points.push([fromP.x, fromP.y + ((toP.y - fromP.y) / 2)]);
+ points.push([toP.x, fromP.y + ((toP.y - fromP.y) / 2)]);
+ points.push([toP.x, toP.y]);
+ }
+
+ //등분된 선분에 그리드 시스템 적용
+ if (me._CONFIG.DRAG_GRIDABLE) {
+ $.each(points, function (index, point) {
+ point[0] = OG.Util.roundGrid(point[0], me._CONFIG.MOVE_SNAP_SIZE / 2);
+ point[1] = OG.Util.roundGrid(point[1], me._CONFIG.MOVE_SNAP_SIZE / 2);
+ points[index] = point;
+ })
+ }
+
+ return me.drawEdge(new OG.PolyLine(points), edge.shape.geom.style, edge.id);
+};
+
+/**
+ * Lane 또는 Pool 내부 도형들을 그룹에 포함시킨다.
+ *
+ * @param {Element|String} Element
+
+ * @return {Element} Element
+ */
+OG.renderer.RaphaelRenderer.prototype.putInnerShapeToPool = function (element) {
+ var me = this;
+ var root = me.getRootGroup();
+ var rootLane;
+
+ if (!me.isLane(element) && !me.isPool(element)) {
+ return element;
+ }
+ if (me.isLane(element)) {
+ rootLane = me.getRootLane(element);
+ } else {
+ rootLane = element;
+ }
+
+ var geometry = rootLane.shape.geom;
+ var envelope = geometry.getBoundary();
+
+ //캔버스 하위 shape 중 그룹 가능한 것의 집합.
+ var rootInnderShape = [];
+ var childs = me.getChilds(root);
+ $.each(childs, function (index, child) {
+ if (!me.isEdge(child) && child.id != element.id) {
+ rootInnderShape.push(child);
+ }
+ });
+ $.each(rootInnderShape, function (index, innderShape) {
+ var boundary = innderShape.shape.geom.getBoundary();
+ if (envelope.isContainsAll(boundary.getVertices())) {
+ rootLane.appendChild(innderShape);
+ }
+ });
+}
+
+
+/**
+ * 신규 Lane 또는 Pool 이 캔버스상에서 드래그하여 그려지도록 사전작업을 수행한다.
+ *
+ * @param {Element|String} Element
+
+ * @return {Element} Element
+ */
+OG.renderer.RaphaelRenderer.prototype.setDropablePool = function (element) {
+ var me = this;
+ var root = me.getRootGroup();
+
+ if (!me.isLane(element) && !me.isPool(element)) {
+ return element;
+ }
+
+ var geometry = element.shape.geom;
+
+ //캔버스 하위 shape 중 그룹 가능한 것의 집합.
+ var rootInnderShape = [];
+ var childs = me.getChilds(root);
+ $.each(childs, function (index, child) {
+ if (!me.isLane(child) && !me.isPool(child) && !me.isEdge(child)) {
+ rootInnderShape.push(child);
+ }
+ });
+
+ var poolDefaultSize = me._CONFIG.POOL_DEFAULT_SIZE;
+ var space = 30;
+ var boundary = geometry.getBoundary();
+ var centroid = boundary.getCentroid();
+ var poolSize = {};
+
+ var calculateDropCorrectionConditions = function () {
+
+ var correctionConditions = [];
+ if (!rootInnderShape.length) {
+ return correctionConditions;
+ }
+
+ var childArea = me.getBoundaryOfElements(rootInnderShape);
+ var elementArea = me.getBoundary(element);
+
+ var center = childArea.getCentroid();
+ var widthEnable = (elementArea.getWidth() / 2) - ((childArea.getWidth() / 2) + space);
+ var heightEnable = (elementArea.getHeight() / 2) - ((childArea.getHeight() / 2) + space);
+ if (widthEnable < 0) {
+ widthEnable = 0;
+ }
+ if (heightEnable < 0) {
+ heightEnable = 0;
+ }
+
+ correctionConditions.push({
+ condition: {
+ maxX: center.x + widthEnable
+ },
+ fixedPosition: {
+ x: center.x + widthEnable
+ }
+ });
+ correctionConditions.push({
+ condition: {
+ minX: center.x - widthEnable
+ },
+ fixedPosition: {
+ x: center.x - widthEnable
+ }
+ });
+ correctionConditions.push({
+ condition: {
+ maxY: center.y + heightEnable
+ },
+ fixedPosition: {
+ y: center.y + heightEnable
+ }
+ });
+ correctionConditions.push({
+ condition: {
+ minY: center.y - heightEnable
+ },
+ fixedPosition: {
+ y: center.y - heightEnable
+ }
+ });
+ return correctionConditions;
+ };
+
+
+ if (me.isVerticalLane(element) || me.isVerticalPool(element)) {
+ poolSize.width = poolDefaultSize[1];
+ poolSize.height = poolDefaultSize[0];
+ } else {
+ poolSize.width = poolDefaultSize[0];
+ poolSize.height = poolDefaultSize[1];
+ }
+
+ if (rootInnderShape.length) {
+ var childArea = me.getBoundaryOfElements(rootInnderShape);
+ centroid = childArea.getCentroid();
+
+ if (poolSize.width < childArea.getWidth() + (space * 2)) {
+ poolSize.width = childArea.getWidth() + (space * 2);
+ }
+ if (poolSize.height < childArea.getHeight() + (space * 2)) {
+ poolSize.height = childArea.getHeight() + (space * 2);
+ }
+ }
+
+ var originalStyle = JSON.parse(JSON.stringify(geometry.style.map));
+ geometry.moveCentroid([centroid.x, centroid.y]);
+ geometry.resizeBox(poolSize.width, poolSize.height);
+ geometry.style.map['stroke-width'] = 2;
+ geometry.style.map['stroke'] = '#FFCC50';
+
+ element = this.redrawShape(element);
+
+ $(element).data('originalStyle', originalStyle);
+ $(root).data('newPool', element);
+ $(root).data('poolInnderShape', rootInnderShape);
+ $(root).data('correctionConditions', calculateDropCorrectionConditions());
+
+ return element;
+};
+
+/**
+ * 신규 Lane 또는 Pool 드랍모드를 해제한다.
+ *
+ */
+OG.renderer.RaphaelRenderer.prototype.offDropablePool = function () {
+ var me = this;
+ var root = me.getRootGroup();
+ $(root).data('newPool', false);
+ $(root).data('poolInnderShape', []);
+};
+
+/**
+ * 주어진 element 를 가로,세로 만큼 이동하여 복사한다.
+ * @param {Element|String} element Element 또는 ID
+ * @param {Number[]} offset [가로, 세로]
+ */
+OG.renderer.RaphaelRenderer.prototype.copyShape = function (element, offset) {
+ var me = this;
+ var handler = this._CANVAS._HANDLER;
+ // copy
+ var boundary = element.shape.geom.getBoundary(), newShape, newElement, newGuide;
+ newShape = element.shape.clone();
+ if (!offset) {
+ offset = [handler._CONFIG.COPY_PASTE_PADDING, handler._CONFIG.COPY_PASTE_PADDING];
+ }
+
+ if ($(element).attr("_shape") === OG.Constants.SHAPE_TYPE.EDGE) {
+ if (element.shape.geom instanceof OG.geometry.BezierCurve) {
+ newShape.geom = new OG.BezierCurve(element.shape.geom.getControlPoints());
+ } else {
+ newShape.geom = new OG.PolyLine(element.shape.geom.getVertices());
+ }
+ newShape.geom.style = element.shape.geom.style;
+ newShape.geom.move(offset[0], offset[1]);
+ newElement = me.drawShape(
+ null, newShape,
+ null, element.shapeStyle
+ );
+
+ } else {
+ newElement = me.drawShape(
+ [boundary.getCentroid().x + offset[0], boundary.getCentroid().y + offset[1]],
+ newShape, [boundary.getWidth(), boundary.getHeight()], element.shapeStyle
+ );
+ }
+
+ // enable event
+ //newGuide = me.drawGuide(newElement);
+ handler.setClickSelectable(newElement, handler._isSelectable(newElement.shape));
+ handler.setMovable(newElement, handler._isMovable(newElement.shape));
+ handler.setConnectGuide(newElement, handler._isConnectable(newElement.shape));
+ handler.setResizable(newElement, newGuide, handler._isResizable(newElement.shape));
+ handler.setConnectable(newElement, newGuide, handler._isConnectable(newElement.shape));
+
+ if (handler._isLabelEditable(newElement.shape)) {
+ handler.enableEditLabel(newElement);
+ }
+
+ // copy children
+ handler._copyChildren(element, newElement);
+ return newElement;
+};
+/**
+ * Event Handler
+ *
+ * @class
+ * @requires OG.renderer.*
+ *
+ * @param {OG.renderer.IRenderer} renderer 렌더러
+ * @param {Object} config Configuration
+ * @author Seungpil Park
+ */
+OG.handler.EventHandler = function (renderer, config) {
+ this._RENDERER = renderer;
+ this._CONFIG = config;
+};
+
+OG.handler.EventHandler.prototype = {
+ /**
+ * 주어진 Shape Element 의 라벨을 수정 가능하도록 한다.
+ *
+ * @param {Element} element Shape Element
+ */
+ enableEditLabel: function (element) {
+ var me = this;
+ var renderer = me._RENDERER;
+
+ $(element).bind({
+ dblclick: function (event) {
+ var container = renderer.getContainer(),
+ envelope = element.shape.geom.getBoundary(),
+ upperLeft = envelope.getUpperLeft(),
+ bBox,
+ left = (upperLeft.x - 1) * me._CONFIG.SCALE,
+ top = (upperLeft.y - 1) * me._CONFIG.SCALE,
+ width = envelope.getWidth() * me._CONFIG.SCALE,
+ height = envelope.getHeight() * me._CONFIG.SCALE,
+ editorId = element.id + OG.Constants.LABEL_EDITOR_SUFFIX,
+ labelEditor,
+ textAlign = "center",
+ fromLabel,
+ toLabel,
+ beforeLabel,
+ afterLabel,
+ /**
+ * 라인(꺽은선)의 중심위치를 반환한다.
+ *
+ * @param {Element} element Edge 엘리먼트
+ * @return {OG.Coordinate}
+ */
+ getCenterOfEdge = function (element) {
+ var vertices, from, to, lineLength, distance = 0, i, intersectArray;
+
+ if (element.shape.geom.style.get("edge-type") === OG.Constants.EDGE_TYPE.BEZIER) {
+ vertices = element.shape.geom.getControlPoints();
+ from = vertices[0];
+ to = vertices[vertices.length - 1];
+ return new OG.geometry.Coordinate(OG.Util.round((from.x + to.x) / 2), OG.Util.round((from.y + to.y) / 2));
+ } else {
+
+ // Edge Shape 인 경우 라인의 중간 지점 찾기
+ vertices = element.shape.geom.getVertices();
+ lineLength = element.shape.geom.getLength();
+
+ for (var i = 0, leni = vertices.length - 1; i < leni; i++) {
+ distance += vertices[i].distance(vertices[i + 1]);
+ if (distance > lineLength / 2) {
+ intersectArray = element.shape.geom.intersectCircleToLine(
+ vertices[i + 1], distance - lineLength / 2, vertices[i + 1], vertices[i]
+ );
+ break;
+ }
+ }
+
+ return intersectArray[0];
+ }
+ },
+ centerOfEdge;
+
+ //상위 그룹의 라벨수정을 방지하기 위해
+ var eventOffset = me._getOffset(event)
+ var frontElement = renderer.getFrontForCoordinate([eventOffset.x, eventOffset.y]);
+ if (!frontElement) {
+ event.stopImmediatePropagation();
+ return;
+ }
+ if (frontElement.id !== element.id) {
+ event.stopImmediatePropagation();
+ return;
+ }
+
+ if (element.shape.isCollapsed === false) {
+ // textarea
+ $(container).append("");
+ labelEditor = $("#" + editorId);
+
+ // text-align 스타일 적용
+ switch (element.shape.geom.style.get("text-anchor")) {
+ case "start":
+ textAlign = "left";
+ break;
+ case "middle":
+ textAlign = "center";
+ break;
+ case "end":
+ textAlign = "right";
+ break;
+ default:
+ textAlign = "center";
+ break;
+ }
+
+ if ($(element).attr("_shape") === OG.Constants.SHAPE_TYPE.HTML) {
+ // Html Shape
+ $(labelEditor).css(OG.Util.apply(me._CONFIG.DEFAULT_STYLE.LABEL_EDITOR, {
+ left: left,
+ top: top,
+ width: width,
+ height: height,
+ "text-align": 'left',
+ overflow: "hidden",
+ resize: "none"
+ }));
+ $(labelEditor).focus();
+ $(labelEditor).val(element.shape.html);
+ beforeLabel = element.shape.html;
+
+ $(labelEditor).bind({
+ focusout: function () {
+ element.shape.html = this.value;
+ afterLabel = this.value;
+ if (element.shape.html) {
+ renderer.redrawShape(element);
+ this.parentNode.removeChild(this);
+ } else {
+ renderer.removeShape(element);
+ this.parentNode.removeChild(this);
+ }
+ if (beforeLabel !== afterLabel) {
+ renderer.addHistory();
+ }
+ }
+ });
+ } else if ($(element).attr("_shape") === OG.Constants.SHAPE_TYPE.TEXT) {
+ // Text Shape
+ $(labelEditor).css(OG.Util.apply(me._CONFIG.DEFAULT_STYLE.LABEL_EDITOR, {
+ left: left,
+ top: top,
+ width: width,
+ height: height,
+ "text-align": textAlign,
+ overflow: "hidden",
+ resize: "none"
+ }));
+ $(labelEditor).focus();
+ $(labelEditor).val(element.shape.text);
+ beforeLabel = element.shape.text;
+
+ $(labelEditor).bind({
+ focusout: function () {
+ element.shape.text = this.value;
+ afterLabel = this.value;
+ if (element.shape.text) {
+ renderer.redrawShape(element);
+ this.parentNode.removeChild(this);
+ } else {
+ renderer.removeShape(element);
+ this.parentNode.removeChild(this);
+ }
+ if (beforeLabel !== afterLabel) {
+ renderer.addHistory();
+ }
+ }
+ });
+ } else if ($(element).attr("_shape") === OG.Constants.SHAPE_TYPE.EDGE) {
+ // Edge Shape
+ if (element.shape.label && renderer.isSVG()) {
+ $(element).children('[id$=_LABEL]').each(function (idx, item) {
+ $(item).find("text").each(function (idx2, item2) {
+ bBox = renderer.getBBox(item2);
+ left = bBox.x - 10;
+ top = bBox.y;
+ width = bBox.width + 20;
+ height = bBox.height;
+ });
+ });
+ } else {
+ centerOfEdge = getCenterOfEdge(element);
+ left = centerOfEdge.x - me._CONFIG.LABEL_EDITOR_WIDTH / 2;
+ top = centerOfEdge.y - me._CONFIG.LABEL_EDITOR_HEIGHT / 2;
+ width = me._CONFIG.LABEL_EDITOR_WIDTH;
+ height = me._CONFIG.LABEL_EDITOR_HEIGHT;
+ }
+
+ // 시작점 라벨인 경우
+ $(event.srcElement).parents('[id$=_FROMLABEL]').each(function (idx, item) {
+ $(item).find("text").each(function (idx2, item2) {
+ bBox = renderer.getBBox(item2);
+ left = bBox.x - 10;
+ top = bBox.y;
+ width = bBox.width + 20;
+ height = bBox.height;
+ fromLabel = element.shape.fromLabel;
+ });
+ });
+
+ // 끝점 라벨인 경우
+ $(event.srcElement).parents('[id$=_TOLABEL]').each(function (idx, item) {
+ $(item).find("text").each(function (idx2, item2) {
+ bBox = renderer.getBBox(item2);
+ left = bBox.x - 10;
+ top = bBox.y;
+ width = bBox.width + 20;
+ height = bBox.height;
+ toLabel = element.shape.toLabel;
+ });
+ });
+
+ $(labelEditor).css(OG.Util.apply(me._CONFIG.DEFAULT_STYLE.LABEL_EDITOR, {
+ left: left * me._CONFIG.SCALE,
+ top: top * me._CONFIG.SCALE,
+ width: width * me._CONFIG.SCALE,
+ height: height * me._CONFIG.SCALE,
+ overflow: "hidden",
+ resize: "none"
+ }));
+ $(labelEditor).focus();
+
+ if (fromLabel || toLabel) {
+ $(labelEditor).val(fromLabel ? element.shape.fromLabel : element.shape.toLabel);
+ } else {
+ $(labelEditor).val(element.shape.label);
+ beforeLabel = element.shape.label;
+ }
+
+ $(labelEditor).bind({
+ focusout: function () {
+ if (fromLabel) {
+ renderer.drawEdgeLabel(element, this.value, 'FROM');
+ } else if (toLabel) {
+ renderer.drawEdgeLabel(element, this.value, 'TO');
+ } else {
+ renderer.drawLabel(element, this.value);
+ afterLabel = this.value;
+ if (beforeLabel !== afterLabel) {
+ renderer.addHistory();
+ }
+ }
+
+ this.parentNode.removeChild(this);
+ }
+ });
+ } else {
+ $(labelEditor).css(OG.Util.apply(me._CONFIG.DEFAULT_STYLE.LABEL_EDITOR, {
+ left: left,
+ top: top,
+ width: width,
+ height: height,
+ "text-align": textAlign,
+ overflow: "hidden",
+ resize: "none"
+ }));
+ $(labelEditor).focus();
+ $(labelEditor).val(element.shape.label);
+ beforeLabel = element.shape.label;
+
+ $(labelEditor).bind({
+ focusout: function () {
+ renderer.drawLabel(element, this.value);
+ this.parentNode.removeChild(this);
+ afterLabel = this.value;
+ if (beforeLabel !== afterLabel) {
+ renderer.addHistory();
+ }
+ }
+ });
+ }
+ }
+ }
+ });
+ },
+
+ /**
+ * Shape 엘리먼트의 이동 가능여부를 설정한다.
+ *
+ * @param {Element} element Shape 엘리먼트
+ * @param {Boolean} isMovable 가능여부
+ */
+ setMovable: function (element, isMovable) {
+ var me = this, guide;
+ var renderer = me._RENDERER;
+ var root = renderer.getRootGroup();
+ if (!element) {
+ return;
+ }
+
+ var isEdge = $(element).attr("_shape") === OG.Constants.SHAPE_TYPE.EDGE;
+ var isLane = renderer.isLane(element);
+
+ if (isEdge) {
+ return;
+ }
+
+ var calculateMoveCorrectionConditions = function (bBoxArray) {
+ //이동 딜레이
+ var delay = me._CONFIG.EDGE_MOVE_DELAY_SIZE;
+ //조건집합
+ var correctionConditions = [];
+
+ if (!me._CONFIG.AUTOMATIC_GUIDANCE) {
+ return correctionConditions;
+ }
+
+ //이동 타켓이 다수인 경우 해당되지 않는다.
+ if (!bBoxArray) {
+ return correctionConditions;
+ }
+ if (bBoxArray.length !== 1) {
+ return correctionConditions;
+ }
+
+ //모든 Shape 의 중심점,상하좌우 끝을 조건에 포함한다.
+ var moveBoundary = renderer.getBoundary(element);
+ var moveCenter = moveBoundary.getCentroid();
+ var moveHeight = moveBoundary.getHeight();
+ var moveWidth = moveBoundary.getWidth();
+ var allShapes = renderer.getAllShapes();
+ $.each(allShapes, function (idx, shape) {
+ if (renderer.isEdge(shape)) {
+ return;
+ }
+ if (shape.id === element.id) {
+ return;
+ }
+ var boundary = renderer.getBoundary(shape);
+ var center = boundary.getCentroid();
+ var upperLeft = boundary.getUpperLeft();
+ var lowerRight = boundary.getLowerRight();
+
+
+ //top boundary range
+ correctionConditions.push({
+ condition: {
+ minY: (upperLeft.y - moveHeight / 2) - delay,
+ maxY: (upperLeft.y - moveHeight / 2) + delay
+ },
+ fixedPosition: {
+ y: (upperLeft.y - moveHeight / 2)
+ },
+ guidePosition: {
+ y: upperLeft.y
+ },
+ id: idx
+ });
+ correctionConditions.push({
+ condition: {
+ minY: (upperLeft.y + moveHeight / 2) - delay,
+ maxY: (upperLeft.y + moveHeight / 2) + delay
+ },
+ fixedPosition: {
+ y: (upperLeft.y + moveHeight / 2)
+ },
+ guidePosition: {
+ y: upperLeft.y
+ },
+ id: idx
+ });
+
+ //low boundary range
+ correctionConditions.push({
+ condition: {
+ minY: (lowerRight.y + moveHeight / 2) - delay,
+ maxY: (lowerRight.y + moveHeight / 2) + delay
+ },
+ fixedPosition: {
+ y: (lowerRight.y + moveHeight / 2)
+ },
+ guidePosition: {
+ y: lowerRight.y
+ },
+ id: idx
+ });
+ correctionConditions.push({
+ condition: {
+ minY: (lowerRight.y - moveHeight / 2) - delay,
+ maxY: (lowerRight.y - moveHeight / 2) + delay
+ },
+ fixedPosition: {
+ y: (lowerRight.y - moveHeight / 2)
+ },
+ guidePosition: {
+ y: lowerRight.y
+ },
+ id: idx
+ });
+
+ //left boundary range
+ correctionConditions.push({
+ condition: {
+ minX: (upperLeft.x - moveWidth / 2) - delay,
+ maxX: (upperLeft.x - moveWidth / 2) + delay
+ },
+ fixedPosition: {
+ x: (upperLeft.x - moveWidth / 2)
+ },
+ guidePosition: {
+ x: upperLeft.x
+ },
+ id: idx
+ });
+ correctionConditions.push({
+ condition: {
+ minX: (upperLeft.x + moveWidth / 2) - delay,
+ maxX: (upperLeft.x + moveWidth / 2) + delay
+ },
+ fixedPosition: {
+ x: (upperLeft.x + moveWidth / 2)
+ },
+ guidePosition: {
+ x: upperLeft.x
+ },
+ id: idx
+ });
+
+ //right boundary range
+ correctionConditions.push({
+ condition: {
+ minX: (lowerRight.x + moveWidth / 2) - delay,
+ maxX: (lowerRight.x + moveWidth / 2) + delay
+ },
+ fixedPosition: {
+ x: (lowerRight.x + moveWidth / 2)
+ },
+ guidePosition: {
+ x: lowerRight.x
+ },
+ id: idx
+ });
+ correctionConditions.push({
+ condition: {
+ minX: (lowerRight.x - moveWidth / 2) - delay,
+ maxX: (lowerRight.x - moveWidth / 2) + delay
+ },
+ fixedPosition: {
+ x: (lowerRight.x - moveWidth / 2)
+ },
+ guidePosition: {
+ x: lowerRight.x
+ },
+ id: idx
+ });
+
+ //center vertical boundary range
+ correctionConditions.push({
+ condition: {
+ minX: center.x - delay,
+ maxX: center.x + delay
+ },
+ fixedPosition: {
+ x: center.x
+ },
+ guidePosition: {
+ x: center.x
+ },
+ id: idx
+ });
+
+ //center horizontal boundary range
+ correctionConditions.push({
+ condition: {
+ minY: center.y - delay,
+ maxY: center.y + delay,
+ },
+ fixedPosition: {
+ y: center.y
+ },
+ guidePosition: {
+ y: center.y
+ },
+ id: idx
+ });
+ });
+
+ //연결된 Shape 들의 터미널이 수평,수직으로 교차하는 순간을 조건에 포함한다.
+ var edges = [];
+ var prevEdges = renderer.getPrevEdges(element);
+ var nextEdges = renderer.getNextEdges(element);
+ $.each(prevEdges, function (idx, edge) {
+ edges.push({
+ edge: edge,
+ type: 'prev'
+ });
+ });
+ $.each(nextEdges, function (idx, edge) {
+ edges.push({
+ edge: edge,
+ type: 'next'
+ });
+ });
+ $.each(edges, function (idx, edgeObj) {
+ var edge = edgeObj.edge;
+ var type = edgeObj.type;
+ var from = $(edge).attr("_from");
+ var to = $(edge).attr("_to");
+ if (!from || !to) {
+ return;
+ }
+ var moveTerminal;
+ var conditionTermianl;
+ if (type === 'prev') {
+ moveTerminal = to;
+ conditionTermianl = from;
+ } else {
+ moveTerminal = from;
+ conditionTermianl = to;
+ }
+ var movePosition = renderer._getPositionFromTerminal(moveTerminal);
+ var conditionPosition = renderer._getPositionFromTerminal(conditionTermianl);
+ var incrementX = moveCenter.x - movePosition.x;
+ var incrementY = moveCenter.y - movePosition.y;
+
+ //vertical boundary range
+ correctionConditions.push({
+ condition: {
+ minX: conditionPosition.x + incrementX - delay,
+ maxX: conditionPosition.x + incrementX + delay
+ },
+ fixedPosition: {
+ x: conditionPosition.x + incrementX
+ },
+ guidePosition: {
+ x: conditionPosition.x
+ },
+ id: idx
+ });
+
+ //horizontal boundary range
+ correctionConditions.push({
+ condition: {
+ minY: conditionPosition.y + incrementY - delay,
+ maxY: conditionPosition.y + incrementY + delay
+ },
+ fixedPosition: {
+ y: conditionPosition.y + incrementY
+ },
+ guidePosition: {
+ y: conditionPosition.y
+ },
+ id: idx
+ });
+ });
+ return correctionConditions;
+ };
+
+ //엘리먼트 이동시 범위조건에 따라 새로운 포지션을 계산한다.
+ //조건이 일치할 시 스틱가이드를 생선한다.
+ var correctionConditionAnalysis = function (dx, dy) {
+ var fixedPosition = {
+ dx: dx,
+ dy: dy
+ };
+ if (!me._CONFIG.AUTOMATIC_GUIDANCE) {
+ return fixedPosition;
+ }
+ var boundary = renderer.getBoundary(element);
+ var centroid = boundary.getCentroid();
+ var center = {
+ x: centroid.x + dx,
+ y: centroid.y + dy
+ };
+
+ var calculateFixedPosition = function (expectedPosition) {
+ if (!expectedPosition) {
+ return fixedPosition;
+ }
+ if (expectedPosition.x && !expectedPosition.y) {
+ return {
+ dx: expectedPosition.x - centroid.x,
+ dy: fixedPosition.dy
+ }
+ }
+ if (expectedPosition.y && !expectedPosition.x) {
+ return {
+ dx: fixedPosition.dx,
+ dy: expectedPosition.y - centroid.y
+ }
+ }
+ if (expectedPosition.x && expectedPosition.y) {
+ return {
+ dx: expectedPosition.x - centroid.x,
+ dy: expectedPosition.y - centroid.y
+ }
+ }
+ return fixedPosition;
+ };
+ var correctionConditions = $(element).data('correctionConditions');
+ if (!correctionConditions) {
+ return fixedPosition;
+ }
+
+ var conditionsPassCandidates = [];
+ $.each(correctionConditions, function (index, correctionCondition) {
+ var condition = correctionCondition.condition;
+
+ var conditionsPass = true;
+ if (condition.minX) {
+ if (center.x < condition.minX) {
+ conditionsPass = false;
+ }
+ }
+ if (condition.maxX) {
+ if (center.x > condition.maxX) {
+ conditionsPass = false;
+ }
+ }
+ if (condition.minY) {
+ if (center.y < condition.minY) {
+ conditionsPass = false;
+ }
+ }
+ if (condition.maxY) {
+ if (center.y > condition.maxY) {
+ conditionsPass = false;
+ }
+ }
+
+ if (conditionsPass) {
+ conditionsPassCandidates.push(correctionCondition);
+ }
+ });
+ $.each(conditionsPassCandidates, function (index, conditionsPassCandidate) {
+ fixedPosition = calculateFixedPosition(conditionsPassCandidate.fixedPosition);
+ var guidePosition = conditionsPassCandidate.guidePosition;
+ renderer.drawStickGuide(guidePosition);
+ });
+ if (!conditionsPassCandidates.length) {
+ renderer.removeAllStickGuide();
+ }
+
+ return fixedPosition;
+ };
+
+ var removeDropOverBox = function () {
+ var dropOverBoxes = $('[id$=' + OG.Constants.DROP_OVER_BBOX_SUFFIX + ']');
+ $.each(dropOverBoxes, function (index, dropOverBox) {
+ renderer.remove(dropOverBox.id);
+ });
+ $(root).removeData("groupTarget");
+ };
+
+ var setGroupTarget = function () {
+ removeDropOverBox();
+
+ var bBoxArray = $(root).data("bBoxArray");
+ if (!bBoxArray) {
+ return;
+ }
+
+ var moveElements = [];
+ var transform = [];
+ $.each(bBoxArray, function (idx, item) {
+ transform = renderer.getAttr(item.box, 'transform')[0];
+ moveElements.push(renderer.getElementById(item.id));
+ });
+ if (!transform.length) {
+ return;
+ }
+ var moveBoundary = renderer.getBoundaryOfElements(moveElements);
+ var moveTop = moveBoundary.getUpperCenter().y + transform[2];
+ var moveLeft = moveBoundary.getLeftCenter().x + transform[1];
+ var bBoxBoundary = new OG.geometry.Envelope(
+ [moveLeft, moveTop], moveBoundary.getWidth(), moveBoundary.getHeight());
+
+ var frontGroup = renderer.getFrontForBoundary(bBoxBoundary);
+ if (!frontGroup) {
+ return;
+ }
+
+ if (!me._CONFIG.GROUP_DROPABLE || !frontGroup.shape.GROUP_DROPABLE) {
+ return;
+ }
+
+ //Lane 도형에 접근할 경우 최상위 Lane 으로 타겟변경한다.
+ if (renderer.isLane(frontGroup)) {
+ frontGroup = renderer.getRootLane(frontGroup);
+ }
+
+ //A_Task 일 경우 반응하지 않는다.
+ if (frontGroup.shape instanceof OG.shape.bpmn.A_Task) {
+ return;
+ }
+
+ //접혀진 상태면 반응하지 않는다.
+ if (frontGroup.shape.isCollapsed === true) {
+ return;
+ }
+
+ //이동중인 도형들 속에 타겟이 겹친 경우 반응하지 않는다.
+ var isSelf = false;
+ $.each(bBoxArray, function (idx, item) {
+ if (frontGroup.id === item.id) {
+ isSelf = true;
+ }
+ });
+ if (isSelf) {
+ return;
+ }
+
+ //이동중인 도형 중 Lane 이 있다면 반응하지 않는다.
+ var blackList = false;
+ $.each(bBoxArray, function (idx, item) {
+ if (renderer.isLane(item.id)) {
+ blackList = true;
+ }
+ });
+ if (blackList) {
+ return;
+ }
+ $(root).data("groupTarget", frontGroup);
+ renderer.drawDropOverGuide(frontGroup);
+ };
+
+ if (isMovable === true) {
+ $(element).draggable({
+ start: function (event) {
+ var eventOffset = me._getOffset(event), guide;
+
+ // 선택되지 않은 Shape 을 drag 시 다른 모든 Shape 은 deselect 처리
+ if (!me._isSelectedElement(element)) {
+ $.each(me._getSelectedElement(), function (idx, selected) {
+ if (OG.Util.isElement(selected) && selected.id) {
+ renderer.removeGuide(selected);
+ }
+ })
+ }
+
+ //Edge 의 가이드는 모두 제거
+ renderer.removeAllEdgeGuide();
+
+ //가이드 생성
+ renderer.removeGuide(element);
+ guide = renderer.drawGuide(element);
+
+ //드래그 대상이 Lane 일 경우는 RootLane에 드래그를 생성한다.
+ if (isLane) {
+ renderer.drawGuide(renderer.getRootLane(element));
+ }
+
+ //그룹 이동처리 시작
+ var moveTargets = [];
+ $(me._RENDERER.getRootElement()).find("[id$=" + OG.Constants.GUIDE_SUFFIX.BBOX + "]").each(function (index, item) {
+ if (item.id && item.id.indexOf(OG.Constants.CONNECT_GUIDE_SUFFIX.BBOX) == -1) {
+ var elementId = item.id.replace(OG.Constants.GUIDE_SUFFIX.BBOX, "");
+ moveTargets.push({
+ id: elementId
+ });
+ }
+ });
+ var dragTargetGuideLost = false;
+ var newTargetElement;
+
+ var removeGroupInnerGuides = function (group) {
+ if (group.id === element.id) {
+ dragTargetGuideLost = true;
+ }
+ var childs = renderer.getChilds(group);
+ $.each(childs, function (index, child) {
+ renderer.removeGuide(child);
+ if (group.id === element.id) {
+ dragTargetGuideLost = true;
+ }
+ if (renderer.isGroup(child)) {
+ removeGroupInnerGuides(child);
+ }
+ })
+ };
+
+ var findParentGuideTarget = function (target) {
+ //부모가 루트일때는 루프를 중단.
+ if (!target) {
+ return;
+ }
+ $.each(moveTargets, function (index, moveTarget) {
+ if (moveTarget.id === target.id) {
+ newTargetElement = target;
+ }
+ });
+ findParentGuideTarget(renderer.getParent(target));
+ };
+
+ $.each(moveTargets, function (index, moveTarget) {
+ var moveElm = renderer.getElementById(moveTarget.id);
+ if (renderer.isGroup(moveElm)) {
+ removeGroupInnerGuides(moveElm);
+ }
+ });
+
+ if (dragTargetGuideLost) {
+ findParentGuideTarget(element);
+ if (newTargetElement) {
+ renderer.removeGuide(newTargetElement);
+ guide = renderer.drawGuide(newTargetElement);
+ }
+ }
+ //그룹 이동처리 종료.
+
+
+ $(this).data("start", {x: eventOffset.x, y: eventOffset.y});
+ $(this).data("offset", {
+ x: eventOffset.x - me._num(renderer.getAttr(guide.bBox, "x")),
+ y: eventOffset.y - me._num(renderer.getAttr(guide.bBox, "y"))
+ });
+
+ var bBoxArray = me._getMoveTargets();
+ $(root).data("bBoxArray", bBoxArray);
+ $(element).data('correctionConditions', calculateMoveCorrectionConditions(bBoxArray));
+ renderer.removeRubberBand(renderer.getRootElement());
+ },
+ drag: function (event) {
+ var eventOffset = me._getOffset(event),
+ start = $(this).data("start"),
+ bBoxArray = $(root).data("bBoxArray"),
+ dx = eventOffset.x - start.x,
+ dy = eventOffset.y - start.y,
+ offset = $(this).data("offset");
+
+ var conditionAnalysis = correctionConditionAnalysis(dx, dy);
+ if ('Y' == element.shape.AXIS) {
+ conditionAnalysis.dx = 0;
+ } else if ('X' == element.shape.AXIS) {
+ conditionAnalysis.dy = 0;
+ }
+ dx = me._grid(conditionAnalysis.dx, 'move');
+ dy = me._grid(conditionAnalysis.dy, 'move');
+
+ // Canvas 영역을 벗어나서 드래그되는 경우 Canvas 확장
+ me._autoExtend(eventOffset.x, eventOffset.y, element);
+
+ $(this).css({"position": "", "left": "", "top": ""});
+ $.each(bBoxArray, function (k, item) {
+ renderer.setAttr(item.box, {
+ transform: "t" + dx + "," + dy,
+ 'stroke-width': 1
+ });
+ });
+
+ setGroupTarget();
+
+ renderer.removeAllConnectGuide();
+ },
+ stop: function (event) {
+ var eventOffset = me._getOffset(event),
+ start = $(this).data("start"),
+ bBoxArray = $(root).data("bBoxArray"),
+ dx = eventOffset.x - start.x,
+ dy = eventOffset.y - start.y,
+ groupTarget = $(root).data("groupTarget"),
+ offset = $(this).data("offset"),
+ eleArray;
+
+ // 자동 붙기 보정
+ var conditionAnalysis = correctionConditionAnalysis(dx, dy);
+ if ('Y' == element.shape.AXIS) {
+ conditionAnalysis.dx = 0;
+ } else if ('X' == element.shape.AXIS) {
+ conditionAnalysis.dy = 0;
+ }
+ dx = me._grid(conditionAnalysis.dx, 'move');
+ dy = me._grid(conditionAnalysis.dy, 'move');
+
+ // 이동 처리
+ $(this).css({"position": "", "left": "", "top": ""});
+ eleArray = me._moveElements(bBoxArray, dx, dy);
+
+ $(root).removeData("bBoxArray");
+ renderer.removeAllGuide();
+
+ // group target 이 있는 경우 grouping 처리
+ if (groupTarget && OG.Util.isElement(groupTarget)) {
+ // grouping
+ renderer.addToGroup(groupTarget, eleArray, eventOffset);
+ renderer.remove(groupTarget.id + OG.Constants.DROP_OVER_BBOX_SUFFIX);
+ $(root).removeData("groupTarget");
+ } else {
+ // ungrouping
+ var addToGroupArray = [];
+ $.each(eleArray, function (idx, ele) {
+ /**
+ * IE 10,11 use parentNode instead parentElement
+ */
+ var parentNode = ele.parentElement;
+ if (!parentNode) {
+ parentNode = ele.parentNode;
+ }
+ if (parentNode && parentNode.id !== root.id) {
+ addToGroupArray.push(ele);
+ }
+ });
+ renderer.addToGroup(root, addToGroupArray, eventOffset);
+ }
+
+ $.each(me._getSelectedElement(), function (idx, selected) {
+ guide = renderer.drawGuide(selected);
+ if (guide) {
+ me.setResizable(selected, guide, me._isResizable(selected.shape));
+ me.setConnectable(selected, guide, me._isConnectable(selected.shape));
+ renderer.toFront(guide.group);
+ }
+ });
+
+ renderer.removeAllConnectGuide();
+ renderer.toFrontEdges();
+ renderer.checkAllBridgeEdge();
+ renderer.addHistory();
+ }
+ });
+ renderer.setAttr(element, {cursor: 'move'});
+ OG.Util.apply(element.shape.geom.style.map, {cursor: 'move'});
+ } else {
+ renderer.setAttr(element, {cursor: me._isSelectable(element.shape) ? 'pointer' : me._CONFIG.DEFAULT_STYLE.SHAPE.cursor});
+ OG.Util.apply(element.shape.geom.style.map, {cursor: me._isSelectable(element.shape) ? 'pointer' : me._CONFIG.DEFAULT_STYLE.SHAPE.cursor});
+
+ if (me._CONFIG.DRAG_PAGE_MOVABLE) {
+ var container = me._RENDERER._CANVAS._CONTAINER;
+ $(element).bind("mousedown", function (event) {
+ if (event.button != 0) {
+ return;
+ }
+ root = renderer.getRootGroup();
+ var isConnectMode = $(root).data(OG.Constants.GUIDE_SUFFIX.LINE_CONNECT_MODE);
+ var isRectMode = $(root).data(OG.Constants.GUIDE_SUFFIX.RECT_CONNECT_MODE);
+ if (isConnectMode === 'active' || isRectMode === 'active') {
+ return;
+ }
+ $(root).data("dragPageMove", {x: event.pageX, y: event.pageY});
+ $(root).data("dragPageScroll", {x: container.scrollLeft, y: container.scrollTop});
+ });
+ }
+ }
+ },
+
+ /**
+ * Shape 엘리먼트의 라인모양을 클릭하여 Shape 끼리 커넥트가 가능하게 한다.
+ *
+ * @param {Element} element Shape 엘리먼트
+ * @param {Object} guide JSON 포맷 가이드 정보
+ * @param {Boolean} isConnectable 가능여부
+ */
+ setConnectable: function (element, guide, isConnectable) {
+ var me = this, root = me._RENDERER.getRootGroup(),
+ virtualEdge, eventOffset;
+ var isEdge = $(element).attr("_shape") === OG.Constants.SHAPE_TYPE.EDGE ? true : false;
+ if (!element || !guide) {
+ return;
+ }
+
+ if (isConnectable) {
+ if (!isEdge) {
+ $.each(guide.line, function (i, line) {
+ $(line.node).bind({
+ click: function (event) {
+ eventOffset = me._getOffset(event);
+ virtualEdge = me._RENDERER.createVirtualEdge(eventOffset.x, eventOffset.y, element);
+ if (virtualEdge) {
+ $(root).data(OG.Constants.GUIDE_SUFFIX.LINE_CONNECT_MODE, 'created');
+ $(root).data(OG.Constants.GUIDE_SUFFIX.LINE_CONNECT_TEXT, line.text);
+ $(root).data(OG.Constants.GUIDE_SUFFIX.LINE_CONNECT_LABEL, line.label);
+ $(root).data(OG.Constants.GUIDE_SUFFIX.LINE_CONNECT_SHAPE, line.shape);
+ }
+ }
+ });
+
+ $(line.node).draggable({
+ start: function (event) {
+ me.deselectAll();
+ me._RENDERER.removeAllConnectGuide();
+ me._RENDERER.removeAllVirtualEdge();
+ eventOffset = me._getOffset(event);
+ virtualEdge = me._RENDERER.createVirtualEdge(eventOffset.x, eventOffset.y, element);
+ $(root).data(OG.Constants.GUIDE_SUFFIX.LINE_CONNECT_MODE, 'active');
+ $(root).data(OG.Constants.GUIDE_SUFFIX.LINE_CONNECT_TEXT, line.text);
+ $(root).data(OG.Constants.GUIDE_SUFFIX.LINE_CONNECT_LABEL, line.label);
+ $(root).data(OG.Constants.GUIDE_SUFFIX.LINE_CONNECT_SHAPE, line.shape);
+ }
+ });
+ });
+
+ if (guide.rect && guide.rect.length) {
+ $.each(guide.rect, function (i, rect) {
+ $(rect.node).bind({
+ click: function (event) {
+ eventOffset = me._getOffset(event);
+ virtualEdge = me._RENDERER.createVirtualEdge(eventOffset.x, eventOffset.y, element);
+ if (virtualEdge) {
+ $(root).data(OG.Constants.GUIDE_SUFFIX.RECT_CONNECT_MODE, 'created');
+ if (rect.shape) {
+ $(root).data(OG.Constants.GUIDE_SUFFIX.RECT_CONNECT_TO_DRAW, rect);
+ } else {
+ $(root).data(OG.Constants.GUIDE_SUFFIX.RECT_CONNECT_TO_DRAW, false);
+ }
+ }
+ }
+ });
+
+ $(rect.node).draggable({
+ start: function (event) {
+ me.deselectAll();
+ me._RENDERER.removeAllConnectGuide();
+ me._RENDERER.removeAllVirtualEdge();
+ eventOffset = me._getOffset(event);
+ virtualEdge = me._RENDERER.createVirtualEdge(eventOffset.x, eventOffset.y, element);
+ $(root).data(OG.Constants.GUIDE_SUFFIX.RECT_CONNECT_MODE, 'active');
+ if (rect.shape) {
+ $(root).data(OG.Constants.GUIDE_SUFFIX.RECT_CONNECT_TO_DRAW, rect);
+ } else {
+ $(root).data(OG.Constants.GUIDE_SUFFIX.RECT_CONNECT_TO_DRAW, false);
+ }
+ }
+ });
+ });
+ }
+ }
+ }
+ },
+
+ /**
+ * Shape 엘리먼트의 리사이즈 가능여부를 설정한다.
+ *
+ * @param {Element} element Shape 엘리먼트
+ * @param {Object} guide JSON 포맷 가이드 정보
+ * @param {Boolean} isResizable 가능여부
+ */
+ setResizable: function (element, guide, isResizable) {
+ var me = this;
+ var root = me._RENDERER.getRootGroup();
+ if (!element || !guide) {
+ return;
+ }
+
+ var isEdge = $(element).attr("_shape") === OG.Constants.SHAPE_TYPE.EDGE ? true : false;
+ var renderer = me._RENDERER;
+
+ var boundary = me._RENDERER.getBoundary(element);
+ var uP = boundary.getUpperCenter().y;
+ var lwP = boundary.getLowerCenter().y;
+ var lP = boundary.getLeftCenter().x;
+ var rP = boundary.getRightCenter().x;
+ var bWidth = boundary.getWidth();
+ var bHeight = boundary.getHeight();
+
+ var calculateResizeCorrectionConditions = function (direction) {
+ //조건집합
+ var correctionConditions = [];
+ var minSize = me._CONFIG.GUIDE_MIN_SIZE;
+ var laneMinSize = me._CONFIG.LANE_MIN_SIZE;
+ var groupInnerSapce = me._CONFIG.GROUP_INNER_SAPCE;
+
+ if (!me._CONFIG.AUTOMATIC_GUIDANCE) {
+ return correctionConditions;
+ }
+
+ //모든 Shape 의 중심점,상하좌우 끝을 조건에 포함한다.
+ //이 조건은 다른 조건과 달리 리사이즈 최소 보정치 계산과 함께 하기 때문에 min,max 가 반대이다.
+ var delay = me._CONFIG.EDGE_MOVE_DELAY_SIZE;
+ var allShapes = renderer.getAllShapes();
+ $.each(allShapes, function (idx, shape) {
+ if (renderer.isEdge(shape)) {
+ return;
+ }
+ if (shape.id === element.id) {
+ return;
+ }
+ var boundary = renderer.getBoundary(shape);
+ var center = boundary.getCentroid();
+ var upperLeft = boundary.getUpperLeft();
+ var lowerRight = boundary.getLowerRight();
+
+ //top boundary range
+ correctionConditions.push({
+ condition: {
+ maxY: upperLeft.y - delay,
+ minY: upperLeft.y + delay
+ },
+ fixedPosition: {
+ y: upperLeft.y
+ },
+ guidePosition: {
+ y: upperLeft.y
+ },
+ id: idx
+ });
+
+ //low boundary range
+ correctionConditions.push({
+ condition: {
+ maxY: lowerRight.y - delay,
+ minY: lowerRight.y + delay
+ },
+ fixedPosition: {
+ y: lowerRight.y
+ },
+ guidePosition: {
+ y: lowerRight.y
+ },
+ id: idx
+ });
+
+ //left boundary range
+ correctionConditions.push({
+ condition: {
+ maxX: upperLeft.x - delay,
+ minX: upperLeft.x + delay
+ },
+ fixedPosition: {
+ x: upperLeft.x
+ },
+ guidePosition: {
+ x: upperLeft.x
+ },
+ id: idx
+ });
+
+ //right boundary range
+ correctionConditions.push({
+ condition: {
+ maxX: lowerRight.x - delay,
+ minX: lowerRight.x + delay
+ },
+ fixedPosition: {
+ x: lowerRight.x
+ },
+ guidePosition: {
+ x: lowerRight.x
+ },
+ id: idx
+ });
+
+ //center vertical boundary range
+ correctionConditions.push({
+ condition: {
+ maxX: center.x - delay,
+ minX: center.x + delay
+ },
+ fixedPosition: {
+ x: center.x
+ },
+ guidePosition: {
+ x: center.x
+ },
+ id: idx
+ });
+
+ //center horizontal boundary range
+ correctionConditions.push({
+ condition: {
+ maxY: center.y - delay,
+ minY: center.y + delay,
+ },
+ fixedPosition: {
+ y: center.y
+ },
+ guidePosition: {
+ y: center.y
+ },
+ id: idx
+ });
+ });
+
+ function addRightCtrlCondition() {
+ correctionConditions.push({
+ condition: {
+ minX: lP + minSize
+ },
+ fixedPosition: {
+ x: lP + minSize
+ }
+ });
+ }
+
+ function addLeftCtrlCondition() {
+ correctionConditions.push({
+ condition: {
+ maxX: rP - minSize
+ },
+ fixedPosition: {
+ x: rP - minSize
+ }
+ });
+ }
+
+ function addUpperCtrlCondition() {
+ correctionConditions.push({
+ condition: {
+ maxY: lwP - minSize
+ },
+ fixedPosition: {
+ y: lwP - minSize
+ }
+ });
+ }
+
+ function addLowCtrlCondition() {
+ correctionConditions.push({
+ condition: {
+ minY: uP + minSize
+ },
+ fixedPosition: {
+ y: uP + minSize
+ }
+ });
+ }
+
+ //상단 컨트롤러를 위쪽으로 드래그할 경우
+ function laneUpHandleMoveup() {
+ if (renderer.isHorizontalLane(element)) {
+ var baseLanes = renderer.getBaseLanes(element);
+ var indexAsDirection = renderer.getNearestBaseLaneIndexAsDirection(element, 'upper');
+ if (indexAsDirection > 0) {
+ var compareLane = baseLanes[indexAsDirection - 1];
+ var resizableSpace = renderer.getBoundary(compareLane).getHeight() - laneMinSize;
+ correctionConditions.push({
+ condition: {
+ minY: uP - resizableSpace
+ },
+ fixedPosition: {
+ y: uP - resizableSpace
+ }
+ });
+ }
+ }
+ }
+
+ //상단 컨트롤러를 아래쪽으로 드래그할 경우
+ function laneUpHandleMovedown() {
+ if (renderer.isHorizontalLane(element)) {
+ var baseLanes = renderer.getBaseLanes(element);
+ var indexAsDirection = renderer.getNearestBaseLaneIndexAsDirection(element, 'upper');
+ var compareLane = baseLanes[indexAsDirection];
+ var resizableSpace = renderer.getBoundary(compareLane).getHeight() - laneMinSize;
+ correctionConditions.push({
+ condition: {
+ maxY: uP + resizableSpace
+ },
+ fixedPosition: {
+ y: uP + resizableSpace
+ }
+ });
+ if (indexAsDirection === 0) {
+ var boundaryOfInnerShapes = renderer.getBoundaryOfInnerShapesGroup(element);
+ if (boundaryOfInnerShapes) {
+ correctionConditions.push({
+ condition: {
+ maxY: boundaryOfInnerShapes.getUpperCenter().y - groupInnerSapce
+ },
+ fixedPosition: {
+ y: boundaryOfInnerShapes.getUpperCenter().y - groupInnerSapce
+ }
+ });
+ }
+ }
+ }
+ if (renderer.isVerticalLane(element)) {
+ var smallestBaseLane = renderer.getSmallestBaseLane(element);
+ var resizableSpace = renderer.getExceptTitleLaneArea(smallestBaseLane).getHeight() - laneMinSize;
+ correctionConditions.push({
+ condition: {
+ maxY: uP + resizableSpace
+ },
+ fixedPosition: {
+ y: uP + resizableSpace
+ }
+ });
+
+ var boundaryOfInnerShapes = renderer.getBoundaryOfInnerShapesGroup(element);
+ if (boundaryOfInnerShapes) {
+ var rootLane = renderer.getRootLane(element);
+ var rootTitleSpace = renderer.getBoundary(rootLane).getHeight() - renderer.getExceptTitleLaneArea(rootLane).getHeight();
+ correctionConditions.push({
+ condition: {
+ maxY: boundaryOfInnerShapes.getUpperCenter().y - groupInnerSapce - rootTitleSpace
+ },
+ fixedPosition: {
+ y: boundaryOfInnerShapes.getUpperCenter().y - groupInnerSapce - rootTitleSpace
+ }
+ });
+ }
+ }
+ }
+
+ //하단 컨트롤러를 위쪽으로 드래그할 경우
+ function laneLowHandleMoveup() {
+ if (renderer.isHorizontalLane(element)) {
+ var baseLanes = renderer.getBaseLanes(element);
+ var indexAsDirection = renderer.getNearestBaseLaneIndexAsDirection(element, 'low');
+ var compareLane = baseLanes[indexAsDirection];
+ var resizableSpace = renderer.getBoundary(compareLane).getHeight() - laneMinSize;
+ correctionConditions.push({
+ condition: {
+ minY: lwP - resizableSpace
+ },
+ fixedPosition: {
+ y: lwP - resizableSpace
+ }
+ });
+
+ if (indexAsDirection === baseLanes.length - 1) {
+ var boundaryOfInnerShapes = renderer.getBoundaryOfInnerShapesGroup(element);
+ if (boundaryOfInnerShapes) {
+ correctionConditions.push({
+ condition: {
+ minY: boundaryOfInnerShapes.getLowerCenter().y + groupInnerSapce
+ },
+ fixedPosition: {
+ y: boundaryOfInnerShapes.getLowerCenter().y + groupInnerSapce
+ }
+ });
+ }
+ }
+ }
+ if (renderer.isVerticalLane(element)) {
+ var smallestBaseLane = renderer.getSmallestBaseLane(element);
+ var resizableSpace = renderer.getExceptTitleLaneArea(smallestBaseLane).getHeight() - laneMinSize;
+ correctionConditions.push({
+ condition: {
+ minY: lwP - resizableSpace
+ },
+ fixedPosition: {
+ y: lwP - resizableSpace
+ }
+ });
+
+ var boundaryOfInnerShapes = renderer.getBoundaryOfInnerShapesGroup(element);
+ if (boundaryOfInnerShapes) {
+ correctionConditions.push({
+ condition: {
+ minY: boundaryOfInnerShapes.getLowerCenter().y + groupInnerSapce
+ },
+ fixedPosition: {
+ y: boundaryOfInnerShapes.getLowerCenter().y + groupInnerSapce
+ }
+ });
+ }
+ }
+ }
+
+ //하단 컨트롤러를 아래쪽으로 드래그할 경우
+ function laneLowHandleMovedown() {
+ if (renderer.isHorizontalLane(element)) {
+ var baseLanes = renderer.getBaseLanes(element);
+ var indexAsDirection = renderer.getNearestBaseLaneIndexAsDirection(element, 'low');
+ if (indexAsDirection < baseLanes.length - 1) {
+ var compareLane = baseLanes[indexAsDirection + 1];
+ var resizableSpace = renderer.getBoundary(compareLane).getHeight() - laneMinSize;
+ correctionConditions.push({
+ condition: {
+ maxY: lwP + resizableSpace
+ },
+ fixedPosition: {
+ y: lwP + resizableSpace
+ }
+ });
+ }
+ }
+ }
+
+ //좌측 컨트롤러를 우측으로 드래그할 경우
+ function laneLeftHandleMoveright() {
+ if (renderer.isHorizontalLane(element)) {
+ var smallestBaseLane = renderer.getSmallestBaseLane(element);
+ var resizableSpace = renderer.getExceptTitleLaneArea(smallestBaseLane).getWidth() - laneMinSize;
+ correctionConditions.push({
+ condition: {
+ maxX: lP + resizableSpace
+ },
+ fixedPosition: {
+ x: lP + resizableSpace
+ }
+ });
+
+ var boundaryOfInnerShapes = renderer.getBoundaryOfInnerShapesGroup(element);
+ if (boundaryOfInnerShapes) {
+ var rootLane = renderer.getRootLane(element);
+ var rootTitleSpace = renderer.getBoundary(rootLane).getWidth() - renderer.getExceptTitleLaneArea(rootLane).getWidth();
+ correctionConditions.push({
+ condition: {
+ maxX: boundaryOfInnerShapes.getLeftCenter().x - groupInnerSapce - rootTitleSpace
+ },
+ fixedPosition: {
+ x: boundaryOfInnerShapes.getLeftCenter().x - groupInnerSapce - rootTitleSpace
+ }
+ });
+ }
+ }
+ if (renderer.isVerticalLane(element)) {
+ var baseLanes = renderer.getBaseLanes(element);
+ var indexAsDirection = renderer.getNearestBaseLaneIndexAsDirection(element, 'left');
+ var compareLane = baseLanes[indexAsDirection];
+ var resizableSpace = renderer.getBoundary(compareLane).getWidth() - laneMinSize;
+ correctionConditions.push({
+ condition: {
+ maxX: lP + resizableSpace
+ },
+ fixedPosition: {
+ x: lP + resizableSpace
+ }
+ });
+
+ if (indexAsDirection === baseLanes.length - 1) {
+ var boundaryOfInnerShapes = renderer.getBoundaryOfInnerShapesGroup(element);
+ if (boundaryOfInnerShapes) {
+ correctionConditions.push({
+ condition: {
+ maxX: boundaryOfInnerShapes.getLeftCenter().x - groupInnerSapce
+ },
+ fixedPosition: {
+ x: boundaryOfInnerShapes.getLeftCenter().x - groupInnerSapce
+ }
+ });
+ }
+ }
+ }
+ }
+
+ //좌측 컨트롤러를 좌측으로 드래그할 경우
+ function laneLeftHandleMoveleft() {
+ if (renderer.isVerticalLane(element)) {
+ var baseLanes = renderer.getBaseLanes(element);
+ var indexAsDirection = renderer.getNearestBaseLaneIndexAsDirection(element, 'left');
+ if (indexAsDirection < baseLanes.length - 1) {
+ var compareLane = baseLanes[indexAsDirection + 1];
+ var resizableSpace = renderer.getBoundary(compareLane).getWidth() - laneMinSize;
+ correctionConditions.push({
+ condition: {
+ minX: lP - resizableSpace
+ },
+ fixedPosition: {
+ x: lP - resizableSpace
+ }
+ });
+ }
+ }
+ }
+
+ //우측 컨트롤러를 좌측으로 드래그할 경우
+ function laneRightHandleMoveleft() {
+ if (renderer.isHorizontalLane(element)) {
+ var smallestBaseLane = renderer.getSmallestBaseLane(element);
+ var resizableSpace = renderer.getExceptTitleLaneArea(smallestBaseLane).getWidth() - laneMinSize;
+ correctionConditions.push({
+ condition: {
+ minX: rP - resizableSpace
+ },
+ fixedPosition: {
+ x: rP - resizableSpace
+ }
+ });
+
+ var boundaryOfInnerShapes = renderer.getBoundaryOfInnerShapesGroup(element);
+ if (boundaryOfInnerShapes) {
+ correctionConditions.push({
+ condition: {
+ minX: boundaryOfInnerShapes.getRightCenter().x + groupInnerSapce
+ },
+ fixedPosition: {
+ x: boundaryOfInnerShapes.getRightCenter().x + groupInnerSapce
+ }
+ });
+ }
+ }
+ if (renderer.isVerticalLane(element)) {
+ var baseLanes = renderer.getBaseLanes(element);
+ var indexAsDirection = renderer.getNearestBaseLaneIndexAsDirection(element, 'right');
+ var compareLane = baseLanes[indexAsDirection];
+ var resizableSpace = renderer.getBoundary(compareLane).getWidth() - laneMinSize;
+ correctionConditions.push({
+ condition: {
+ minX: rP - resizableSpace
+ },
+ fixedPosition: {
+ x: rP - resizableSpace
+ }
+ });
+ if (indexAsDirection === 0) {
+ var boundaryOfInnerShapes = renderer.getBoundaryOfInnerShapesGroup(element);
+ if (boundaryOfInnerShapes) {
+ correctionConditions.push({
+ condition: {
+ minX: boundaryOfInnerShapes.getRightCenter().x + groupInnerSapce
+ },
+ fixedPosition: {
+ x: boundaryOfInnerShapes.getRightCenter().x + groupInnerSapce
+ }
+ });
+ }
+ }
+ }
+ }
+
+ //우측 컨트롤러를 우측으로 드래그할 경우
+ function laneRightHandleMoveright() {
+ if (renderer.isVerticalLane(element)) {
+ var baseLanes = renderer.getBaseLanes(element);
+ var indexAsDirection = renderer.getNearestBaseLaneIndexAsDirection(element, 'right');
+ if (indexAsDirection > 0) {
+ var compareLane = baseLanes[indexAsDirection - 1];
+ var resizableSpace = renderer.getBoundary(compareLane).getWidth() - laneMinSize;
+ correctionConditions.push({
+ condition: {
+ maxX: rP + resizableSpace
+ },
+ fixedPosition: {
+ x: rP + resizableSpace
+ }
+ });
+ }
+ }
+ }
+
+ if (upHandle(direction)) {
+ addUpperCtrlCondition();
+ }
+ if (lowHandle(direction)) {
+ addLowCtrlCondition();
+ }
+ if (leftHandle(direction)) {
+ addLeftCtrlCondition();
+ }
+ if (rightHandle(direction)) {
+ addRightCtrlCondition();
+ }
+
+ if (renderer.isLane(element)) {
+ if (upHandle(direction)) {
+ laneUpHandleMoveup();
+ laneUpHandleMovedown();
+ }
+ if (lowHandle(direction)) {
+ laneLowHandleMoveup();
+ laneLowHandleMovedown();
+ }
+ if (leftHandle(direction)) {
+ laneLeftHandleMoveright();
+ laneLeftHandleMoveleft();
+ }
+ if (rightHandle(direction)) {
+ laneRightHandleMoveleft();
+ laneRightHandleMoveright();
+ }
+ }
+ return correctionConditions;
+ };
+ //element가 가지고있는 범위조건에 따라 새로운 포지션을 계산한다.
+ var correctionConditionAnalysis = function (controller, offset) {
+ var fixedPosition = {
+ x: offset.x,
+ y: offset.y
+ };
+ if (!me._CONFIG.AUTOMATIC_GUIDANCE) {
+ return fixedPosition;
+ }
+ var calculateFixedPosition = function (expectedPosition) {
+ if (!expectedPosition) {
+ return fixedPosition;
+ }
+ if (expectedPosition.x && !expectedPosition.y) {
+ return {
+ x: expectedPosition.x,
+ y: fixedPosition.y
+ }
+ }
+ if (expectedPosition.y && !expectedPosition.x) {
+ return {
+ x: fixedPosition.x,
+ y: expectedPosition.y
+ }
+ }
+ if (expectedPosition.x && expectedPosition.y) {
+ return expectedPosition;
+ }
+ return fixedPosition;
+ };
+ var correctionConditions = $(controller).data('correctionConditions');
+ if (!correctionConditions) {
+ return fixedPosition;
+ }
+
+ var conditionsPassCandidates = [];
+ $.each(correctionConditions, function (index, correctionCondition) {
+ var condition = correctionCondition.condition;
+
+ var conditionsPassToFix = true;
+ if (condition.minX) {
+ if (offset.x > condition.minX) {
+ conditionsPassToFix = false;
+ }
+ }
+ if (condition.maxX) {
+ if (offset.x < condition.maxX) {
+ conditionsPassToFix = false;
+ }
+ }
+ if (condition.minY) {
+ if (offset.y > condition.minY) {
+ conditionsPassToFix = false;
+ }
+ }
+ if (condition.maxY) {
+ if (offset.y < condition.maxY) {
+ conditionsPassToFix = false;
+ }
+ }
+ if (conditionsPassToFix) {
+ conditionsPassCandidates.push(correctionCondition);
+ }
+ });
+
+ $.each(conditionsPassCandidates, function (index, conditionsPassCandidate) {
+ fixedPosition = calculateFixedPosition(conditionsPassCandidate.fixedPosition);
+ var guidePosition = conditionsPassCandidate.guidePosition;
+ if (guidePosition) {
+ renderer.drawStickGuide(guidePosition);
+ }
+ });
+ if (!conditionsPassCandidates.length) {
+ renderer.removeAllStickGuide();
+ }
+
+ return fixedPosition;
+
+ };
+
+ var upHandle = function (handleName) {
+ if (handleName === 'ul' || handleName === 'uc' || handleName === 'ur') {
+ return true;
+ }
+ return false;
+ };
+
+ var lowHandle = function (handleName) {
+ if (handleName === 'lwl' || handleName === 'lwc' || handleName === 'lwr') {
+ return true;
+ }
+ return false;
+ };
+
+ var rightHandle = function (handleName) {
+ if (handleName === 'ur' || handleName === 'rc' || handleName === 'lwr') {
+ return true;
+ }
+ return false;
+ };
+
+ var leftHandle = function (handleName) {
+ if (handleName === 'ul' || handleName === 'lc' || handleName === 'lwl') {
+ return true;
+ }
+ return false;
+ };
+
+ if (isResizable === true) {
+ if (!isEdge) {
+ for (var _handleName in guide) {
+ var handles = ['ul', 'uc', 'ur', 'rc', 'lwr', 'lwc', 'lwl', 'lc'];
+ var indexOfHandle = handles.indexOf(_handleName);
+ var canvasSize;
+ if (indexOfHandle === -1) {
+ continue;
+ }
+
+ $(guide[_handleName]).data('handleName', _handleName);
+ $(guide[_handleName]).draggable({
+ start: function (event) {
+ $(root).data(OG.Constants.GUIDE_SUFFIX.ONRESIZE, 'active');
+ var handleName = $(this).data('handleName');
+ var eventOffset = me._getOffset(event);
+ var hx = renderer.getAttr(guide[handleName], "x");
+ var hy = renderer.getAttr(guide[handleName], "y");
+ var hWidth = renderer.getAttr(guide[handleName], "width");
+ var hHeight = renderer.getAttr(guide[handleName], "height");
+ $(this).data("start", {x: eventOffset.x, y: eventOffset.y});
+ $(this).data("offset", {
+ x: eventOffset.x - me._num(hx + hWidth / 2),
+ y: eventOffset.y - me._num(hy + hHeight / 2)
+ });
+
+ $(this).data('correctionConditions', calculateResizeCorrectionConditions(handleName));
+ renderer.removeRubberBand(renderer.getRootElement());
+ },
+ drag: function (event) {
+ $(root).data(OG.Constants.GUIDE_SUFFIX.ONRESIZE, 'active');
+ var handleName = $(this).data('handleName');
+ var eventOffset = me._getOffset(event),
+ start = $(this).data("start"),
+ offset = $(this).data("offset");
+ var hWidth = renderer.getAttr(guide[handleName], "width");
+ var hHeight = renderer.getAttr(guide[handleName], "height");
+ var newXY = correctionConditionAnalysis($(this), {
+ x: eventOffset.x - offset.x,
+ y: eventOffset.y - offset.y
+ });
+ var newX = me._grid(newXY.x);
+ var newY = me._grid(newXY.y);
+ var newWidth, newHeight
+ var newUp = uP;
+ var newLwp = lwP;
+ var newLp = lP;
+ var newRp = rP;
+ $(this).css({"position": "", "left": "", "top": ""});
+ if (upHandle(handleName)) {
+ newUp = newY;
+ }
+ if (lowHandle(handleName)) {
+ newLwp = newY;
+ }
+ if (rightHandle(handleName)) {
+ newRp = newX;
+ }
+ if (leftHandle(handleName)) {
+ newLp = newX;
+ }
+ newWidth = Math.abs(newRp - newLp);
+ newHeight = Math.abs(newLwp - newUp);
+
+ renderer.setAttr(guide.ul, {
+ x: newLp - hWidth / 2,
+ y: newUp - hHeight / 2
+ });
+ renderer.setAttr(guide.uc, {
+ x: (newLp + newRp) / 2 - hWidth / 2,
+ y: newUp - hHeight / 2
+ });
+ renderer.setAttr(guide.ur, {
+ x: newRp - hWidth / 2,
+ y: newUp - hHeight / 2
+ });
+ renderer.setAttr(guide.lc, {
+ x: newLp - hWidth / 2,
+ y: (newUp + newLwp) / 2 - hHeight / 2
+ });
+ renderer.setAttr(guide.rc, {
+ x: newRp - hWidth / 2,
+ y: (newUp + newLwp) / 2 - hHeight / 2
+ });
+ renderer.setAttr(guide.lwl, {
+ x: newLp - hWidth / 2,
+ y: newLwp - hHeight / 2
+ });
+ renderer.setAttr(guide.lwc, {
+ x: (newLp + newRp) / 2 - hWidth / 2,
+ y: newLwp - hHeight / 2
+ });
+ renderer.setAttr(guide.lwr, {
+ x: newRp - hWidth / 2,
+ y: newLwp - hHeight / 2
+ });
+ renderer.setAttr(guide.bBox, {
+ x: newLp,
+ y: newUp,
+ width: newWidth,
+ height: newHeight
+ });
+ renderer.removeAllConnectGuide();
+
+ canvasSize = renderer.getCanvasSize();
+ if (canvasSize[0] < newRp + me._CONFIG.RESIZE_CANVAS_MARGIN || canvasSize[1] < newLwp + me._CONFIG.RESIZE_CANVAS_MARGIN) {
+ if (canvasSize[0] < newRp + me._CONFIG.RESIZE_CANVAS_MARGIN) {
+ canvasSize[0] = newRp + me._CONFIG.RESIZE_CANVAS_MARGIN;
+ }
+ if (canvasSize[1] < newLwp + me._CONFIG.RESIZE_CANVAS_MARGIN) {
+ canvasSize[1] = newLwp + me._CONFIG.RESIZE_CANVAS_MARGIN;
+ }
+ renderer.setCanvasSize(canvasSize);
+ }
+
+ },
+ stop: function (event) {
+ $(root).data(OG.Constants.GUIDE_SUFFIX.ONRESIZE, false);
+ var handleName = $(this).data('handleName');
+ var eventOffset = me._getOffset(event),
+ start = $(this).data("start"),
+ offset = $(this).data("offset");
+ var newXY = correctionConditionAnalysis($(this), {
+ x: eventOffset.x - offset.x,
+ y: eventOffset.y - offset.y
+ });
+ var newX = me._grid(newXY.x);
+ var newY = me._grid(newXY.y);
+ var newUp = uP;
+ var newLwp = lwP;
+ var newLp = lP;
+ var newRp = rP;
+ if (upHandle(handleName)) {
+ newUp = newY;
+ }
+ if (lowHandle(handleName)) {
+ newLwp = newY;
+ }
+ if (rightHandle(handleName)) {
+ newRp = newX;
+ }
+ if (leftHandle(handleName)) {
+ newLp = newX;
+ }
+ var newWidth = Math.abs(newRp - newLp);
+ var newHeight = Math.abs(newLwp - newUp);
+ var du = uP - newUp;
+ var dlw = newLwp - lwP;
+ var dl = lP - newLp;
+ var dr = newRp - rP;
+
+ //다른 selected 엘리먼트 리사이즈용 변수
+ var stBoundary, stUp, stLwp, stLp, stRp,
+ newStUp, newStLwp, newStLp, newStRp,
+ stDu, stDlw, stDl, stDr;
+
+ $(this).css({"position": "absolute", "left": "0px", "top": "0px"});
+ if (element && element.shape.geom) {
+ if (renderer.isLane(element)) {
+ renderer.resizeLane(element, [du, dlw, dl, dr]);
+ } else {
+ renderer.resize(element, [du, dlw, dl, dr]);
+ }
+ renderer.removeGuide(element);
+ var _guide = renderer.drawGuide(element);
+ if (_guide) {
+ me.setResizable(element, _guide, me._isResizable(element.shape));
+ me.setConnectable(element, _guide, me._isConnectable(element.shape));
+ }
+
+ //선택된 다른 엘리먼트들의 리사이즈 처리
+ $.each(me._getSelectedElement(), function (idx, selected) {
+ if (selected.id === element.id) {
+ return;
+ }
+ if (renderer.isShape(selected) && !renderer.isEdge(selected)) {
+ stBoundary = renderer.getBoundary(selected);
+ stUp = stBoundary.getUpperCenter().y;
+ stLwp = stBoundary.getLowerCenter().y;
+ stLp = stBoundary.getLeftCenter().x;
+ stRp = stBoundary.getRightCenter().x;
+ newStUp = stUp;
+ newStLwp = stLwp;
+ newStLp = stLp;
+ newStRp = stRp;
+ if (upHandle(handleName)) {
+ newStUp = stLwp - newHeight;
+ }
+ if (lowHandle(handleName)) {
+ newStLwp = stUp + newHeight;
+ }
+ if (rightHandle(handleName)) {
+ newStRp = stLp + newWidth;
+ }
+ if (leftHandle(handleName)) {
+ newStLp = stRp - newWidth;
+ }
+ stDu = stUp - newStUp;
+ stDlw = newStLwp - stLwp;
+ stDl = stLp - newStLp;
+ stDr = newStRp - stRp;
+
+ if (renderer.isLane(selected)) {
+ renderer.resizeLane(selected, [stDu, stDlw, stDl, stDr]);
+ } else {
+ renderer.resize(selected, [stDu, stDlw, stDl, stDr]);
+ }
+ renderer.removeGuide(selected);
+ var _stGuide = renderer.drawGuide(selected);
+ if (_stGuide) {
+ me.setResizable(selected, _stGuide, me._isResizable(selected.shape));
+ me.setConnectable(selected, _stGuide, me._isConnectable(selected.shape));
+ }
+ }
+ });
+ }
+ renderer.removeAllConnectGuide();
+ renderer.addHistory();
+ }
+ });
+ }
+ // add tooltip for guide activity icon
+ for (var item in guide) {
+ if ($(guide[item]).attr('tooltip') == 'enable')
+ if ($(guide[item]).tooltip)
+ $(guide[item]).tooltip();
+ }
+ }
+ } else {
+ if ($(element).attr("_shape") !== OG.Constants.SHAPE_TYPE.EDGE) {
+ renderer.setAttr(guide.ul, {cursor: 'default'});
+ renderer.setAttr(guide.ur, {cursor: 'default'});
+ renderer.setAttr(guide.lwl, {cursor: 'default'});
+ renderer.setAttr(guide.lwr, {cursor: 'default'});
+ renderer.setAttr(guide.lc, {cursor: 'default'});
+ renderer.setAttr(guide.uc, {cursor: 'default'});
+ renderer.setAttr(guide.rc, {cursor: 'default'});
+ renderer.setAttr(guide.lwc, {cursor: 'default'});
+ }
+ }
+ },
+
+ /**
+ * 주어진 Shape Element 를 마우스 클릭하여 선택가능하도록 한다.
+ * 선택가능해야 리사이즈가 가능하다.
+ * 선택시 커넥트 모드일 경우 connect 가능하게 한다.
+ *
+ * @param {Element} element Shape Element
+ * @param {Boolean} isSelectable 선택가능여부
+ */
+ setClickSelectable: function (element, isSelectable) {
+ var me = this;
+ var renderer = me._RENDERER;
+ var root = me._RENDERER.getRootGroup();
+ if (isSelectable === true) {
+ // 마우스 클릭하여 선택 처리
+ $(element).bind({
+ click: function (event, param) {
+ root = me._RENDERER.getRootGroup();
+ if (me._CONFIG.FOCUS_CANVAS_ONSELECT) {
+ $(me._RENDERER.getContainer()).focus();
+ }
+ if (element.shape) {
+ me._RENDERER.removeAllVirtualEdge();
+
+ if ($(element).attr("_selected") === "true") {
+ me.deselectShape(element);
+ if (param) {
+ if (!param.shiftKey && !param.ctrlKey) {
+ me.selectShape(element, event, param);
+ }
+ } else {
+ if (!event.shiftKey && !event.ctrlKey) {
+ me.selectShape(element);
+ }
+ }
+ } else {
+ me.selectShape(element, event, param);
+ }
+ return false;
+ }
+ },
+ mousedown: function (event) {
+ event.stopPropagation();
+ },
+ mouseup: function (event) {
+ root = me._RENDERER.getRootGroup();
+ if (element.shape) {
+ var isConnectable = me._isConnectable(element.shape);
+ var isConnectMode = $(root).data(OG.Constants.GUIDE_SUFFIX.LINE_CONNECT_MODE);
+ var connectText = $(root).data(OG.Constants.GUIDE_SUFFIX.LINE_CONNECT_TEXT);
+ var connectLabel = $(root).data(OG.Constants.GUIDE_SUFFIX.LINE_CONNECT_LABEL);
+ var connectShape = $(root).data(OG.Constants.GUIDE_SUFFIX.LINE_CONNECT_SHAPE);
+ var isRectMode = $(root).data(OG.Constants.GUIDE_SUFFIX.RECT_CONNECT_MODE);
+ var isEdge = $(element).attr("_shape") === OG.Constants.SHAPE_TYPE.EDGE;
+ if (isConnectMode) {
+
+ if (isConnectable && !isEdge) {
+ var target = me._RENDERER.getTargetfromVirtualEdge();
+ if (target.id === element.id) {
+ return;
+ }
+ me._RENDERER.removeAllVirtualEdge();
+ //From,To 가능여부 확인
+ if (!me._isConnectableFrom(target.shape)) {
+ isConnectable = false;
+ }
+ if (!me._isConnectableTo(element.shape)) {
+ isConnectable = false;
+ }
+ if (isConnectable) {
+ if (connectShape) {
+ eval('connectShape = new ' + connectShape + '()');
+ }
+ me._RENDERER._CANVAS.connect(target, element, null, connectLabel, null, null, null, null, connectShape);
+ $(root).removeData(OG.Constants.GUIDE_SUFFIX.LINE_CONNECT_SHAPE);
+ $(root).removeData(OG.Constants.GUIDE_SUFFIX.LINE_CONNECT_TEXT);
+ $(root).removeData(OG.Constants.GUIDE_SUFFIX.LINE_CONNECT_LABEL);
+ renderer.addHistory();
+ }
+ } else {
+ me._RENDERER.removeAllVirtualEdge();
+ }
+ }
+
+ if (isRectMode === 'active') {
+ me.cloneElementControll();
+ }
+ }
+ }
+ });
+
+ // 마우스 우클릭하여 선택 처리
+ if (me._CONFIG.ENABLE_CONTEXTMENU) {
+ $(element).bind("contextmenu", function (event) {
+
+ //중복된 콘텍스트를 방지
+ var eventOffset = me._getOffset(event);
+ var frontElement = renderer.getFrontForCoordinate([eventOffset.x, eventOffset.y]);
+ if (!frontElement) {
+ return;
+ }
+ if (frontElement.id !== element.id) {
+ return;
+ }
+
+ if (element.shape) {
+ if ($(element).attr("_selected") !== "true") {
+ me.selectShape(element, event);
+ }
+ }
+ });
+ }
+
+ me._RENDERER.setAttr(element, {cursor: 'pointer'});
+ OG.Util.apply(element.shape.geom.style.map, {cursor: 'pointer'});
+
+ } else {
+ $(element).unbind('click');
+ me._RENDERER.setAttr(element, {cursor: me._CONFIG.DEFAULT_STYLE.SHAPE.cursor});
+ OG.Util.apply(element.shape.geom.style.map, {cursor: me._CONFIG.DEFAULT_STYLE.SHAPE.cursor});
+ }
+ },
+
+ /**
+ * Lane,Pool 엘리먼트가 새로 생성될 시 그룹을 맺도록 한다.
+ *
+ * @param {Element} element Shape 엘리먼트
+ */
+ setGroupDropable: function (element) {
+ var me = this;
+ var renderer = me._RENDERER;
+ var root = renderer.getRootGroup();
+
+ $(element).bind("mousedown", function () {
+ var newPool = $(root).data('newPool');
+ var poolInnderShape = $(root).data('poolInnderShape');
+ if (!renderer.isLane(element) && !renderer.isPool(element)) {
+ return;
+ }
+ if (!newPool) {
+ return;
+ }
+
+ $.each(poolInnderShape, function (index, innderShape) {
+ newPool.appendChild(innderShape);
+ });
+
+ if ($(newPool).data('originalStyle')) {
+ newPool.shape.geom.style.map = $(newPool).data('originalStyle');
+ }
+ renderer.redrawShape(newPool);
+ renderer.offDropablePool();
+ });
+ },
+
+ /**
+ * 드래그하여 페이지 이동이 가능하게 한다.
+ * @param {Boolean} dragPageMovable 드래그 페이지 이동 가능 여부
+ */
+ setDragPageMovable: function () {
+ var renderer = this._RENDERER;
+ var me = this, rootEle = renderer.getRootElement();
+ var root = renderer.getRootGroup();
+ var container = renderer._CANVAS._CONTAINER;
+ $(rootEle).bind("mousedown", function (event) {
+ if (!me._CONFIG.DRAG_PAGE_MOVABLE || event.button != 0) {
+ return;
+ }
+ root = renderer.getRootGroup();
+ var isConnectMode = $(root).data(OG.Constants.GUIDE_SUFFIX.LINE_CONNECT_MODE);
+ var isRectMode = $(root).data(OG.Constants.GUIDE_SUFFIX.RECT_CONNECT_MODE);
+ if (isConnectMode === 'active' || isRectMode === 'active') {
+ return;
+ }
+ $(root).data("dragPageMove", {x: event.pageX, y: event.pageY});
+ $(root).data("dragPageScroll", {x: container.scrollLeft, y: container.scrollTop});
+ });
+ $(rootEle).bind("mousemove", function (event) {
+ if (!me._CONFIG.DRAG_PAGE_MOVABLE) {
+ return;
+ }
+ root = renderer.getRootGroup();
+ var isResizing = $(root).data(OG.Constants.GUIDE_SUFFIX.ONRESIZE);
+ var isConnectMode = $(root).data(OG.Constants.GUIDE_SUFFIX.LINE_CONNECT_MODE);
+ var isRectMode = $(root).data(OG.Constants.GUIDE_SUFFIX.RECT_CONNECT_MODE);
+ if (isConnectMode === 'active' || isRectMode === 'active' || isResizing === 'active') {
+ return;
+ }
+ var pageMove = $(root).data("dragPageMove");
+ var pageScroll = $(root).data("dragPageScroll");
+
+ if (pageMove) {
+ var moveX = event.pageX - pageMove.x;
+ var moveY = event.pageY - pageMove.y;
+ $(container).scrollLeft(pageScroll.x - moveX);
+ $(container).scrollTop(pageScroll.y - moveY);
+ }
+ });
+ $(rootEle).bind("mouseup", function (event) {
+ if (!me._CONFIG.DRAG_PAGE_MOVABLE) {
+ return;
+ }
+ root = renderer.getRootGroup();
+ var isConnectMode = $(root).data(OG.Constants.GUIDE_SUFFIX.LINE_CONNECT_MODE);
+ var isRectMode = $(root).data(OG.Constants.GUIDE_SUFFIX.RECT_CONNECT_MODE);
+ if (isConnectMode === 'active' || isRectMode === 'active') {
+ return;
+ }
+ $(root).removeData('dragPageMove');
+ $(root).removeData('dragPageScroll');
+ });
+ },
+
+ /**
+ * 휠 스케일을 가능하게 한다.
+ */
+ setWheelScale: function () {
+ var renderer = this._RENDERER;
+ var me = this, rootEle = renderer.getRootElement();
+ var updateScale = function (event, isUp) {
+ var eventOffset = me._getOffset(event);
+ // var scrollLeft = rootEle.scrollLeft;
+ // var scrollTop = rootEle.scrollTop;
+ var cuScale;
+ var preScale = renderer.getScale();
+ if (isUp) {
+ cuScale = preScale + 0.02;
+ } else {
+ cuScale = preScale - 0.02;
+ }
+ if (cuScale < 0.25) {
+ cuScale = 0.25;
+ }
+ if (cuScale > 4) {
+ cuScale = 4;
+ }
+ var container = renderer._CANVAS._CONTAINER;
+ var preCenterX = eventOffset.x;
+ var preCenterY = eventOffset.y;
+
+ renderer.setScale(cuScale);
+
+ eventOffset = me._getOffset(event);
+ var cuCenterX = eventOffset.x;
+ var cuCenterY = eventOffset.y;
+
+ var moveX = (preCenterX - cuCenterX) * cuScale;
+ var moveY = (preCenterY - cuCenterY) * cuScale;
+
+ var cuScrollLeft = container.scrollLeft;
+ var cuScrollTop = container.scrollTop;
+ $(container).scrollLeft(cuScrollLeft + moveX);
+ $(container).scrollTop(cuScrollTop + moveY);
+ renderer._CANVAS.updateNavigatior();
+
+ };
+
+ $(rootEle).bind('mousewheel DOMMouseScroll', function (event) {
+ if (me._CONFIG.WHEEL_SCALABLE) {
+
+ //터치패드 이벤트와 마우스 휠 스피드의 차이는 매우 크다.
+ //터치패드 0~10, 마우스 휠 400 이상.
+
+ if (!event.shiftKey) return;
+
+ var deltaX = 0;
+ var deltaY = 0;
+ deltaX = event.originalEvent.wheelDeltaX || event.deltaX || 0;
+ deltaY = event.originalEvent.wheelDeltaY || event.deltaY || 0;
+ var isTrackPad = false;
+ if (Math.abs(deltaX) < 120 && Math.abs(deltaY) < 120) {
+ isTrackPad = true;
+ }
+ if (!me._CONFIG.ENABLE_TRACKPAD) {
+ isTrackPad = false;
+ }
+ if (isTrackPad) {
+ //chrome pinch-to-zoom
+ if (event.ctrlKey) {
+ event.preventDefault();
+ event.stopPropagation();
+ if (event.originalEvent.wheelDelta > 0 || event.deltaY > 0) {
+ // scroll up
+ updateScale(event, true);
+ }
+ else {
+ updateScale(event, false);
+ }
+ }
+ } else {
+ event.preventDefault();
+ event.stopPropagation();
+ if (event.originalEvent.wheelDelta > 0 || event.deltaY > 0) {
+ // scroll up
+ updateScale(event, true);
+ }
+ else {
+ updateScale(event, false);
+ }
+ }
+ }
+ });
+ },
+
+ cloneElementControll: function () {
+ var renderer = this._RENDERER;
+ var me = this,
+ root = renderer.getRootGroup();
+ var isRectMode = $(root).data(OG.Constants.GUIDE_SUFFIX.RECT_CONNECT_MODE);
+
+ if (isRectMode === 'created') {
+ $(root).data(OG.Constants.GUIDE_SUFFIX.RECT_CONNECT_MODE, 'active');
+ }
+ if (isRectMode === 'active') {
+ //새로운 것 만드는 과정
+ var toDraw = $(root).data(OG.Constants.GUIDE_SUFFIX.RECT_CONNECT_TO_DRAW);
+ if (!toDraw) {
+ var eventOffset = me._getOffset(event);
+
+ var target = me._RENDERER.getTargetfromVirtualEdge();
+ renderer.removeAllVirtualEdge();
+ var shapeId = $(target).attr('_shape_id');
+ var newShape;
+ eval('newShape = new ' + shapeId + '()');
+
+ var style = target.shape.geom.style;
+ var boundary = renderer.getBoundary(target);
+ var width = boundary.getWidth();
+ var height = boundary.getHeight();
+
+ //From,To 가능여부 확인
+ var isConnectable = me._isConnectable(target.shape);
+ if (!me._isConnectableFrom(target.shape)) {
+ isConnectable = false;
+ }
+ if (!me._isConnectableTo(target.shape)) {
+ isConnectable = false;
+ }
+ if (isConnectable) {
+ newShape.setData(JSON.parse(JSON.stringify(target.shape.getData())));
+ var rectShape = renderer._CANVAS.drawShape([eventOffset.x, eventOffset.y], newShape, [width, height], style);
+ var edge = renderer._CANVAS.connect(target, rectShape, null, null, null, null, true);
+ $(renderer._PAPER.canvas).trigger('duplicated', [edge, target, rectShape]);
+ }
+ } else {
+ var eventOffset = me._getOffset(event);
+
+ var target = me._RENDERER.getTargetfromVirtualEdge();
+ renderer.removeAllVirtualEdge();
+ var shapeId = toDraw.shape;
+ var newShape;
+ if (shapeId instanceof OG.IShape) {
+ newShape = shapeId;
+ } else {
+ eval('newShape = new ' + shapeId + '()');
+ }
+
+ var style = toDraw.style;
+ var width = toDraw.width;
+ var height = toDraw.height;
+
+ //From 가능여부 확인
+ var isConnectable = me._isConnectable(target.shape);
+ if (!me._isConnectableFrom(target.shape)) {
+ isConnectable = false;
+ }
+ if (isConnectable) {
+ var rectShape = renderer._CANVAS.drawShape([eventOffset.x, eventOffset.y], newShape, [width, height], style);
+ var edge = rende
+ rer._CANVAS.connect(target, rectShape, null, null, null, null, true);
+ if (target.shape.onDuplicated) {
+ target.shape.onDuplicated(edge, target, rectShape);
+ }
+ if (rectShape.shape.onDuplicated) {
+ rectShape.shape.onDuplicated(edge, target, rectShape);
+ }
+ $(renderer._PAPER.canvas).trigger('duplicated', [edge, target, rectShape]);
+ }
+ }
+ }
+ },
+
+ /**
+ * 마우스 드래그 영역지정 선택가능여부를 설정한다.
+ * 선택가능해야 리사이즈가 가능하다.
+ *
+ * @param {Boolean} isSelectable 선택가능여부
+ */
+ setDragSelectable: function (isSelectable) {
+ var renderer = this._RENDERER;
+ var me = this, rootEle = renderer.getRootElement(),
+ root = renderer.getRootGroup();
+
+ var correctionConditionAnalysis = function (correctionConditions, offset) {
+ var fixedPosition = {
+ x: offset.x,
+ y: offset.y
+ };
+ var calculateFixedPosition = function (expectedPosition) {
+ if (!expectedPosition) {
+ return fixedPosition;
+ }
+ if (expectedPosition.x && !expectedPosition.y) {
+ return {
+ x: expectedPosition.x,
+ y: fixedPosition.y
+ }
+ }
+ if (expectedPosition.y && !expectedPosition.x) {
+ return {
+ x: fixedPosition.x,
+ y: expectedPosition.y
+ }
+ }
+ if (expectedPosition.x && expectedPosition.y) {
+ return expectedPosition;
+ }
+ return fixedPosition;
+ };
+ if (!correctionConditions || !correctionConditions.length) {
+ return fixedPosition;
+ }
+
+ var conditionsPassCandidates = [];
+ $.each(correctionConditions, function (index, correctionCondition) {
+ var condition = correctionCondition.condition;
+
+ var conditionsPassToFix = true;
+ if (condition.minX) {
+ if (offset.x > condition.minX) {
+ conditionsPassToFix = false;
+ }
+ }
+ if (condition.maxX) {
+ if (offset.x < condition.maxX) {
+ conditionsPassToFix = false;
+ }
+ }
+ if (condition.minY) {
+ if (offset.y > condition.minY) {
+ conditionsPassToFix = false;
+ }
+ }
+ if (condition.maxY) {
+ if (offset.y < condition.maxY) {
+ conditionsPassToFix = false;
+ }
+ }
+
+ if (conditionsPassToFix) {
+ conditionsPassCandidates.push(correctionCondition);
+ }
+ });
+ $.each(conditionsPassCandidates, function (index, conditionsPassCandidate) {
+ fixedPosition = calculateFixedPosition(conditionsPassCandidate.fixedPosition);
+ });
+
+ return fixedPosition;
+ };
+
+ // 배경클릭한 경우 deselect 하도록
+ $(rootEle).bind("click", function (event) {
+ root = me._RENDERER.getRootGroup();
+ if (!$(this).data("dragBox")) {
+ me.deselectAll();
+ renderer.removeRubberBand(rootEle);
+ renderer.removeAllConnectGuide();
+ }
+ //가상선 생성된 경우 액티브로 등록
+ //가상선 액티브인 경우 삭제
+ root = renderer.getRootGroup();
+ var isConnectMode = $(root).data(OG.Constants.GUIDE_SUFFIX.LINE_CONNECT_MODE);
+ var isRectMode = $(root).data(OG.Constants.GUIDE_SUFFIX.RECT_CONNECT_MODE);
+ if (isConnectMode) {
+ if (isConnectMode === 'created') {
+ $(root).data(OG.Constants.GUIDE_SUFFIX.LINE_CONNECT_MODE, 'active');
+ }
+ if (isConnectMode === 'active') {
+ renderer.removeAllVirtualEdge();
+ }
+ }
+ me.cloneElementControll();
+ });
+
+ $(rootEle).bind("mousemove", function (event) {
+ var isConnectMode = $(root).data(OG.Constants.GUIDE_SUFFIX.LINE_CONNECT_MODE);
+ var isRectMode = $(root).data(OG.Constants.GUIDE_SUFFIX.RECT_CONNECT_MODE);
+ if (isConnectMode === 'active' || isRectMode === 'active') {
+ eventOffset = me._getOffset(event);
+ renderer.updateVirtualEdge(eventOffset.x, eventOffset.y);
+ }
+
+ //Lane,Pool 이 새로 그려졌을 경우 위치조정
+ var newPool = $(root).data('newPool');
+ var correctionConditions = $(root).data('correctionConditions');
+ if (newPool) {
+
+ var geometry = newPool.shape.geom;
+ var eventOffset = me._getOffset(event);
+ var newX = eventOffset.x;
+ var newY = eventOffset.y;
+
+ var conditionAnalysis = correctionConditionAnalysis(correctionConditions, {x: newX, y: newY});
+ newX = me._grid(conditionAnalysis.x, 'move');
+ newY = me._grid(conditionAnalysis.y, 'move');
+
+ geometry.moveCentroid([newX, newY]);
+ renderer.redrawShape(newPool);
+ }
+ });
+
+ $(rootEle).bind("contextmenu", function (event) {
+ if (event.target.nodeName == 'svg') {
+ me.deselectAll();
+ renderer.removeRubberBand(rootEle);
+
+ }
+ });
+
+ if (isSelectable === true) {
+ // 마우스 영역 드래그하여 선택 처리
+ $(rootEle).bind("mousedown", function (event) {
+ var isConnectMode = $(root).data(OG.Constants.GUIDE_SUFFIX.LINE_CONNECT_MODE);
+ var isRectMode = $(root).data(OG.Constants.GUIDE_SUFFIX.RECT_CONNECT_MODE);
+ if (isConnectMode === 'active' || isRectMode === 'active') {
+ return;
+ }
+ if (!me._CONFIG.DRAG_PAGE_MOVABLE) {
+ var eventOffset = me._getOffset(event);
+ $(this).data("dragBox_first", {x: eventOffset.x, y: eventOffset.y});
+ $(this).removeData("dragBox");
+ }
+ });
+ $(rootEle).bind("mousemove", function (event) {
+ var isConnectMode = $(root).data(OG.Constants.GUIDE_SUFFIX.LINE_CONNECT_MODE);
+ var isRectMode = $(root).data(OG.Constants.GUIDE_SUFFIX.RECT_CONNECT_MODE);
+ if (isConnectMode === 'active' || isRectMode === 'active') {
+ $(this).removeData("dragBox_first");
+ return;
+ }
+ var first = $(this).data("dragBox_first"),
+ eventOffset, width, height, x, y;
+
+ if (first && !me._CONFIG.DRAG_PAGE_MOVABLE) {
+ eventOffset = me._getOffset(event);
+ width = eventOffset.x - first.x;
+ height = eventOffset.y - first.y;
+
+ if (Math.abs(width) > OG.Constants.RUBBER_BAND_TOLERANCE
+ && Math.abs(height) > OG.Constants.RUBBER_BAND_TOLERANCE) {
+ $(this).data("rubber_band_status", "start");
+
+ x = width <= 0 ? first.x + width : first.x;
+ y = height <= 0 ? first.y + height : first.y;
+ renderer.drawRubberBand([x, y], [Math.abs(width), Math.abs(height)]);
+ }
+ }
+ });
+ $(rootEle).bind("mouseup", function (event) {
+ var isConnectMode = $(root).data(OG.Constants.GUIDE_SUFFIX.LINE_CONNECT_MODE);
+ var isRectMode = $(root).data(OG.Constants.GUIDE_SUFFIX.RECT_CONNECT_MODE);
+ if (isConnectMode === 'active' || isRectMode === 'active') {
+ return;
+ }
+ if ("start" == $(this).data("rubber_band_status") && !me._CONFIG.DRAG_PAGE_MOVABLE) {
+ var first = $(this).data("dragBox_first"),
+ eventOffset, width, height, x, y, envelope, guide, elements = [];
+ renderer.removeRubberBand(rootEle);
+ if (first) {
+ eventOffset = me._getOffset(event);
+ width = eventOffset.x - first.x;
+ height = eventOffset.y - first.y;
+ x = width <= 0 ? first.x + width : first.x;
+ y = height <= 0 ? first.y + height : first.y;
+ envelope = new OG.Envelope([x, y], Math.abs(width), Math.abs(height));
+
+ $.each(renderer.getAllShapes(), function (index, element) {
+ if (!element.shape.geom) {
+ return;
+ }
+ if (!envelope.isContainsAll(element.shape.geom.getVertices())) {
+ return;
+ }
+ if (renderer.isEdge(element)) {
+ return;
+ }
+ elements.push(element);
+ });
+ me.selectShapes(elements);
+ $(this).data("dragBox", {"width": width, "height": height, "x": x, "y": y});
+ }
+ $(this).data("rubber_band_status", "none");
+ }
+ });
+
+ $(rootEle).bind("contextmenu", function (event) {
+ renderer.removeRubberBand(rootEle);
+ });
+ } else {
+ $(rootEle).unbind("mousedown");
+ $(rootEle).unbind("mousemove");
+ $(rootEle).unbind("mouseup");
+ $(rootEle).unbind("contextmenu");
+ }
+ },
+
+ /**
+ * HotKey 사용 가능여부를 설정한다. (Delete, Ctrl+A, Ctrl+C, Ctrl+V, Ctrl+G, Ctrl+U)
+ *
+ * @param {Boolean} isEnableHotKey 핫키가능여부
+ */
+ setEnableHotKey: function (isEnableHotKey) {
+ var me = this;
+ var renderer = me._RENDERER;
+ if (isEnableHotKey === true) {
+ // delete, ctrl+A
+ var _container;
+ if (me._CONFIG.FOCUS_CANVAS_ONSELECT) {
+ _container = $(renderer.getContainer());
+ } else {
+ _container = $(document);
+ }
+ _container.bind("keydown", function (event) {
+ // 라벨수정중엔 keydown 이벤트무시
+ if (!/^textarea$/i.test(event.target.tagName) && !/^input$/i.test(event.target.tagName)) {
+ // Undo Redo
+ if (me._CONFIG.ENABLE_HOTKEY_CTRL_Z && (event.ctrlKey || event.metaKey) && event.keyCode === KeyEvent.DOM_VK_Z) {
+ if (event.shiftKey) {
+ event.preventDefault();
+ renderer.redo();
+ } else {
+ event.preventDefault();
+ renderer.undo();
+ }
+ }
+
+ // Delete : 삭제
+ //if (me._CONFIG.ENABLE_HOTKEY_DELETE && (event.keyCode === KeyEvent.DOM_VK_DELETE || event.keyCode === KeyEvent.DOM_VK_BACK_SPACE)) {
+ // event.preventDefault();
+ // me.deleteSelectedShape();
+ //}
+
+ // Ctrl+A : 전체선택
+ if (me._CONFIG.ENABLE_HOTKEY_CTRL_A && me._CONFIG.SELECTABLE && (event.ctrlKey || event.metaKey) && event.keyCode === KeyEvent.DOM_VK_A) {
+ event.preventDefault();
+ me.selectAll();
+ }
+
+ // Ctrl+C : 복사
+ if (me._CONFIG.ENABLE_HOTKEY_CTRL_C && (event.ctrlKey || event.metaKey) && event.keyCode === KeyEvent.DOM_VK_C) {
+ event.preventDefault();
+ me.copySelectedShape();
+ }
+
+ // Ctrl+X : 잘라내기
+ if (me._CONFIG.ENABLE_HOTKEY_CTRL_C && (event.ctrlKey || event.metaKey) && event.keyCode === KeyEvent.DOM_VK_X) {
+ event.preventDefault();
+ me.cutSelectedShape();
+ }
+
+ // Ctrl+V: 붙여넣기
+ if (me._CONFIG.ENABLE_HOTKEY_CTRL_V && (event.ctrlKey || event.metaKey) && event.keyCode === KeyEvent.DOM_VK_V) {
+ event.preventDefault();
+ me.pasteSelectedShape();
+ }
+
+ // Ctrl+D: 복제하기
+ if (me._CONFIG.ENABLE_HOTKEY_CTRL_D && (event.ctrlKey || event.metaKey) && event.keyCode === KeyEvent.DOM_VK_D) {
+ event.preventDefault();
+ me.duplicateSelectedShape();
+ }
+
+ // Ctrl+G : 그룹
+ if (me._CONFIG.ENABLE_HOTKEY_CTRL_G && (event.ctrlKey || event.metaKey) && event.keyCode === KeyEvent.DOM_VK_G) {
+ event.preventDefault();
+ me.groupSelectedShape();
+ }
+
+ // Ctrl+U : 언그룹
+ if (me._CONFIG.ENABLE_HOTKEY_CTRL_U && (event.ctrlKey || event.metaKey) && event.keyCode === KeyEvent.DOM_VK_U) {
+ event.preventDefault();
+ me.ungroupSelectedShape();
+ }
+
+ if (me._CONFIG.ENABLE_HOTKEY_SHIFT_ARROW) {
+ // Shift+화살표 : 이동
+ if (event.shiftKey && event.keyCode === KeyEvent.DOM_VK_LEFT) {
+ event.preventDefault();
+ me._moveElements(me._getMoveTargets(), -1 * (me._CONFIG.DRAG_GRIDABLE ? (me._CONFIG.MOVE_SNAP_SIZE / 2) : 1), 0);
+ renderer.addHistory();
+ }
+ if (event.shiftKey && event.keyCode === KeyEvent.DOM_VK_RIGHT) {
+ event.preventDefault();
+ me._moveElements(me._getMoveTargets(), (me._CONFIG.DRAG_GRIDABLE ? (me._CONFIG.MOVE_SNAP_SIZE / 2) : 1), 0);
+ renderer.addHistory();
+ }
+ if (event.shiftKey && event.keyCode === KeyEvent.DOM_VK_UP) {
+ event.preventDefault();
+ me._moveElements(me._getMoveTargets(), 0, -1 * (me._CONFIG.DRAG_GRIDABLE ? (me._CONFIG.MOVE_SNAP_SIZE / 2) : 1));
+ renderer.addHistory();
+ }
+ if (event.shiftKey && event.keyCode === KeyEvent.DOM_VK_DOWN) {
+ event.preventDefault();
+ me._moveElements(me._getMoveTargets(), 0, (me._CONFIG.DRAG_GRIDABLE ? (me._CONFIG.MOVE_SNAP_SIZE / 2) : 1));
+ renderer.addHistory();
+ }
+ }
+ if (me._CONFIG.ENABLE_HOTKEY_ARROW) {
+ // 화살표 : 이동
+ if (!event.shiftKey && event.keyCode === KeyEvent.DOM_VK_LEFT) {
+ event.preventDefault();
+ me._moveElements(me._getMoveTargets(), -1 * (me._CONFIG.MOVE_SNAP_SIZE / 2), 0);
+ me.selectShapes(me._getSelectedElement());
+ renderer.addHistory();
+ }
+ if (!event.shiftKey && event.keyCode === KeyEvent.DOM_VK_RIGHT) {
+ event.preventDefault();
+ me._moveElements(me._getMoveTargets(), (me._CONFIG.MOVE_SNAP_SIZE / 2), 0);
+ me.selectShapes(me._getSelectedElement());
+ renderer.addHistory();
+ }
+ if (!event.shiftKey && event.keyCode === KeyEvent.DOM_VK_UP) {
+ event.preventDefault();
+ me._moveElements(me._getMoveTargets(), 0, -1 * (me._CONFIG.MOVE_SNAP_SIZE / 2));
+ me.selectShapes(me._getSelectedElement());
+ renderer.addHistory();
+ }
+ if (!event.shiftKey && event.keyCode === KeyEvent.DOM_VK_DOWN) {
+ event.preventDefault();
+ me._moveElements(me._getMoveTargets(), 0, (me._CONFIG.MOVE_SNAP_SIZE / 2));
+ me.selectShapes(me._getSelectedElement());
+ renderer.addHistory();
+ }
+ }
+ }
+ });
+ } else {
+ $(renderer.getContainer()).unbind("keydown");
+ }
+ },
+
+ /**
+ * 캔버스에 마우스 우클릭 메뉴를 가능하게 한다.
+ */
+ enableRootContextMenu: function () {
+ var me = this;
+ var renderer = me._RENDERER;
+
+ $.contextMenu({
+ position: function (opt, x, y) {
+ opt.$menu.css({top: y + 10, left: x + 10});
+ },
+ selector: '#' + me._RENDERER.getRootElement().id,
+ build: function ($trigger, e) {
+ var root = me._RENDERER.getRootGroup(), copiedElement = $(root).data("copied");
+ if (me._CONFIG.FOCUS_CANVAS_ONSELECT) {
+ $(me._RENDERER.getContainer()).focus();
+ }
+ return {
+ items: {
+ 'selectAll': {
+ name: '모두 선택', callback: function () {
+ me.selectAll();
+ }
+ },
+ 'sep1': '---------',
+ 'paste': {
+ name: '붙여넣기', callback: function () {
+ me.pasteSelectedShape(e);
+ },
+ disabled: (copiedElement ? false : true)
+ },
+ 'sep2': '---------',
+ 'view': {
+ name: '스케일',
+ items: {
+ 'view_actualSize': {
+ name: '실제 사이즈', callback: function () {
+ me._RENDERER.setScale(1);
+ renderer.addHistory();
+ }
+ },
+ 'sep2_1': '---------',
+ 'view_fitWindow': {
+ name: '윈도우에 맞추기', callback: function () {
+ me.fitWindow();
+ }
+ },
+ 'sep2_2': '---------',
+ 'view_25': {
+ name: '25%', callback: function () {
+ me._RENDERER.setScale(0.25);
+ renderer.addHistory();
+ }
+ },
+ 'view_50': {
+ name: '50%', callback: function () {
+ me._RENDERER.setScale(0.5);
+ renderer.addHistory();
+ }
+ },
+ 'view_75': {
+ name: '75%', callback: function () {
+ me._RENDERER.setScale(0.75);
+ renderer.addHistory();
+ }
+ },
+ 'view_100': {
+ name: '100%', callback: function () {
+ me._RENDERER.setScale(1);
+ renderer.addHistory();
+ }
+ },
+ 'view_150': {
+ name: '150%', callback: function () {
+ me._RENDERER.setScale(1.5);
+ renderer.addHistory();
+ }
+ },
+ 'view_200': {
+ name: '200%', callback: function () {
+ me._RENDERER.setScale(2);
+ renderer.addHistory();
+ }
+ },
+ 'view_300': {
+ name: '300%', callback: function () {
+ me._RENDERER.setScale(3);
+ renderer.addHistory();
+ }
+ },
+ 'view_400': {
+ name: '400%', callback: function () {
+ me._RENDERER.setScale(4);
+ renderer.addHistory();
+ }
+ },
+ 'sep2_3': '---------',
+ 'view_zoomin': {
+ name: '확대', callback: function () {
+ me.zoomIn();
+ }
+ },
+ 'view_zoomout': {
+ name: '축소', callback: function () {
+ me.zoomOut();
+ }
+ }
+ }
+ }
+ }
+ };
+ }
+ });
+ },
+
+ makeRotate: function () {
+ var me = this;
+
+ return {
+ 'rotate': {
+ name: '회전',
+ items: {
+ 'rotate_select': {
+ name: '선택',
+ type: 'select',
+ options: {
+ '0': '0',
+ '45': '45',
+ '90': '90',
+ '135': '135',
+ '180': '180',
+ '-45': '-45',
+ '-90': '-90',
+ '-135': '-135',
+ '-180': '-180'
+ },
+ selected: '0',
+ events: {
+ change: function (e) {
+ me.rotateSelectedShape(e.target.value);
+ }
+ }
+ },
+ 'sep8_8_1': '---------',
+ 'rotate_custom': {
+ name: '직접입력',
+ type: 'text',
+ events: {
+ keyup: function (e) {
+ if (e.target.value !== '') {
+ me.rotateSelectedShape(e.target.value);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+
+ makeFillColor: function () {
+ var me = this;
+
+ return {
+ 'fillColor': {
+ name: '색상',
+ items: {
+ 'fillColor_select': {
+ name: '선택',
+ type: 'select',
+ options: {
+ '': '',
+ 'white': '하양',
+ 'gray': '회색',
+ 'blue': '파랑',
+ 'red': '빨강',
+ 'yellow': '노랑',
+ 'orange': '오렌지',
+ 'green': '녹색',
+ 'black': '검정'
+ },
+ selected: '',
+ events: {
+ change: function (e) {
+ if (e.target.value !== '') {
+ me.setFillColorSelectedShape(e.target.value);
+ }
+ }
+ }
+ },
+ 'sep5_1_1': '---------',
+ 'fillColor_custom': {
+ name: '직접입력',
+ type: 'text',
+ events: {
+ keyup: function (e) {
+ if (e.target.value !== '') {
+ me.setFillColorSelectedShape(e.target.value);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+
+ makeFillOpacity: function () {
+ var me = this;
+
+ return {
+ 'fillOpacity': {
+ name: '투명도',
+ items: {
+ 'fillOpacity_select': {
+ name: '선택',
+ type: 'select',
+ options: {
+ '': '',
+ '0.0': '0%',
+ '0.1': '10%',
+ '0.2': '20%',
+ '0.3': '30%',
+ '0.4': '40%',
+ '0.5': '50%',
+ '0.6': '60%',
+ '0.7': '70%',
+ '0.8': '80%',
+ '0.9': '90%',
+ '1.0': '100%'
+ },
+ selected: '',
+ events: {
+ change: function (e) {
+ if (e.target.value !== '') {
+ me.setFillOpacitySelectedShape(e.target.value);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+
+ makeLineStyle: function () {
+ var me = this;
+
+ return {
+ 'lineStyle': {
+ name: '선 스타일',
+ items: {
+ 'lineStyle_1': {
+ name: '──────',
+ type: 'radio',
+ radio: 'lineStyle',
+ value: '',
+ events: {
+ change: function (e) {
+ me.setLineStyleSelectedShape(e.target.value);
+ }
+ }
+ },
+ 'lineStyle_2': {
+ name: '---------',
+ type: 'radio',
+ radio: 'lineStyle',
+ value: '-',
+ events: {
+ change: function (e) {
+ me.setLineStyleSelectedShape(e.target.value);
+ }
+ }
+ },
+ 'lineStyle_3': {
+ name: '············',
+ type: 'radio',
+ radio: 'lineStyle',
+ value: '.',
+ events: {
+ change: function (e) {
+ me.setLineStyleSelectedShape(e.target.value);
+ }
+ }
+ },
+ 'lineStyle_4': {
+ name: '-·-·-·-·-·',
+ type: 'radio',
+ radio: 'lineStyle',
+ value: '-.',
+ events: {
+ change: function (e) {
+ me.setLineStyleSelectedShape(e.target.value);
+ }
+ }
+ },
+ 'lineStyle_5': {
+ name: '-··-··-··-',
+ type: 'radio',
+ radio: 'lineStyle',
+ value: '-..',
+ events: {
+ change: function (e) {
+ me.setLineStyleSelectedShape(e.target.value);
+ }
+ }
+ },
+ 'lineStyle_6': {
+ name: '· · · · · ·',
+ type: 'radio',
+ radio: 'lineStyle',
+ value: '. ',
+ events: {
+ change: function (e) {
+ me.setLineStyleSelectedShape(e.target.value);
+ }
+ }
+ },
+ 'lineStyle_7': {
+ name: '- - - - -',
+ type: 'radio',
+ radio: 'lineStyle',
+ value: '- ',
+ events: {
+ change: function (e) {
+ me.setLineStyleSelectedShape(e.target.value);
+ }
+ }
+ },
+ 'lineStyle_8': {
+ name: '─ ─ ─ ─',
+ type: 'radio',
+ radio: 'lineStyle',
+ value: '--',
+ events: {
+ change: function (e) {
+ me.setLineStyleSelectedShape(e.target.value);
+ }
+ }
+ },
+ 'lineStyle_9': {
+ name: '- ·- ·- ·-',
+ type: 'radio',
+ radio: 'lineStyle',
+ value: '- .',
+ events: {
+ change: function (e) {
+ me.setLineStyleSelectedShape(e.target.value);
+ }
+ }
+ },
+ 'lineStyle_10': {
+ name: '--·--·--·-',
+ type: 'radio',
+ radio: 'lineStyle',
+ value: '--.',
+ events: {
+ change: function (e) {
+ me.setLineStyleSelectedShape(e.target.value);
+ }
+ }
+ },
+ 'lineStyle_11': {
+ name: '--··--··--',
+ type: 'radio',
+ radio: 'lineStyle',
+ value: '--..',
+ events: {
+ change: function (e) {
+ me.setLineStyleSelectedShape(e.target.value);
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+
+ makeLineColor: function () {
+ var me = this;
+
+ return {
+ 'lineColor': {
+ name: '선 색상',
+ items: {
+ 'lineColor_select': {
+ name: '석택',
+ type: 'select',
+ options: {
+ '': '',
+ 'white': '하양',
+ 'gray': '회색',
+ 'blue': '파랑',
+ 'red': '빨강',
+ 'yellow': '노랑',
+ 'orange': '오렌지',
+ 'green': '녹색',
+ 'black': '검정'
+ },
+ selected: '',
+ events: {
+ change: function (e) {
+ if (e.target.value !== '') {
+ me.setLineColorSelectedShape(e.target.value);
+ }
+ }
+ }
+ },
+ 'sep5_4_1': '---------',
+ 'lineColor_custom': {
+ name: '직접입력',
+ type: 'text',
+ events: {
+ keyup: function (e) {
+ if (e.target.value !== '') {
+ me.setLineColorSelectedShape(e.target.value);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+
+ makeLineWidth: function () {
+ var me = this;
+
+ return {
+ 'lineWidth': {
+ name: '선 두께',
+ items: {
+ 'lineWidth_select': {
+ name: '선택',
+ type: 'select',
+ options: {
+ 0: '',
+ 1: '1px',
+ 2: '2px',
+ 3: '3px',
+ 4: '4px',
+ 5: '5px',
+ 6: '6px',
+ 8: '8px',
+ 10: '10px',
+ 12: '12px',
+ 16: '16px',
+ 24: '24px'
+ },
+ selected: 0,
+ events: {
+ change: function (e) {
+ if (e.target.value !== 0) {
+ me.setLineWidthSelectedShape(e.target.value);
+ }
+ }
+ }
+ },
+ 'sep5_5_1': '---------',
+ 'lineWidth_custom': {
+ name: '직접입력',
+ type: 'text',
+ events: {
+ keyup: function (e) {
+ if (e.target.value !== '') {
+ me.setLineWidthSelectedShape(e.target.value);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+
+ makeFont: function () {
+ var me = this;
+
+ return {
+ 'text': {
+ name: '글꼴',
+ items: {
+ 'fontFamily': {
+ name: '폰트',
+ items: {
+ 'fontFamily_1': {
+ name: 'Arial',
+ type: 'radio',
+ radio: 'fontFamily',
+ value: 'Arial',
+ events: {
+ change: function (e) {
+ me.setFontFamilySelectedShape(e.target.value);
+ }
+ }
+ },
+ 'fontFamily_2': {
+ name: 'Comic Sans MS',
+ type: 'radio',
+ radio: 'fontFamily',
+ value: 'Comic Sans MS',
+ events: {
+ change: function (e) {
+ me.setFontFamilySelectedShape(e.target.value);
+ }
+ }
+ },
+ 'fontFamily_3': {
+ name: 'Courier New',
+ type: 'radio',
+ radio: 'fontFamily',
+ value: 'Courier New',
+ events: {
+ change: function (e) {
+ me.setFontFamilySelectedShape(e.target.value);
+ }
+ }
+ },
+ 'fontFamily_4': {
+ name: 'Garamond',
+ type: 'radio',
+ radio: 'fontFamily',
+ value: 'Garamond',
+ events: {
+ change: function (e) {
+ me.setFontFamilySelectedShape(e.target.value);
+ }
+ }
+ },
+ 'fontFamily_5': {
+ name: 'Georgia',
+ type: 'radio',
+ radio: 'fontFamily',
+ value: 'Georgia',
+ events: {
+ change: function (e) {
+ me.setFontFamilySelectedShape(e.target.value);
+ }
+ }
+ },
+ 'fontFamily_6': {
+ name: 'Lucida Console',
+ type: 'radio',
+ radio: 'fontFamily',
+ value: 'Lucida Console',
+ events: {
+ change: function (e) {
+ me.setFontFamilySelectedShape(e.target.value);
+ }
+ }
+ },
+ 'fontFamily_7': {
+ name: 'MS Gothic',
+ type: 'radio',
+ radio: 'fontFamily',
+ value: 'MS Gothic',
+ events: {
+ change: function (e) {
+ me.setFontFamilySelectedShape(e.target.value);
+ }
+ }
+ },
+ 'fontFamily_8': {
+ name: 'MS Sans Serif',
+ type: 'radio',
+ radio: 'fontFamily',
+ value: 'MS Sans Serif',
+ events: {
+ change: function (e) {
+ me.setFontFamilySelectedShape(e.target.value);
+ }
+ }
+ },
+ 'fontFamily_9': {
+ name: 'Verdana',
+ type: 'radio',
+ radio: 'fontFamily',
+ value: 'Verdana',
+ events: {
+ change: function (e) {
+ me.setFontFamilySelectedShape(e.target.value);
+ }
+ }
+ },
+ 'fontFamily_10': {
+ name: 'Times New Roman',
+ type: 'radio',
+ radio: 'fontFamily',
+ value: 'Times New Roman',
+ events: {
+ change: function (e) {
+ me.setFontFamilySelectedShape(e.target.value);
+ }
+ }
+ },
+ 'sep6_1_1': '---------',
+ 'fontFamily_custom': {
+ name: 'Custom',
+ type: 'text',
+ events: {
+ keyup: function (e) {
+ if (e.target.value !== '') {
+ me.setFontFamilySelectedShape(e.target.value);
+ }
+ }
+ }
+ }
+ }
+ },
+ 'fontColor': {
+ name: '글 색상',
+ items: {
+ 'fontColor_select': {
+ name: '선택',
+ type: 'select',
+ options: {
+ '': '',
+ 'white': '하양',
+ 'gray': '회색',
+ 'blue': '파랑',
+ 'red': '빨강',
+ 'yellow': '노랑',
+ 'orange': '오렌지',
+ 'green': '녹색',
+ 'black': '검정'
+ },
+ selected: '',
+ events: {
+ change: function (e) {
+ if (e.target.value !== '') {
+ me.setFontColorSelectedShape(e.target.value);
+ }
+ }
+ }
+ },
+ 'sep6_1_2': '---------',
+ 'fontColor_custom': {
+ name: '직접입력',
+ type: 'text',
+ events: {
+ keyup: function (e) {
+ if (e.target.value !== '') {
+ me.setFontColorSelectedShape(e.target.value);
+ }
+ }
+ }
+ }
+ }
+ },
+ 'fontSize': {
+ name: '글 크기',
+ items: {
+ 'fontSize_select': {
+ name: '선택',
+ type: 'select',
+ options: {
+ '': '',
+ '6': '6',
+ '8': '8',
+ '9': '9',
+ '10': '10',
+ '11': '11',
+ '12': '12',
+ '14': '14',
+ '18': '18',
+ '24': '24',
+ '36': '36',
+ '48': '48',
+ '72': '72'
+ },
+ selected: '',
+ events: {
+ change: function (e) {
+ if (e.target.value !== '') {
+ me.setFontSizeSelectedShape(e.target.value);
+ }
+ }
+ }
+ },
+ 'sep6_1_3': '---------',
+ 'fontSize_custom': {
+ name: '직접입력',
+ type: 'text',
+ events: {
+ keyup: function (e) {
+ if (e.target.value !== '') {
+ me.setFontSizeSelectedShape(e.target.value);
+ }
+ }
+ }
+ }
+ }
+ },
+ 'sep6_1': '---------',
+ 'fontWeight_bold': {
+ name: '굵게',
+ type: 'checkbox',
+ events: {
+ change: function (e) {
+ if (e.target.checked) {
+ me.setFontWeightSelectedShape('bold');
+ } else {
+ me.setFontWeightSelectedShape('normal');
+ }
+ }
+ }
+ },
+ 'fontWeight_italic': {
+ name: '이탤릭',
+ type: 'checkbox',
+ events: {
+ change: function (e) {
+ if (e.target.checked) {
+ me.setFontStyleSelectedShape('italic');
+ } else {
+ me.setFontStyleSelectedShape('normal');
+ }
+ }
+ }
+ },
+ 'sep6_2': '---------',
+ 'position': {
+ name: '글 위치',
+ items: {
+ 'position_left': {
+ name: '왼쪽',
+ type: 'radio',
+ radio: 'position',
+ value: 'left',
+ events: {
+ change: function (e) {
+ me.setLabelPositionSelectedShape(e.target.value);
+ }
+ }
+ },
+ 'position_center': {
+ name: '가운데',
+ type: 'radio',
+ radio: 'position',
+ value: 'center',
+ events: {
+ change: function (e) {
+ me.setLabelPositionSelectedShape(e.target.value);
+ }
+ }
+ },
+ 'position_right': {
+ name: '오른쪽',
+ type: 'radio',
+ radio: 'position',
+ value: 'right',
+ events: {
+ change: function (e) {
+ me.setLabelPositionSelectedShape(e.target.value);
+ }
+ }
+ },
+ 'position_top': {
+ name: '위',
+ type: 'radio',
+ radio: 'position',
+ value: 'top',
+ events: {
+ change: function (e) {
+ me.setLabelPositionSelectedShape(e.target.value);
+ }
+ }
+ },
+ 'position_bottom': {
+ name: '아래',
+ type: 'radio',
+ radio: 'position',
+ value: 'bottom',
+ events: {
+ change: function (e) {
+ me.setLabelPositionSelectedShape(e.target.value);
+ }
+ }
+ }
+ }
+ },
+ 'vertical': {
+ name: '수직 정렬',
+ items: {
+ 'vertical_top': {
+ name: '위',
+ type: 'radio',
+ radio: 'vertical',
+ value: 'top',
+ events: {
+ change: function (e) {
+ me.setLabelVerticalSelectedShape(e.target.value);
+ }
+ }
+ },
+ 'vertical_middle': {
+ name: '가운데',
+ type: 'radio',
+ radio: 'vertical',
+ value: 'middle',
+ events: {
+ change: function (e) {
+ me.setLabelVerticalSelectedShape(e.target.value);
+ }
+ }
+ },
+ 'vertical_bottom': {
+ name: '아래',
+ type: 'radio',
+ radio: 'vertical',
+ value: 'bottom',
+ events: {
+ change: function (e) {
+ me.setLabelVerticalSelectedShape(e.target.value);
+ }
+ }
+ }
+ }
+ },
+ 'horizontal': {
+ name: '수평 정렬',
+ items: {
+ 'vertical_start': {
+ name: '왼쪽',
+ type: 'radio',
+ radio: 'horizontal',
+ value: 'start',
+ events: {
+ change: function (e) {
+ me.setLabelHorizontalSelectedShape(e.target.value);
+ }
+ }
+ },
+ 'horizontal_middle': {
+ name: '가운데',
+ type: 'radio',
+ radio: 'horizontal',
+ value: 'middle',
+ events: {
+ change: function (e) {
+ me.setLabelHorizontalSelectedShape(e.target.value);
+ }
+ }
+ },
+ 'horizontal_end': {
+ name: '오른쪽',
+ type: 'radio',
+ radio: 'horizontal',
+ value: 'end',
+ events: {
+ change: function (e) {
+ me.setLabelHorizontalSelectedShape(e.target.value);
+ }
+ }
+ }
+ }
+ },
+ 'sep6_5': '---------',
+ 'textRotate': {
+ name: '글 회전각',
+ items: {
+ 'textRotate_select': {
+ name: '선택',
+ type: 'select',
+ options: {
+ '0': '0',
+ '45': '45',
+ '90': '90',
+ '135': '135',
+ '180': '180',
+ '-45': '-45',
+ '-90': '-90',
+ '-135': '-135',
+ '-180': '-180'
+ },
+ selected: '0',
+ events: {
+ change: function (e) {
+ me.setLabelAngleSelectedShape(e.target.value);
+ }
+ }
+ },
+ 'sep6_6_1': '---------',
+ 'textRotate_custom': {
+ name: '직접입력',
+ type: 'text',
+ events: {
+ keyup: function (e) {
+ if (e.target.value !== '') {
+ me.setLabelAngleSelectedShape(e.target.value);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+
+ makeBring: function () {
+ var me = this;
+
+ return {
+ 'bringToFront': {
+ name: '맨 앞으로 가져오기',
+ items: {
+ 'bringToFront': {
+ name: '맨 앞으로 가져오기', callback: function () {
+ me.bringToFront();
+ }
+ },
+ 'bringForward': {
+ name: '앞으로 가져오기', callback: function () {
+ me.bringForward();
+ }
+ }
+ }
+ }
+ }
+ },
+
+ makeSend: function () {
+ var me = this;
+
+ return {
+ 'sendToBack': {
+ name: '맨 뒤로 보내기',
+ items: {
+ 'sendToBack': {
+ name: '맨 뒤로 보내기', callback: function () {
+ me.sendToBack();
+ }
+ },
+ 'sendBackward': {
+ name: '뒤로 보내기', callback: function () {
+ me.sendBackward();
+ }
+ }
+ }
+ }
+ }
+ },
+
+
+ makeDelete: function () {
+ var me = this;
+
+ return {
+ 'delete': {
+ name: '삭제', callback: function () {
+ me.deleteSelectedShape();
+ }
+ }
+ }
+ },
+
+ makeCopy: function () {
+ var me = this;
+
+ return {
+ 'copy': {
+ name: '복사', callback: function () {
+ me.copySelectedShape();
+ }
+ }
+ }
+ },
+
+ makeAlign: function () {
+ var me = this;
+
+ return {
+ 'align': {
+ name: '도형 정렬',
+ items: {
+ 'Top': {
+ name: '위로정렬',
+ type: 'radio',
+ radio: 'align',
+ value: 'Top',
+ events: {
+ change: function (e) {
+ me._RENDERER.alignTop();
+ me._RENDERER.addHistory();
+ }
+ }
+ },
+ 'Left': {
+ name: '왼쪽정렬',
+ type: 'radio',
+ radio: 'align',
+ value: 'Left',
+ events: {
+ change: function (e) {
+ me._RENDERER.alignLeft();
+ me._RENDERER.addHistory();
+ }
+ }
+ },
+ 'Right': {
+ name: '오른쪽정렬',
+ type: 'radio',
+ radio: 'align',
+ value: 'Right',
+ events: {
+ change: function (e) {
+ me._RENDERER.alignRight();
+ me._RENDERER.addHistory();
+ }
+ }
+ },
+ 'Bottom': {
+ name: '아래로정렬',
+ type: 'radio',
+ radio: 'align',
+ value: "Bottom",
+ events: {
+ change: function (e) {
+ me._RENDERER.alignBottom();
+ me._RENDERER.addHistory();
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+
+ makeFormat: function () {
+ return {
+ format: {
+ name: '형식',
+ items: this.mergeContextMenu(
+ this.makeFillColor(),
+ this.makeFillOpacity(),
+ this.makeRotate(),
+ this.makeLineStyle(),
+ this.makeLineColor(),
+ this.makeLineWidth()
+ )
+ }
+ }
+ },
+
+ makeMultiContextMenu: function () {
+ return this.mergeContextMenu(
+ this.makeDelete(),
+ this.makeCopy(),
+ this.makeAlign()
+ );
+ },
+
+ mergeContextMenu: function () {
+ var menu = {};
+ for (var i = 0, leni = arguments.length; i < leni; i++) {
+ for (var key in arguments[i]) {
+ menu[key] = arguments[i][key];
+ }
+ }
+
+ return menu;
+ },
+
+ makeDefaultContextMenu: function (key) {
+ if (key) {
+ if (key == 'delete') {
+ return this.makeDelete();
+ }
+ else if (key == 'copy') {
+ return this.makeCopy();
+ }
+ else if (key == 'format') {
+ return this.makeFormat();
+ }
+ else if (key == 'text') {
+ return this.makeFont();
+ }
+ else if (key == 'bringToFront') {
+ return this.makeBring();
+ }
+ else if (key == 'sendToBack') {
+ return this.makeSend();
+ }
+ } else {
+ return this.mergeContextMenu(
+ this.makeDelete(),
+ this.makeCopy(),
+ this.makeFormat(),
+ this.makeFont(),
+ this.makeBring(),
+ this.makeSend()
+ );
+ }
+ },
+
+ /**
+ * Shape 에 마우스 우클릭 메뉴를 가능하게 한다.
+ */
+ enableShapeContextMenu: function () {
+ var me = this;
+ $.contextMenu({
+ position: function (opt, x, y) {
+ opt.$menu.css({top: y + 10, left: x + 10});
+ opt.canvas = me._CANVAS;
+ },
+ selector: '#' + me._RENDERER.getRootElement().id + ' [_type=SHAPE]',
+ build: function ($trigger, event) {
+ //1. shape 에 contextMenu 를 정의시 실행된다.
+
+ //2. canvas.setContextMenu 실행시 shape 의 콘텍스트에 오버라이드 된다.
+
+ //$trigger 실행시 shape 에 커스텀 컨텍스트 메뉴가 있을 경우, 그에 맞는 컨텍스트 메뉴를 빌드한다.
+
+ //default 컨텍스트 메뉴를 설정한다.
+
+ if (me._CONFIG.FOCUS_CANVAS_ONSELECT) {
+ $(me._RENDERER.getContainer()).focus();
+ }
+ var items = {};
+
+ if (me._getSelectedElement().length == 1) {
+ var defaultList = ['delete', 'copy', 'format', 'text', 'bringToFront', 'sendToBack'];
+ var eventShape = $trigger.get(0).shape;
+ //사용자가 지정한 customContextMenu 가 있다면 기본 createContextMenu 보다 우선한다.
+ var customMenu = eventShape.customContextMenu;
+ if (!customMenu) {
+ if (eventShape.createContextMenu) {
+ var eventOffset = me._getOffset(event);
+ customMenu = eventShape.createContextMenu();
+ }
+ }
+
+ //커스텀 콘텍스트 메뉴가 있을경우 처리
+ if (customMenu) {
+ if (customMenu == null || $.isEmptyObject(customMenu)) {
+ return false;
+ }
+ for (var key in customMenu) {
+ if (!customMenu[key]) {
+ continue;
+ }
+ //기본 메뉴인경우
+ if (defaultList.indexOf(key) != -1) {
+ items[key] = me.makeDefaultContextMenu()[key];
+ }
+ //기본 메뉴가 아닌경우
+ else {
+ items[key] = customMenu[key];
+ }
+ }
+ }
+ //커스텀 콘텍스트 메뉴가 없을경우 처리
+ else {
+ items = me.makeDefaultContextMenu();
+ }
+
+ } else {
+ items = me.makeMultiContextMenu();
+ }
+ return {
+ items: items
+ };
+ }
+ });
+ },
+
+ /**
+ * 주어진 Shape Element 를 선택된 상태로 되게 한다.
+ *
+ * @param {Element} element Shape 엘리먼트
+ */
+ selectShape: function (element, event, param) {
+ var me = this, guide, root = me._RENDERER.getRootGroup();
+
+ //단일 선택 다중 선택 여부 판단
+ if (event) {
+ if (param) {
+ if (!param.shiftKey && !param.ctrlKey) {
+ me.deselectAll();
+ me._RENDERER.removeAllGuide();
+ } else {
+ //no operation
+ }
+ } else {
+ if (!event.shiftKey && !event.ctrlKey) {
+ me.deselectAll();
+ me._RENDERER.removeAllGuide();
+ } else {
+ //no operation
+ }
+ }
+ } else {
+ //기본 단일 선택
+ me.deselectAll();
+ me._RENDERER.removeAllGuide();
+ }
+
+ if (me._isSelectable(element.shape)) {
+ //BUG : remove guide를 반드시 해주어야만 새로운 가이드가 null로 나오지 않는다.
+ me._RENDERER.removeGuide(element);
+ guide = me._RENDERER.drawGuide(element);
+ // enable event
+ me.setResizable(element, guide, me._isResizable(element.shape));
+ me.setConnectable(element, guide, me._isConnectable(element.shape));
+
+ //선택상태 설정
+ $(element).attr("_selected", "true");
+
+ //Edge 일 경우 상단으로
+ if (element.shape && element.shape instanceof OG.EdgeShape) {
+ me._RENDERER._CANVAS.toFront(element);
+ }
+
+ //선택요소배열 추가
+ me._addSelectedElement(element);
+ }
+ },
+
+ /**
+ * 주어진 다수의 Shape Element 를 선택된 상태로 되게 한다.
+ *
+ * @param {Element} element Shape 엘리먼트
+ */
+ selectShapes: function (elementArray) {
+ var me = this, guide, _element;
+
+ if (!elementArray) {
+ return;
+ } else {
+ //route selectShape
+ if (elementArray.length == 1) {
+ me.selectShape(elementArray[0]);
+ return;
+ }
+ }
+ me.deselectAll();
+ me._RENDERER.removeAllGuide();
+
+ $.each(elementArray, function (index, element) {
+ $(element).attr("_selected", "true");
+ guide = me._RENDERER.drawGuide(element);
+ if (guide) {
+ // enable event
+ me.setResizable(element, guide, me._isResizable(element.shape));
+ me.setConnectable(element, guide, me._isConnectable(element.shape));
+ }
+ me._addSelectedElement(element);
+ })
+ },
+
+ //TODO : 선택된 모든 Shape를 선택 해제
+ deselectShape: function (element) {
+ var me = this;
+ if (OG.Util.isElement(element) && element.id) {
+ $(element).attr("_selected", "");
+ me._RENDERER.removeGuide(element);
+
+ //선택요소배열 삭제
+ me._delSelectedElement(element);
+ }
+ },
+
+
+ deselectAll: function () {
+ var me = this;
+
+ var dragBox = $(this).data("dragBox");
+ if (me._CONFIG.FOCUS_CANVAS_ONSELECT) {
+ $(me._RENDERER.getContainer()).focus();
+ }
+ if (!dragBox || (dragBox && dragBox.width < 1 && dragBox.height < 1)) {
+ $(me._RENDERER.getRootElement())
+ .find("[_type=" + OG.Constants.NODE_TYPE.SHAPE + "][_selected=true]").each(
+ function (index, item) {
+ if (OG.Util.isElement(item) && item.id) {
+ $(item).attr("_selected", "");
+ me._RENDERER.removeGuide(item);
+ }
+ }
+ );
+
+ //선택요소배열 모두삭제 (초기화)
+ me._removeAllSelectedElement();
+ }
+ },
+
+ /**
+ * 메뉴 : 맨 앞으로 가져오기
+ */
+ bringToFront: function () {
+ var me = this, root = $(me._RENDERER.getRootGroup());
+ $(me._RENDERER.getRootElement()).find("[_selected=true]").each(function (index, item) {
+ var moveTarget = item;
+ if (me._RENDERER.isLane(item)) {
+ moveTarget = me._RENDERER.getRootLane(item);
+ }
+ root[0].appendChild(moveTarget);
+ me.selectShape(item);
+ });
+ me._RENDERER.addHistory();
+ },
+
+ /**
+ * 메뉴 : 맨 뒤로 보내기
+ */
+ sendToBack: function () {
+ var me = this, root = $(me._RENDERER.getRootGroup());
+ $(me._RENDERER.getRootElement()).find("[_selected=true]").each(function (index, item) {
+ var moveTarget = item;
+ if (me._RENDERER.isLane(item)) {
+ moveTarget = me._RENDERER.getRootLane(item);
+ }
+ root[0].insertBefore(moveTarget, OG.Util.isIE() ? root[0].childNodes[0] : root[0].children[0]);
+ me.selectShape(item);
+ });
+ me._RENDERER.addHistory();
+ },
+
+ /**
+ * 메뉴 : 앞으로 가져오기
+ */
+ bringForward: function () {
+ var me = this, root = $(me._RENDERER.getRootGroup());
+ $(me._RENDERER.getRootElement()).find("[_selected=true]").each(function (index, item) {
+ var moveTarget = item;
+ if (me._RENDERER.isLane(item)) {
+ moveTarget = me._RENDERER.getRootLane(item);
+ }
+ var length = $(moveTarget).prevAll().length;
+ root[0].insertBefore(moveTarget, OG.Util.isIE() ? root[0].childNodes[length + 1] : root[0].children[length + 1]);
+ });
+ me._RENDERER.addHistory();
+ },
+
+ /**
+ * 메뉴 : 뒤로 보내기
+ */
+ sendBackward: function () {
+ var me = this, root = $(me._RENDERER.getRootGroup());
+ $(me._RENDERER.getRootElement()).find("[_selected=true]").each(function (index, item) {
+ var moveTarget = item;
+ if (me._RENDERER.isLane(item)) {
+ moveTarget = me._RENDERER.getRootLane(item);
+ }
+ var length = $(moveTarget).prevAll().length;
+ root[0].insertBefore(moveTarget, OG.Util.isIE() ? root[0].childNodes[length - 2] : root[0].children[length - 2]);
+ me.selectShape(item);
+ });
+ me._RENDERER.addHistory();
+ },
+
+ /**
+ * 메뉴 : 선택된 Shape 들을 삭제한다.
+ */
+ deleteSelectedShape: function (event) {
+
+ var me = this;
+ $(me._RENDERER.getRootElement()).find("[_type=" + OG.Constants.NODE_TYPE.SHAPE + "][_shape=EDGE][_selected=true]").each(function (index, item) {
+ if (item.id) {
+ me._RENDERER.removeShape(item);
+ }
+ });
+ $(me._RENDERER.getRootElement()).find("[_type=" + OG.Constants.NODE_TYPE.SHAPE + "][_selected=true]").each(function (index, item) {
+ if (item.id) {
+ if (me._RENDERER.isLane(item)) {
+ me._RENDERER.removeLaneShape(item);
+ } else {
+ me._RENDERER.removeShape(item);
+ }
+
+ }
+ });
+ me._RENDERER.addHistory();
+ },
+
+ /**
+ * 메뉴 : Shape를 선택한 모양으로 변경한다.
+ */
+ changeShape: function (value, label) {
+ var me = this, geometry, position, width, height, shape;
+
+ $(me._RENDERER.getRootElement()).find("[_selected=true]").each(function (index, item) {
+ shape = eval('new ' + value + '()');
+
+ shape.currentCanvas = me._RENDERER._CANVAS
+ if (label) {
+ shape.label = label;
+ } else {
+ shape.label = undefined;
+ }
+
+ if (shape instanceof OG.EdgeShape) {
+ geometry = shape.createShape();
+ geometry.vertices = item.shape.geom.vertices;
+ shape.geom = geometry;
+ } else {
+ position = [item.shape.geom.boundary.getCentroid().x, item.shape.geom.boundary.getCentroid().y];
+ width = item.shape.geom.boundary.getWidth();
+ height = item.shape.geom.boundary.getHeight();
+ geometry = shape.createShape();
+
+ // 좌상단으로 이동 및 크기 조정
+ geometry.moveCentroid(position);
+ geometry.resizeBox(width, height);
+ shape.geom = geometry;
+ }
+
+ //데이터 복제
+ shape.setData(JSON.parse(JSON.stringify(item.shape.getData())));
+
+ //shape 등록
+ item.shape = shape;
+
+ me._RENDERER.redrawShape(item);
+
+ var type;
+ if (value == 'OG.shape.bpmn.A_Task') {
+ type = "Abstract";
+ } else if (value == 'OG.shape.bpmn.A_HumanTask') {
+ type = "Human";
+ } else if (value == 'OG.shape.bpmn.A_WebServiceTask') {
+ type = "Service";
+ } else if (value == 'OG.shape.bpmn.A_ManualTask') {
+ type = "Manual";
+ }
+
+ $(item).trigger("changeTo" + type);
+ });
+ me._RENDERER.addHistory();
+ },
+
+
+ /**
+ * 메뉴 : 속성 창 이벤트
+ */
+ showProperty: function (event) {
+ var me = this;
+ $(me._RENDERER.getRootElement()).find("[_selected=true]").each(function (index, item) {
+ $(item).trigger('property');
+ });
+ },
+
+ /**
+ * 메뉴 : 모든 Shape 들을 선택한다.
+ */
+ selectAll: function () {
+ var me = this;
+ var elements = [];
+ $(me._RENDERER.getRootElement()).find("[_type=" + OG.Constants.NODE_TYPE.SHAPE + "]").each(function (index, element) {
+ elements.push(element);
+ });
+ me.selectShapes(elements);
+ },
+
+ /**
+ * 메뉴 : 선택된 Shape 들을 복사한다.
+ */
+ copySelectedShape: function () {
+ var me = this, root = me._RENDERER.getRootGroup(), selectedElement = [];
+ $(me._RENDERER.getRootElement()).find("[_type=" + OG.Constants.NODE_TYPE.SHAPE + "][_selected=true]").each(function (index, element) {
+ if (element.shape.COPYABLE) {
+ selectedElement.push(element);
+ }
+ });
+ $(root).data("copied", selectedElement);
+ },
+
+ /**
+ * 메뉴 : 선택된 Shape 들을 잘라내기한다.
+ */
+ cutSelectedShape: function () {
+ var me = this;
+ me.copySelectedShape();
+ me.deleteSelectedShape();
+ },
+
+ /**
+ * 메뉴 : 선택된 Shape 들을 붙여넣기 한다.
+ */
+ pasteSelectedShape: function (e) {
+ var me = this;
+ var renderer = me._RENDERER;
+ var root = renderer.getRootGroup(),
+ copiedElement = $(root).data("copied"),
+ selectedElement = [], dx, dy, avgX = 0, avgY = 0,
+ copiedMap = [];
+ if (copiedElement) {
+ $(renderer.getRootElement()).find("[_type=" + OG.Constants.NODE_TYPE.SHAPE + "][_selected=true]").each(function (index, item) {
+ if (item.id) {
+ renderer.removeGuide(item);
+ }
+ });
+
+ $.each(copiedElement, function (idx, item) {
+ avgX += item.shape.geom.getBoundary().getCentroid().x;
+ avgY += item.shape.geom.getBoundary().getCentroid().y;
+ });
+
+ avgX = avgX / (copiedElement.length);
+ avgY = avgY / (copiedElement.length);
+
+ $.each(copiedElement, function (idx, item) {
+ // copy
+ var boundary = item.shape.geom.getBoundary(), newShape, newElement, newGuide;
+ newShape = item.shape.clone();
+
+ if ($(item).attr("_shape") === OG.Constants.SHAPE_TYPE.EDGE) {
+ if (item.shape.geom instanceof OG.geometry.BezierCurve) {
+ newShape.geom = new OG.BezierCurve(item.shape.geom.getControlPoints());
+ } else {
+ newShape.geom = new OG.PolyLine(item.shape.geom.getVertices());
+ }
+ newShape.geom.style = item.shape.geom.style;
+ newShape.geom.move(me._CONFIG.COPY_PASTE_PADDING, me._CONFIG.COPY_PASTE_PADDING);
+ newElement = renderer.drawShape(
+ null, newShape,
+ null, item.shapeStyle
+ );
+
+ } else {
+ if (e) {
+ dx = e.offsetX - avgX;
+ dy = e.offsetY - avgY;
+ newElement = renderer.drawShape(
+ [boundary.getCentroid().x + dx, boundary.getCentroid().y + dy],
+ newShape, [boundary.getWidth(), boundary.getHeight()], item.shapeStyle
+ );
+ } else {
+ newElement = renderer.drawShape(
+ [boundary.getCentroid().x + me._CONFIG.COPY_PASTE_PADDING, boundary.getCentroid().y + me._CONFIG.COPY_PASTE_PADDING],
+ newShape, [boundary.getWidth(), boundary.getHeight()], item.shapeStyle
+ );
+ }
+ }
+
+ // custom data
+ newElement.data = item.data;
+
+ // enable event
+ newGuide = renderer.drawGuide(newElement);
+ me.setClickSelectable(newElement, me._isSelectable(newElement.shape));
+ me.setMovable(newElement, me._isMovable(newElement.shape));
+ me.setConnectGuide(newElement, me._isConnectable(newElement.shape));
+ me.setResizable(newElement, newGuide, me._isResizable(newElement.shape));
+ me.setConnectable(newElement, newGuide, me._isConnectable(newElement.shape));
+
+ if (me._isLabelEditable(newElement.shape)) {
+ me.enableEditLabel(newElement);
+ }
+
+ // copy children
+ me._copyChildren(item, newElement);
+
+ selectedElement.push(newElement);
+ copiedMap.push({
+ copied: item,
+ pasted: newElement
+ })
+ });
+
+ var getPastedElementByCopied = function (copied) {
+ var pasted;
+ for (var i = 0, leni = copiedMap.length; i < leni; i++) {
+ if (copiedMap[i]['copied'].id == copied.id) {
+ pasted = copiedMap[i]['pasted'];
+ }
+ }
+ return pasted;
+ };
+
+ $.each(copiedElement, function (idx, item) {
+ var relatedElementsFromEdge, relatedFrom, relatedTo,
+ copiedFrom, copiedTo, copiedEdge, pastedFrom, pastedTo, pastedEdge;
+ //연결선이 복제된 경우, 복제된 대상 중 연결선의 From 과 To 가 모두 있을경우 연결선을 복원한다.
+ if ($(item).attr("_shape") === OG.Constants.SHAPE_TYPE.EDGE) {
+ relatedElementsFromEdge = renderer._CANVAS.getRelatedElementsFromEdge(item);
+ relatedFrom = relatedElementsFromEdge.from;
+ relatedTo = relatedElementsFromEdge.to;
+ copiedFrom = undefined, copiedTo = undefined, pastedFrom = undefined, pastedTo = undefined, pastedEdge = undefined;
+ copiedEdge = item;
+
+ for (var i = 0, leni = copiedElement.length; i < leni; i++) {
+ if (relatedFrom) {
+ if (copiedElement[i].id == relatedFrom.id) {
+ copiedFrom = copiedElement[i];
+ }
+ }
+ if (relatedTo) {
+ if (copiedElement[i].id == relatedTo.id) {
+ copiedTo = copiedElement[i];
+ }
+ }
+ }
+ if (copiedFrom && copiedTo) {
+ pastedFrom = getPastedElementByCopied(copiedFrom);
+ pastedTo = getPastedElementByCopied(copiedTo);
+ pastedEdge = getPastedElementByCopied(copiedEdge);
+ var pastedShape = pastedEdge.shape;
+ var pastedId = pastedEdge.id;
+
+ renderer._CANVAS.removeShape(pastedEdge);
+ pastedEdge = renderer._CANVAS.connect(pastedFrom, pastedTo, null, null, null, null, null, pastedId);
+ pastedEdge.shape = pastedShape;
+ renderer.redrawShape(pastedEdge);
+ }
+ }
+ // 일반 도형이 복제된 경우, 복제된 대상 중 연결된 도형이 있을 경우, 그 연결선 또한 복제된 대상 속에 있는지 찾는다.
+ // 만약 연결선이 복제 되지 않은 상태라면, 연결선을 복원한다.
+ // 연결선을 복원 한 후에는,
+ // copiedElement , selectedElement(pasted),copiedMap 에 각각 연결선을 추가하도록 한다.
+ else {
+ var prevEdges = renderer._CANVAS.getPrevEdges(item);
+ var nextEdges = renderer._CANVAS.getNextEdges(item);
+ var relatedEdges = prevEdges.concat(nextEdges);
+ for (var i = 0, leni = relatedEdges.length; i < leni; i++) {
+ relatedElementsFromEdge = renderer._CANVAS.getRelatedElementsFromEdge(relatedEdges[i]);
+ relatedFrom = relatedElementsFromEdge.from;
+ relatedTo = relatedElementsFromEdge.to;
+ copiedFrom = undefined, copiedTo = undefined, copiedEdge = undefined, pastedFrom = undefined, pastedTo = undefined, pastedEdge = undefined;
+
+ for (var c = 0, lenc = copiedElement.length; c < lenc; c++) {
+ if (relatedFrom) {
+ if (copiedElement[c].id == relatedFrom.id) {
+ copiedFrom = copiedElement[c];
+ }
+ }
+ if (relatedTo) {
+ if (copiedElement[c].id == relatedTo.id) {
+ copiedTo = copiedElement[c];
+ }
+ }
+ if (copiedElement[c].id == relatedEdges[i].id) {
+ copiedEdge = copiedElement[c];
+ }
+ }
+ if (copiedFrom && copiedTo && !copiedEdge) {
+ pastedFrom = getPastedElementByCopied(copiedFrom);
+ pastedTo = getPastedElementByCopied(copiedTo);
+ copiedEdge = relatedEdges[i];
+
+ //relatedEdges[i] 를 복사한다.
+ var newShape = relatedEdges[i].shape.clone();
+ if (relatedEdges[i].shape.geom instanceof OG.geometry.BezierCurve) {
+ newShape.geom = new OG.BezierCurve(relatedEdges[i].shape.geom.getControlPoints());
+ } else {
+ newShape.geom = new OG.PolyLine(relatedEdges[i].shape.geom.getVertices());
+ }
+ newShape.geom.style = relatedEdges[i].shape.geom.style;
+ newShape.geom.move(me._CONFIG.COPY_PASTE_PADDING, me._CONFIG.COPY_PASTE_PADDING);
+
+ pastedEdge = renderer._CANVAS.connect(pastedFrom, pastedTo);
+ pastedEdge.shape = newShape;
+ pastedEdge.data = relatedEdges[i].data;
+
+ renderer.redrawShape(pastedEdge);
+ copiedElement.push(copiedEdge);
+ selectedElement.push(pastedEdge);
+ copiedMap.push({
+ copied: copiedEdge.id,
+ pasted: pastedEdge.id
+ });
+ }
+ }
+ }
+ });
+
+ $(root).data("copied", selectedElement);
+ renderer.addHistory();
+ }
+
+ var copiedShapes = [];
+ var pastedShapes = [];
+
+ var setPastedShapes = function (copied, selected) {
+ copiedShapes.push(copied);
+ pastedShapes.push(selected);
+ copied.shape.onPasteShape(copied, selected);
+
+ if (renderer.isGroup(copied)) {
+ var copiedChilds = renderer.getChilds(copied);
+ var selectedChilds = renderer.getChilds(selected);
+ $.each(copiedChilds, function (idx, copiedChild) {
+ setPastedShapes(copiedChild, selectedChilds[idx]);
+ });
+ }
+ };
+ $.each(copiedElement, function (index, copied) {
+ setPastedShapes(copied, selectedElement[index]);
+ });
+
+ $(renderer._CANVAS._CONTAINER).trigger('pasteShape', [copiedShapes, pastedShapes]);
+ },
+
+ /**
+ * 메뉴 : 선택된 Shape 들을 복제한다.
+ */
+ duplicateSelectedShape: function () {
+ var me = this;
+ me.copySelectedShape();
+ me.pasteSelectedShape();
+ },
+
+ /**
+ * 메뉴 : 선택된 Shape 들을 그룹핑한다.
+ */
+ groupSelectedShape: function () {
+ var me = this, guide,
+ groupElement = me._RENDERER.group($(me._RENDERER.getRootElement()).find("[_type=" + OG.Constants.NODE_TYPE.SHAPE + "][_selected=true]"));
+
+ if (groupElement) {
+ $(me._RENDERER.getRootElement()).find("[_type=" + OG.Constants.NODE_TYPE.SHAPE + "][_selected=true]").each(function (idx, item) {
+ me._RENDERER.removeGuide(item);
+ });
+
+ guide = me._RENDERER.drawGuide(groupElement);
+ if (guide) {
+ // enable event
+ me.setClickSelectable(groupElement, me._isSelectable(groupElement.shape));
+ me.setMovable(groupElement, me._isMovable(groupElement.shape));
+ me.setConnectGuide(groupElement, me._isConnectable(groupElement.shape));
+ me.setResizable(groupElement, guide, me._isResizable(groupElement.shape));
+ me.setConnectable(groupElement, guide, me._isConnectable(groupElement.shape));
+
+ me._RENDERER.toFront(guide.group);
+ }
+ }
+ me._RENDERER.addHistory();
+ },
+
+ /**
+ * 메뉴 : 선택된 Shape 들을 그룹해제한다.
+ */
+ ungroupSelectedShape: function () {
+ var me = this, guide,
+ ungroupedElements = me._RENDERER.ungroup($(me._RENDERER.getRootElement()).find("[_shape=" + OG.Constants.SHAPE_TYPE.GROUP + "][_selected=true]"));
+ $.each(ungroupedElements, function (idx, item) {
+ guide = me._RENDERER.drawGuide(item);
+ if (guide) {
+ me._RENDERER.toFront(guide.group);
+ }
+ });
+ me._RENDERER.addHistory();
+ },
+
+ /**
+ * 메뉴 : 선택된 Shape 들을 회전한다.
+ *
+ * @param {Number} angle 회전각도
+ */
+ rotateSelectedShape: function (angle) {
+ var me = this, guide;
+ $(me._RENDERER.getRootElement()).find("[_type=" + OG.Constants.NODE_TYPE.SHAPE + "][_shape=" + OG.Constants.SHAPE_TYPE.EDGE + "][_selected=true]").each(function (idx, edge) {
+ me._RENDERER.removeGuide(edge);
+ });
+ $(me._RENDERER.getRootElement()).find("[_type=" + OG.Constants.NODE_TYPE.SHAPE + "][_selected=true]").each(function (idx, item) {
+ if (item.shape && item.shape.TYPE !== OG.Constants.SHAPE_TYPE.EDGE &&
+ item.shape.TYPE !== OG.Constants.SHAPE_TYPE.GROUP) {
+ me._RENDERER.rotate(item, angle);
+
+ me._RENDERER.removeGuide(item);
+ guide = me._RENDERER.drawGuide(item);
+ me.setResizable(item, guide, me._isResizable(item.shape));
+ me.setConnectable(item, guide, me._isConnectable(item.shape));
+ me._RENDERER.toFront(guide.group);
+ }
+ });
+ me._RENDERER.addHistory();
+ },
+
+ /**
+ * 메뉴 : 선택된 Shape 들의 Line Width 를 설정한다.
+ *
+ * @param {Number} lineWidth
+ */
+ setLineWidthSelectedShape: function (lineWidth) {
+ var me = this;
+ $(me._RENDERER.getRootElement()).find("[_type=" + OG.Constants.NODE_TYPE.SHAPE + "][_selected=true]").each(function (idx, item) {
+ me._RENDERER.setShapeStyle(item, {"stroke-width": lineWidth});
+ });
+ me._RENDERER.addHistory();
+ },
+
+ /**
+ * 메뉴 : 선택된 Shape 들의 Line Color 를 설정한다.
+ *
+ * @param {String} lineColor
+ */
+ setLineColorSelectedShape: function (lineColor) {
+ var me = this;
+ $(me._RENDERER.getRootElement()).find("[_type=" + OG.Constants.NODE_TYPE.SHAPE + "][_selected=true]").each(function (idx, item) {
+ me._RENDERER.setShapeStyle(item, {"stroke": lineColor});
+ });
+ me._RENDERER.addHistory();
+ },
+
+ /**
+ * 메뉴 : 선택된 Shape 들의 Line Type 을 설정한다.
+ *
+ * @param {String} lineType ['straight' | 'plain' | 'bezier']
+ */
+ setLoopTypeSelectedShape: function (loopType) {
+ var me = this;
+ $(me._RENDERER.getRootElement()).find("[_type=" + OG.Constants.NODE_TYPE.SHAPE + "][_selected=true]").each(function (idx, item) {
+ if (item.shape instanceof OG.shape.bpmn.A_Task) {
+ item.shape.LoopType = loopType;
+ me._RENDERER.redrawShape(item);
+ }
+ if (item.shape instanceof OG.shape.HorizontalPoolShape) {
+ item.shape.LoopType = loopType;
+ me._RENDERER.redrawShape(item);
+ }
+ });
+ me._RENDERER.addHistory();
+ },
+
+ setAddEventSelectedShape: function (value) {
+ var me = this;
+ var newElement, shape, boundary;
+ switch (value) {
+ case "Message":
+ shape = new OG.shape.bpmn.E_Intermediate_Message();
+ break;
+ case "Timer":
+ shape = new OG.shape.bpmn.E_Intermediate_Timer();
+ break;
+ case "Error":
+ shape = new OG.shape.bpmn.E_Intermediate_Error();
+ break;
+ case "Compensate":
+ shape = new OG.shape.bpmn.E_Intermediate_Compensation();
+ break;
+ case "Conditional":
+ shape = new OG.shape.bpmn.E_Intermediate_Rule();
+ break;
+ case "Signal":
+ shape = new OG.shape.bpmn.Signal();
+ break;
+ case "Multiple":
+ shape = new OG.shape.bpmn.E_Intermediate_Multiple();
+ break;
+ case "Parallel Multiple":
+ shape = new OG.shape.bpmn.ParallelMultiple();
+ break;
+ case "Escalation":
+ shape = new OG.shape.bpmn.E_Intermediate_Escalation();
+ break;
+ }
+
+ $(me._RENDERER.getRootElement()).find("[_type=" + OG.Constants.NODE_TYPE.SHAPE + "][_selected=true]").each(function (idx, item) {
+
+ boundary = item.shape.geom.getBoundary();
+ item.shape.Events.push(value);
+ //아래 위 라인에는 5개씩 양 옆라인에는 3개씩
+ if (item.shape.Events.length < 6) {
+ newElement = me._RENDERER._CANVAS.drawShape([boundary.getLowerLeft().x + (((boundary.getLowerRight().x - boundary.getLowerLeft().x) / 6) * (item.shape.Events.length)), boundary.getLowerCenter().y], shape, [30, 30]);
+ }
+ else if (item.shape.Events.length < 9) {
+ newElement = me._RENDERER._CANVAS.drawShape([boundary.getLowerRight().x, boundary.getLowerRight().y - (((boundary.getLowerRight().y - boundary.getUpperRight().y) / 4) * (item.shape.Events.length - 5))], shape, [30, 30]);
+ }
+ else if (item.shape.Events.length < 14) {
+ newElement = me._RENDERER._CANVAS.drawShape([boundary.getUpperRight().x - (((boundary.getUpperRight().x - boundary.getUpperLeft().x) / 6) * (item.shape.Events.length - 8)), boundary.getUpperCenter().y], shape, [30, 30]);
+ }
+ else if (item.shape.Events.length < 17) {
+ newElement = me._RENDERER._CANVAS.drawShape([boundary.getUpperLeft().x, boundary.getUpperLeft().y - (((boundary.getUpperLeft().y - boundary.getLowerLeft().y) / 4) * (item.shape.Events.length - 13))], shape, [30, 30]);
+ }
+
+ item.appendChild(newElement);
+ });
+
+ me._RENDERER.addHistory();
+ },
+
+ setTaskTypeSelectedShape: function (taskType) {
+ var me = this;
+ $(me._RENDERER.getRootElement()).find("[_type=" + OG.Constants.NODE_TYPE.SHAPE + "][_selected=true]").each(function (idx, item) {
+ if (item.shape instanceof OG.shape.bpmn.A_Task) {
+ item.shape.TaskType = taskType;
+
+ //FIXME : refactor
+ if (taskType === "User") {
+ $(item).attr("_shape_id", "OG.shape.bpmn.A_HumanTask");
+ item.shape.SHAPE_ID = "OG.shape.bpmn.A_HumanTask";
+ } else if (taskType === "Service") {
+ $(item).attr("_shape_id", "OG.shape.bpmn.A_ServiceTask");
+ item.shape.SHAPE_ID = "OG.shape.bpmn.A_ServiceTask";
+ } else if (taskType === "Mapper") {
+ $(item).attr("_shape_id", "OG.shape.bpmn.A_MapperTask");
+ item.shape.SHAPE_ID = "OG.shape.bpmn.A_MapperTask";
+ } else {
+ $(item).attr("_shape_id", "OG.shape.bpmn.A_Task");
+ item.shape.SHAPE_ID = "OG.shape.bpmn.A_Task";
+ }
+ me._RENDERER.redrawShape(item);
+ }
+ });
+ me._RENDERER.addHistory();
+ },
+
+ setExceptionType: function (element, exceptionType) {
+ var me = this;
+ element.shape.exceptionType = exceptionType;
+ me._RENDERER.redrawShape(element);
+ me._RENDERER.addHistory();
+ },
+
+ setInclusion: function (element, inclusion) {
+ var me = this;
+ element.shape.inclusion = inclusion;
+ me._RENDERER.redrawShape(element, null, true);
+ me._RENDERER.addHistory();
+ },
+
+ /**
+ * 메뉴 : 선택된 Shape 들의 Fill Opacity 를 설정한다.
+ *
+ * @param {String} opacity
+ */
+ setFillOpacitySelectedShape: function (opacity) {
+ var me = this;
+ $(me._RENDERER.getRootElement()).find("[_type=" + OG.Constants.NODE_TYPE.SHAPE + "][_selected=true]").each(function (idx, item) {
+ me._RENDERER.setShapeStyle(item, {"fill-opacity": opacity});
+ });
+ me._RENDERER.addHistory();
+ },
+
+ /**
+ * 메뉴 : 선택된 Shape 들의 Line Style 을 설정한다.
+ *
+ * @param {String} lineStyle ['' | '-' | '.' | '-.' | '-..' | '. ' | '- ' | '--' | '- .' | '--.' | '--..']
+ */
+ setLineStyleSelectedShape: function (lineStyle) {
+ var me = this;
+ $(me._RENDERER.getRootElement()).find("[_type=" + OG.Constants.NODE_TYPE.SHAPE + "][_selected=true]").each(function (idx, item) {
+ me._RENDERER.setShapeStyle(item, {"stroke-dasharray": lineStyle});
+ });
+ me._RENDERER.addHistory();
+ },
+
+ /**
+ * 메뉴 : 선택된 Edge Shape 들의 시작점 화살표 스타일을 설정한다.
+ *
+ * @param {String} arrowType ['block' | 'open_block' | 'classic' | 'diamond' | 'open_diamond' | 'open' | 'oval' | 'open_oval']
+ */
+ setArrowStartSelectedShape: function (arrowType) {
+ var me = this;
+ $(me._RENDERER.getRootElement()).find("[_type=" + OG.Constants.NODE_TYPE.SHAPE + "][_selected=true]").each(function (idx, item) {
+ me._RENDERER.setShapeStyle(item, {"arrow-start": arrowType + '-wide-long'});
+ });
+ me._RENDERER.addHistory();
+ },
+
+ /**
+ * 메뉴 : 선택된 Edge Shape 들의 끝점 화살표 스타일을 설정한다.
+ *
+ * @param {String} arrowType [] ['block' | 'open_block' | 'classic' | 'diamond' | 'open_diamond' | 'open' | 'oval' | 'open_oval']
+ */
+ setArrowEndSelectedShape: function (arrowType) {
+ var me = this;
+ $(me._RENDERER.getRootElement()).find("[_type=" + OG.Constants.NODE_TYPE.SHAPE + "][_selected=true]").each(function (idx, item) {
+ me._RENDERER.setShapeStyle(item, {"arrow-end": arrowType + '-wide-long'});
+ });
+ me._RENDERER.addHistory();
+ },
+
+ /**
+ * 메뉴 : 선택된 Shape 들의 Fill Color 를 설정한다.
+ *
+ * @param {String} fillColor
+ */
+ setFillColorSelectedShape: function (fillColor) {
+ var me = this;
+ $(me._RENDERER.getRootElement()).find("[_type=" + OG.Constants.NODE_TYPE.SHAPE + "][_selected=true]").each(function (idx, item) {
+ if (item.shape.SHAPE_ID == "OG.shape.bpmn.Value_Chain" || item.shape.SHAPE_ID == "OG.shape.bpmn.A_Subprocess") {
+ me._RENDERER.setShapeStyle(item, {"fill": "#FFFFFF-" + fillColor, "fill-opacity": 1});
+ } else {
+ me._RENDERER.setShapeStyle(item, {"fill": fillColor, "fill-opacity": 1});
+ }
+ });
+ me._RENDERER.addHistory();
+ },
+
+ /**
+ * 메뉴 : 선택된 Shape 들의 Font Family 를 설정한다.
+ *
+ * @param {String} fontFamily
+ */
+ setFontFamilySelectedShape: function (fontFamily) {
+ var me = this;
+ $(me._RENDERER.getRootElement()).find("[_type=" + OG.Constants.NODE_TYPE.SHAPE + "][_selected=true]").each(function (idx, item) {
+ me._RENDERER.setShapeStyle(item, {"font-family": fontFamily});
+ });
+ me._RENDERER.addHistory();
+ },
+
+ /**
+ * 메뉴 : 선택된 Shape 들의 Font Size 를 설정한다.
+ *
+ * @param {Number} fontSize
+ */
+ setFontSizeSelectedShape: function (fontSize) {
+ var me = this;
+ $(me._RENDERER.getRootElement()).find("[_type=" + OG.Constants.NODE_TYPE.SHAPE + "][_selected=true]").each(function (idx, item) {
+ me._RENDERER.setShapeStyle(item, {"font-size": fontSize});
+ });
+ me._RENDERER.addHistory();
+ },
+
+ /**
+ * 메뉴 : 선택된 Shape 들의 Font Color 를 설정한다.
+ *
+ * @param {String} fontColor
+ */
+ setFontColorSelectedShape: function (fontColor) {
+ var me = this;
+ $(me._RENDERER.getRootElement()).find("[_type=" + OG.Constants.NODE_TYPE.SHAPE + "][_selected=true]").each(function (idx, item) {
+ me._RENDERER.setShapeStyle(item, {"font-color": fontColor});
+ });
+ me._RENDERER.addHistory();
+ },
+
+ /**
+ * 메뉴 : 선택된 Shape 들의 Font Weight 를 설정한다.
+ *
+ * @param {String} fontWeight ['bold' | 'normal']
+ */
+ setFontWeightSelectedShape: function (fontWeight) {
+ var me = this;
+ $(me._RENDERER.getRootElement()).find("[_type=" + OG.Constants.NODE_TYPE.SHAPE + "][_selected=true]").each(function (idx, item) {
+ me._RENDERER.setShapeStyle(item, {"font-weight": fontWeight});
+ });
+ me._RENDERER.addHistory();
+ },
+
+ /**
+ * 메뉴 : 선택된 Shape 들의 Font Style 을 설정한다.
+ *
+ * @param {String} fontStyle ['italic' | 'normal']
+ */
+ setFontStyleSelectedShape: function (fontStyle) {
+ var me = this;
+ $(me._RENDERER.getRootElement()).find("[_type=" + OG.Constants.NODE_TYPE.SHAPE + "][_selected=true]").each(function (idx, item) {
+ me._RENDERER.setShapeStyle(item, {"font-style": fontStyle});
+ });
+ me._RENDERER.addHistory();
+ },
+
+ /**
+ * 메뉴 : 선택된 Shape 들의 Text Decoration 을 설정한다.
+ *
+ * @param {String} textDecoration ['underline' | 'none']
+ */
+ setTextDecorationSelectedShape: function (textDecoration) {
+ var me = this;
+ $(me._RENDERER.getRootElement()).find("[_type=" + OG.Constants.NODE_TYPE.SHAPE + "][_selected=true]").each(function (idx, item) {
+ me._RENDERER.setShapeStyle(item, {"text-decoration": textDecoration});
+ });
+ me._RENDERER.addHistory();
+ },
+
+ /**
+ * 메뉴 : 선택된 Shape 들의 Label Direction 을 설정한다.
+ *
+ * @param {String} labelDirection ['vertical' | 'horizontal']
+ */
+ setLabelDirectionSelectedShape: function (labelDirection) {
+ var me = this;
+ $(me._RENDERER.getRootElement()).find("[_type=" + OG.Constants.NODE_TYPE.SHAPE + "][_selected=true]").each(function (idx, item) {
+ me._RENDERER.setShapeStyle(item, {"label-direction": labelDirection});
+ });
+ me._RENDERER.addHistory();
+ },
+
+ /**
+ * 메뉴 : 선택된 Shape 들의 Label Angle 을 설정한다.
+ *
+ * @param {Number} labelAngle
+ */
+ setLabelAngleSelectedShape: function (labelAngle) {
+ var me = this;
+ $(me._RENDERER.getRootElement()).find("[_type=" + OG.Constants.NODE_TYPE.SHAPE + "][_selected=true]").each(function (idx, item) {
+ me._RENDERER.setShapeStyle(item, {"label-angle": labelAngle});
+ });
+ me._RENDERER.addHistory();
+ },
+
+ /**
+ * 메뉴 : 선택된 Shape 들의 Label Position 을 설정한다.
+ *
+ * @param {String} labelPosition ['top' | 'bottom' | 'left' | 'right' | 'center']
+ */
+ setLabelPositionSelectedShape: function (labelPosition) {
+ var me = this;
+ $(me._RENDERER.getRootElement()).find("[_type=" + OG.Constants.NODE_TYPE.SHAPE + "][_selected=true]").each(function (idx, item) {
+ if (labelPosition === 'top') {
+ me._RENDERER.setShapeStyle(item, {
+ "label-position": labelPosition,
+ "text-anchor": "middle",
+ "vertical-align": "bottom"
+ });
+ } else if (labelPosition === 'bottom') {
+ me._RENDERER.setShapeStyle(item, {
+ "label-position": labelPosition,
+ "text-anchor": "middle",
+ "vertical-align": "top"
+ });
+ } else if (labelPosition === 'left') {
+ me._RENDERER.setShapeStyle(item, {
+ "label-position": labelPosition,
+ "text-anchor": "end",
+ "vertical-align": "center"
+ });
+ } else if (labelPosition === 'right') {
+ me._RENDERER.setShapeStyle(item, {
+ "label-position": labelPosition,
+ "text-anchor": "start",
+ "vertical-align": "center"
+ });
+ } else if (labelPosition === 'center') {
+ me._RENDERER.setShapeStyle(item, {
+ "label-position": labelPosition,
+ "text-anchor": "middle",
+ "vertical-align": "center"
+ });
+ }
+ });
+ me._RENDERER.addHistory();
+ },
+
+ /**
+ * 메뉴 : 선택된 Shape 들의 라벨 Vertical Align 를 설정한다.
+ *
+ * @param {String} verticalAlign ['top' | 'middle' | 'bottom']
+ */
+ setLabelVerticalSelectedShape: function (verticalAlign) {
+ var me = this;
+ $(me._RENDERER.getRootElement()).find("[_type=" + OG.Constants.NODE_TYPE.SHAPE + "][_selected=true]").each(function (idx, item) {
+ me._RENDERER.setShapeStyle(item, {"vertical-align": verticalAlign});
+ });
+ me._RENDERER.addHistory();
+ },
+
+ /**
+ * 메뉴 : 선택된 Shape 들의 라벨 Horizontal Align 를 설정한다.
+ *
+ * @param {String} horizontalAlign ['start' | 'middle' | 'end']
+ */
+ setLabelHorizontalSelectedShape: function (horizontalAlign) {
+ var me = this;
+ $(me._RENDERER.getRootElement()).find("[_type=" + OG.Constants.NODE_TYPE.SHAPE + "][_selected=true]").each(function (idx, item) {
+ me._RENDERER.setShapeStyle(item, {"text-anchor": horizontalAlign});
+ });
+ me._RENDERER.addHistory();
+ },
+
+ /**
+ * 메뉴 : 선택된 Shape 의 라벨을 설정한다.
+ *
+ * @param {String} label
+ */
+ setLabelSelectedShape: function (label) {
+ var me = this;
+ $(me._RENDERER.getRootElement()).find("[_type=" + OG.Constants.NODE_TYPE.SHAPE + "][_selected=true]").each(function (idx, item) {
+ me._RENDERER.drawLabel(item, label);
+ });
+ me._RENDERER.addHistory();
+ },
+
+ /**
+ * 메뉴 : 선택된 Edge Shape 의 시작점 라벨을 설정한다.
+ *
+ * @param {String} label
+ */
+ setEdgeFromLabelSelectedShape: function (label) {
+ var me = this;
+ $(me._RENDERER.getRootElement()).find("[_type=" + OG.Constants.NODE_TYPE.SHAPE + "][_shape=" + OG.Constants.SHAPE_TYPE.EDGE + "][_selected=true]").each(function (idx, item) {
+ me._RENDERER.drawEdgeLabel(item, label, 'FROM');
+ });
+ me._RENDERER.addHistory();
+ },
+
+ /**
+ * 메뉴 : 선택된 Edge Shape 의 끝점 라벨을 설정한다.
+ *
+ * @param {String} label
+ */
+ setEdgeToLabelSelectedShape: function (label) {
+ var me = this;
+ $(me._RENDERER.getRootElement()).find("[_type=" + OG.Constants.NODE_TYPE.SHAPE + "][_shape=" + OG.Constants.SHAPE_TYPE.EDGE + "][_selected=true]").each(function (idx, item) {
+ me._RENDERER.drawEdgeLabel(item, label, 'TO');
+ });
+ me._RENDERER.addHistory();
+ },
+
+ /**
+ * 메뉴 : Zoom In
+ */
+ zoomIn: function () {
+ var me = this;
+ if (me._CONFIG.SCALE + me._CONFIG.SCALE * 0.1 <= me._CONFIG.SCALE_MAX) {
+ me._RENDERER.setScale(me._CONFIG.SCALE + me._CONFIG.SCALE * 0.1);
+ }
+ me._RENDERER.addHistory();
+ },
+
+ /**
+ * 메뉴 : Zoom Out
+ */
+ zoomOut: function () {
+ var me = this;
+ if (me._CONFIG.SCALE - me._CONFIG.SCALE * 0.1 >= me._CONFIG.SCALE_MIN) {
+ me._RENDERER.setScale(me._CONFIG.SCALE - me._CONFIG.SCALE * 0.1);
+ }
+ me._RENDERER.addHistory();
+ },
+
+ /**
+ * 메뉴 : 그려진 Shape 들을 캔버스 사이즈에 맞게 조절한다.
+ */
+ fitWindow: function () {
+ var me = this, container = me._RENDERER.getContainer();
+ me._RENDERER.fitCanvasSize([container.clientWidth, container.clientHeight], true);
+ me._RENDERER.addHistory();
+ },
+
+ /**
+ * Edge 와 선택된 Shape 정보들과의 시작, 끝점 연결 정보를 반환한다.
+ *
+ * @param {Element} edgeEle
+ * @param {Array} bBoxArray
+ * @return {Object} 연결 정보. {none, all, either, attrEither}
+ * @private
+ */
+ _isContainsConnectedShape: function (edgeEle, bBoxArray) {
+ var me = this, fromTerminal, toTerminal, fromShape, toShape, isContainsFrom = false, isContainsTo = false, i;
+
+ fromTerminal = $(edgeEle).attr("_from");
+ toTerminal = $(edgeEle).attr("_to");
+ if (fromTerminal) {
+ fromShape = me._getShapeFromTerminal(fromTerminal);
+ }
+ if (toTerminal) {
+ toShape = me._getShapeFromTerminal(toTerminal);
+ }
+
+ for (var i = 0, leni = bBoxArray.length; i < leni; i++) {
+ if (fromShape && bBoxArray[i].id === fromShape.id) {
+ isContainsFrom = true;
+ }
+ if (toShape && bBoxArray[i].id === toShape.id) {
+ isContainsTo = true;
+ }
+ }
+
+ return {
+ none: !isContainsFrom && !isContainsTo,
+ all: isContainsFrom && isContainsTo,
+ any: isContainsFrom || isContainsTo,
+ either: (isContainsFrom && !isContainsTo) || (!isContainsFrom && isContainsTo),
+ attrEither: (fromTerminal && !toTerminal) || (!fromTerminal && toTerminal)
+ };
+ },
+
+ /**
+ * 주어진 터미널 정보로 이를 포함하는 Shape 엘리먼트를 반환한다.
+ *
+ * @param {OG.shape.Terminal} terminal 연결 터미널
+ * @return {Element} Shape 엘리먼트
+ * @private
+ */
+ _getShapeFromTerminal: function (terminal) {
+ var me = this;
+ var element;
+ if (terminal) {
+ var shapeId = terminal.substring(0, terminal.indexOf(OG.Constants.TERMINAL));
+ element = me._RENDERER.getElementById(shapeId);
+ }
+ return element;
+ },
+
+ /**
+ * Page 및 Scroll offset 과 Scale 을 반영한 이벤트의 실제 offset 좌표를 반환한다.
+ *
+ * @param {Event} event
+ * @return {Object} offset 정보. {x, y}
+ * @private
+ */
+ _getOffset: function (event) {
+ var me = this, container = me._RENDERER.getContainer();
+
+ return {
+ x: (event.pageX - $(container).offset().left + container.scrollLeft) / me._CONFIG.SCALE,
+ y: (event.pageY - $(container).offset().top + container.scrollTop) / me._CONFIG.SCALE
+ };
+ },
+
+ /**
+ * 이동할 대상 즉, 선택된 Shape 정보를 반환한다.
+ *
+ * @return {Array} 선택된 Shape 정보. {id, box}' Array
+ * @private
+ */
+ _getMoveTargets: function () {
+ var me = this, bBoxArray = [], box;
+ var root = me._RENDERER.getRootElement();
+ $(root).find("[id$=" + OG.Constants.GUIDE_SUFFIX.BBOX + "]").each(function (index, item) {
+ if (item.id && item.id.indexOf(OG.Constants.CONNECT_GUIDE_SUFFIX.BBOX) == -1) {
+ var ele = me._RENDERER.getElementById(item.id);
+ var isEdge = $(ele).attr("_shape") === OG.Constants.SHAPE_TYPE.EDGE;
+
+ //엣지는 제외한다.
+ if (!isEdge) {
+ box = me._RENDERER.clone(item);
+ me._RENDERER.setAttr(box, me._CONFIG.DEFAULT_STYLE.GUIDE_SHADOW);
+ bBoxArray.push({
+ id: item.id.replace(OG.Constants.GUIDE_SUFFIX.BBOX, ""),
+ box: box
+ });
+ }
+ }
+ });
+ return bBoxArray;
+ },
+
+ /**
+ * 가로, 세로 Offset 만큼 주어진 Shape을 이동한다.
+ *
+ * @param {Array} bBoxArray 선택된 Shape 정보. {id, box}' Array
+ * @param {Number} dx 가로 Offset
+ * @param {Number} dy 세로 Offset
+ * @return {Array} 이동된 Shape 정보. {id, box}' Array
+ * @private
+ */
+ _moveElements: function (bBoxArray, dx, dy) {
+ var renderer = this._RENDERER;
+ var me = this, eleArray = [];
+
+ //이동시에 연결상태를 체크하여야 하는 shape 들을 모은다.
+ var connectCheckShapes = [];
+ $.each(bBoxArray, function (k, item) {
+ var ele = renderer.getElementById(item.id);
+ if (renderer.isEdge(ele)) {
+ return;
+ }
+ if (ele.shape && !ele.shape.MOVABLE) {
+ return;
+ }
+ connectCheckShapes.push(ele);
+ if (renderer.isGroup(ele)) {
+ $.each(renderer.getInnerShapesOfGroup(ele), function (idx, innerShape) {
+ connectCheckShapes.push(innerShape);
+ });
+ }
+ });
+
+ //이동 대상 엣지일 경우 엣지를 이동시킨다.
+ var excludeEdgeId = [];
+ var edges = renderer.getAllEdges();
+ $.each(edges, function (index, edge) {
+ var status = me._isContainsConnectedShape(edge, connectCheckShapes);
+ if (status && status.all) {
+ renderer.move(edge, [dx, dy]);
+ excludeEdgeId.push(edge.id);
+ }
+ });
+
+ //shape 이동 처리를 수행한다.
+ $.each(bBoxArray, function (k, item) {
+ var ele = renderer.getElementById(item.id);
+ if (renderer.isEdge(ele)) {
+ return;
+ }
+ // cloned box 삭제
+ renderer.remove(item.box);
+
+ // 이동
+ if (ele.shape && ele.shape.MOVABLE) {
+ renderer.move(ele, [dx, dy], excludeEdgeId);
+ eleArray.push(ele);
+ }
+ renderer.drawGuide(ele);
+ });
+
+ return eleArray;
+ },
+
+ /**
+ * Canvas 영역을 벗어나서 드래그되는 경우 Canvas 확장한다.
+ *
+ * @param {Number} currentX
+ * @param {Number} currentY
+ * @private
+ */
+ _autoExtend: function (currentX, currentY, element) {
+ var me = this, rootBBox = me._RENDERER.getRootBBox(),
+ width = element.shape.geom.boundary.getWidth(), height = element.shape.geom.boundary.getHeight()
+ // Canvas 영역을 벗어나서 드래그되는 경우 Canvas 확장
+ if (me._CONFIG.AUTO_EXTENSIONAL && rootBBox.width < (currentX + width)) {
+ me._RENDERER.setCanvasSize([rootBBox.width + me._CONFIG.AUTO_EXTENSION_SIZE, rootBBox.height]);
+ }
+ if (me._CONFIG.AUTO_EXTENSIONAL && rootBBox.height < (currentY + height)) {
+ me._RENDERER.setCanvasSize([rootBBox.width, rootBBox.height + me._CONFIG.AUTO_EXTENSION_SIZE]);
+ }
+ },
+
+ /**
+ * 그룹 Shape 인 경우 포함된 하위 Shape 들을 복사한다.
+ *
+ * @param {Element} element 원본 부모 Shape 엘리먼트
+ * @param {Element} newCopiedElement 복사된 부모 Shape 엘리먼트
+ * @private
+ */
+ _copyChildren: function (element, newCopiedElement) {
+ var me = this, children = element.childNodes;
+ $.each(children, function (idx, item) {
+ if ($(item).attr("_type") === OG.Constants.NODE_TYPE.SHAPE) {
+ // copy
+ var boundary = item.shape.geom.getBoundary(), newShape, newElement, newGuide;
+ newShape = item.shape.clone();
+
+ if ($(item).attr("_shape") === OG.Constants.SHAPE_TYPE.EDGE) {
+ newShape.geom = new OG.PolyLine(item.shape.geom.getVertices());
+ newShape.geom.style = item.shape.geom.style;
+ newShape.geom.move(me._CONFIG.COPY_PASTE_PADDING, me._CONFIG.COPY_PASTE_PADDING);
+ newElement = me._RENDERER.drawShape(
+ null, newShape,
+ null, item.shapeStyle
+ );
+
+ } else {
+ newElement = me._RENDERER.drawShape(
+ [boundary.getCentroid().x + me._CONFIG.COPY_PASTE_PADDING, boundary.getCentroid().y + me._CONFIG.COPY_PASTE_PADDING],
+ newShape, [boundary.getWidth(), boundary.getHeight()], item.shapeStyle
+ );
+ }
+
+ // custom data
+ newElement.data = item.data;
+
+ // append child
+ newCopiedElement.appendChild(newElement);
+
+ // enable event
+ me.setClickSelectable(newElement, me._isSelectable(newElement.shape));
+ me.setMovable(newElement, me._isMovable(newElement.shape));
+ me.setConnectGuide(newElement, me._isConnectable(newElement.shape));
+
+ if (me._isLabelEditable(newElement.shape)) {
+ me.enableEditLabel(newElement);
+ }
+
+ // recursive call
+ if (item.childNodes.length > 0) {
+ me._copyChildren(item, newElement);
+ }
+ }
+ });
+ },
+
+ /**
+ * 하위 Shape 자식노드를 모두 deselect 처리한다.
+ *
+ * @param {Element} element
+ * @private
+ */
+ _deselectChildren: function (element) {
+ var me = this, children = element.childNodes;
+ $.each(children, function (idx, item) {
+ if ($(item).attr("_type") === OG.Constants.NODE_TYPE.SHAPE) {
+ if (item.childNodes.length > 0) {
+ me._deselectChildren(item);
+ me._delSelectedElement(item);
+ }
+
+ if ($(item).attr("_selected") === "true") {
+ me._RENDERER.removeGuide(item);
+ $(item).draggable("destroy");
+ }
+ }
+ });
+ },
+
+ /**
+ * 선택되어진 Shape 부모노드가 하나라도 있다면 true 를 반환한다.
+ *
+ * @param {Element} element
+ * @return {Boolean}
+ * @private
+ */
+ _isParentSelected: function (element) {
+ var me = this, parentNode = element.parentNode;
+ if (parentNode) {
+ if (me._isParentSelected(parentNode)) {
+ return true;
+ }
+
+ if ($(parentNode).attr("_type") === OG.Constants.NODE_TYPE.SHAPE &&
+ $(parentNode).attr("_selected") === "true") {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ _num: function (str) {
+ return parseInt(str, 10);
+ },
+
+ _grid: function (value, move) {
+ var me = this;
+ if (move)
+ return me._CONFIG.DRAG_GRIDABLE ? OG.Util.roundGrid(value, me._CONFIG.MOVE_SNAP_SIZE / 2) : value;
+ else
+ return me._CONFIG.DRAG_GRIDABLE ? OG.Util.roundGrid(value, me._CONFIG.MOVE_SNAP_SIZE) : value;
+ },
+
+ _isSelectable: function (shape) {
+ var me = this;
+ return me._CONFIG.SELECTABLE && shape.SELECTABLE;
+ },
+
+ _isConnectable: function (shape) {
+ var me = this;
+ return me._CONFIG.CONNECTABLE && shape.CONNECTABLE;
+ },
+
+ _isConnectableFrom: function (shape) {
+ var me = this;
+ return shape.ENABLE_FROM;
+ },
+
+ _isConnectableTo: function (shape) {
+ var me = this;
+ return shape.ENABLE_TO;
+ },
+
+ _isSelfConnectable: function (shape) {
+ var me = this;
+ return me._CONFIG.SELF_CONNECTABLE && shape.SELF_CONNECTABLE;
+ },
+
+ _isConnectCloneable: function (shape) {
+ var me = this;
+ return me._CONFIG.CONNECT_CLONEABLE && shape.CONNECT_CLONEABLE;
+ },
+
+ _isMovable: function (shape) {
+ var me = this;
+ return (me._CONFIG.SELECTABLE && shape.SELECTABLE) &&
+ (me._CONFIG.MOVABLE && me._CONFIG.MOVABLE_[shape.TYPE] && shape.MOVABLE);
+ },
+
+ _isDeletable: function (shape) {
+ var me = this;
+ return (me._CONFIG.DELETABLE && shape.DELETABLE) &&
+ (me._CONFIG.DELETABLE && me._CONFIG.DELETABLE_[shape.TYPE] && shape.DELETABLE);
+ },
+
+ _isConnectStyleChangable: function (shape) {
+ var me = this;
+ return (me._CONFIG.CONNECT_STYLE_CHANGE && shape.CONNECT_STYLE_CHANGE) &&
+ (me._CONFIG.CONNECT_STYLE_CHANGE && me._CONFIG.CONNECT_STYLE_CHANGE_[shape.TYPE] && shape.CONNECT_STYLE_CHANGE);
+ },
+
+ _isResizable: function (shape) {
+ var me = this;
+ return (me._CONFIG.SELECTABLE && shape.SELECTABLE) &&
+ (me._CONFIG.RESIZABLE && me._CONFIG.RESIZABLE_[shape.TYPE] && shape.RESIZABLE);
+ },
+
+ _isLabelEditable: function (shape) {
+ var me = this;
+ return me._CONFIG.LABEL_EDITABLE && me._CONFIG.LABEL_EDITABLE_[shape.TYPE] && shape.LABEL_EDITABLE;
+ },
+
+ //TODO : 선택된 요소를 선택요소배열에 추가
+ _addSelectedElement: function (element) {
+ if (undefined == this.selectedElements) {
+ this.selectedElements = {};
+ }
+ this.selectedElements[element.attributes["id"].value] = element;
+ //선택 이벤트
+ if (element.shape) {
+ element.shape.onSelectShape();
+ }
+ },
+
+ //TODO : 선택된 요소를 선택요소배열에서 삭제
+ _delSelectedElement: function (element) {
+ if (this.selectedElements) {
+ delete this.selectedElements[element.attributes["id"].value];
+ if (element.shape) {
+ element.shape.onDeSelectShape();
+ }
+ }
+ },
+
+ //TODO : 선택요소배열 반환
+ _getSelectedElement: function () {
+ var key, returnArray = [];
+ for (key in this.selectedElements) {
+ returnArray.push(this.selectedElements[key]);
+ }
+ return returnArray;
+ },
+
+
+ _isSelectedElement: function (element) {
+ var isSelected = false;
+ if (element && element.id) {
+ for (var key in this.selectedElements) {
+ if (key === element.id) {
+ isSelected = true;
+ }
+ }
+ }
+ return isSelected;
+ },
+
+ _removeAllSelectedElement: function () {
+ //init
+ var key;
+ for (key in this.selectedElements) {
+ var element = this.selectedElements[key]
+ delete this.selectedElements[key];
+ if (element && element.shape) {
+ element.shape.onDeSelectShape();
+ }
+ }
+ },
+ /**
+ * Shape 엘리먼트의 setConnectGuide 에 관련된 이벤트
+ *
+ * @param {Element} element Shape 엘리먼트
+ * @param {Boolean} isConnectable 가능여부
+ */
+ setConnectGuide: function (element, isConnectable) {
+ var renderer = this._RENDERER;
+ var me = this, spotBBOX, spots, circleSpots, eventOffset, skipRemove, root = renderer.getRootGroup();
+ var root = renderer.getRootGroup();
+ //스팟 이동량 보정치의 범위조건을 설정한다.
+ //드래그시작시에 한번만 계산된다.
+ var calculateSpotCorrectionConditions = function (spot) {
+
+ //이동 딜레이
+ var delay = me._CONFIG.EDGE_MOVE_DELAY_SIZE;
+ //조건집합
+ var correctionConditions = [];
+ //스팟 기준으로 이웃한 변곡점
+ var reativePoints = [];
+ var type = $(spot).data('type');
+ var vertices = element.shape.geom.getVertices();
+ var allVertices;
+
+ //AUTOMATIC_GUIDANCE 가 아닌경우는 나 자신의 변곡점 집합
+ if (!me._CONFIG.AUTOMATIC_GUIDANCE) {
+ allVertices = element.shape.geom.getVertices();
+ $.each(allVertices, function (i, vertice) {
+ reativePoints.push(vertice);
+ });
+ }
+ //모든 엣지의 변곡점 집합
+ else {
+ var allEdges = renderer.getAllEdges();
+ $.each(allEdges, function (idx, edge) {
+ allVertices = edge.shape.geom.getVertices();
+ $.each(allVertices, function (i, vertice) {
+ reativePoints.push(vertice);
+ });
+ });
+ }
+
+ //서클 타입 스팟이고, 마지막 변곡점인 경우 shape 바운더리의 십자 영역을 번위조건에 추가한다.
+ if (type === OG.Constants.CONNECT_GUIDE_SUFFIX.SPOT_CIRCLE) {
+ var index = $(spot).data('index');
+ if (index === 0 || index === vertices.length - 1) {
+ $.each(root.childNodes, function (idx, childNode) {
+
+ if ($(childNode).attr("_type") === OG.Constants.NODE_TYPE.SHAPE
+ && $(childNode).attr("_shape") !== OG.Constants.SHAPE_TYPE.EDGE
+ ) {
+ var boundary = renderer.getBoundary(childNode);
+
+ if (boundary && boundary._upperLeft) {
+ var upperLeft = boundary._upperLeft;
+ var width = boundary._width;
+ var height = boundary._height;
+
+ //vertical boundary range
+ correctionConditions.push({
+ condition: {
+ minX: upperLeft.x + (width / 2) - delay,
+ maxX: upperLeft.x + (width / 2) + delay,
+ minY: upperLeft.y,
+ maxY: upperLeft.y + height,
+ },
+ fixedPosition: {
+ x: upperLeft.x + (width / 2)
+ },
+ id: idx
+ });
+
+ //horizontal boundary range
+ correctionConditions.push({
+ condition: {
+ minX: upperLeft.x,
+ maxX: upperLeft.x + width,
+ minY: upperLeft.y + (height / 2) - delay,
+ maxY: upperLeft.y + (height / 2) + delay,
+ },
+ fixedPosition: {
+ y: upperLeft.y + (height / 2)
+ },
+ id: idx
+ });
+ }
+ }
+ });
+ }
+ }
+
+ //변곡점 보정치 조건은 x 또는 y 가 일치할 경우
+ $.each(reativePoints, function (idx, point) {
+ correctionConditions.push({
+ condition: {
+ minX: point.x - delay,
+ maxX: point.x + delay,
+ },
+ fixedPosition: {
+ x: point.x
+ },
+ id: idx
+ });
+ correctionConditions.push({
+ condition: {
+ minY: point.y - delay,
+ maxY: point.y + delay,
+ },
+ fixedPosition: {
+ y: point.y
+ },
+ id: idx
+ });
+ });
+
+ // spot 에 데이터 저장
+ $(spot).data('correctionConditions', correctionConditions);
+
+ };
+
+ //스팟이 가지고있는 범위조건에 따라 새로운 포지션을 계산한다.
+ var correctionConditionAnalysis = function (spot, offset) {
+ var fixedPosition = {
+ x: offset.x,
+ y: offset.y
+ };
+ var calculateFixedPosition = function (expectedPosition) {
+ if (!expectedPosition) {
+ return fixedPosition;
+ }
+ if (expectedPosition.x && !expectedPosition.y) {
+ return {
+ x: expectedPosition.x,
+ y: fixedPosition.y
+ }
+ }
+ if (expectedPosition.y && !expectedPosition.x) {
+ return {
+ x: fixedPosition.x,
+ y: expectedPosition.y
+ }
+ }
+ if (expectedPosition.x && expectedPosition.y) {
+ return expectedPosition;
+ }
+ return fixedPosition;
+ };
+ var correctionConditions = $(spot).data('correctionConditions');
+ if (!correctionConditions) {
+ return fixedPosition;
+ }
+
+ var conditionsPassCandidates = [];
+ $.each(correctionConditions, function (index, correctionCondition) {
+ var condition = correctionCondition.condition;
+
+ var conditionsPass = true;
+ if (condition.minX) {
+ if (offset.x < condition.minX) {
+ conditionsPass = false;
+ }
+ }
+ if (condition.maxX) {
+ if (offset.x > condition.maxX) {
+ conditionsPass = false;
+ }
+ }
+ if (condition.minY) {
+ if (offset.y < condition.minY) {
+ conditionsPass = false;
+ }
+ }
+ if (condition.maxY) {
+ if (offset.y > condition.maxY) {
+ conditionsPass = false;
+ }
+ }
+
+ if (conditionsPass) {
+ conditionsPassCandidates.push(correctionCondition);
+ }
+ });
+ $.each(conditionsPassCandidates, function (index, conditionsPassCandidate) {
+ fixedPosition = calculateFixedPosition(conditionsPassCandidate.fixedPosition);
+ });
+ return fixedPosition;
+ };
+
+ var isConnectableSpot = function (spot) {
+ var isConnectable;
+ var vertices = element.shape.geom.getVertices();
+ if ($(spot).data('type') === OG.Constants.CONNECT_GUIDE_SUFFIX.SPOT_CIRCLE) {
+ var index = $(spot).data("index");
+ if (index || index === 0) {
+ if (index === 0) {
+ isConnectable = 'from'
+ }
+ if (index === vertices.length - 1) {
+ isConnectable = 'to'
+ }
+ }
+ }
+ return isConnectable;
+ };
+
+ $(element).bind({
+ mousemove: function (event) {
+ if (!me._isConnectable(element.shape)) {
+ return;
+ }
+
+ var isShape = $(element).attr("_type") === OG.Constants.NODE_TYPE.SHAPE;
+ var isEdge = $(element).attr("_shape") === OG.Constants.SHAPE_TYPE.EDGE;
+ var isSpotFocusing = $(root).data(OG.Constants.CONNECT_GUIDE_SUFFIX.SPOT_EVENT_MOUSEROVER);
+ var isDragging = $(root).data(OG.Constants.CONNECT_GUIDE_SUFFIX.SPOT_EVENT_DRAG);
+ var isConnectMode = $(root).data(OG.Constants.GUIDE_SUFFIX.LINE_CONNECT_MODE);
+
+ if (!isShape || isSpotFocusing || isDragging || isConnectMode) {
+ return;
+ }
+
+ if (isEdge) {
+ //엣지일 경우 선택되었을때만 동작
+ if (me._CONFIG.SPOT_ON_SELECT && $(element).attr("_selected") != "true") {
+ return;
+ }
+
+ eventOffset = me._getOffset(event);
+ var virtualSpot = renderer.createVirtualSpot(eventOffset.x, eventOffset.y, element);
+ if (virtualSpot) {
+
+ $(virtualSpot).bind({
+ mousedown: function () {
+ $(root).data(OG.Constants.CONNECT_GUIDE_SUFFIX.SPOT_EVENT_MOUSEROVER, true);
+ },
+ mouseup: function () {
+ $(root).data(OG.Constants.CONNECT_GUIDE_SUFFIX.SPOT_EVENT_MOUSEROVER, false);
+ }
+ });
+
+ $(virtualSpot).draggable({
+ start: function (event) {
+ renderer.removeAllGuide();
+ $(root).data(OG.Constants.CONNECT_GUIDE_SUFFIX.SPOT_EVENT_DRAG, true);
+
+ var eventOffset = me._getOffset(event);
+ var vertice = $(this).data('vertice');
+ var geometry = element.shape.geom;
+ $(this).data('offset', {
+ x: eventOffset.x - vertice.x,
+ y: eventOffset.y - vertice.y
+ });
+
+ var prev = $(this).data('prev');
+ var next = $(this).data('next');
+ var vertices = geometry.getVertices();
+
+ var offset = $(this).data('offset');
+ var newX = eventOffset.x - offset.x;
+ var newY = eventOffset.y - offset.y;
+ var newVertice = geometry.convertCoordinate([newX, newY]);
+
+ //기존 변곡점 스팟들의 인덱스값을 업데이트한다.
+ circleSpots = renderer.getCircleSpots(element);
+ $.each(circleSpots, function (index, circleSpot) {
+ var circleSpotIndex = $(circleSpot).data('index');
+ if (circleSpotIndex >= next) {
+ $(circleSpot).data('index', circleSpotIndex + 1);
+ }
+ });
+
+ //Edge 의 geometry 의 vertieces를 업데이트한다.
+ vertices.splice(next, 0, newVertice);
+ geometry.setVertices(vertices);
+
+ //가상스팟을 고정스팟으로 변경한다.
+ $(this).attr('name', OG.Constants.CONNECT_GUIDE_SUFFIX.SPOT);
+ $(this).data('index', next);
+
+ //양 끝 변곡점의 커넥션 포인트로의 변환.
+ var needToRedrawVertices = true;
+ if ($(this).data('type') === OG.Constants.CONNECT_GUIDE_SUFFIX.SPOT_CIRCLE) {
+ var index = $(this).data("index");
+ if (index === 0 && index === vertices.length - 1) {
+ needToRedrawVertices = false;
+ }
+ }
+ if (needToRedrawVertices) {
+ var from = $(element).attr("_from");
+ var to = $(element).attr("_to");
+ if (from) {
+ var fromPosition = renderer._getPositionFromTerminal(from);
+ vertices[0] =
+ geometry.convertCoordinate([fromPosition.x, fromPosition.y]);
+ }
+ if (to) {
+ var toPosition = renderer._getPositionFromTerminal(to);
+ vertices[vertices.length - 1] =
+ geometry.convertCoordinate([toPosition.x, toPosition.y]);
+ }
+ geometry.setVertices(vertices);
+ }
+
+ //이동 보정 조건 추가
+ $(this).data('corrections', calculateSpotCorrectionConditions(virtualSpot));
+
+ element = renderer.drawEdge(new OG.PolyLine(vertices), geometry.style, element.id);
+
+ renderer.removeRubberBand(renderer.getRootElement());
+ renderer.selectSpot(virtualSpot);
+ },
+ drag: function (event) {
+ if (!renderer._getREleById(virtualSpot.id)) {
+ renderer.removeAllConnectGuide();
+ $(root).data(OG.Constants.CONNECT_GUIDE_SUFFIX.SPOT_EVENT_DRAG, false);
+ $(root).data(OG.Constants.CONNECT_GUIDE_SUFFIX.SPOT_EVENT_MOUSEROVER, false);
+ return;
+ }
+
+ var eventOffset = me._getOffset(event);
+ var offset = $(this).data("offset");
+ var newX = eventOffset.x - offset.x;
+ var newY = eventOffset.y - offset.y;
+ var vertices = element.shape.geom.getVertices();
+
+ var analysisPosition = correctionConditionAnalysis(virtualSpot, {x: newX, y: newY});
+ newX = analysisPosition.x;
+ newY = analysisPosition.y;
+
+ renderer.setAttr(virtualSpot, {cx: newX});
+ renderer.setAttr(virtualSpot, {cy: newY});
+
+ var index = $(this).data("index");
+
+ vertices[index].x = newX;
+ vertices[index].y = newY;
+
+ renderer.drawEdge(new OG.PolyLine(vertices), element.shape.geom.style, element.id);
+ renderer.trimConnectIntersection(element);
+ },
+ stop: function (event) {
+ $(root).data(OG.Constants.CONNECT_GUIDE_SUFFIX.SPOT_EVENT_DRAG, false);
+ $(root).data(OG.Constants.CONNECT_GUIDE_SUFFIX.SPOT_EVENT_MOUSEROVER, false);
+
+ var eventOffset = me._getOffset(event);
+ var offset = $(this).data("offset");
+ var index = $(this).data("index");
+ var newX = eventOffset.x - offset.x;
+ var newY = eventOffset.y - offset.y;
+ var vertices = element.shape.geom.getVertices();
+
+ var analysisPosition = correctionConditionAnalysis(virtualSpot, {x: newX, y: newY});
+ analysisPosition.x = me._grid(analysisPosition.x, 'move');
+ analysisPosition.y = me._grid(analysisPosition.y, 'move');
+ newX = analysisPosition.x;
+ newY = analysisPosition.y;
+
+ vertices[index].x = newX;
+ vertices[index].y = newY;
+
+ renderer.drawEdge(new OG.PolyLine(vertices), element.shape.geom.style, element.id);
+
+ renderer.removeConnectGuide(element);
+ renderer.removeVirtualSpot(element);
+
+ renderer.trimConnectInnerVertice(element);
+ renderer.trimConnectIntersection(element);
+ renderer.trimEdge(element);
+ renderer.addHistory();
+ }
+ });
+ }
+ }
+ },
+ mouseout: function (event) {
+ if (!me._isConnectable(element.shape)) {
+ return;
+ }
+ var isShape = $(element).attr("_type") === OG.Constants.NODE_TYPE.SHAPE;
+ var isEdge = $(element).attr("_shape") === OG.Constants.SHAPE_TYPE.EDGE;
+ var isDragging = $(root).data(OG.Constants.CONNECT_GUIDE_SUFFIX.SPOT_EVENT_DRAG);
+
+ if (!isShape) {
+ return;
+ }
+
+ if (!isEdge) {
+ if (isDragging) {
+ return;
+ }
+ renderer.removeConnectGuide(element);
+ }
+
+ if (isEdge) {
+ if (isDragging) {
+ return;
+ }
+
+ //스팟이 선택되어 바운더리 영역 밖으로 나갔다고 판단될 경우 예외처리한다.
+ skipRemove = false;
+ eventOffset = me._getOffset(event);
+ spots = renderer.getSpots(element);
+ $(spots).each(function (index, spot) {
+ spotBBOX = renderer.getBBox(spot);
+ if (eventOffset.x >= spotBBOX.x && eventOffset.x <= spotBBOX.x2
+ && eventOffset.y >= spotBBOX.y && eventOffset.y <= spotBBOX.y2) {
+ skipRemove = true;
+ }
+ });
+
+ //가상스팟이 선택되어 바운더리 영역 밖으로 나갔다고 판단될 경우 예외처리한다.
+ var virtualSpot = renderer.getVirtualSpot(element);
+ if (virtualSpot) {
+ spotBBOX = renderer.getBBox(virtualSpot);
+ if (eventOffset.x >= spotBBOX.x && eventOffset.x <= spotBBOX.x2
+ && eventOffset.y >= spotBBOX.y && eventOffset.y <= spotBBOX.y2) {
+ skipRemove = true;
+ }
+ }
+
+ if (!skipRemove) {
+ renderer.removeConnectGuide(element);
+ renderer.removeVirtualSpot(element);
+ }
+ }
+ event.stopImmediatePropagation();
+ },
+ mouseover: function (event) {
+ if (!me._isConnectable(element.shape)) {
+ return;
+ }
+ var guide;
+ //마우스가 어떠한 shape 에 접근할 때
+
+ //1. 어떠한 shpae가 Edge 가 아닐경우 커넥트 가이드를 생성한다.
+ // 1) 드래그중인 스팟이 처음 또는 끝의 변곡점일경우,
+ // 1. 접근한 shape 의 정보를 root 에 알린다.
+ // 2. 빠져나간 shape 의 정보를 root 에 알린다.
+ // 3. 접근한 shpae 의 스타일을 변경한다.
+ // 4. 빠져나간 shape 의 스타일을 변경한다.
+ // 5. 접근한 shape 의 정보를 root 에 알린다.
+ // 5. 빠져나간 shape 의 정보를 root 에 삭제한다.
+
+ //2. 어떠한 shape가 Edge 일 경우
+ // 1) 다른 Edge의 커넥트가이드를 정리한다.
+ // 2) 어떠한 Edge의 Spot이 드래그중일 경우 커넥트가이드 생성을 막는다.
+ // 3) 어떠한 Edge의 Spot이 드래그중이 아닐 경우 커넥트가이드를 생성한다.
+ var enableStyle = me._CONFIG.DEFAULT_STYLE.CONNECTABLE_HIGHLIGHT;
+ var isShape = $(element).attr("_type") === OG.Constants.NODE_TYPE.SHAPE;
+ var isEdge = $(element).attr("_shape") === OG.Constants.SHAPE_TYPE.EDGE;
+ var isDragging = $(root).data(OG.Constants.CONNECT_GUIDE_SUFFIX.SPOT_EVENT_DRAG);
+ var isConnectMode = $(root).data(OG.Constants.GUIDE_SUFFIX.LINE_CONNECT_MODE);
+ if (!isShape) {
+ return;
+ }
+
+ if (!isEdge) {
+ //스팟을 드래그 중일때는 예외처리한다.
+ if (isDragging) {
+ return;
+ }
+ renderer.removeAllConnectGuide();
+ renderer.drawConnectGuide(element);
+ }
+
+ if (isEdge) {
+ //엣지일 경우 선택되었을때만 동작
+ if (me._CONFIG.SPOT_ON_SELECT && $(element).attr("_selected") != "true") {
+ return;
+ }
+
+ if (isConnectMode) {
+ return;
+ }
+ if (isDragging) {
+ return;
+ }
+ renderer.removeOtherConnectGuide(element);
+ guide = renderer.drawConnectGuide(element);
+ if (guide && guide.spots) {
+ $(guide.spots).each(function (index, spot) {
+
+ $(spot).bind({
+ mouseover: function () {
+ var skipRemove = false;
+
+ //드래그중일때는 예외처리한다.
+ if ($(root).data(OG.Constants.CONNECT_GUIDE_SUFFIX.SPOT_EVENT_DRAG)) {
+ skipRemove = true;
+ }
+ if (!skipRemove) {
+ renderer.selectSpot(spot);
+ $(root).data(OG.Constants.CONNECT_GUIDE_SUFFIX.SPOT_EVENT_MOUSEROVER, true);
+ }
+ },
+ mouseout: function () {
+ var skipRemove = false;
+
+ //드래그중일때는 예외처리한다.
+ if ($(root).data(OG.Constants.CONNECT_GUIDE_SUFFIX.SPOT_EVENT_DRAG)) {
+ skipRemove = true;
+ }
+ if (!skipRemove) {
+ $(root).data(OG.Constants.CONNECT_GUIDE_SUFFIX.SPOT_EVENT_MOUSEROVER, false);
+ }
+ }
+ });
+
+ $(spot).draggable({
+ start: function (event) {
+ renderer.removeAllGuide();
+ renderer.toFront(element);
+
+ $(root).data(OG.Constants.CONNECT_GUIDE_SUFFIX.SPOT_EVENT_DRAG, true);
+
+ var eventOffset = me._getOffset(event);
+ var vertice = $(this).data('vertice');
+ var geometry = element.shape.geom;
+ var vertices = geometry.getVertices();
+
+ $(this).data('offset', {
+ x: eventOffset.x - vertice.x,
+ y: eventOffset.y - vertice.y
+ });
+
+ //양 끝의 변곡점을 커넥트 포지션으로 변경.
+ var needToRedrawVertices = true;
+ if ($(this).data('type') === OG.Constants.CONNECT_GUIDE_SUFFIX.SPOT_CIRCLE) {
+ var index = $(this).data('index');
+ if (index === 0 || index === vertices.length - 1) {
+ needToRedrawVertices = false;
+ }
+ }
+ $(this).data('needToRedrawVertices', needToRedrawVertices);
+
+ if (needToRedrawVertices) {
+ var from = $(element).attr('_from');
+ var to = $(element).attr('_to');
+ if (from) {
+ var fromPosition = renderer._getPositionFromTerminal(from);
+ vertices[0] =
+ geometry.convertCoordinate([fromPosition.x, fromPosition.y]);
+ }
+ if (to) {
+ var toPosition = renderer._getPositionFromTerminal(to);
+ vertices[vertices.length - 1] =
+ geometry.convertCoordinate([toPosition.x, toPosition.y]);
+ }
+ geometry.setVertices(vertices);
+ }
+
+ if ($(this).data('type') === OG.Constants.CONNECT_GUIDE_SUFFIX.SPOT_CIRCLE) {
+ $(this).data('corrections', calculateSpotCorrectionConditions(spot));
+ renderer.remove(guide.bBox);
+ renderer.removeRubberBand(renderer.getRootElement());
+ }
+
+ if ($(this).data('type') === OG.Constants.CONNECT_GUIDE_SUFFIX.SPOT_RECT) {
+ vertices = geometry.getVertices();
+ var prev = $(this).data('prev');
+ var next = $(this).data('next');
+ if (prev === 0) {
+ var newPreVertice =
+ element.shape.geom.convertCoordinate([vertices[0].x, vertices[0].y]);
+ vertices.splice(prev, 0, newPreVertice);
+ $(this).data('prev', prev + 1);
+ $(this).data('next', next + 1);
+ next = next + 1;
+ }
+ if (next === vertices.length - 1) {
+ var newNextVertice =
+ geometry.convertCoordinate([vertices[vertices.length - 1].x, vertices[vertices.length - 1].y]);
+ vertices.splice(next, 0, newNextVertice);
+ }
+
+ //Edge 의 geometry 의 vertieces를 업데이트한다.
+ geometry.setVertices(vertices);
+ $(this).data('corrections', calculateSpotCorrectionConditions(spot));
+ renderer.remove(guide.bBox);
+ renderer.removeRubberBand(renderer.getRootElement());
+ }
+ },
+ drag: function (event) {
+ if (!renderer._getREleById(spot.id)) {
+ renderer.removeAllConnectGuide();
+ $(root).data(OG.Constants.CONNECT_GUIDE_SUFFIX.SPOT_EVENT_DRAG, false);
+ $(root).data(OG.Constants.CONNECT_GUIDE_SUFFIX.SPOT_EVENT_MOUSEROVER, false);
+ return;
+ }
+
+ var eventOffset = me._getOffset(event);
+ var offset = $(this).data("offset");
+ var newX = eventOffset.x - offset.x;
+ var newY = eventOffset.y - offset.y;
+ var vertices = element.shape.geom.getVertices();
+
+ var analysisPosition = correctionConditionAnalysis(spot, {x: newX, y: newY});
+ if ($(this).data('type') === OG.Constants.CONNECT_GUIDE_SUFFIX.SPOT_CIRCLE) {
+ newX = analysisPosition.x;
+ newY = analysisPosition.y;
+
+ renderer.setAttr(spot, {cx: newX});
+ renderer.setAttr(spot, {cy: newY});
+
+ var index = $(this).data("index");
+ vertices[index].x = newX;
+ vertices[index].y = newY;
+ }
+ if ($(this).data('type') === OG.Constants.CONNECT_GUIDE_SUFFIX.SPOT_RECT) {
+ newX = analysisPosition.x;
+ newY = analysisPosition.y;
+
+ var spotRectStyle = me._CONFIG.DEFAULT_STYLE.CONNECT_GUIDE_SPOT_RECT;
+ var height = spotRectStyle.h;
+ var direction = $(this).data('direction');
+ var prev = $(this).data("prev");
+ var next = $(this).data("next");
+ if (direction === 'vertical') {
+ vertices[prev].x = newX;
+ vertices[next].x = newX;
+ renderer.setAttr(spot, {x: newX - (height / 2)});
+ }
+ if (direction === 'horizontal') {
+ vertices[prev].y = newY;
+ vertices[next].y = newY;
+ renderer.setAttr(spot, {y: newY - (height / 2)});
+ }
+ }
+ renderer.drawEdge(new OG.PolyLine(vertices), element.shape.geom.style, element.id);
+ if ($(this).data('needToRedrawVertices')) {
+ renderer.trimConnectIntersection(element);
+ }
+
+ var connectableDirection = isConnectableSpot(spot);
+ var frontElement = renderer.getFrontForCoordinate([eventOffset.x, eventOffset.y]);
+ $.each(renderer.getAllNotEdges(), function (idx, otherElement) {
+ if (frontElement && connectableDirection) {
+ if (otherElement.id === frontElement.id) {
+ renderer.setHighlight(otherElement, enableStyle);
+ renderer.drawConnectGuide(otherElement);
+ } else {
+ renderer.removeHighlight(otherElement, enableStyle);
+ renderer.removeConnectGuide(otherElement);
+ }
+ } else {
+ renderer.removeHighlight(otherElement, enableStyle);
+ renderer.removeConnectGuide(otherElement);
+ }
+ });
+ },
+ stop: function (event) {
+ $(root).data(OG.Constants.CONNECT_GUIDE_SUFFIX.SPOT_EVENT_DRAG, false);
+ $(root).data(OG.Constants.CONNECT_GUIDE_SUFFIX.SPOT_EVENT_MOUSEROVER, false);
+
+ var eventOffset = me._getOffset(event);
+ var offset = $(this).data("offset");
+ var index = $(this).data("index");
+ var newX = eventOffset.x - offset.x;
+ var newY = eventOffset.y - offset.y;
+ var vertices = element.shape.geom.getVertices();
+
+ var analysisPosition = correctionConditionAnalysis(spot, {x: newX, y: newY});
+ analysisPosition.x = me._grid(analysisPosition.x, 'move');
+ analysisPosition.y = me._grid(analysisPosition.y, 'move');
+
+ if ($(this).data('type') === OG.Constants.CONNECT_GUIDE_SUFFIX.SPOT_CIRCLE) {
+ newX = analysisPosition.x;
+ newY = analysisPosition.y;
+
+ renderer.setAttr(spot, {cx: newX});
+ renderer.setAttr(spot, {cy: newY});
+
+ var index = $(this).data("index");
+ vertices[index].x = newX;
+ vertices[index].y = newY;
+
+ }
+ if ($(this).data('type') === OG.Constants.CONNECT_GUIDE_SUFFIX.SPOT_RECT) {
+ newX = analysisPosition.x;
+ newY = analysisPosition.y;
+
+ var spotRectStyle = me._CONFIG.DEFAULT_STYLE.CONNECT_GUIDE_SPOT_RECT;
+ var height = spotRectStyle.h;
+ var direction = $(this).data('direction');
+ var prev = $(this).data("prev");
+ var next = $(this).data("next");
+ if (direction === 'vertical') {
+ vertices[prev].x = newX;
+ vertices[next].x = newX;
+ renderer.setAttr(spot, {x: newX - (height / 2)});
+ }
+ if (direction === 'horizontal') {
+ vertices[prev].y = newY;
+ vertices[next].y = newY;
+ renderer.setAttr(spot, {y: newY - (height / 2)});
+ }
+ }
+ renderer.drawEdge(new OG.PolyLine(vertices), element.shape.geom.style, element.id);
+ renderer.removeConnectGuide(element);
+ renderer.removeVirtualSpot(element);
+
+ var connectableDirection = isConnectableSpot(spot);
+ var frontElement = renderer.getFrontForCoordinate([eventOffset.x, eventOffset.y]);
+ if (frontElement) {
+ renderer.removeHighlight(frontElement, enableStyle);
+ }
+ if (connectableDirection && frontElement) {
+ var point = [newX, newY];
+ var terminal = renderer.createTerminalString(frontElement, point);
+ var isConnectable = me._isConnectable(frontElement.shape);
+ if (isConnectable) {
+ if (connectableDirection === 'from') {
+ if (me._isConnectableFrom(frontElement.shape)) {
+ renderer.connect(terminal, null, element, element.shape.geom.style);
+ }
+ }
+ if (connectableDirection === 'to') {
+ if (me._isConnectableTo(frontElement.shape)) {
+ renderer.connect(null, terminal, element, element.shape.geom.style);
+ }
+ }
+ }
+ }
+ if (connectableDirection && !frontElement) {
+ renderer.disconnectOneWay(element, connectableDirection);
+ }
+
+ renderer.trimConnectInnerVertice(element);
+ renderer.trimConnectIntersection(element);
+ renderer.trimEdge(element);
+
+ renderer.addHistory();
+ }
+ });
+
+ })
+ }
+ }
+ event.stopImmediatePropagation();
+ }
+ })
+
+ }
+};
+OG.handler.EventHandler.prototype.constructor = OG.handler.EventHandler;
+OG.EventHandler = OG.handler.EventHandler;
+/**
+ * Remote User
+ *
+ * @class
+ * @requires OG.*
+ *
+ * @example
+ * var user = new OG.handler.RemoteUser(key, name, sessionId);
+ *
+ * @author Seungpil Park
+ * @private
+ */
+OG.handler.RemoteUser = function (key, name, sessionId) {
+
+ this.key = key;
+ this.name = name;
+ this.sessionId = sessionId;
+ this.isMaster = false;
+ this.editable = false;
+};
+OG.handler.RemoteUser.prototype = {
+ getKey: function () {
+ return this.key;
+ },
+ setKey: function (key) {
+ this.key = key;
+ },
+ getName: function () {
+ return this.name;
+ },
+ setName: function (name) {
+ this.name = name;
+ },
+ getSessionId: function () {
+ return this.sessionId;
+ },
+ setSessionId: function (sessionId) {
+ this.sessionId = sessionId;
+ },
+ getIsMaster: function () {
+ return this.isMaster;
+ },
+ setIsMaster: function (ismaster) {
+ this.isMaster = ismaster;
+ },
+ getEditable: function () {
+ return this.editable;
+ },
+ setEditable: function (editable) {
+ this.editable = editable;
+ }
+};
+OG.handler.RemoteUser.prototype.constructor = OG.handler.RemoteUser;
+OG.RemoteUser = OG.handler.RemoteUser();
+/**
+ * Remote Repository
+ *
+ * @type {{}}
+ * @private
+ */
+OG.handler.RemoteRepo = {};
+/**
+ * Remote Handler
+ *
+ * @class
+ * @requires OG.*
+ *
+ * @author Seungpil Park
+ * @private
+ */
+OG.handler.RemoteHandler = function () {
+ this._REPO = OG.handler.RemoteRepo;
+ this._CLASSNAME = 'org.uengine.opengraph.RemoteService';
+ this._SESSIONID = null;
+ this._ENCODESUFFIX = '_%$';
+ this._SCHEDULER = null;
+};
+OG.handler.RemoteHandler.prototype = {
+ encodeJson: function (obj) {
+ return JSON.stringify(obj).replace(/"/gi, this._ENCODESUFFIX);
+ },
+ getRepo: function () {
+ return this._REPO;
+ },
+ setCanvasForIdentifier: function (canvas, identifier) {
+ var repo = this.getRepo();
+ if (!repo[identifier]) {
+ repo[identifier] = {
+ canvas: null,
+ users: []
+ };
+ }
+ repo[identifier].canvas = canvas;
+ return repo[identifier];
+ },
+ getCanvasByIdentifier: function (identifier) {
+ var repo = this.getRepo();
+ if (!repo[identifier]) {
+ return null;
+ }
+
+ return repo[identifier].canvas;
+ },
+ getRemote: function (param) {
+ var obj = {
+ __className: this._CLASSNAME
+ };
+ if (param) {
+ for (var key in param) {
+ obj[key] = param[key];
+ }
+ }
+ return new MetaworksObject(obj, 'body');
+ },
+ /**
+ * 서버로부터 온 Json User 객체를 OG.handler.RemoteUser 로 변환한다.
+ *
+ * @param serverUser Object
+ * @return OG.handler.RemoteUser
+ */
+ convertRemoteUser: function (serverUser) {
+ var remoteUser = new OG.handler.RemoteUser();
+ remoteUser.setKey(serverUser['key']);
+ remoteUser.setName(serverUser['name']);
+ remoteUser.setSessionId(serverUser['sessionId']);
+ remoteUser.setIsMaster(serverUser['isMaster']);
+ remoteUser.setEditable(serverUser['editable']);
+ return remoteUser;
+ },
+ /**
+ * 사용된 메타웍스 오브젝트를 제거한다.
+ * 브라우저 가비지 제거
+ *
+ * @param remote MetaworksObject
+ *
+ */
+ closeRemote: function (remote) {
+ var objectId = remote.__objectId;
+ $('#objDiv_' + objectId).remove();
+ delete mw3.objects[objectId];
+ },
+ getCanvasId: function (canvas) {
+ return canvas._CONTAINER.id;
+ },
+
+ /**
+ * 캔버스를 리모트모드로 변경한다.
+ *
+ * @param canvas 캔버스
+ * @param identifier 리모트그룹 식별자
+ * @param user OG.handler.RemoteUser 유저
+ * @param callback Callback
+ *
+ * @return callback(OG.handler.RemoteUser) 서버 등록 후 갱신된 유저
+ *
+ */
+ startRemote: function (canvas, identifier, user, callback) {
+ var me = this;
+ if (!canvas || !identifier || !user) {
+ return;
+ }
+ if (!user.key || !user.name) {
+ return;
+ }
+ canvas.setRemotable(true);
+ canvas.setIdentifier(identifier);
+ me.setCanvasForIdentifier(canvas, identifier);
+
+ me.registeToServer(identifier, user, function (user) {
+ callback(user);
+ });
+ },
+ /**
+ * 현재 자신의 세션아이디를 구한다.
+ *
+ * @param callback Callback
+ *
+ * @return callback(String sessionId) sessionId
+ *
+ */
+ getSelfSession: function (callback) {
+ if (this._SESSIONID) {
+ callback(this._SESSIONID);
+ return;
+ }
+ var me = this;
+ var remote = this.getRemote();
+ var objectId = remote.__objectId;
+ remote.selfSession(null, function () {
+ var sessionId = mw3.objects[objectId]['currentSessionId'];
+ me.closeRemote(remote);
+
+ if (sessionId) {
+ me._SESSIONID = sessionId;
+ }
+ callback(sessionId);
+ });
+ },
+ /**
+ * 서버에 현재 사용자를 등록한다.
+ *
+ * @param identifier 리모트그룹 식별자
+ * @param user OG.handler.RemoteUser 유저
+ * @param callback Callback
+ *
+ * @return callback(OG.handler.RemoteUser) 등록된 유저
+ *
+ */
+ registeToServer: function (identifier, user, callback) {
+
+ var me = this, result;
+ var remote = this.getRemote({
+ identifier: identifier,
+ remoteUser: me.encodeJson(user)
+ });
+ var objectId = remote.__objectId;
+ remote.registe(null, function (value) {
+
+ result = mw3.objects[objectId];
+ me.closeRemote(remote);
+ var remoteUser = me.convertRemoteUser(JSON.parse(result['remoteUser']));
+ callback(remoteUser);
+ })
+ },
+
+ /**
+ * From Server.
+ * 주어진 캔바스의 유저 목록을 업데이트한다.
+ *
+ * @param data identifier, 유저목록 remoteUsers
+ *
+ */
+ updateRemoteUser: function (data) {
+ var me = this;
+ var parse = JSON.parse(data);
+ var identifier = parse.identifier;
+ var remoteServerUsers = parse.remoteUsers;
+
+ if (!this.getRepo()[identifier]) {
+ return;
+ }
+
+ var remoteUsers = [];
+ var canvas = this.getCanvasByIdentifier(identifier);
+ this.getSelfSession(function (sessionId) {
+ $.each(remoteServerUsers, function (index, serverUser) {
+ if (serverUser.sessionId && serverUser.sessionId == sessionId) {
+ canvas.setRemoteEditable(serverUser.editable);
+ canvas.setRemoteIsMaster(serverUser.isMaster);
+
+ $(canvas.getRootElement()).trigger('remoteStatusUpdated', [serverUser]);
+ }
+ if (serverUser.isMaster) {
+ remoteUsers.push(me.convertRemoteUser(serverUser));
+ }
+ });
+ $.each(remoteServerUsers, function (index, serverUser) {
+ if (!serverUser.isMaster) {
+ remoteUsers.push(me.convertRemoteUser(serverUser));
+ }
+ });
+ me.getRepo()[identifier]['users'] = remoteUsers;
+
+
+ var canvasDiv = $('#' + me.getCanvasId(canvas));
+ canvasDiv.find('.userPanel').remove();
+ var userPanel = $('');
+ userPanel.css({
+ position: 'absolute',
+ top: '0px',
+ left: '0px'
+ });
+ canvasDiv.append(userPanel);
+ $.each(remoteUsers, function (index, remoteUser) {
+ var userDiv = $('');
+ if (remoteUser.isMaster) {
+ userDiv.append('' + remoteUser.getName() + ' (Master)');
+ } else {
+ userDiv.append('' + remoteUser.getName() + ' ');
+
+ if (canvas.getRemoteIsMaster()) {
+ var controller;
+ if (!remoteUser.editable) {
+ controller = $('');
+ controller.data('identifier', identifier);
+ controller.data('mode', 'readonly');
+ controller.data('user', remoteUser);
+ } else {
+ controller = $('');
+ controller.data('identifier', identifier);
+ controller.data('mode', 'editable');
+ controller.data('user', remoteUser);
+ }
+ userDiv.append(controller);
+
+ controller.click(function () {
+ var clickedIdentifier = $(this).data('identifier');
+ var clickedMode = $(this).data('mode');
+ var user = $(this).data('user');
+ if (clickedMode === 'readonly') {
+ user.setEditable(true);
+ } else {
+ user.setEditable(false);
+ }
+ me.updateUserState(clickedIdentifier, user, function () {
+
+ });
+ });
+ }
+ }
+ userPanel.append(userDiv);
+ });
+ });
+ },
+
+ /**
+ * 서버에 사용자 상태를 업데이트시킨다.
+ *
+ * @param identifier 리모트그룹 식별자
+ * @param user OG.handler.RemoteUser 유저
+ * @param callback Callback
+ *
+ * @return callback(OG.handler.RemoteUser) 변경된 유저
+ *
+ */
+ updateUserState: function (identifier, user, callback) {
+
+ var me = this, result;
+ var remote = this.getRemote({
+ identifier: identifier,
+ remoteUser: me.encodeJson(user)
+ });
+ var objectId = remote.__objectId;
+ remote.updateUserState(null, function () {
+
+ result = mw3.objects[objectId];
+ me.closeRemote(remote);
+ var remoteUser = me.convertRemoteUser(JSON.parse(result['remoteUser']));
+ callback(remoteUser);
+ })
+ },
+
+ /**
+ * From Server.
+ * 주어진 캔버스를 업데이트한다.
+ *
+ * @param data identifier, canvasJsonString
+ *
+ */
+ updateCanvas: function (data) {
+ var parse = JSON.parse(data);
+ var identifier = parse.identifier;
+ var canvasData = parse.canvasData;
+
+ var canvas = this.getCanvasByIdentifier(identifier);
+ if (!canvas || !canvas.getRemotable()) {
+ return;
+ }
+ canvas.setRemoteDuring(true);
+ canvas.loadJSON(canvasData);
+ canvas.setRemoteDuring(false);
+ $(canvas.getRootElement()).trigger('updateCanvas', [canvasData]);
+ },
+
+ /**
+ * To Server.
+ * 주어진 캔바스를 브로드캐스팅한다.
+ *
+ * @param OG.graph.Canvas canvas
+ * @param callback Callback
+ *
+ * @return callback(OG.graph.Canvas) canvas
+ */
+ broadCastCanvas: function (canvas, callback) {
+ if (!canvas.getRemotable()) {
+ callback(null);
+ return;
+ }
+ if (!canvas.getRemoteEditable()) {
+ callback(null);
+ return;
+ }
+
+ var identifier = canvas.getIdentifier();
+ var me = this, result;
+ var remote = this.getRemote({
+ identifier: identifier,
+ canvasData: me.encodeJson(canvas.toJSON())
+ });
+ var objectId = remote.__objectId;
+ remote.broadCastCanvas(null, function () {
+ result = mw3.objects[objectId];
+ me.closeRemote(remote);
+ callback(canvas);
+ })
+ },
+ /**
+ * To Server.
+ * 주어진 리모트그룹에 사용자가 종료하였음을 알린다.
+ *
+ * @param identifier 리모트그룹 식별자
+ * @param callback Callback
+ *
+ */
+ remoteExit: function (identifier, callback) {
+
+ var me = this, result;
+ var remote = this.getRemote({
+ identifier: identifier
+ });
+ var objectId = remote.__objectId;
+ remote.remoteUserExited(null, function () {
+ result = mw3.objects[objectId];
+ me.closeRemote(remote);
+ callback(null);
+ })
+ },
+ /**
+ * Scheduler, To Server.
+ *
+ * 브라우저의 재종료 없이 캔버스가 삭제되었을 경우
+ * 관련 캐쉬를 삭제하고 서버에 사용자가 종료되었음을 알린다.
+ *
+ */
+ checkExpiredRemoteCanvas: function () {
+ if (this._SCHEDULER) {
+ return;
+ }
+
+ var me = this, repo, identifier, canvas;
+ this._SCHEDULER = setInterval(function () {
+ repo = me.getRepo();
+ for (identifier in repo) {
+ canvas = repo[identifier].canvas;
+ if (canvas && me.getCanvasId(canvas) && $('#' + me.getCanvasId(canvas)).length) {
+ return;
+ }
+ delete repo[identifier];
+ me.remoteExit(identifier, function () {
+ });
+ }
+ }, 1000
+ );
+ }
+};
+OG.handler.RemoteHandler.prototype.constructor = OG.handler.RemoteHandler;
+OG.RemoteHandler = new OG.handler.RemoteHandler();
+OG.RemoteHandler.checkExpiredRemoteCanvas();
+
+/**
+ * OpenGraph 캔버스 클래스
+ *
+ * @class
+ * @requires OG.common.*
+ * @requires OG.geometry.*
+ * @requires OG.shape.*
+ * @requires OG.renderer.*
+ * @requires OG.handler.*
+ * @requires OG.layout.*
+ * @requires raphael-2.1.0
+ *
+ * @example
+ * var canvas = new OG.Canvas('canvas', [1000, 800], 'white', 'url(./images/grid.gif)');
+ *
+ * var circleShape = canvas.drawShape([100, 100], new OG.CircleShape(), [100, 100]);
+ * var ellipseShape = canvas.drawShape([300, 200], new OG.EllipseShape('label'), [100, 50]);
+ *
+ * var edge = canvas.connect(circleShape, ellipseShape);
+ *
+ * @param {HTMLElement|String} container 컨테이너 DOM element or ID
+ * @param {Number[]} containerSize 컨테이너 Width, Height
+ * @param {String} backgroundColor 캔버스 배경색
+ * @param {String} backgroundImage 캔버스 배경이미지
+ * @author Seungpil Park
+ */
+OG.graph.Canvas = function (container, containerSize, backgroundColor, backgroundImage) {
+
+ this._CONFIG = {
+
+ /**
+ * 트랙패드 허용
+ */
+ ENABLE_TRACKPAD: false,
+ /**
+ * 풀, 래인 도형의 드랍시 자동 위치 조정 기능
+ */
+ POOL_DROP_EVENT: false,
+ /**
+ * 도형, 스팟 이동시 이웃한 도형에 대해 자동보정이 이루어지는 여부.
+ */
+ AUTOMATIC_GUIDANCE: true,
+ /**
+ * 선연결을 클릭하였을때만 변곡점 변경 가능 여부
+ */
+ SPOT_ON_SELECT: false,
+
+ /**
+ * 백도어
+ */
+ BACKDOOR: {
+ url: null,
+ scale: 100,
+ opacity: 1,
+ width: null,
+ height: null,
+ container: null,
+ container_updated: false
+ },
+ /**
+ * 빠른 로딩
+ */
+ FAST_LOADING: false,
+ /**
+ * 자동 슬라이더 업데이트 여부
+ */
+ AUTO_SLIDER_UPDATE: true,
+ /**
+ * 자동 히스토리 저장
+ */
+ AUTO_HISTORY: false,
+ /**
+ * 마우스 휠 스케일 변경 여부
+ */
+ WHEEL_SCALABLE: false,
+ /**
+ * 마우스 드래그 페이지 이동 가능 여부
+ */
+ DRAG_PAGE_MOVABLE: false,
+ /**
+ * 도형 선택시 캔버스 포거싱 여부
+ */
+ FOCUS_CANVAS_ONSELECT: true,
+ /**
+ * 연결된 두 오브젝트의 소속에 따른 연결선 스타일 변화 여부
+ */
+ CHECK_BRIDGE_EDGE: true,
+ /**
+ * 스틱 가이드 생성 여부
+ */
+ STICK_GUIDE: true,
+ /**
+ * 슬라이더
+ */
+ SLIDER: null,
+ /**
+ * 서버 수신 데이터 처리중
+ */
+ REMOTE_PERFORMED_DURING: false,
+ /**
+ * 리모트 데피니션
+ */
+ REMOTE_IDENTIFIER: null,
+ /**
+ * 리모트 모드
+ */
+ REMOTEABLE: false,
+ /**
+ * 리모트 모드 수정권한
+ */
+ REMOTE_EDITABLE: false,
+ /**
+ * 리모트 모드 마스터모드
+ */
+ REMOTE_ISMASTER: false,
+ /**
+ * 히스토리 인덱스
+ */
+ HISTORY_INDEX: 0,
+ /**
+ * 히스토리 저장소
+ */
+ HISTORY: [],
+ /**
+ * 히스토리 저장 횟수
+ */
+ HISTORY_SIZE: 100,
+
+ /**
+ * 클릭선택 가능여부
+ */
+ SELECTABLE: true,
+
+ /**
+ * 마우스드래그선택 가능여부
+ */
+ DRAG_SELECTABLE: true,
+
+ /**
+ * 이동 가능여부
+ */
+ MOVABLE: true,
+ MOVABLE_: {
+ GEOM: true,
+ TEXT: true,
+ HTML: true,
+ IMAGE: true,
+ EDGE: true,
+ GROUP: true
+ }
+ ,
+
+ /**
+ * 리사이즈 가능여부
+ */
+ RESIZE_CANVAS_MARGIN: 5,
+ RESIZABLE: true,
+ RESIZABLE_: {
+ GEOM: true,
+ TEXT: true,
+ HTML: true,
+ IMAGE: true,
+ EDGE: true,
+ GROUP: true
+ }
+ ,
+
+ /**
+ * 연결 가능여부
+ */
+ CONNECTABLE: true,
+
+ /**
+ * Self 연결 가능여부
+ */
+ SELF_CONNECTABLE: true,
+
+ /**
+ * 가이드에 자기자신을 복사하는 컨트롤러 여부.
+ */
+ CONNECT_CLONEABLE: true,
+
+ /**
+ * 드래그하여 연결시 그룹을 건너뛸때 스타일 변경 여부
+ */
+ CONNECT_STYLE_CHANGE: true,
+ CONNECT_STYLE_CHANGE_: {
+ GEOM: true,
+ TEXT: true,
+ HTML: true,
+ IMAGE: true,
+ EDGE: true,
+ GROUP: true
+ }
+ ,
+
+ /**
+ * 가이드에 삭제 컨트롤러 여부
+ */
+ DELETABLE: true,
+ DELETABLE_: {
+ GEOM: true,
+ TEXT: true,
+ HTML: true,
+ IMAGE: true,
+ EDGE: true,
+ GROUP: true
+ }
+ ,
+
+ /**
+ * 라벨 수정여부
+ */
+ LABEL_EDITABLE: true,
+ LABEL_EDITABLE_: {
+ GEOM: true,
+ TEXT: true,
+ HTML: true,
+ IMAGE: true,
+ EDGE: true,
+ GROUP: true
+ }
+ ,
+
+ /**
+ * 그룹핑 가능여부
+ */
+ GROUP_DROPABLE: true,
+
+ /**
+ * 이동, 리사이즈 드래그시 MOVE_SNAP_SIZE 적용 여부
+ */
+ DRAG_GRIDABLE: true,
+
+ /**
+ * 핫키 가능여부
+ */
+ ENABLE_HOTKEY: true,
+
+ /**
+ * 핫키 : UNDO REDO 키 가능여부
+ */
+ ENABLE_HOTKEY_CTRL_Z: true,
+
+ /**
+ * 핫키 : DELETE 삭제 키 가능여부
+ */
+ ENABLE_HOTKEY_DELETE: false,
+
+ /**
+ * 핫키 : Ctrl+A 전체선택 키 가능여부
+ */
+ ENABLE_HOTKEY_CTRL_A: true,
+
+ /**
+ * 핫키 : Ctrl+C 복사 키 가능여부
+ */
+ ENABLE_HOTKEY_CTRL_C: false,
+
+ /**
+ * 핫키 : Ctrl+V 붙여넣기 키 가능여부
+ */
+ ENABLE_HOTKEY_CTRL_V: false,
+
+ /**
+ * 핫키 : Ctrl+D 복제하기 키 가능여부
+ */
+ ENABLE_HOTKEY_CTRL_D: true,
+
+ /**
+ * 핫키 : Ctrl+G 그룹 키 가능여부
+ */
+ ENABLE_HOTKEY_CTRL_G: true,
+
+ /**
+ * 핫키 : Ctrl+U 언그룹 키 가능여부
+ */
+ ENABLE_HOTKEY_CTRL_U: true,
+
+ /**
+ * 핫키 : 방향키 가능여부
+ */
+ ENABLE_HOTKEY_ARROW: true,
+
+ /**
+ * 핫키 : Shift + 방향키 가능여부
+ */
+ ENABLE_HOTKEY_SHIFT_ARROW: true,
+
+ /**
+ * 마우스 우클릭 메뉴 가능여부
+ */
+ ENABLE_CONTEXTMENU: true,
+
+ /**
+ * 루트 컨텍스트 메뉴 가능여부
+ */
+ ENABLE_ROOT_CONTEXTMENU: true,
+
+ /**
+ * 캔버스 스케일(리얼 사이즈 : Scale = 1)
+ */
+ SCALE: 1,
+
+ /**
+ * 캔버스 최소 스케일
+ */
+ SCALE_MIN: 0.1,
+
+ /**
+ * 캔버스 최대 스케일
+ */
+ SCALE_MAX: 10,
+
+ /**
+ * Edge 꺽은선 패딩 사이즈
+ */
+ EDGE_PADDING: 20,
+
+ /**
+ * 라벨의 패딩 사이즈
+ */
+ LABEL_PADDING: 5,
+
+ /**
+ * 라벨 에디터(textarea)의 디폴트 width
+ */
+ LABEL_EDITOR_WIDTH: 500,
+
+ /**
+ * 라벨 에디터(textarea)의 디폴트 height
+ */
+ LABEL_EDITOR_HEIGHT: 16,
+
+ /**
+ * 시작, 끝점 라벨의 offsetTop 값
+ */
+ FROMTO_LABEL_OFFSET_TOP: 15,
+
+ /**
+ * Move & Resize 용 가이드 선 콘트롤 Rect 사이즈
+ */
+ GUIDE_LINE_SIZE: 20,
+ /**
+ * Move & Resize 용 가이드 선 콘트롤 마진 사이즈
+ */
+ GUIDE_LINE_MARGIN: 10,
+
+ /**
+ * Move & Resize 용 가이드 콘트롤 Rect 사이즈
+ */
+ GUIDE_RECT_SIZE: 8,
+
+ /**
+ * Move & Resize 용 가이드 가로, 세로 최소 사이즈
+ */
+ GUIDE_MIN_SIZE: 18,
+
+ /**
+ * 도형 컨트롤러 아이콘 커넥트 라인 표시 개수
+ */
+ GUIDE_CONTROL_LINE_NUM: 2,
+
+ /**
+ * Collapse & Expand 용 가이드 Rect 사이즈
+ */
+ COLLAPSE_SIZE: 10,
+
+ /**
+ * Shape Move & Resize 시 이동 간격
+ */
+ MOVE_SNAP_SIZE: 8,
+
+ /**
+ * 터미널 cross 사이즈
+ */
+ TERMINAL_SIZE: 5,
+
+ /**
+ * Shape 복사시 패딩 사이즈
+ */
+ COPY_PASTE_PADDING: 20,
+
+ /**
+ * Fit Canvas 시 패딩 사이즈
+ */
+ FIT_CANVAS_PADDING: 20,
+
+ /**
+ * 캔버스 영역 자동 확장 여부
+ */
+ AUTO_EXTENSIONAL: true,
+
+ /**
+ * 캔버스 영역 자동 확장시 증가 사이즈
+ */
+ AUTO_EXTENSION_SIZE: 100,
+
+ /**
+ * 캔버스 배경색
+ */
+ CANVAS_BACKGROUND: "#f9f9f9",
+
+ /**
+ * 이미지 베이스 패스
+ */
+ IMAGE_BASE: 'https://raw.githubusercontent.com/kimsanghoon1/k8s-UI/master/public/static/images/symbol/',
+
+ /**
+ * 이미지 url 정보
+ */
+ IMAGE_USER: "http://processcodi.com/images/opengraph/User.png",
+ IMAGE_SEND: "http://processcodi.com/images/opengraph/Send.png",
+ IMAGE_RECEIVE: "http://processcodi.com/images/opengraph/Receive.png",
+ IMAGE_MANUAL: "http://processcodi.com/images/opengraph/Manual.png",
+ IMAGE_SERVICE: "http://processcodi.com/images/opengraph/Service.png",
+ IMAGE_RULE: "http://processcodi.com/images/opengraph/BusinessRule.png",
+ IMAGE_SCRIPT: "http://processcodi.com/images/opengraph/Script.png",
+ IMAGE_MAPPER: "http://processcodi.com/images/opengraph/mapper.png",
+ IMAGE_WEB: "http://processcodi.com/images/opengraph/w_services.png",
+
+ /**
+ * Edge 선 자동마춤 각도 최소값
+ */
+ TRIM_EDGE_ANGLE_SIZE: 170,
+ /**
+ * Edge 선 이동딜레이 거리
+ */
+ EDGE_MOVE_DELAY_SIZE: 14,
+
+ /**
+ * swimLane 리사이즈 최소 폭
+ */
+ LANE_MIN_SIZE: 50,
+
+ /**
+ * swimLane 확장 기본 폭
+ */
+ LANE_DEFAULT_SIZE: 100,
+
+ /**
+ * swimLane, pool 생성 기본 가로,세로
+ */
+ POOL_DEFAULT_SIZE: [300, 200],
+
+ /**
+ * 그룹 하위 shape 와 그룹사이의 여유폭
+ */
+ GROUP_INNER_SAPCE: 10,
+
+ /**
+ * 라벨 최소 크기(IE)
+ */
+ LABEL_MIN_SIZE: 0,
+
+ /**
+ * 라벨 최대 크기(IE)
+ */
+ LABEL_MAX_SIZE: 1000,
+
+ /**
+ * 디폴트 스타일 정의
+ */
+ DEFAULT_STYLE: {
+ SHAPE: {
+ cursor: "default"
+ }
+ ,
+ GEOM: {
+ stroke: "black",
+ "fill-r": ".5",
+ "fill-cx": ".5",
+ "fill-cy": ".5",
+ fill: "white",
+ "fill-opacity": 0,
+ "label-position": "center"
+ }
+ ,
+ TEXT: {
+ stroke: "none", "text-anchor": "middle"
+ }
+ ,
+ HTML: {
+ "label-position": "bottom", "text-anchor": "middle", "vertical-align": "top"
+ }
+ ,
+ IMAGE: {
+ "label-position": "bottom", "text-anchor": "middle", "vertical-align": "top"
+ },
+ SVG: {
+ "label-position": "bottom", "text-anchor": "middle", "vertical-align": "top"
+ }
+ ,
+ EDGE: {
+ stroke: "black",
+ fill: "none",
+ "fill-opacity": 0,
+ "stroke-width": 1.5,
+ "stroke-opacity": 1,
+ "edge-type": "plain",
+ "arrow-start": "none",
+ "arrow-end": "block",
+ "stroke-dasharray": "",
+ "label-position": "center",
+ "stroke-linejoin": "round",
+ cursor: "pointer"
+ }
+ ,
+ EDGE_SHADOW: {
+ stroke: "#00FF00",
+ fill: "none",
+ "fill-opacity": 0,
+ "stroke-width": 1,
+ "stroke-opacity": 1,
+ "arrow-start": "none",
+ "arrow-end": "none",
+ "stroke-dasharray": "- ",
+ "edge-type": "plain",
+ cursor: "pointer"
+ }
+ ,
+ EDGE_HIDDEN: {
+ stroke: "white",
+ fill: "none",
+ "fill-opacity": 0,
+ "stroke-width": 10,
+ "stroke-opacity": 0,
+ cursor: "pointer"
+ }
+ ,
+ GROUP: {
+ stroke: "black",
+ fill: "none",
+ "fill-opacity": 0,
+ "label-position": "bottom",
+ "text-anchor": "middle",
+ "vertical-align": "top"
+ }
+ ,
+ GROUP_HIDDEN: {
+ stroke: "black", fill: "white", "fill-opacity": 0, "stroke-opacity": 0, cursor: "move"
+ }
+ ,
+ GROUP_SHADOW: {
+ stroke: "white",
+ fill: "none",
+ "fill-opacity": 0,
+ "stroke-width": 15,
+ "stroke-opacity": 0,
+ cursor: "pointer"
+ }
+ ,
+ GROUP_SHADOW_MAPPER: {
+ stroke: "white",
+ fill: "none",
+ "fill-opacity": 0,
+ "stroke-width": 1,
+ "stroke-opacity": 0,
+ cursor: "pointer"
+ }
+ ,
+ GUIDE_BBOX: {
+ stroke: "#00FF00",
+ fill: "white",
+ "fill-opacity": 0,
+ "stroke-dasharray": "- ",
+ "shape-rendering": "crispEdges",
+ cursor: "move"
+ }
+ ,
+ GUIDE_UL: {
+ stroke: "#03689a",
+ fill: "#03689a",
+ "fill-opacity": 0.5,
+ cursor: "nwse-resize",
+ "shape-rendering": "crispEdges"
+ }
+ ,
+ GUIDE_UR: {
+ stroke: "#03689a",
+ fill: "#03689a",
+ "fill-opacity": 0.5,
+ cursor: "nesw-resize",
+ "shape-rendering": "crispEdges"
+ }
+ ,
+ GUIDE_LL: {
+ stroke: "#03689a",
+ fill: "#03689a",
+ "fill-opacity": 0.5,
+ cursor: "nesw-resize",
+ "shape-rendering": "crispEdges"
+ }
+ ,
+ GUIDE_LR: {
+ stroke: "#03689a",
+ fill: "#03689a",
+ "fill-opacity": 0.5,
+ cursor: "nwse-resize",
+ "shape-rendering": "crispEdges"
+ }
+ ,
+ GUIDE_LC: {
+ stroke: "#03689a",
+ fill: "#03689a",
+ "fill-opacity": 0.5,
+ cursor: "ew-resize",
+ "shape-rendering": "crispEdges"
+ }
+ ,
+ GUIDE_UC: {
+ stroke: "black",
+ fill: "#03689a",
+ "fill-opacity": 0.5,
+ cursor: "ns-resize",
+ "shape-rendering": "crispEdges"
+ }
+ ,
+ GUIDE_RC: {
+ stroke: "black",
+ fill: "#03689a",
+ "fill-opacity": 0.5,
+ cursor: "ew-resize",
+ "shape-rendering": "crispEdges"
+ }
+ ,
+ GUIDE_LWC: {
+ stroke: "black",
+ fill: "#03689a",
+ "fill-opacity": 0.5,
+ cursor: "ns-resize",
+ "shape-rendering": "crispEdges"
+ }
+ ,
+ GUIDE_FROM: {
+ stroke: "black", fill: "#00FF00", cursor: "move", "shape-rendering": "crispEdges"
+ }
+ ,
+ GUIDE_TO: {
+ stroke: "black", fill: "#00FF00", cursor: "move", "shape-rendering": "crispEdges"
+ }
+ ,
+ GUIDE_CTL_H: {
+ stroke: "black", fill: "#00FF00", cursor: "ew-resize", "shape-rendering": "crispEdges"
+ }
+ ,
+ GUIDE_CTL_V: {
+ stroke: "black", fill: "#00FF00", cursor: "ns-resize", "shape-rendering": "crispEdges"
+ }
+ ,
+ GUIDE_SHADOW: {
+ stroke: "black", fill: "none", "stroke-dasharray": "- ", "shape-rendering": "crispEdges"
+ }
+ ,
+ GUIDE_LINE: {
+ stroke: "black",
+ fill: "none",
+ "fill-opacity": 0,
+ "stroke-width": 1.2,
+ "stroke-opacity": 1,
+ "stroke-dasharray": "",
+ "arrow-end": "block",
+ "stroke-linejoin": "round",
+ cursor: "pointer"
+ }
+ ,
+ GUIDE_LINE_ESSENSIA: {
+ stroke: "black",
+ fill: "none",
+ "fill-opacity": 0,
+ "stroke-width": 1.2,
+ "stroke-opacity": 1,
+ "stroke-dasharray": "",
+ "arrow-start": "diamond",
+ "arrow-end": "none",
+ "stroke-linejoin": "round",
+ cursor: "pointer"
+ }
+ ,
+ GUIDE_VIRTUAL_EDGE: {
+ stroke: "black",
+ fill: "none",
+ "fill-opacity": 0,
+ "stroke-width": 1,
+ "stroke-opacity": 1,
+ "stroke-dasharray": "- ",
+ "stroke-linejoin": "round",
+ "arrow-start": "none",
+ "arrow-end": "none"
+ }
+ ,
+ GUIDE_LINE_AREA: {
+ stroke: "#ffffff",
+ fill: "#ffffff",
+ "fill-opacity": 0.1,
+ "stroke-width": 1,
+ "stroke-opacity": 0.2,
+ cursor: "pointer"
+ }
+ ,
+ GUIDE_RECT_AREA: {
+ stroke: "black",
+ fill: "#ffffff",
+ "fill-opacity": 0,
+ "stroke-width": 1,
+ "stroke-opacity": 1,
+ cursor: "pointer"
+ }
+ ,
+ RUBBER_BAND: {
+ stroke: "#0000FF", opacity: 0.2, fill: "#0077FF"
+ }
+ ,
+ DROP_OVER_BBOX: {
+ stroke: "#0077FF", fill: "none", opacity: 0.3, "shape-rendering": "crispEdges"
+ }
+ ,
+ LABEL: {
+ "font-size": 12, "font-color": "black", "fill": "none"
+ }
+ ,
+ LABEL_EDITOR: {
+ position: "absolute",
+ overflow: "visible",
+ resize: "none",
+ "text-align": "center",
+ display: "block",
+ padding: 0
+ }
+ ,
+ COLLAPSE: {
+ stroke: "black",
+ fill: "none",
+ "fill-opacity": 0,
+ cursor: "pointer",
+ "shape-rendering": "crispEdges"
+ }
+ ,
+ COLLAPSE_BBOX: {
+ stroke: "none", fill: "none", "fill-opacity": 0
+ }
+ ,
+ BUTTON: {
+ stroke: "#9FD7FF",
+ fill: "white",
+ "fill-opacity": 0,
+ cursor: "pointer",
+ "shape-rendering": "crispEdges"
+ }
+ ,
+ CONNECT_GUIDE_EVENT_AREA: {
+ stroke: "#ffffff",
+ fill: "none",
+ "fill-opacity": 0,
+ "stroke-width": 20,
+ "stroke-opacity": 0
+ }
+ ,
+ CONNECT_GUIDE_BBOX: {
+ stroke: "#00FF00",
+ fill: "none",
+ "stroke-dasharray": "- ",
+ "shape-rendering": "crispEdges"
+ }
+ ,
+ CONNECT_GUIDE_BBOX_EXPEND: 10,
+ CONNECT_GUIDE_SPOT_CIRCLE: {
+ r: 7,
+ stroke: "#A6A6A6",
+ "stroke-width": 1,
+ fill: "#FFE400",
+ "fill-opacity": 0.5,
+ cursor: "pointer"
+ }
+ ,
+ CONNECT_GUIDE_SPOT_RECT: {
+ stroke: "#A6A6A6",
+ "stroke-width": 1,
+ fill: "#FFE400",
+ "fill-opacity": 0.2,
+ cursor: "ns-resize",
+ w: 20,
+ h: 10
+ }
+ ,
+ CONNECTABLE_HIGHLIGHT: {
+ "stroke-width": 2
+ }
+ ,
+ NOT_CONNECTABLE_HIGHLIGHT: {
+ fill: "#FAAFBE",
+ "fill-opacity": 0.5
+ }
+ }
+ };
+
+ this._RENDERER = container ? new OG.RaphaelRenderer(container, containerSize, backgroundColor, backgroundImage, this._CONFIG) : null;
+ this._RENDERER._CANVAS = this;
+ this._HANDLER = new OG.EventHandler(this._RENDERER, this._CONFIG);
+ this._CONTAINER = OG.Util.isElement(container) ? container : document.getElementById(container);
+}
+;
+
+OG.graph.Canvas.prototype = {
+
+ //me._CONFIG.WHEEL_SCALABLE
+
+ fastLoadingON: function () {
+ this._CONFIG.FAST_LOADING = true;
+ // this._CONFIG.AUTO_SLIDER_UPDATE = false;
+ // this._CONFIG.AUTO_HISTORY = false;
+ },
+ fastLoadingOFF: function () {
+ this._CONFIG.FAST_LOADING = false;
+ // this._CONFIG.AUTO_SLIDER_UPDATE = true;
+ // this._CONFIG.AUTO_HISTORY = true;
+ this.updateSlider();
+ },
+ getEventHandler: function () {
+ return this._HANDLER;
+ },
+ setRemoteDuring: function (during) {
+ this._CONFIG.REMOTE_PERFORMED_DURING = during;
+ },
+ getRemoteDuring: function () {
+ return this._CONFIG.REMOTE_PERFORMED_DURING;
+ },
+ setIdentifier: function (identifier) {
+ this._CONFIG.REMOTE_IDENTIFIER = identifier;
+ },
+ getIdentifier: function () {
+ return this._CONFIG.REMOTE_IDENTIFIER;
+ },
+ getRemotable: function () {
+ return this._CONFIG.REMOTEABLE;
+ },
+ setRemotable: function (remotable) {
+ this._CONFIG.REMOTEABLE = remotable;
+ },
+ setRemoteEditable: function (editable) {
+ this._CONFIG.REMOTE_EDITABLE = editable;
+ },
+ getRemoteEditable: function () {
+ return this._CONFIG.REMOTE_EDITABLE;
+ },
+ setRemoteIsMaster: function (isMaster) {
+ this._CONFIG.REMOTE_ISMASTER = isMaster;
+ },
+ getRemoteIsMaster: function () {
+ return this._CONFIG.REMOTE_ISMASTER;
+ },
+ setCurrentCanvas: function (canvas) {
+ this._RENDERER.setCanvas(canvas);
+ },
+ /**
+ * Canvas 의 설정값을 초기화한다.
+ *
+ *
+ * - selectable : 클릭선택 가능여부(디폴트 true)
+ * - dragSelectable : 마우스드래그선택 가능여부(디폴트 true)
+ * - movable : 이동 가능여부(디폴트 true)
+ * - resizable : 리사이즈 가능여부(디폴트 true)
+ * - connectable : 연결 가능여부(디폴트 true)
+ * - selfConnectable : Self 연결 가능여부(디폴트 true)
+ * - connectCloneable : 드래그하여 연결시 대상 없을 경우 자동으로 Shape 복사하여 연결 처리 여부(디폴트 true)
+ * - labelEditable : 라벨 수정여부(디폴트 true)
+ * - groupDropable : 그룹핑 가능여부(디폴트 true)
+ * - enableHotKey : 핫키 가능여부(디폴트 true)
+ * - enableContextMenu : 마우스 우클릭 메뉴 가능여부(디폴트 true)
+ * - autoExtensional : 캔버스 자동 확장 기능(디폴트 true)
+ * - useSlider : 확대축소 슬라이더 사용 여부
+ * - stickGuide : 스틱 가이드 표시 여부
+ * - checkBridgeEdge : 연결된 두 오브젝트의 소속에 따른 연결선 스타일 변화 여부
+ *
+ *
+ * @param {Object} config JSON 포맷의 configuration
+ */
+ initConfig: function (config) {
+ if (config) {
+ this._CONFIG.REMOTEABLE = config.remoteable === undefined ? this._CONFIG.REMOTEABLE : config.remoteable;
+ this._CONFIG.SELECTABLE = config.selectable === undefined ? this._CONFIG.SELECTABLE : config.selectable;
+ this._CONFIG.DRAG_SELECTABLE = config.dragSelectable === undefined ? this._CONFIG.DRAG_SELECTABLE : config.dragSelectable;
+ this._CONFIG.MOVABLE = config.movable === undefined ? this._CONFIG.MOVABLE : config.movable;
+ this._CONFIG.RESIZABLE = config.resizable === undefined ? this._CONFIG.RESIZABLE : config.resizable;
+ this._CONFIG.CONNECTABLE = config.connectable === undefined ? this._CONFIG.CONNECTABLE : config.connectable;
+ this._CONFIG.SELF_CONNECTABLE = config.selfConnectable === undefined ? this._CONFIG.SELF_CONNECTABLE : config.selfConnectable;
+ this._CONFIG.CONNECT_CLONEABLE = config.connectCloneable === undefined ? this._CONFIG.CONNECT_CLONEABLE : config.connectCloneable;
+ this._CONFIG.LABEL_EDITABLE = config.labelEditable === undefined ? this._CONFIG.LABEL_EDITABLE : config.labelEditable;
+ this._CONFIG.GROUP_DROPABLE = config.groupDropable === undefined ? this._CONFIG.GROUP_DROPABLE : config.groupDropable;
+ this._CONFIG.ENABLE_HOTKEY = config.enableHotKey === undefined ? this._CONFIG.ENABLE_HOTKEY : config.enableHotKey;
+ this._CONFIG.ENABLE_CONTEXTMENU = config.enableContextMenu === undefined ? this._CONFIG.ENABLE_CONTEXTMENU : config.enableContextMenu;
+ this._CONFIG.AUTO_EXTENSIONAL = config.autoExtensional === undefined ? this._CONFIG.AUTO_EXTENSIONAL : config.autoExtensional;
+ this._CONFIG.STICK_GUIDE = config.stickGuide === undefined ? this._CONFIG.STICK_GUIDE : config.stickGuide;
+ this._CONFIG.CHECK_BRIDGE_EDGE = config.checkBridgeEdge === undefined ? this._CONFIG.CHECK_BRIDGE_EDGE : config.checkBridgeEdge;
+ this._CONFIG.AUTO_HISTORY = config.autoHistory === undefined ? this._CONFIG.AUTO_HISTORY : config.autoHistory;
+ this._CONFIG.AUTO_SLIDER_UPDATE = config.autoSliderUpdate === undefined ? this._CONFIG.AUTO_SLIDER_UPDATE : config.autoSliderUpdate;
+ }
+
+ this._HANDLER.setDragSelectable(this._CONFIG.SELECTABLE && this._CONFIG.DRAG_SELECTABLE);
+ this._HANDLER.setWheelScale();
+ this._HANDLER.setDragPageMovable();
+ this._HANDLER.setEnableHotKey(this._CONFIG.ENABLE_HOTKEY);
+ if (this._CONFIG.ENABLE_CONTEXTMENU) {
+ if (this._CONFIG.ENABLE_ROOT_CONTEXTMENU) {
+ this._HANDLER.enableRootContextMenu();
+ }
+ this._HANDLER.enableShapeContextMenu();
+ }
+
+ this.CONFIG_INITIALIZED = true;
+ },
+
+ /**
+ * 랜더러를 반환한다.
+ *
+ * @return {OG.RaphaelRenderer}
+ */
+ getRenderer: function () {
+ return this._RENDERER;
+ },
+
+ /**
+ * 컨테이너 DOM element 를 반환한다.
+ *
+ * @return {HTMLElement}
+ */
+ getContainer: function () {
+ return this._CONTAINER;
+ },
+
+ /**
+ * 이벤트 핸들러를 반환한다.
+ *
+ * @return {OG.EventHandler}
+ */
+ getEventHandler: function () {
+ return this._HANDLER;
+ },
+
+ /**
+ * 백도어를 삽입한다.
+ * @param url
+ * @param scale
+ * @param opacity
+ */
+ addBackDoor: function (url, scale, opacity) {
+ var me = this;
+ var image = new Image();
+ image.src = url;
+ image.onload = function () {
+ var w = image.naturalWidth;
+ var h = image.naturalHeight;
+ me._CONFIG.BACKDOOR.url = url;
+ me._CONFIG.BACKDOOR.scale = scale ? scale : me._CONFIG.BACKDOOR.scale;
+ me._CONFIG.BACKDOOR.opacity = opacity ? opacity : me._CONFIG.BACKDOOR.opacity;
+ me._CONFIG.BACKDOOR.width = w;
+ me._CONFIG.BACKDOOR.height = h;
+ me._CONFIG.BACKDOOR.container_updated = true;
+
+ var container = me._CONTAINER;
+ var backdoorId = container.id + OG.Constants.BACKDOOR_SUFFIX;
+
+ //백도어 생성
+ var existBackdoor = $('#' + container.id).find('#' + backdoorId);
+ if (!existBackdoor || existBackdoor.length < 1) {
+ existBackdoor = $('');
+ existBackdoor.attr('id', backdoorId);
+ existBackdoor.css({
+ position: 'absolute',
+ top: '0px',
+ left: '0px'
+ });
+ $(me._RENDERER.getRootElement()).before(existBackdoor);
+ existBackdoor.attr('src', url);
+ } else {
+ existBackdoor.attr('src', url);
+ }
+ me._CONFIG.BACKDOOR.container = existBackdoor;
+
+ //최초에는 캔버스 크기를 조정해줌.
+ var canvasScale = me.getScale();
+ var canvasSize = me.getCanvasSize();
+ if (canvasSize[0] < w * (scale / 100) * canvasScale) {
+ canvasSize[0] = w * (scale / 100) * canvasScale;
+ }
+ if (canvasSize[1] < h * (scale / 100) * canvasScale) {
+ canvasSize[1] = h * (scale / 100) * canvasScale;
+ }
+ me.setCanvasSize(canvasSize);
+
+
+ //기존 캔버스의 백그라운드 속성 초기화
+ $(me._RENDERER._PAPER.canvas).css({
+ "background-color": "transparent"
+ });
+
+ me.updateBackDoor();
+ me.updateSlider();
+ };
+ },
+ updateBackDoor: function (scale, opacity) {
+ var me = this;
+ if (!me._CONFIG.BACKDOOR.container) {
+ return;
+ }
+ me._CONFIG.BACKDOOR.scale = scale ? scale : me._CONFIG.BACKDOOR.scale;
+ me._CONFIG.BACKDOOR.opacity = opacity ? opacity : me._CONFIG.BACKDOOR.opacity;
+ var backDoorScale = me._CONFIG.BACKDOOR.scale;
+ var backDoorOpacity = me._CONFIG.BACKDOOR.opacity;
+ var width = me._CONFIG.BACKDOOR.width;
+ var height = me._CONFIG.BACKDOOR.height;
+
+ var existBackdoor = me._CONFIG.BACKDOOR.container;
+ var canvasScale = me.getScale();
+
+ existBackdoor.width(width * (backDoorScale / 100) * canvasScale);
+ existBackdoor.height(height * (backDoorScale / 100) * canvasScale);
+ existBackdoor.css('opacity', backDoorOpacity);
+
+ //scale 값이 실제로 들어오면 캔버스 사이즈를 조정해준다.
+ if (scale) {
+ var canvasSize = me.getCanvasSize();
+ if (canvasSize[0] < width * (backDoorScale / 100) * canvasScale) {
+ canvasSize[0] = width * (backDoorScale / 100) * canvasScale;
+ }
+ if (canvasSize[1] < height * (backDoorScale / 100) * canvasScale) {
+ canvasSize[1] = height * (backDoorScale / 100) * canvasScale;
+ }
+ me.setCanvasSize(canvasSize);
+ me.updateSlider();
+ }
+ },
+
+ /**
+ * 확대 축소 슬라이더를 설치한다.
+ */
+ addSlider: function (option) {
+ var me = this;
+ var slider;
+ var sliderBarWrapper;
+ var sliderText;
+ var sliderBar;
+ var sliderImageWrapper;
+ var sliderImage;
+ var sliderNavigator;
+ var onNavigatorMove;
+ var sliderParent;
+ var expandBtn;
+ var container = me._CONTAINER;
+ console.log(container)
+ if (!option.slider) {
+ return;
+ }
+ if (!option.slider.length) {
+ return;
+ }
+ slider = option.slider;
+ slider.css({
+ width: option.width + 'px'
+ });
+
+
+ // slider.dialog({
+ // title: option.title ? option.title : "확대/축소",
+ // position: option.position ? option.position : {my: "right top", at: "right top", of: container},
+ // height: option.height ? option.height : 500,
+ // width: option.width ? option.width : 250,
+ // dialogClass: "no-close",
+ // appendTo: option.appendTo ? option.appendTo : '#' + container.id,
+ // resize: function (event, ui) {
+ // me.updateNavigatior();
+ // }
+ // }
+ // );
+
+ //클로즈버튼 이벤트를 collape,expand 이벤트로..
+ // sliderParent = slider.parent();
+ // expandBtn = sliderParent.find('.ui-dialog-titlebar-close');
+ // expandBtn.html('');
+ // expandBtn.append();
+ // expandBtn.unbind('click');
+ // expandBtn.bind('click', function () {
+ // //접혀있는 상태라면
+ // if ($(this).data('collape')) {
+ // var height = $(this).data('collape');
+ // sliderParent.height(height);
+ // slider.show();
+ // $(this).data('collape', false);
+ // }
+ // //접혀있지 않은 상태라면
+ // else {
+ // $(this).data('collape', sliderParent.height());
+ // slider.hide();
+ // sliderParent.height(40);
+ // }
+ // });
+
+ sliderBarWrapper = $('');
+ sliderBarWrapper.css({
+ position: 'relative',
+ width: '100%'
+ });
+
+ sliderText = $('');
+ sliderBar = $('');
+ sliderBar.bind('change', function () {
+ me.updateSlider($(this).val());
+ });
+ sliderBar.bind('input', function () {
+ me.updateSlider($(this).val());
+ });
+ sliderBar.css({
+ position: 'relative',
+ //'writing-mode': 'bt-lr', /* IE */
+ 'width': '100%',
+ 'height': '8px'
+ });
+
+ // sliderImageWrapper = $('');
+ // sliderImageWrapper.css({
+ // position: 'absolute',
+ // top: '50px',
+ // bottom: '5px',
+ // left: '5px',
+ // right: '5px',
+ // 'overflow-x': 'hidden',
+ // 'overflow-y': 'auto',
+ // 'background': 'black'
+ // });
+
+ sliderImage = $('');
+ sliderImage.css({
+ position: 'absolute',
+ top: '0px',
+ left: '0px'
+ });
+ sliderImage.attr("width", "100%");
+ console.log(container.id)
+ sliderImage.attr('id', container.id + 'sliderImage');
+
+
+ sliderNavigator = $('');
+ sliderNavigator.css({
+ position: 'absolute',
+ top: '0px',
+ left: '0px',
+ width: '100px',
+ height: '100px',
+ background: 'transparent'
+ });
+
+ //네비게이터가 이동되었을경우의 이벤트
+ onNavigatorMove = function () {
+ var svg, svgW, svgH, imgW, imgH, xRate, yRate, xOffset, yOffset, sliderX, sliderY;
+ svg = me._RENDERER.getRootElement();
+ imgW = sliderImage.width();
+ imgH = sliderImage.height();
+ svgW = $(svg).attr('width');
+ svgH = $(svg).attr('height');
+ xRate = imgW / svgW;
+ yRate = imgH / svgH;
+ if (xRate > 1) {
+ xRate = 1;
+ }
+ if (yRate > 1) {
+ yRate = 1;
+ }
+ xOffset = sliderNavigator.offset().left - sliderImage.offset().left;
+ if (xOffset < 0) {
+ xOffset = 0;
+ }
+ yOffset = sliderNavigator.offset().top - sliderImage.offset().top;
+ if (yOffset < 0) {
+ yOffset = 0;
+ }
+ sliderX = xOffset * (1 / xRate);
+ sliderY = yOffset * (1 / yRate);
+ container.scrollLeft = sliderX;
+ container.scrollTop = sliderY;
+ };
+
+ $(container).unbind('scroll');
+ $(container).bind('scroll', function (event) {
+ if (sliderNavigator.data('drag')) {
+ return;
+ }
+ me.updateNavigatior();
+ });
+
+ // sliderImage.click(function (event) {
+ // var eX, eY, nX, nY;
+ // eX = event.pageX - sliderImage.offset().left;
+ // eY = event.pageY - sliderImage.offset().top;
+ // nX = eX - (sliderNavigator.width() / 2);
+ // nY = eY - (sliderNavigator.height() / 2);
+ // if (nX < 0) {
+ // nX = 0;
+ // }
+ // if (nY < 0) {
+ // nY = 0;
+ // }
+ // if ((nX + sliderNavigator.width()) > sliderImage.width()) {
+ // nX = sliderImage.width() - sliderNavigator.width();
+ // }
+ // if ((nY + sliderNavigator.height()) > sliderImage.height()) {
+ // nY = sliderImage.height() - sliderNavigator.height();
+ // }
+ // sliderNavigator.css({
+ // "left": nX + 'px',
+ // "top": nY + 'px'
+ // });
+ // onNavigatorMove();
+ // });
+
+ // sliderNavigator.draggable({
+ // containment: "#" + container.id + 'sliderImage',
+ // scroll: false,
+ // start: function (event) {
+ // sliderNavigator.data('drag', true);
+ // },
+ // drag: function (event) {
+ // onNavigatorMove();
+ // },
+ // stop: function (event) {
+ // onNavigatorMove();
+ // sliderNavigator.data('drag', false);
+ // }
+ // });
+
+ slider.append(sliderBarWrapper);
+ sliderBarWrapper.append(sliderText);
+ sliderBarWrapper.append(sliderBar);
+
+ // slider.append(sliderImageWrapper);
+ // sliderImageWrapper.append(sliderImage);
+ // sliderImageWrapper.append(sliderNavigator);
+
+ //캔버스 삭제시 슬라이더도 삭제
+ $(container).on("remove", function () {
+ me.removeSlider();
+ });
+
+ //기존에 등록된 슬라이더 삭제
+ if (this._CONFIG.SLIDER) {
+ me.removeSlider();
+ }
+
+ //슬라이더를 캔버스에 등록
+ this._CONFIG.SLIDER = slider;
+
+ //슬라이더 업데이트
+ this.updateSlider(this._CONFIG.SCALE * 100);
+ },
+
+ /**
+ * 슬라이더의 네비게이터 박스를 현재 뷰에 맞게 사이즈 및 위치를 조정한다.
+ */
+ updateNavigatior: function () {
+ var me = this;
+ var svg = me._RENDERER.getRootElement();
+ var svgWidth, svgHeight, vx, vy, xRate, yRate, xImgRate, yImgRate;
+ var slider = this._CONFIG.SLIDER;
+ if (!slider) {
+ return;
+ }
+ var sliderImage = slider.find('.sliderImage');
+ var sliderNavigator = slider.find('.sliderNavigator');
+ var container = me._CONTAINER;
+
+ svgWidth = $(svg).attr('width');
+ svgHeight = $(svg).attr('height');
+ vx = container.scrollLeft;
+ vy = container.scrollTop;
+ xRate = $(container).width() / svgWidth;
+ yRate = $(container).height() / svgHeight;
+ if (xRate > 1) {
+ xRate = 1;
+ }
+ if (yRate > 1) {
+ yRate = 1;
+ }
+ xImgRate = sliderImage.width() / svgWidth;
+ yImgRate = sliderImage.height() / svgHeight;
+
+ sliderNavigator.width(sliderImage.width() * xRate);
+ sliderNavigator.height(sliderImage.height() * yRate);
+ sliderNavigator.css({
+ left: (vx * xImgRate) + 'px',
+ top: (vy * yImgRate) + 'px'
+ });
+
+ var sliderText = slider.find('.scaleSliderText');
+ var sliderBar = slider.find('.scaleSlider');
+ sliderText.html(Math.round(this._CONFIG.SCALE * 100));
+ sliderBar.val(Math.round(this._CONFIG.SCALE * 100));
+ }
+ ,
+ updateSlider: function (val) {
+ if (this._CONFIG.AUTO_SLIDER_UPDATE && !this._CONFIG.FAST_LOADING) {
+ var me = this;
+ if (!this._CONFIG.SLIDER) {
+ return;
+ }
+ if (!val) {
+ val = this._CONFIG.SCALE * 100;
+ }
+
+ var slider = this._CONFIG.SLIDER;
+ var sliderText = slider.find('.scaleSliderText');
+ var sliderBar = slider.find('.scaleSlider');
+ var sliderImage = slider.find('.sliderImage');
+ var sliderNavigator = slider.find('.sliderNavigator');
+ // var sliderImageWrapper = slider.find('.sliderImageWrapper');
+
+
+ //여기서부터는 캔버스의 내용을 슬라이더에 투영시킨다.
+ sliderText.html(Math.round(val));
+ sliderBar.val(Math.round(val));
+ me._RENDERER.setScale(val / 100);
+
+ var svg = me._RENDERER.getRootElement();
+ var svgData = new XMLSerializer().serializeToString(svg);
+ var canvasSize = this.getCanvasSize();
+
+ if (OG.Util.isIE()) {
+ svgData = '' + svgData.replace('xmlns="http://www.w3.org/2000/svg"', '').replace('xmlns:NS1=""', '').replace('NS1:', '');
+ var encoded = window.btoa(unescape(encodeURIComponent(svgData)));
+ var image = document.createElement('img');
+ $(image).css({
+ position: 'absolute',
+ top: '0px',
+ left: '0px',
+ opacity: 0
+ });
+ document.body.appendChild(image);
+ image.src = 'data:image/svg+xml;base64,' + encoded;
+ image.onload = function () {
+ var canvas = document.getElementById(sliderImage.attr('id'));
+ // canvas.width = sliderImageWrapper.width();
+ // canvas.height = sliderImageWrapper.width() * image.height / image.width;
+ var context = canvas.getContext('2d');
+ try {
+ context.drawImage(image, 0, 0, sliderImageWrapper.width(), sliderImageWrapper.width() * image.height / image.width);
+ $(image).remove();
+ me.updateNavigatior();
+ } catch (e) {
+ $(image).remove();
+ }
+ };
+ } else {
+ var image = new Image();
+ image.src = 'data:image/svg+xml;utf-8,' + svgData;
+ image.onload = function () {
+ var canvas = document.getElementById(sliderImage.attr('id'));
+ // canvas.width = sliderImageWrapper.width();
+ // canvas.height = sliderImageWrapper.width() * image.height / image.width;
+ var context = canvas.getContext('2d');
+ try {
+ // context.drawImage(image, 0, 0, sliderImageWrapper.width(), sliderImageWrapper.width() * image.height / image.width);
+ $(image).remove();
+ me.updateNavigatior();
+ } catch (e) {
+ $(image).remove();
+ }
+ };
+ }
+
+ //여기서부터는 백도어의 내용을 슬라이더에 투영시킨다.
+ if (me._CONFIG.BACKDOOR.container) {
+ var scale = me._CONFIG.BACKDOOR.scale;
+ var opacity = me._CONFIG.BACKDOOR.opacity;
+ var width = me._CONFIG.BACKDOOR.width;
+ var height = me._CONFIG.BACKDOOR.height;
+ var canvasScale = me.getScale();
+ // var contextWidth = sliderImageWrapper.width();
+ var fixedWidth = (width * (scale / 100) / canvasSize[0] * canvasScale) * contextWidth;
+ var fixedHeight = fixedWidth * height / width;
+ // var sliderBackDoorWrapper = sliderImageWrapper.find('.sliderBackDoorWrapper');
+
+ if (!sliderBackDoorWrapper || sliderBackDoorWrapper.length < 1) {
+ sliderBackDoorWrapper = $('');
+ sliderBackDoorWrapper.css({
+ position: 'absolute',
+ top: '0px',
+ left: '0px',
+ width: '100%',
+ height: '100%'
+ });
+
+ // sliderImageWrapper.data('backdoor', true);
+ sliderBackDoorWrapper.css({
+ 'background-image': 'url(' + me._CONFIG.BACKDOOR.url + ')',
+ 'background-repeat': 'no-repeat',
+ 'background-size': fixedWidth + 'px ' + fixedHeight + 'px',
+ 'opacity': opacity
+ });
+ sliderImage.before(sliderBackDoorWrapper);
+ } else {
+ //백도어 이미지가 교체되었다면 url 까지, 아니면 사이즈만 조정한다.
+ if (me._CONFIG.BACKDOOR.container_updated) {
+ me._CONFIG.BACKDOOR.container_updated = false;
+ sliderBackDoorWrapper.css({
+ 'background-image': 'url(' + me._CONFIG.BACKDOOR.url + ')',
+ 'background-repeat': 'no-repeat',
+ 'background-size': fixedWidth + 'px ' + fixedHeight + 'px',
+ 'opacity': opacity
+ });
+ } else {
+ sliderBackDoorWrapper.css({
+ 'background-size': fixedWidth + 'px ' + fixedHeight + 'px',
+ 'opacity': opacity
+ });
+ }
+ }
+ }
+ }
+ }
+ ,
+ /**
+ * 확대 축소 슬라이더를 삭제한다.
+ */
+ removeSlider: function () {
+ if (this._CONFIG.SLIDER) {
+ this._CONFIG.SLIDER.dialog("destroy");
+ this._CONFIG.SLIDER.remove();
+ }
+ }
+ ,
+
+ /**
+ * Shape 을 캔버스에 위치 및 사이즈 지정하여 드로잉한다.
+ *
+ * @example
+ * canvas.drawShape([100, 100], new OG.CircleShape(), [50, 50], {stroke:'red'});
+ *
+ * @param {Number[]} position 드로잉할 위치 좌표(중앙 기준)
+ * @param {OG.shape.IShape} shape Shape
+ * @param {Number[]} size Shape Width, Height
+ * @param {OG.geometry.Style|Object} style 스타일 (Optional)
+ * @param {String} id Element ID 지정 (Optional)
+ * @param {String} parentId 부모 Element ID 지정 (Optional)
+ * @param {Boolean} preventEvent 이벤트 생성 방지
+ * @param {Boolean} preventDropEvent 드랍 이벤트 방지
+ * @return {Element} Group DOM Element with geometry
+ */
+ drawShape: function (position, shape, size, style, id, parentId, preventEvent, preventDropEvent) {
+ var existElement
+ if (id) {
+ existElement = this.getElementById(id);
+ }
+ var element = this._RENDERER.drawShape(position, shape, size, style, id, preventEvent, preventDropEvent);
+
+ if (position && (shape.TYPE === OG.Constants.SHAPE_TYPE.EDGE)) {
+ element = this._RENDERER.move(element, position);
+ }
+
+ if (parentId && this._RENDERER.getElementById(parentId)) {
+ this._RENDERER.appendChild(element, parentId);
+ }
+
+ if (!this.CONFIG_INITIALIZED) {
+ this.initConfig();
+ }
+
+ if (!existElement) {
+ this._HANDLER.setClickSelectable(element, this._HANDLER._isSelectable(element.shape));
+ this._HANDLER.setMovable(element, this._HANDLER._isMovable(element.shape));
+ this._HANDLER.setGroupDropable(element);
+ this._HANDLER.setConnectGuide(element, this._HANDLER._isConnectable(element.shape));
+
+ if (this._HANDLER._isLabelEditable(element.shape)) {
+ this._HANDLER.enableEditLabel(element);
+ }
+ if (!id) {
+ this._RENDERER.addHistory();
+ }
+ }
+ this.updateSlider();
+ return element;
+ }
+ ,
+
+ /**
+ * Transfomer Shape 을 캔버스에 위치 및 사이즈 지정하여 드로잉한다.
+ *
+ * @example
+ * canvas.drawTransformer([100, 100], 'label' ['str1','str2'],['out']);
+ *
+ * @param {Number[]} position 드로잉할 위치 좌표(중앙 기준)
+ * @param {String} label Label
+ * @param {String[]} inputs 인풋에 위치할 리스트
+ * @param {String[]} outputs 아웃풋에 위치할 리스트
+ * @param {String} id Element ID 지정 (Optional)
+ * @return {Element} Group DOM Element with geometry
+ */
+ drawTransformer: function (position, label, inputs, outputs, drawData, id) {
+ var me = this, shape, element, style, envelope, i, toShape, fromShape, toElement, fromElement, textShape,
+ textElement;
+ shape = new OG.shape.Transformer(label);
+
+ if (!Array.isArray(inputs) || !Array.isArray(outputs)) {
+ return null;
+ }
+ var lines = Math.max(inputs.length, outputs.length);
+ element = me.drawShape(position, shape, [120, 30 + (lines * 25)], style, id);
+
+ envelope = element.shape.geom.getBoundary();
+
+ $.each(inputs, function (idx, input) {
+ textShape = new OG.shape.bpmn.M_Text(input);
+ textShape.MOVABLE = false;
+ textShape.SELECTABLE = false;
+ textShape.CONNECTABLE = false;
+ textShape.DELETABLE = false;
+ textElement = me.drawShape([envelope.getUpperLeft().x + 35, envelope.getUpperLeft().y + (idx * 25) + 40], textShape, [50, 20]);
+ element.appendChild(textElement);
+ toShape = new OG.shape.To();
+ toElement = me.drawShape([envelope.getUpperLeft().x + 15, envelope.getUpperLeft().y + (idx * 25) + 40], toShape, [5, 5], {"r": 5});
+ element.appendChild(toElement);
+ var data = JSON.parse(JSON.stringify(drawData));
+ data['type'] = 'input';
+ data['name'] = input;
+ data['parentId'] = element.id;
+ me.setCustomData(toElement, data);
+ });
+
+ $.each(outputs, function (idx, output) {
+ textShape = new OG.shape.bpmn.M_Text(output);
+ textShape.MOVABLE = false;
+ textShape.SELECTABLE = false;
+ textShape.CONNECTABLE = false;
+ textShape.DELETABLE = false;
+ textElement = me.drawShape([envelope.getUpperRight().x - 35, envelope.getUpperRight().y + (idx * 25) + 40], textShape, [50, 20]);
+ element.appendChild(textElement);
+ fromShape = new OG.shape.From();
+ fromElement = me.drawShape([envelope.getUpperRight().x - 15, envelope.getUpperRight().y + (idx * 25) + 40], fromShape, [5, 5], {"r": 5});
+ element.appendChild(fromElement);
+ var data = JSON.parse(JSON.stringify(drawData));
+ data['type'] = 'output';
+ data['name'] = output;
+ data['parentId'] = element.id;
+ me.setCustomData(fromElement, data);
+ });
+
+ if (!id) {
+ this._RENDERER.addHistory();
+ }
+ }
+ ,
+
+ setExceptionType: function (element, exceptionType) {
+ this._HANDLER.setExceptionType(element, exceptionType);
+ }
+ ,
+
+ setInclusion: function (element, inclusion) {
+ this._HANDLER.setInclusion(element, inclusion);
+ }
+ ,
+
+ /**
+ * Shape 의 스타일을 변경한다.
+ *
+ * @param {Element} shapeElement Shape DOM element
+ * @param {Object} style 스타일
+ */
+ setShapeStyle: function (shapeElement, style) {
+ this._RENDERER.setShapeStyle(shapeElement, style);
+ }
+ ,
+
+ /**
+ * Shape 의 선 연결 커스텀 컨트롤러를 설정한다.
+ *
+ * @param {Element} shapeElement Shape DOM element
+ * @param {Array} textList 텍스트 리스트
+ */
+ setTextListInController: function (shapeElement, textList) {
+ this._RENDERER.setTextListInController(shapeElement, textList);
+ }
+ ,
+
+ /**
+ * Shape 의 선 연결 커스텀 컨트롤러를 가져온다.
+ *
+ * @param {Element} shapeElement Shape DOM element
+ */
+ getTextListInController: function (shapeElement) {
+ this._RENDERER.getTextListInController(shapeElement);
+ }
+ ,
+
+ /**
+ * Shape 의 Label 을 캔버스에 위치 및 사이즈 지정하여 드로잉한다.
+ *
+ * @param {Element|String} shapeElement Shape DOM element or ID
+ * @param {String} text 텍스트
+ * @param {OG.geometry.Style|Object} style 스타일
+ * @return {Element} DOM Element
+ * @override
+ */
+ drawLabel: function (shapeElement, text, style) {
+ return this._RENDERER.drawLabel(shapeElement, text, style);
+ }
+ ,
+
+ /**
+ * Shape 의 연결된 Edge 를 redraw 한다.(이동 또는 리사이즈시)
+ *
+ * @param {Element} element
+ */
+ redrawConnectedEdge: function (element) {
+ this._RENDERER.redrawConnectedEdge(element);
+ }
+ ,
+
+ /**
+ * 연결된 터미널의 vertices 를 초기화한다.
+ *
+ * @param {Element} edge Edge Shape
+ * @return {Element} 연결된 Edge 엘리먼트
+ * @override
+ */
+ reconnect: function (edge) {
+ return this._RENDERER.reconnect(edge);
+ }
+ ,
+
+ /**
+ * 두개의 Shape 을 Edge 로 연결한다.
+ *
+ * @param {Element} fromElement from Shape Element
+ * @param {Element} toElement to Shape Element
+ * @param {OG.geometry.Style|Object} style 스타일
+ * @param {String} label Label
+ * @param fromP fromElement 와 연결될 터미널 좌표 [x,y](optional)
+ * @param toP toElement 와 연결될 터미널 좌표 [x,y](optional)
+ * @param preventTrigger 참 일 경우 이벤트 발생을 방지
+ * @param id 연결선의 아이디
+ * @param {Element} edgeShape 이 값이 없으면 신규 OG.EdgeShape 를 생성
+ * @returns {*|Element}
+ */
+ connect: function (fromElement, toElement, style, label, fromP, toP, preventTrigger, id, edgeShape) {
+ var existElement
+ if (id) {
+ existElement = this.getElementById(id);
+ }
+ var fromTerminal, toTerminal, edge, fromPosition, toPosition;
+
+ if (fromP) {
+ fromTerminal = this._RENDERER.createTerminalString(fromElement, fromP);
+ } else {
+ fromTerminal = this._RENDERER.createDefaultTerminalString(fromElement);
+ }
+
+ if (toP) {
+ toTerminal = this._RENDERER.createTerminalString(toElement, toP);
+ } else {
+ toTerminal = this._RENDERER.createDefaultTerminalString(toElement);
+ }
+
+ fromPosition = this._RENDERER._getPositionFromTerminal(fromTerminal);
+ fromPosition = [fromPosition.x, fromPosition.y];
+ toPosition = this._RENDERER._getPositionFromTerminal(toTerminal);
+ toPosition = [toPosition.x, toPosition.y];
+
+ // draw edge
+ edgeShape = edgeShape ? edgeShape : new OG.EdgeShape();
+ edgeShape.from = fromPosition;
+ edgeShape.to = toPosition;
+ edge = this._RENDERER.drawShape(null, edgeShape, null, style, id);
+ //edge = this._RENDERER.trimEdgeDirection(edge, fromElement, toElement);
+
+ //if label null, convert undefined
+ label = label ? label : undefined;
+
+ // connect
+ edge = this._RENDERER.connect(fromTerminal, toTerminal, edge, style, label, preventTrigger);
+
+ if (edge && !existElement) {
+ this._HANDLER.setClickSelectable(edge, edge.shape.SELECTABLE);
+ this._HANDLER.setMovable(edge, edge.shape.SELECTABLE && edge.shape.MOVABLE);
+ this._HANDLER.setConnectGuide(edge, this._HANDLER._isConnectable(edge.shape));
+ if (edge.shape.LABEL_EDITABLE) {
+ this._HANDLER.enableEditLabel(edge);
+ }
+ }
+ this.updateSlider();
+ return edge;
+ }
+ ,
+
+ /**
+ * 두개의 터미널 아이디로 부터 얻어진 도형들을 Edge 로 연결한다.
+ *
+ * @param {String} fromTerminal from Terminal Id
+ * @param {String} toTerminal to Terminal Id
+ * @param {OG.geometry.Style|Object} style 스타일
+ * @param {String} label Label
+ * @return {String} id 부여 할 아이디
+ * @return {String} shapeId shapeId
+ * @return {OG.geometry} geom Edge geometry
+ */
+ connectWithTerminalId: function (fromTerminal, toTerminal, style, label, id, shapeId, geom) {
+ var existElement
+ if (id) {
+ existElement = this.getElementById(id);
+ }
+ var vertices, edge, fromPosition, toPosition, fromto, shape;
+
+ fromPosition = this._RENDERER._getPositionFromTerminal(fromTerminal);
+ fromPosition = [fromPosition.x, fromPosition.y];
+
+ toPosition = this._RENDERER._getPositionFromTerminal(toTerminal);
+ toPosition = [toPosition.x, toPosition.y];
+
+ if (!geom) {
+ vertices = [fromPosition, toPosition];
+ } else {
+ vertices = geom.vertices;
+ }
+
+ fromto = JSON.stringify(vertices[0]) + ',' + JSON.stringify(vertices[vertices.length - 1]);
+ shape = eval('new ' + shapeId + '(' + fromto + ')');
+
+ //if label null, convert undefined
+ label = label ? label : undefined;
+ if (label) {
+ shape.label = label;
+ }
+
+ if (geom) {
+ if (geom.type === OG.Constants.GEOM_NAME[OG.Constants.GEOM_TYPE.POLYLINE]) {
+ geom = new OG.geometry.PolyLine(geom.vertices);
+ shape.geom = geom;
+ } else if (geom.type === OG.Constants.GEOM_NAME[OG.Constants.GEOM_TYPE.CURVE]) {
+ geom = new OG.geometry.Curve(geom.controlPoints);
+ shape.geom = geom;
+ }
+ }
+ edge = this.drawShape(null, shape, null, style, id, null);
+
+ // connect
+ edge = this._RENDERER.connect(fromTerminal, toTerminal, edge, style, label, true);
+
+ if (edge && !existElement) {
+ this._HANDLER.setClickSelectable(edge, edge.shape.SELECTABLE);
+ this._HANDLER.setMovable(edge, edge.shape.SELECTABLE && edge.shape.MOVABLE);
+ this._HANDLER.setConnectGuide(edge, this._HANDLER._isConnectable(edge.shape));
+ if (edge.shape.LABEL_EDITABLE) {
+ this._HANDLER.enableEditLabel(edge);
+ }
+ }
+ this.updateSlider();
+ return edge;
+ }
+ ,
+
+ /**
+ * 연결속성정보를 삭제한다. Edge 인 경우는 라인만 삭제하고, 일반 Shape 인 경우는 연결된 모든 Edge 를 삭제한다.
+ *
+ * @param {Element} element
+ */
+ disconnect: function (element) {
+ this._RENDERER.disconnect(element);
+ }
+ ,
+ /**
+ * 주어진 도형을 신규 아이디로 변경한다.
+ * @param element
+ * @param id
+ */
+ updateId: function (element, id) {
+ return this._RENDERER.updateId(element, id);
+ },
+
+ /**
+ * 주어진 Shape 들을 그룹핑한다.
+ *
+ * @param {Element[]} elements
+ * @return {Element} Group Shape Element
+ */
+ group: function (elements) {
+ var group = this._RENDERER.group(elements);
+
+ // enable event
+ this._HANDLER.setClickSelectable(group, group.shape.SELECTABLE);
+ this._HANDLER.setMovable(group, group.shape.SELECTABLE && group.shape.MOVABLE);
+ if (group.shape.LABEL_EDITABLE) {
+ this._HANDLER.enableEditLabel(group);
+ }
+
+ return group;
+ }
+ ,
+
+ /**
+ * 주어진 그룹들을 그룹해제한다.
+ *
+ * @param {Element[]} groupElements
+ * @return {Element[]} ungrouped Elements
+ */
+ ungroup: function (groupElements) {
+ return this._RENDERER.ungroup(groupElements);
+ }
+ ,
+
+ /**
+ * 주어진 Shape 들을 그룹에 추가한다.
+ *
+ * @param {Element} groupElement
+ * @param {Element[]} elements
+ */
+ addToGroup: function (groupElement, elements) {
+ this._RENDERER.addToGroup(groupElement, elements);
+ }
+ ,
+
+ /**
+ * 주어진 Shape 이 그룹인 경우 collapse 한다.
+ *
+ * @param {Element} element
+ */
+ collapse: function (element) {
+ this._RENDERER.collapse(element);
+ }
+ ,
+
+ /**
+ * 주어진 Shape 이 그룹인 경우 expand 한다.
+ *
+ * @param {Element} element
+ */
+ expand: function (element) {
+ this._RENDERER.expand(element);
+ }
+ ,
+
+ /**
+ * 드로잉된 모든 오브젝트를 클리어한다.
+ */
+ clear: function () {
+ this._RENDERER.clear();
+ }
+ ,
+
+ /**
+ * Shape 을 캔버스에서 관련된 모두를 삭제한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ */
+ removeShape: function (element, preventEvent) {
+ this._RENDERER.removeShape(element, preventEvent);
+ }
+ ,
+
+ /**
+ * 하위 엘리먼트만 제거한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ */
+ removeChild: function (element) {
+ this._RENDERER.removeChild(element);
+ }
+ ,
+
+ /**
+ * ID에 해당하는 Element 의 Move & Resize 용 가이드를 제거한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ */
+ removeGuide: function (element) {
+ this._RENDERER.removeGuide(element);
+ }
+ ,
+
+ /**
+ * 모든 Move & Resize 용 가이드를 제거한다.
+ */
+ removeAllGuide: function () {
+ this._RENDERER.removeAllGuide();
+ }
+ ,
+
+ /**
+ * 랜더러 캔버스 Root Element 를 반환한다.
+ *
+ * @return {Element} Element
+ */
+ getRootElement: function () {
+ return this._RENDERER.getRootElement();
+ }
+ ,
+
+ /**
+ * 랜더러 캔버스 Root Group Element 를 반환한다.
+ *
+ * @return {Element} Element
+ */
+ getRootGroup: function () {
+ return this._RENDERER.getRootGroup();
+ }
+ ,
+
+ /**
+ * 주어진 지점을 포함하는 Top Element 를 반환한다.
+ *
+ * @param {Number[]} position 위치 좌표
+ * @return {Element} Element
+ */
+ getElementByPoint: function (position) {
+ return this._RENDERER.getElementByPoint(position);
+ }
+ ,
+
+ /**
+ * 주어진 Boundary Box 영역에 포함되는 Shape(GEOM, TEXT, IMAGE) Element 를 반환한다.
+ * 모든 vertices를 포함한 엘리먼트를 반환한다.
+ *
+ * @param {OG.geometry.Envelope} envelope Boundary Box 영역
+ * @return {Element[]} Element
+ */
+ getElementsByBBox: function (envelope) {
+ return this._RENDERER.getElementsByBBox(envelope);
+ }
+ ,
+
+ /**
+ * 엘리먼트에 속성값을 설정한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @param {Object} attribute 속성값
+ */
+ setAttr: function (element, attribute) {
+ this._RENDERER.setAttr(element, attribute);
+ }
+ ,
+
+ /**
+ * 엘리먼트 속성값을 반환한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @param {String} attrName 속성이름
+ * @return {Object} attribute 속성값
+ */
+ getAttr: function (element, attrName) {
+ return this._RENDERER.getAttr(element, attrName);
+ }
+ ,
+
+ /**
+ * ID에 해당하는 Element 를 최상단 레이어로 이동한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ */
+ toFront: function (element) {
+ this._RENDERER.toFront(element);
+ }
+ ,
+
+ /**
+ * ID에 해당하는 Element 를 최하단 레이어로 이동한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ */
+ toBack: function (element) {
+ this._RENDERER.toBack(element);
+ }
+ ,
+
+ /**
+ * ID에 해당하는 Element 를 앞으로 한단계 이동한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ */
+ bringForward: function (element) {
+ this._RENDERER.bringForward(element);
+ },
+
+ /**
+ * ID에 해당하는 Element 를 뒤로 한단계 이동한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ */
+ sendBackward: function (element) {
+ this._RENDERER.sendBackward(element);
+ },
+
+ /**
+ * 랜더러 캔버스의 사이즈(Width, Height)를 반환한다.
+ *
+ * @return {Number[]} Canvas Width, Height
+ */
+ getCanvasSize: function () {
+ return this._RENDERER.getCanvasSize();
+ }
+ ,
+
+ /**
+ * 랜더러 캔버스의 사이즈(Width, Height)를 변경한다.
+ *
+ * @param {Number[]} size Canvas Width, Height
+ */
+ setCanvasSize: function (size) {
+ this._RENDERER.setCanvasSize(size);
+ }
+ ,
+
+ /**
+ * 랜더러 캔버스의 사이즈(Width, Height)를 실제 존재하는 Shape 의 영역에 맞게 변경한다.
+ *
+ * @param {Number[]} minSize Canvas 최소 Width, Height
+ * @param {Boolean} fitScale 주어진 minSize 에 맞게 fit 여부(Default:false)
+ */
+ fitCanvasSize: function (minSize, fitScale) {
+ this._RENDERER.fitCanvasSize(minSize, fitScale);
+ }
+ ,
+
+ /**
+ * 새로운 View Box 영역을 설정한다. (ZoomIn & ZoomOut 가능)
+ *
+ * @param {Number[]} position 위치 좌표(좌상단 기준)
+ * @param {Number[]} size Canvas Width, Height
+ * @param {Boolean} isFit Fit 여부
+ */
+ setViewBox: function (position, size, isFit) {
+ this._RENDERER.setViewBox(position, size, isFit);
+ }
+ ,
+
+ /**
+ * Scale 을 반환한다. (리얼 사이즈 : Scale = 1)
+ *
+ * @return {Number} 스케일값
+ */
+ getScale: function () {
+ return this._RENDERER.getScale();
+ }
+ ,
+
+ /**
+ * Scale 을 설정한다. (리얼 사이즈 : Scale = 1)
+ *
+ * @param {Number} scale 스케일값
+ */
+ setScale: function (scale) {
+ this._RENDERER.setScale(scale);
+ }
+ ,
+
+ /**
+ * ID에 해당하는 Element 를 캔버스에서 show 한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ */
+ show: function (element) {
+ this._RENDERER.show(element);
+ }
+ ,
+
+ /**
+ * ID에 해당하는 Element 를 캔버스에서 hide 한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ */
+ hide: function (element) {
+ this._RENDERER.hide(element);
+ }
+ ,
+
+ /**
+ * Source Element 를 Target Element 아래에 append 한다.
+ *
+ * @param {Element|String} srcElement Element 또는 ID
+ * @param {Element|String} targetElement Element 또는 ID
+ * @return {Element} Source Element
+ */
+ appendChild: function (srcElement, targetElement) {
+ return this._RENDERER.appendChild(srcElement, targetElement);
+ }
+ ,
+
+ /**
+ * Source Element 를 Target Element 이후에 insert 한다.
+ *
+ * @param {Element|String} srcElement Element 또는 ID
+ * @param {Element|String} targetElement Element 또는 ID
+ * @return {Element} Source Element
+ */
+ insertAfter: function (srcElement, targetElement) {
+ return this._RENDERER.insertAfter(srcElement, targetElement);
+ }
+ ,
+
+ /**
+ * Source Element 를 Target Element 이전에 insert 한다.
+ *
+ * @param {Element|String} srcElement Element 또는 ID
+ * @param {Element|String} targetElement Element 또는 ID
+ * @return {Element} Source Element
+ */
+ insertBefore: function (srcElement, targetElement) {
+ return this._RENDERER.insertBefore(srcElement, targetElement);
+ }
+ ,
+
+ /**
+ * 해당 Element 를 가로, 세로 Offset 만큼 이동한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @param {Number[]} offset [가로, 세로]
+ * @return {Element} Element
+ */
+ move: function (element, offset) {
+ return this._RENDERER.move(element, offset);
+ }
+ ,
+
+ /**
+ * 주어진 중심좌표로 해당 Element 를 이동한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @param {Number[]} position [x, y]
+ * @return {Element} Element
+ */
+ moveCentroid: function (element, position) {
+ return this._RENDERER.moveCentroid(element, position);
+ }
+ ,
+
+ /**
+ * 중심 좌표를 기준으로 주어진 각도 만큼 회전한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @param {Number} angle 각도
+ * @return {Element} Element
+ */
+ rotate: function (element, angle) {
+ return this._RENDERER.rotate(element, angle);
+ }
+ ,
+
+ /**
+ * 상, 하, 좌, 우 외곽선을 이동한 만큼 리사이즈 한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @param {Number[]} offset [상, 하, 좌, 우] 각 방향으로 + 값
+ * @return {Element} Element
+ */
+ resize: function (element, offset, preventEvent) {
+ return this._RENDERER.resize(element, offset, preventEvent);
+ }
+ ,
+
+ /**
+ * 중심좌표는 고정한 채 Bounding Box 의 width, height 를 리사이즈 한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @param {Number[]} size [Width, Height]
+ * @return {Element} Element
+ */
+ resizeBox: function (element, size, preventEvent) {
+ return this._RENDERER.resizeBox(element, size, preventEvent);
+ }
+ ,
+
+ /**
+ * 노드 Element 를 복사한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @return {Element} Element
+ */
+ clone: function (element) {
+ return this._RENDERER.clone(element);
+ }
+ ,
+
+ /**
+ * ID에 해당하는 Element 의 바운더리 영역을 리턴한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @return {OG.geometry.Envelope} Envelope 영역
+ */
+ getBoundary: function (element) {
+ return this._RENDERER.getBoundary(element);
+ },
+
+ /**
+ * ID로 Node Element 를 반환한다.
+ *
+ * @param {String} id
+ * @return {Element} Element
+ */
+ getElementById: function (id) {
+ return this._RENDERER.getElementById(id);
+ }
+ ,
+
+ /**
+ * Shape 타입에 해당하는 Node Element 들을 반환한다.
+ *
+ * @param {String} shapeType Shape 타입(GEOM, HTML, IMAGE, EDGE, GROUP), Null 이면 모든 타입
+ * @param {String} excludeType 제외 할 타입
+ * @return {Element[]} Element's Array
+ */
+ getElementsByType: function (shapeType, excludeType) {
+ return this._RENDERER.getElementsByType(shapeType, excludeType);
+ }
+ ,
+
+ /**
+ * Shape ID에 해당하는 Node Element 들을 반환한다.
+ *
+ * @param {String} shapeId Shape ID
+ * @return {Element[]} Element's Array
+ */
+ getElementsByShapeId: function (shapeId) {
+ var root = this.getRootGroup();
+ return $(root).find("[_type=SHAPE][_shape_id='" + shapeId + "']");
+ }
+ ,
+
+ /**
+ * 두 도형 사이의 연결된 Edge 를 반환한다.
+ * @param elements
+ * @returns {Element} edge
+ */
+ getRelatedEdgeFromShapes: function (elements) {
+ return this._RENDERER.getRelatedEdgeFromShapes(elements);
+ },
+
+ /**
+ * Edge 엘리먼트와 연결된 fromShape, toShape 엘리먼트를 반환한다.
+ *
+ * @param {Element|String} edgeElement Element 또는 ID
+ * @return {Object}
+ */
+ getRelatedElementsFromEdge: function (edgeElement) {
+ var me = this,
+ edge = OG.Util.isElement(edgeElement) ? edgeElement : this.getElementById(edgeElement),
+ getShapeFromTerminal = function (terminal) {
+ if (terminal) {
+ return me._RENDERER.getElementById(terminal.substring(0, terminal.indexOf(OG.Constants.TERMINAL)));
+ } else {
+ return null;
+ }
+ };
+
+
+ if ($(edge).attr('_shape') === OG.Constants.SHAPE_TYPE.EDGE) {
+ return {
+ from: getShapeFromTerminal($(edgeElement).attr('_from')),
+ to: getShapeFromTerminal($(edgeElement).attr('_to'))
+ };
+ } else {
+ return {
+ from: null,
+ to: null
+ };
+ }
+ }
+ ,
+ /**
+ * 부모 엘리먼트를 반환한다. 부모가 루트일때는 반환하지 않는다.
+ *
+ * @param {Element} Element 엘리먼트
+ * @return {Element} Element 엘리먼트
+ */
+ getParent: function (element) {
+ return this._RENDERER.getParent(element);
+ },
+
+ /**
+ * 그룹의 하위 엘리먼트를 반환한다.
+ *
+ * @param {Element} element 엘리먼트
+ * @returns {Array} Elements
+ */
+ getChilds: function (element) {
+ return this._RENDERER.getChilds(element);
+ },
+
+ /**
+ * 캔버스의 모든 Shape 들을 리턴
+ *
+ * @return {Array} Elements
+ */
+ getAllShapes: function () {
+ return this._RENDERER.getAllShapes();
+ },
+
+ /**
+ * 캔버스의 모든 Edge를 리턴
+ *
+ * @return {Array} Edge Elements
+ */
+ getAllEdges: function () {
+ return this._RENDERER.getAllEdges();
+ },
+
+ /**
+ * 해당 엘리먼트의 BoundingBox 영역 정보를 반환한다.
+ *
+ * @param {Element|String} element
+ * @return {Object} {width, height, x, y, x2, y2}
+ */
+ getBBox: function (element) {
+ return this._RENDERER.getBBox(element);
+ }
+ ,
+
+ /**
+ * 부모노드기준으로 캔버스 루트 엘리먼트의 BoundingBox 영역 정보를 반환한다.
+ *
+ * @return {Object} {width, height, x, y, x2, y2}
+ */
+ getRootBBox: function () {
+ return this._RENDERER.getRootBBox();
+ }
+ ,
+
+ /**
+ * 부모노드기준으로 캔버스 루트 엘리먼트의 실제 Shape 이 차지하는 BoundingBox 영역 정보를 반환한다.
+ *
+ * @return {Object} {width, height, x, y, x2, y2}
+ */
+ getRealRootBBox: function () {
+ return this._RENDERER.getRealRootBBox();
+ }
+ ,
+
+ /**
+ * SVG 인지 여부를 반환한다.
+ *
+ * @return {Boolean} svg 여부
+ */
+ isSVG: function () {
+ return this._RENDERER.isSVG();
+ }
+ ,
+
+ /**
+ * VML 인지 여부를 반환한다.
+ *
+ * @return {Boolean} vml 여부
+ */
+ isVML: function () {
+ return this._RENDERER.isVML();
+ }
+ ,
+
+ /**
+ * 주어진 Shape 엘리먼트에 커스텀 데이타를 저장한다.
+ *
+ * @param {Element|String} shapeElement Shape DOM Element or ID
+ * @param {Object} data JSON 포맷의 Object
+ */
+ setCustomData: function (shapeElement, data) {
+ var element = OG.Util.isElement(shapeElement) ? shapeElement : document.getElementById(shapeElement);
+ element.data = data;
+
+ //도형의 shape 에 데이터를 저장한다.
+ element.shape.data = data;
+ }
+ ,
+
+ /**
+ * 주어진 Shape 엘리먼트에 저장된 커스텀 데이터를 반환한다.
+ *
+ * @param {Element|String} shapeElement Shape DOM Element or ID
+ * @return {Object} JSON 포맷의 Object
+ */
+ getCustomData: function (shapeElement) {
+ var element = OG.Util.isElement(shapeElement) ? shapeElement : document.getElementById(shapeElement);
+ // element 에 데이터가 없을 경우 (shape 이 데이터를 가지고 있을 경우)
+ if (!element.data) {
+ element.data = element.shape.data;
+ }
+ return element.data;
+ }
+ ,
+
+ /**
+ * 주어진 Shape 엘리먼트에 확장 커스텀 데이타를 저장한다.
+ *
+ * @param {Element|String} shapeElement Shape DOM Element or ID
+ * @param {Object} data JSON 포맷의 Object
+ */
+ setExtCustomData: function (shapeElement, data) {
+ var element = OG.Util.isElement(shapeElement) ? shapeElement : document.getElementById(shapeElement);
+ element.dataExt = data;
+ }
+ ,
+
+ /**
+ * 주어진 Shape 엘리먼트에 저장된 확장 커스텀 데이터를 반환한다.
+ *
+ * @param {Element|String} shapeElement Shape DOM Element or ID
+ * @return {Object} JSON 포맷의 Object
+ */
+ getExtCustomData: function (shapeElement) {
+ var element = OG.Util.isElement(shapeElement) ? shapeElement : document.getElementById(shapeElement);
+ return element.dataExt;
+ }
+ ,
+
+ /**
+ * 주어진 Shape 엘리먼트에 커스텀 컨텍스트 메뉴를 지정한다.
+ *
+ * @param {Element|String} shapeElement Shape DOM Element or ID
+ * @param {Object} data JSON 포맷의 context Object
+ */
+ setCustomContextMenu: function (shapeElement, data) {
+ var element = OG.Util.isElement(shapeElement) ? shapeElement : document.getElementById(shapeElement);
+
+ element.shape.customContextMenu = data;
+ },
+
+ /**
+ * 주어진 Shape 엘리먼트에 저장된 커스텀 컨텍스트 메뉴를 반환한다.
+ *
+ * @param {Element|String} shapeElement Shape DOM Element or ID
+ * @return {Object} JSON 포맷의 context Object
+ */
+ getCustomContextMenu: function (shapeElement) {
+ var element = OG.Util.isElement(shapeElement) ? shapeElement : document.getElementById(shapeElement);
+ return element.shape.customContextMenu;
+ }
+ ,
+
+ /**
+ * Canvas 에 그려진 Shape 들을 OpenGraph XML 문자열로 export 한다.
+ *
+ * @return {String} XML 문자열
+ */
+ toXML: function () {
+ return OG.Util.jsonToXml(this.toJSON());
+ }
+ ,
+
+ /**
+ * Canvas 에 그려진 Shape 들을 OpenGraph JSON 객체로 export 한다.
+ *
+ * @return {Object} JSON 포맷의 Object
+ */
+ toJSON: function () {
+ var CANVAS = this,
+ rootBBox = this._RENDERER.getRootBBox(),
+ rootGroup = this._RENDERER.getRootGroup(),
+ scale = this.getScale(),
+ canvasWidth = this.getCanvasSize()[0],
+ canvasHeight = this.getCanvasSize()[1],
+ backdoor = this._CONFIG.BACKDOOR,
+ jsonObj = {
+ opengraph: {
+ '@width': canvasWidth,
+ '@height': canvasHeight,
+ '@scale': scale,
+ cell: [],
+ backdoor: [
+ {
+ '@url': backdoor.url,
+ '@scale': backdoor.scale,
+ '@opacity': backdoor.opacity,
+ '@width': backdoor.width,
+ '@height': backdoor.height
+ }
+ ]
+ }
+ },
+ childShape, i, cellMap;
+ cellMap = {};
+
+ childShape = function (node) {
+ $(node).find("[_type=SHAPE]").each(function (idx, item) {
+ // push cell to array
+ var shape = item.shape,
+ style = item.shape.geom.style.map,
+ geom = shape.geom,
+ envelope = geom.getBoundary(),
+ cell = {},
+ vertices,
+ from,
+ to,
+ prevShapeIds,
+ nextShapeIds;
+
+ if (shape.ignoreExport) {
+ return;
+ }
+
+ cell['@id'] = $(item).attr('id');
+ if ($(item).parent().attr('id') === $(node).attr('id')) {
+ cell['@parent'] = $(node).attr('id');
+ } else {
+ cell['@parent'] = $(item).parent().attr('id');
+ }
+ cell['@shapeType'] = shape.TYPE;
+ cell['@shapeId'] = shape.SHAPE_ID;
+ cell['@x'] = envelope.getCentroid().x;
+ cell['@y'] = envelope.getCentroid().y;
+ cell['@width'] = envelope.getWidth();
+ cell['@height'] = envelope.getHeight();
+ if (style) {
+ cell['@style'] = escape(OG.JSON.encode(style));
+ }
+
+ if (shape.TYPE === OG.Constants.SHAPE_TYPE.EDGE) {
+ if ($(item).attr('_from')) {
+ cell['@from'] = $(item).attr('_from');
+ }
+ if ($(item).attr('_to')) {
+ cell['@to'] = $(item).attr('_to');
+ }
+ } else {
+ prevShapeIds = CANVAS.getPrevShapeIds(item);
+ nextShapeIds = CANVAS.getNextShapeIds(item);
+ if (prevShapeIds.length > 0) {
+ cell['@from'] = prevShapeIds.toString();
+ }
+ if (nextShapeIds.length > 0) {
+ cell['@to'] = nextShapeIds.toString();
+ }
+ }
+
+ if ($(item).attr('_fromedge')) {
+ cell['@fromEdge'] = $(item).attr('_fromedge');
+ }
+ if ($(item).attr('_toedge')) {
+ cell['@toEdge'] = $(item).attr('_toedge');
+ }
+ if (shape.label) {
+ cell['@label'] = escape(shape.label);
+ }
+ if (shape.fromLabel) {
+ cell['@fromLabel'] = escape(shape.fromLabel);
+ }
+ if (shape.toLabel) {
+ cell['@toLabel'] = escape(shape.toLabel);
+ }
+ if (shape.angle && shape.angle !== 0) {
+ cell['@angle'] = shape.angle;
+ }
+ if (shape instanceof OG.shape.ImageShape) {
+ cell['@value'] = shape.image;
+ } else if (shape instanceof OG.shape.HtmlShape) {
+ cell['@value'] = escape(shape.html);
+ } else if (shape instanceof OG.shape.TextShape) {
+ cell['@value'] = escape(shape.text);
+ } else if (shape instanceof OG.shape.EdgeShape) {
+ vertices = geom.getVertices();
+ cell['@value'] = '';
+ for (var i = 0, leni = vertices.length; i < leni; i++) {
+ cell['@value'] = cell['@value'] + vertices[i];
+ if (i < vertices.length - 1) {
+ cell['@value'] = cell['@value'] + ','
+ }
+ }
+ }
+ if (geom) {
+ cell['@geom'] = escape(geom.toString());
+ }
+ if (item.data) {
+ cell['@data'] = escape(OG.JSON.encode(item.data));
+ } else {
+ cell['@data'] = escape(OG.JSON.encode(item.shape.data));
+ }
+ if (shape.textList) {
+ cell['@textList'] = escape(OG.JSON.encode(shape.textList));
+ }
+ if (item.dataExt) {
+ cell['@dataExt'] = escape(OG.JSON.encode(item.dataExt));
+ }
+ if (shape.LoopType) {
+ cell['@loopType'] = shape.LoopType;
+ }
+ if (shape.TaskType) {
+ cell['@taskType'] = shape.TaskType;
+ }
+ if (shape.exceptionType) {
+ cell['@exceptionType'] = shape.exceptionType;
+ }
+
+ cell['@childs'] = [];
+ jsonObj.opengraph.cell.push(cell);
+
+ // gathering Cell Map
+ cellMap[cell["@id"]] = cell;
+ });
+ };
+
+ if (rootGroup.data) {
+ jsonObj.opengraph['@data'] = escape(OG.JSON.encode(rootGroup.data));
+ }
+ if (rootGroup.dataExt) {
+ jsonObj.opengraph['@dataExt'] = escape(OG.JSON.encode(rootGroup.dataExt));
+ }
+
+ //root check
+ childShape(rootGroup, true);
+ return jsonObj;
+ }
+ ,
+
+ /**
+ * OpenGraph XML 문자열로 부터 Shape 을 드로잉한다.
+ *
+ * @param {String| Element} xml XML 문자열 또는 DOM Element
+ * @return {Object} {width, height, x, y, x2, y2}
+ */
+ loadXML: function (xml) {
+ if (!OG.Util.isElement(xml)) {
+ xml = OG.Util.parseXML(xml);
+ }
+ return this.loadJSON(OG.Util.xmlToJson(xml));
+ }
+ ,
+
+ alignLeft: function () {
+ this._RENDERER.alignLeft();
+ }
+ ,
+
+ alignRight: function () {
+ this._RENDERER.alignRight();
+ }
+ ,
+
+ alignTop: function () {
+ this._RENDERER.alignTop();
+ }
+ ,
+
+ alignBottom: function () {
+ this._RENDERER.alignBottom();
+ }
+ ,
+
+ /**
+ * JSON 객체로 부터 Shape 을 드로잉한다.
+ *
+ * @param {Object} json JSON 포맷의 Object
+ * @return {Object} {width, height, x, y, x2, y2}
+ */
+ loadJSON: function (json) {
+ this.fastLoadingON();
+ var canvasWidth, canvasHeight, rootGroup, canvasScale,
+ minX = Number.MAX_VALUE, minY = Number.MAX_VALUE, maxX = Number.MIN_VALUE, maxY = Number.MIN_VALUE,
+ i, cell, shape, id, parent, shapeType, shapeId, x, y, width, height, style, geom, from, to,
+ fromEdge, toEdge, label, fromLabel, toLabel, angle, value, data, dataExt, element, loopType, taskType,
+ swimlane, textList;
+
+ this._RENDERER.clear();
+ var renderer = this._RENDERER;
+ $(renderer._PAPER.canvas).trigger('loading', ['start']);
+
+ if (json && json.opengraph && json.opengraph.cell && OG.Util.isArray(json.opengraph.cell)) {
+ canvasWidth = json.opengraph['@width'];
+ canvasHeight = json.opengraph['@height'];
+ canvasScale = json.opengraph['@scale'];
+ if (canvasScale) {
+ this.setScale(canvasScale);
+ } else {
+ this.setScale(1);
+ }
+ this.setCanvasSize([canvasWidth, canvasHeight]);
+
+ data = json.opengraph['@data'];
+ dataExt = json.opengraph['@dataExt'];
+ if (data) {
+ rootGroup = this.getRootGroup();
+ rootGroup.data = OG.JSON.decode(unescape(data));
+ }
+ if (dataExt) {
+ rootGroup = this.getRootGroup();
+ rootGroup.dataExt = OG.JSON.decode(unescape(dataExt));
+ }
+
+ cell = json.opengraph.cell;
+ var totalCount = cell.length;
+ var cellCount = 0;
+
+ for (var i = 0, leni = cell.length; i < leni; i++) {
+ id = cell[i]['@id'];
+ parent = cell[i]['@parent'];
+ swimlane = cell[i]['@swimlane'];
+ shapeType = cell[i]['@shapeType'];
+ shapeId = cell[i]['@shapeId'];
+ x = parseInt(cell[i]['@x'], 10);
+ y = parseInt(cell[i]['@y'], 10);
+ width = parseInt(cell[i]['@width'], 10);
+ height = parseInt(cell[i]['@height'], 10);
+ style = unescape(cell[i]['@style']);
+ geom = unescape(cell[i]['@geom']);
+
+ from = cell[i]['@from'];
+ to = cell[i]['@to'];
+ fromEdge = cell[i]['@fromEdge'];
+ toEdge = cell[i]['@toEdge'];
+ label = cell[i]['@label'];
+ fromLabel = cell[i]['@fromLabel'];
+ toLabel = cell[i]['@toLabel'];
+ angle = cell[i]['@angle'];
+ value = cell[i]['@value'];
+ data = cell[i]['@data'];
+ textList = cell[i]['@textList'];
+ dataExt = cell[i]['@dataExt'];
+ loopType = cell[i]['@loopType'];
+ taskType = cell[i]['@taskType'];
+
+ label = label ? unescape(label) : label;
+
+ minX = (minX > (x - width / 2)) ? (x - width / 2) : minX;
+ minY = (minY > (y - height / 2)) ? (y - height / 2) : minY;
+ maxX = (maxX < (x + width / 2)) ? (x + width / 2) : maxX;
+ maxY = (maxY < (y + height / 2)) ? (y + height / 2) : maxY;
+
+ switch (shapeType) {
+ case OG.Constants.SHAPE_TYPE.GEOM:
+ case OG.Constants.SHAPE_TYPE.GROUP:
+ shape = eval('new ' + shapeId + '()');
+ if (label) {
+ shape.label = label;
+ }
+ if (data) {
+ shape.data = OG.JSON.decode(unescape(data));
+ }
+ if (textList) {
+ shape.textList = OG.JSON.decode(unescape(textList));
+ }
+ element = this.drawShape([x, y], shape, [width, height], OG.JSON.decode(style), id, parent);
+ if (element.shape instanceof OG.shape.bpmn.A_Task) {
+ element.shape.LoopType = loopType;
+ element.shape.TaskType = taskType;
+ }
+ break;
+ case OG.Constants.SHAPE_TYPE.EDGE:
+ var list = JSON.parse('[' + value + ']');
+ var fromto = JSON.stringify(list[0]) + ',' + JSON.stringify(list[list.length - 1]);
+ shape = eval('new ' + shapeId + '(' + fromto + ')');
+ if (label) {
+ shape.label = label;
+ }
+ if (data) {
+ shape.data = OG.JSON.decode(unescape(data));
+ }
+ if (textList) {
+ shape.textList = OG.JSON.decode(unescape(textList));
+ }
+ if (fromLabel) {
+ shape.fromLabel = unescape(fromLabel);
+ }
+ if (toLabel) {
+ shape.toLabel = unescape(toLabel);
+ }
+ if (geom) {
+ geom = OG.JSON.decode(geom);
+ if (geom.type === OG.Constants.GEOM_NAME[OG.Constants.GEOM_TYPE.POLYLINE]) {
+ geom = new OG.geometry.PolyLine(geom.vertices);
+ shape.geom = geom;
+ } else if (geom.type === OG.Constants.GEOM_NAME[OG.Constants.GEOM_TYPE.CURVE]) {
+ geom = new OG.geometry.Curve(geom.controlPoints);
+ shape.geom = geom;
+ }
+ }
+ element = this.drawShape(null, shape, null, OG.JSON.decode(style), id, parent);
+ break;
+ case OG.Constants.SHAPE_TYPE.HTML:
+ shape = eval('new ' + shapeId + '()');
+ if (value) {
+ shape.html = unescape(value);
+ }
+ if (label) {
+ shape.label = label;
+ }
+ if (data) {
+ shape.data = OG.JSON.decode(unescape(data));
+ }
+ if (textList) {
+ shape.textList = OG.JSON.decode(unescape(textList));
+ }
+ element = this.drawShape([x, y], shape, [width, height, angle], OG.JSON.decode(style), id, parent);
+ break;
+ case OG.Constants.SHAPE_TYPE.IMAGE:
+ shape = eval('new ' + shapeId + '(\'' + value + '\')');
+ if (label) {
+ shape.label = label;
+ }
+ if (data) {
+ shape.data = OG.JSON.decode(unescape(data));
+ }
+ if (textList) {
+ shape.textList = OG.JSON.decode(unescape(textList));
+ }
+ element = this.drawShape([x, y], shape, [width, height, angle], OG.JSON.decode(style), id, parent);
+ break;
+ case OG.Constants.SHAPE_TYPE.TEXT:
+ shape = eval('new ' + shapeId + '()');
+ if (value) {
+ shape.text = unescape(value);
+ }
+ if (data) {
+ shape.data = OG.JSON.decode(unescape(data));
+ }
+ if (textList) {
+ shape.textList = OG.JSON.decode(unescape(textList));
+ }
+ element = this.drawShape([x, y], shape, [width, height, angle], OG.JSON.decode(style), id, parent);
+ break;
+ }
+
+ if (from) {
+ $(element).attr('_from', from);
+ }
+ if (to) {
+ $(element).attr('_to', to);
+ }
+ if (fromEdge) {
+ $(element).attr('_fromedge', fromEdge);
+ }
+ if (toEdge) {
+ $(element).attr('_toedge', toEdge);
+ }
+
+ if (data) {
+ element.data = OG.JSON.decode(unescape(data));
+ }
+ if (dataExt) {
+ element.dataExt = OG.JSON.decode(unescape(dataExt));
+ }
+
+ cellCount++;
+ $(renderer._PAPER.canvas).trigger('loading', [Math.round((cellCount / totalCount) * 100)]);
+ }
+
+ //백도어를 불러온다.
+ if (json.opengraph.backdoor && OG.Util.isArray(json.opengraph.backdoor) && json.opengraph.backdoor.length) {
+ var backdoor = json.opengraph.backdoor[0];
+ if (backdoor['@url']) {
+ this.addBackDoor(backdoor['@url'], backdoor['@scale'], backdoor['@opacity'])
+ }
+ }
+
+ this.fastLoadingOFF();
+ $(renderer._PAPER.canvas).trigger('loading', ['end']);
+
+ return {
+ width: maxX - minX,
+ height: maxY - minY,
+ x: minX,
+ y: minY,
+ x2: maxX,
+ y2: maxY
+ };
+ }
+
+ this.fastLoadingOFF();
+ $(renderer._PAPER.canvas).trigger('loading', ['end']);
+
+ return {
+ width: 0,
+ height: 0,
+ x: 0,
+ y: 0,
+ x2: 0,
+ y2: 0
+ };
+ }
+ ,
+
+ /**
+ * 캔버스 undo.
+ */
+ undo: function () {
+ this._RENDERER.undo();
+ }
+ ,
+
+ /**
+ * 캔버스 redo.
+ */
+ redo: function () {
+ this._RENDERER.redo();
+ }
+ ,
+
+ /**
+ * 연결된 이전 Edge Element 들을 반환한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @return {Element[]} Previous Element's Array
+ */
+ getPrevEdges: function (element) {
+ return this._RENDERER.getPrevEdges(element);
+ }
+ ,
+
+ /**
+ * 연결된 이후 Edge Element 들을 반환한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @return {Element[]} Previous Element's Array
+ */
+ getNextEdges: function (element) {
+ return this._RENDERER.getNextEdges(element);
+ }
+ ,
+
+ /**
+ * 연결된 이전 노드 Element 들을 반환한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @return {Element[]} Previous Element's Array
+ */
+ getPrevShapes: function (element) {
+ return this._RENDERER.getPrevShapes(element);
+ }
+ ,
+
+ /**
+ * 연결된 이전 노드 Element ID들을 반환한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @return {String[]} Previous Element Id's Array
+ */
+ getPrevShapeIds: function (element) {
+ return this._RENDERER.getPrevShapeIds(element);
+ }
+ ,
+
+ /**
+ * 연결된 이후 노드 Element 들을 반환한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @return {Element[]} Previous Element's Array
+ */
+ getNextShapes: function (element) {
+ return this._RENDERER.getNextShapes(element);
+ }
+ ,
+
+ /**
+ * 연결된 이후 노드 Element ID들을 반환한다.
+ *
+ * @param {Element|String} element Element 또는 ID
+ * @return {String[]} Previous Element Id's Array
+ */
+ getNextShapeIds: function (element) {
+ return this._RENDERER.getNextShapeIds(element);
+ }
+ ,
+
+ /**
+ * Shape 이 처음 Draw 되었을 때의 이벤트 리스너
+ *
+ * @param {Function} callbackFunc 콜백함수(event, shapeElement)
+ */
+ onDrawShape: function (callbackFunc) {
+ $(this.getRootElement()).bind('drawShape', function (event, shapeElement) {
+ callbackFunc(event, shapeElement);
+ });
+ }
+ ,
+
+ /**
+ * History Update 되었을때의 이벤트 리스너
+ *
+ * @param {Function} callbackFunc 콜백함수(event)
+ */
+ onAddHistory: function (callbackFunc) {
+ $(this.getRootElement()).bind('addHistory', function (event) {
+ callbackFunc(event);
+ });
+ },
+
+ /**
+ * Undo 되었을때의 이벤트 리스너
+ *
+ * @param {Function} callbackFunc 콜백함수(event)
+ */
+ onUndo: function (callbackFunc) {
+ $(this.getRootElement()).bind('undo', function (event) {
+ callbackFunc(event);
+ });
+ }
+ ,
+
+ /**
+ * Redo 되었을때의 이벤트 리스너
+ *
+ * @param {Function} callbackFunc 콜백함수(event)
+ */
+ onRedo: function (callbackFunc) {
+ $(this.getRootElement()).bind('redo', function (event) {
+ callbackFunc(event);
+ });
+ }
+ ,
+
+ /**
+ * Lane 이 divide 되었을 때의 이벤트 리스너
+ *
+ * @param {Function} callbackFunc 콜백함수(event, dividedLane)
+ */
+ onDivideLane: function (callbackFunc) {
+ $(this.getRootElement()).bind('divideLane', function (event, dividedLane) {
+ callbackFunc(event, dividedLane);
+ });
+ }
+ ,
+
+ /**
+ * 라벨이 Draw 되었을 때의 이벤트 리스너
+ *
+ * @param {Function} callbackFunc 콜백함수(event, shapeElement, labelText)
+ */
+ onDrawLabel: function (callbackFunc) {
+ $(this.getRootElement()).bind('drawLabel', function (event, shapeElement, labelText) {
+ callbackFunc(event, shapeElement, labelText);
+ })
+ }
+ ,
+
+ /**
+ * 라벨이 Change 되었을 때의 이벤트 리스너
+ *
+ * @param {Function} callbackFunc 콜백함수(event, shapeElement, afterText, beforeText)
+ */
+ onLabelChanged: function (callbackFunc) {
+ $(this.getRootElement()).bind('labelChanged', function (event, shapeElement, afterText, beforeText) {
+ callbackFunc(event, shapeElement, afterText, beforeText);
+ });
+ }
+ ,
+
+ /**
+ * 라벨이 Change 되기전 이벤트 리스너
+ *
+ * @param {Function} callbackFunc 콜백함수(event, shapeElement, afterText, beforeText)
+ */
+ onBeforeLabelChange: function (callbackFunc) {
+ $(this.getRootElement()).bind('beforeLabelChange', function (event) {
+ if (callbackFunc(event, event.element, event.afterText, event.beforeText) === false) {
+ event.stopPropagation();
+ }
+ });
+ }
+ ,
+
+ /**
+ * Shape 이 Redraw 되었을 때의 이벤트 리스너
+ *
+ * @param {Function} callbackFunc 콜백함수(event, shapeElement)
+ */
+ onRedrawShape: function (callbackFunc) {
+ $(this.getRootElement()).bind('redrawShape', function (event, shapeElement) {
+ callbackFunc(event, shapeElement);
+ });
+ }
+ ,
+
+ /**
+ * Shape 이 Remove 될 때의 이벤트 리스너
+ *
+ * @param {Function} callbackFunc 콜백함수(event, shapeElement)
+ */
+ onRemoveShape: function (callbackFunc) {
+ $(this.getRootElement()).bind('removeShape', function (event, shapeElement) {
+ callbackFunc(event, shapeElement);
+ });
+ }
+ ,
+
+ /**
+ * Shape 이 Rotate 될 때의 이벤트 리스너
+ *
+ * @param {Function} callbackFunc 콜백함수(event, element, angle)
+ */
+ onRotateShape: function (callbackFunc) {
+ $(this.getRootElement()).bind('rotateShape', function (event, element, angle) {
+ callbackFunc(event, element, angle);
+ });
+ }
+ ,
+
+ /**
+ * Shape 이 Move 되었을 때의 이벤트 리스너
+ *
+ * @param {Function} callbackFunc 콜백함수(event, shapeElement, offset)
+ */
+ onMoveShape: function (callbackFunc) {
+ $(this.getRootElement()).bind('moveShape', function (event, shapeElement, offset) {
+ callbackFunc(event, shapeElement, offset);
+ });
+ }
+ ,
+
+ /**
+ * Shape 이 Resize 되었을 때의 이벤트 리스너
+ *
+ * @param {Function} callbackFunc 콜백함수(event, shapeElement, offset)
+ */
+ onResizeShape: function (callbackFunc) {
+ $(this.getRootElement()).bind('resizeShape', function (event, shapeElement, offset) {
+ callbackFunc(event, shapeElement, offset);
+ });
+ }
+ ,
+
+ /**
+ * Shape 이 Connect 되기전 이벤트 리스너
+ *
+ * @param {Function} callbackFunc 콜백함수(event, edgeElement, fromElement, toElement)
+ */
+ onBeforeConnectShape: function (callbackFunc) {
+ $(this.getRootElement()).bind('beforeConnectShape', function (event) {
+ if (callbackFunc(event, event.edge, event.fromShape, event.toShape) === false) {
+ event.stopPropagation();
+ }
+ });
+ }
+ ,
+
+ /**
+ * Shape 이 Remove 되기전 이벤트 리스너
+ *
+ * @param {Function} callbackFunc 콜백함수(event, element)
+ */
+ onBeforeRemoveShape: function (callbackFunc) {
+ $(this.getRootElement()).bind('beforeRemoveShape', function (event) {
+ if (callbackFunc(event, event.element) === false) {
+ event.stopPropagation();
+ }
+ });
+ }
+ ,
+
+ /**
+ * Shape 이 Connect 되었을 때의 이벤트 리스너
+ *
+ * @param {Function} callbackFunc 콜백함수(event, edgeElement, fromElement, toElement)
+ */
+ onConnectShape: function (callbackFunc) {
+ $(this.getRootElement()).bind('connectShape', function (event, edgeElement, fromElement, toElement) {
+ callbackFunc(event, edgeElement, fromElement, toElement);
+ });
+ }
+ ,
+
+ /**
+ * Shape 이 Disconnect 되었을 때의 이벤트 리스너
+ *
+ * @param {Function} callbackFunc 콜백함수(event, edgeElement, fromElement, toElement)
+ */
+ onDisconnectShape: function (callbackFunc) {
+ $(this.getRootElement()).bind('disconnectShape', function (event, edgeElement, fromElement, toElement) {
+ callbackFunc(event, edgeElement, fromElement, toElement);
+ });
+ }
+ ,
+
+ /**
+ * Shape 이 Grouping 되었을 때의 이벤트 리스너
+ *
+ * @param {Function} callbackFunc 콜백함수(event, groupElement)
+ */
+ onGroup: function (callbackFunc) {
+ $(this.getRootElement()).bind('group', function (event, groupElement) {
+ callbackFunc(event, groupElement);
+ });
+ }
+ ,
+
+ /**
+ * Shape 이 UnGrouping 되었을 때의 이벤트 리스너
+ *
+ * @param {Function} callbackFunc 콜백함수(event, ungroupedElements)
+ */
+ onUnGroup: function (callbackFunc) {
+ $(this.getRootElement()).bind('ungroup', function (event, ungroupedElements) {
+ callbackFunc(event, ungroupedElements);
+ });
+ }
+ ,
+
+ /**
+ * Group 이 Collapse 되었을 때의 이벤트 리스너
+ *
+ * @param {Function} callbackFunc 콜백함수(event, element)
+ */
+ onCollapsed: function (callbackFunc) {
+ $(this.getRootElement()).bind('collapsed', function (event, element) {
+ callbackFunc(event, element);
+ });
+ }
+ ,
+
+ /**
+ * Group 이 Expand 되었을 때의 이벤트 리스너
+ *
+ * @param {Function} callbackFunc 콜백함수(event, element)
+ */
+ onExpanded: function (callbackFunc) {
+ $(this.getRootElement()).bind('expanded', function (event, element) {
+ callbackFunc(event, element);
+ });
+ },
+
+ /**
+ *
+ * @param {Function} callbackFunc 콜백함수(event, edgeElement, sourceElement, targetElement)
+ */
+ onDuplicated: function (callbackFunc) {
+ $(this.getRootElement()).bind('duplicated', function (event, edgeElement, sourceElement, targetElement) {
+ callbackFunc(event, edgeElement, sourceElement, targetElement);
+ });
+ },
+
+ /**
+ * 캔버스 로딩 이벤트 리스너
+ */
+ onLoading: function (callbackFunc) {
+ $(this.getRootElement()).bind('loading', function (event, progress) {
+ callbackFunc(event, progress);
+ });
+ }
+}
+;
+OG.graph.Canvas.prototype.constructor = OG.graph.Canvas;
+OG.Canvas = OG.graph.Canvas;
diff --git a/public/static/slimscroll/jquery.slimscroll.min.js b/public/static/slimscroll/jquery.slimscroll.min.js
new file mode 100755
index 00000000..092d3366
--- /dev/null
+++ b/public/static/slimscroll/jquery.slimscroll.min.js
@@ -0,0 +1,10 @@
+
+(function(e){e.fn.extend({slimScroll:function(f){var a=e.extend({width:"auto",height:"250px",size:"7px",color:"#000",position:"right",distance:"1px",start:"top",opacity:.4,alwaysVisible:!1,disableFadeOut:!1,railVisible:!1,railColor:"#333",railOpacity:.2,railDraggable:!0,railClass:"slimScrollRail",barClass:"slimScrollBar",wrapperClass:"slimScrollDiv",allowPageScroll:!1,wheelStep:20,touchScrollStep:200,borderRadius:"7px",railBorderRadius:"7px"},f);this.each(function(){function v(d){if(r){d=d||window.event;
+var c=0;d.wheelDelta&&(c=-d.wheelDelta/120);d.detail&&(c=d.detail/3);e(d.target||d.srcTarget||d.srcElement).closest("."+a.wrapperClass).is(b.parent())&&n(c,!0);d.preventDefault&&!k&&d.preventDefault();k||(d.returnValue=!1)}}function n(d,g,e){k=!1;var f=b.outerHeight()-c.outerHeight();g&&(g=parseInt(c.css("top"))+d*parseInt(a.wheelStep)/100*c.outerHeight(),g=Math.min(Math.max(g,0),f),g=0=b.outerHeight()?k=!0:(c.stop(!0,
+!0).fadeIn("fast"),a.railVisible&&m.stop(!0,!0).fadeIn("fast"))}function p(){a.alwaysVisible||(B=setTimeout(function(){a.disableFadeOut&&r||y||z||(c.fadeOut("slow"),m.fadeOut("slow"))},1E3))}var r,y,z,B,A,u,l,C,k=!1,b=e(this);if(b.parent().hasClass(a.wrapperClass)){var q=b.scrollTop(),c=b.siblings("."+a.barClass),m=b.siblings("."+a.railClass);x();if(e.isPlainObject(f)){if("height"in f&&"auto"==f.height){b.parent().css("height","auto");b.css("height","auto");var h=b.parent().parent().height();b.parent().css("height",
+h);b.css("height",h)}else"height"in f&&(h=f.height,b.parent().css("height",h),b.css("height",h));if("scrollTo"in f)q=parseInt(a.scrollTo);else if("scrollBy"in f)q+=parseInt(a.scrollBy);else if("destroy"in f){c.remove();m.remove();b.unwrap();return}n(q,!1,!0)}}else if(!(e.isPlainObject(f)&&"destroy"in f)){a.height="auto"==a.height?b.parent().height():a.height;q=e("").addClass(a.wrapperClass).css({position:"relative",overflow:"hidden",width:a.width,height:a.height});b.css({overflow:"hidden",
+width:a.width,height:a.height});var m=e("").addClass(a.railClass).css({width:a.size,height:"100%",position:"absolute",top:0,display:a.alwaysVisible&&a.railVisible?"block":"none","border-radius":a.railBorderRadius,background:a.railColor,opacity:a.railOpacity,zIndex:90}),c=e("").addClass(a.barClass).css({background:a.color,width:a.size,position:"absolute",top:0,opacity:a.opacity,display:a.alwaysVisible?"block":"none","border-radius":a.borderRadius,BorderRadius:a.borderRadius,MozBorderRadius:a.borderRadius,
+WebkitBorderRadius:a.borderRadius,zIndex:99}),h="right"==a.position?{right:a.distance}:{left:a.distance};m.css(h);c.css(h);b.wrap(q);b.parent().append(c);b.parent().append(m);a.railDraggable&&c.bind("mousedown",function(a){var b=e(document);z=!0;t=parseFloat(c.css("top"));pageY=a.pageY;b.bind("mousemove.slimscroll",function(a){currTop=t+a.pageY-pageY;c.css("top",currTop);n(0,c.position().top,!1)});b.bind("mouseup.slimscroll",function(a){z=!1;p();b.unbind(".slimscroll")});return!1}).bind("selectstart.slimscroll",
+function(a){a.stopPropagation();a.preventDefault();return!1});m.hover(function(){w()},function(){p()});c.hover(function(){y=!0},function(){y=!1});b.hover(function(){r=!0;w();p()},function(){r=!1;p()});b.bind("touchstart",function(a,b){a.originalEvent.touches.length&&(A=a.originalEvent.touches[0].pageY)});b.bind("touchmove",function(b){k||b.originalEvent.preventDefault();b.originalEvent.touches.length&&(n((A-b.originalEvent.touches[0].pageY)/a.touchScrollStep,!0),A=b.originalEvent.touches[0].pageY)});
+x();"bottom"===a.start?(c.css({top:b.outerHeight()-c.outerHeight()}),n(0,!0)):"top"!==a.start&&(n(e(a.start).position().top,null,!0),a.alwaysVisible||c.hide());window.addEventListener?(this.addEventListener("DOMMouseScroll",v,!1),this.addEventListener("mousewheel",v,!1)):document.attachEvent("onmousewheel",v)}});return this}});e.fn.extend({slimscroll:e.fn.slimScroll})})(jQuery);
diff --git a/public/static/templates/spring-boot/Entity.java b/public/static/templates/spring-boot/Entity.java
new file mode 100644
index 00000000..f9aac327
--- /dev/null
+++ b/public/static/templates/spring-boot/Entity.java
@@ -0,0 +1,26 @@
+package hello;
+
+import javax.persistence.*;
+import java.util.List;
+
+@Entity
+@Table(name="{{name}}table")
+public class {{name}} {
+
+ {{#fieldDescriptors}}{{#isKey}}@Id @GeneratedValue(strategy=GenerationType.AUTO){{/isKey}}{{multiplicity}}
+ private {{type}} {{name}};
+ {{/fieldDescriptors}}
+
+ public {{name}}() {}
+
+ {{#fieldDescriptors}}
+ public {{className}} get{{nameCamel}}() {
+ return {{name}};
+ }
+
+ public void set{{nameCamel}}({{className}} {{name}}) {
+ this.{{name}} = {{name}};
+ }
+ {{/fieldDescriptors}}
+
+}
\ No newline at end of file
diff --git a/public/static/v.png b/public/static/v.png
new file mode 100755
index 00000000..a2ce2353
Binary files /dev/null and b/public/static/v.png differ
diff --git a/pusher/.gitignore b/pusher/.gitignore
new file mode 100644
index 00000000..5148e527
--- /dev/null
+++ b/pusher/.gitignore
@@ -0,0 +1,37 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+
+# Runtime data
+pids
+*.pid
+*.seed
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (http://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules
+jspm_packages
+
+# Optional npm cache directory
+.npm
+
+# Optional REPL history
+.node_repl_history
diff --git a/pusher/LICENSE b/pusher/LICENSE
new file mode 100644
index 00000000..b7c0f3c6
--- /dev/null
+++ b/pusher/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017 Rahat Khanna
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/pusher/README.md b/pusher/README.md
new file mode 100644
index 00000000..4fc84197
--- /dev/null
+++ b/pusher/README.md
@@ -0,0 +1,68 @@
+# whats-up-realtime-status-update
+An app that let's you see any User's Status in realtime
+
+[View tutorial](https://pusher.com/tutorials/social-network-javascript/)
+
+## Prerequisite Softwares
+- NodeJS
+- NPM
+- Yarn (Optional)
+
+## Running the Project
+In order to run the app on your machines, please follow the below given steps:
+
+1. Clone the Repo using the URL - https://github.com/mappmechanic/whats-up-realtime-status-update
+
+```
+ git clone https://github.com/mappmechanic/whats-up-realtime-status-update.git
+```
+
+2. Run either of the following commands to install dependencies
+
+```
+ npm install
+```
+
+OR
+
+```
+ yarn
+```
+
+3. Signup at [https://pusher.com/signup](https://pusher.com/signup).
+
+4. Create a new app to obtain the API Key, secret & appId. Also, I have chosen the cluster **'ap2 (Mumbai, India)**, but you will be required to choose a cluster specific to your app users.
+
+Replace the respective key, secret & appId for pusher initialisation in **server.js** file with your values:
+
+```javascript
+ var pusher = new Pusher({
+ appId: '',
+ key: '',
+ secret: '',
+ encrypted: true
+ });
+```
+
+5. Finally you will have to also replace your app-key in **app.js** file too:
+
+```javascript
+ ...
+ pusher = new Pusher('', {
+ authEndpoint: '/usersystem/auth',
+ encrypted: true
+ }),
+ ...
+```
+
+6. Now we are ready to run our app using the following node commands
+
+```
+node server
+```
+
+7. We will be able to access the app at [http://localhost:9000](http://localhost:9000)
+
+For Any clarifications or questions Tweet to me at
+[https://twitter.com/mappmechanic](https://twitter.com/mappmechanic)
+
diff --git a/pusher/article.md b/pusher/article.md
new file mode 100644
index 00000000..519ab1b3
--- /dev/null
+++ b/pusher/article.md
@@ -0,0 +1,689 @@
+# How to update a user's status in realtime in JavaScript
+
+"Hey, what's up?" is not a phrase we need to ask someone these days. These days knowing what someone is up to has become so easy that we keep seeing updated status for all our friends on Whatsapp, Snapchat, Facebook etc. In this blog post, we will learn how can we build a *Realtime user's status update* component along with a list of all members who are online.
+
+We will be using **NodeJS** as the application server, **Vanilla JS** in the front end and **Pusher** for realtime communication between our server and front end.
+
+We will build an app which will be like your friends list or a common chat room where you can see who's online and their latest status update in realtime. In the blog post, we will learn about Pusher's **presence** channel and how to know about the online members to this channel.
+
+![](https://dl.dropboxusercontent.com/s/af8xcqhhpa4v4rf/build-users-status-app-animated.gif)
+
+We will be building the following components during this blog post:
+
+- NodeJS Server using **ExpressJS Framework**
+ - **/register** API - In order to register/login a new user to our channel and server by creating their session and saving their info
+ - **/isLoggedIn** API - To check if a user is already logged in or not in case of refreshing the browser
+ - **/usersystem/auth** API - Auth validation done by Pusher after registering it with our app and on subscribing to a presence or private channel
+ - **/logout** API - To logout the user and remove the session
+
+- Front End App using **Vanilla Javascript**
+ - Register/Login Form - To register/login a new user by filling in their username and initial status
+ - Members List - To see everyone who is online and their updated status
+ - Update Status - To click on the existing status and update it on blur of the status text edit control
+
+
+# Introduction to Pusher
+
+Pusher is a platform which abstracts the complexities of implementing a realtime system on our own using Websockets or Long Polling. We can instantly add realtime features to our existing web applications using Pusher as it supports a wide variety of SDKs. Integration kits are available for variety of front end libraries like **Backbone, React, Angular, jQuery etc** and also backend platforms/languages like **.NET, Java, Python, Ruby, PHP, GO etc**.
+
+## Signing up with Pusher
+
+You can create a free account in Pusher [here](http://pusher.com/signup). After you signup and login for the first time, you will be asked to create a new app as seen in the picture below. You will have to fill in some information about your project and also the front end library or backend language you will be building your app with.
+
+![](https://dl.dropboxusercontent.com/s/4d5nlhj9akuh5tl/build-users-status-update-create_app.png)
+
+For this particular blog post, we will be selecting **Vanilla JS** for the front end and **NodeJS** for the backend as seen in the picture above. This will just show you a set of starter sample codes for these selections, but you can use any integration kit later on with this app.
+
+![](https://dl.dropboxusercontent.com/s/xqqfwqe48x1lfeh/build-users-status-update-getting_started_view.png)
+
+# NodeJS Server
+
+NodeJS should be installed in the system as a prerequisite to this. Now let us begin building the NodeJS server and all the required APIs using **Express**. Initialise a new node project by the following command
+
+``` bash
+npm init
+```
+## Installing Dependencies
+
+We will be installing the required dependencies like Express, express-session, Pusher, body-parser, cookie-parser by the following command:
+
+``` bash
+npm install express express-session body-parser cookie-parser --save
+```
+## Foundation Server
+
+We will now create the basic foundation for Node Server and also enable sessions in that using express-session module.
+
+``` javascript
+var express = require('express');
+var path = require('path');
+var bodyParser = require('body-parser');
+var expressSession = require('express-session');
+var cookieParser = require('cookie-parser');
+
+var app = express();
+
+// must use cookieParser before expressSession
+app.use(cookieParser());
+
+app.use(expressSession({
+ secret:'',
+ resave: true,
+ saveUninitialized: true
+}));
+
+app.use(bodyParser.json());
+app.use(bodyParser.urlencoded({ extended: false }));
+app.use(express.static(path.join(__dirname, 'public')));
+
+// Error Handler for 404 Pages
+app.use(function(req, res, next) {
+ var error404 = new Error('Route Not Found');
+ error404.status = 404;
+ next(error404);
+});
+
+module.exports = app;
+
+app.listen(9000, function(){
+ console.log('Example app listening on port 9000!')
+});
+```
+In the above code, we have created a basic Express server and using the method **.use** we have enabled cookie-parser, body-parser and a static file serving from **public** folder. We have also enabled sessions using **express-session** module. This will enable us to save user information in the appropriate request session for the user.
+
+## Adding Pusher
+
+Pusher has an open source NPM module for **NodeJS** integrations which we will be using. It provides a set of utility methods to integrate with **Pusher** APIs using a unique appId, key and a secret. We will first install the Pusher `npm` module using the following command:
+
+``` bash
+npm install pusher --save
+```
+Now, we can use 'require' to get the Pusher module and to create a new instance passing an options object with important keys to initialise our integration. For this blog post, I have put random keys; you will have to obtain it for your app from the Pusher dashboard.
+
+``` javascript
+var Pusher = require('pusher');
+
+var pusher = new Pusher({
+ appId: '30XXX64',
+ key: '82XXXXXXXXXXXXXXXXXb5',
+ secret: '7bXXXXXXXXXXXXXXXX9e',
+ encrypted: true
+});
+
+var app = express();
+...
+```
+You will have to replace the **appId**, **key** and a **secret** with values specific to your own app. After this, we will write code for a new API which will be used to create a new comment.
+
+## Register/Login API
+
+Now, we will develop the first API route of our application through which a new user can register/login itself and make itself available on our app.
+
+``` javascript
+app.post('/register', function(req, res){
+ console.log(req.body);
+ if(req.body.username && req.body.status){
+ var newMember = {
+ username: req.body.username,
+ status: req.body.status
+ }
+ req.session.user = newMember;
+ res.json({
+ success: true,
+ error: false
+ });
+ }else{
+ res.json({
+ success: false,
+ error: true,
+ message: 'Incomplete information: username and status are required'
+ });
+ }
+});
+```
+In the above code, we have exposed a POST API call on the route **/register** which would expect **username** and **status** parameters to be passed in the request body. We will be saving this user info in the request session.
+
+## User System Auth API
+
+In order to enable any client subscribing to Pusher **Private** and **Presence** channels, we need to implement an auth API which would authenticate the user request by calling **Pusher.authenticate** method at the server side. Add the following code in the server in order to fulfil this condition:
+
+``` javascript
+app.post('/usersystem/auth', function(req, res) {
+ var socketId = req.body.socket_id;
+ var channel = req.body.channel_name;
+ var currentMember = req.session.user;
+ var presenceData = {
+ user_id: currentMember.username,
+ user_info: {
+ status: currentMember.status,
+ }
+ };
+ var auth = pusher.authenticate(socketId, channel, presenceData);
+ res.send(auth);
+});
+```
+We need to provide the specific route in the initialisation of **Pusher Client** side library which we will see later in this blog post. The Pusher client library will automatically call this route and pass in the channel_name and socket_id properties. We will simultaneously get the user information from the user session object and pass it as presenceData to the **Pusher.authenticate** method call.
+
+## IsLoggedIn and Logout API
+
+If the user refreshes the browser, the client side app should detect if the user is already registered or not. We will implement an **isLoggedIn** API route for this. Also, we need a **logout** route to enable any user to logout from the app.
+
+``` javascript
+app.get('/isLoggedIn', function(req,res){
+ if(req.session.user){
+ res.send({
+ authenticated: true
+ });
+ }else{
+ res.send({ authenticated: false });
+ }
+});
+
+app.get('/logout', function(req,res){
+ if(req.session.user){
+ req.session.user = null;
+ }
+ res.redirect('/');
+});
+```
+# Front End App using Vanilla JS
+
+We will be developing the front end app now to register a new user with an initial status, see the members who are online and their statuses. We will also build the feature for the logged in user to update their users and all other users will see the updated status in realtime.
+
+## Step 1: Create a folder named public and create an index.html
+
+We have already written code in our `server.js` to serve static content from `public` folder, so we will write all our front end code in this folder.
+
+Please create a new folder `public` and also create an empty `index.html` for now.
+
+## Step 2: Add Boilerplate Code to our index.html
+
+We will be adding some basic boilerplate code to set up the base structure for our web app like Header, Sections where registration form and the members list can be placed.
+
+``` javascript
+
+
+
+ Whats Up ! Know what other's are up to in Realtime !
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+In the above boilerplate code, we have referenced our main Javascript file **app.js** and the Pusher client side JS library. We also have a script tag where we will place the template for a member row in the member list. Also, we have two empty div tags with ids **me** and **membersList** to contain the logged in member name and info, as well as the list of all other members with their statuses.
+
+### Step3: Style.css
+
+Important to note that we will be showing the signup form for the first time and the MembersList and Logout button will be hidden by default initially. Please create a new file called **style.css** and add the following css to it:
+
+``` css
+body{
+ margin:0;
+ padding:0;
+ overflow: hidden;
+ font-family: Raleway;
+}
+
+header{
+ background: #2b303b;
+ height: 50px;
+ width:100%;
+ display: flex;
+ color:#fff;
+}
+
+
+.loader,
+.loader:after {
+ border-radius: 50%;
+ width: 10em;
+ height: 10em;
+}
+.loader {
+ margin: 60px auto;
+ font-size: 10px;
+ position: relative;
+ text-indent: -9999em;
+ border-top: 1.1em solid rgba(82,0,115, 0.2);
+ border-right: 1.1em solid rgba(82,0,115, 0.2);
+ border-bottom: 1.1em solid rgba(82,0,115, 0.2);
+ border-left: 1.1em solid #520073;
+ -webkit-transform: translateZ(0);
+ -ms-transform: translateZ(0);
+ transform: translateZ(0);
+ -webkit-animation: load8 1.1s infinite linear;
+ animation: load8 1.1s infinite linear;
+}
+@-webkit-keyframes load8 {
+ 0% {
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg);
+ }
+ 100% {
+ -webkit-transform: rotate(360deg);
+ transform: rotate(360deg);
+ }
+}
+@keyframes load8 {
+ 0% {
+ -webkit-transform: rotate(0deg);
+ transform: rotate(0deg);
+ }
+ 100% {
+ -webkit-transform: rotate(360deg);
+ transform: rotate(360deg);
+ }
+}
+
+
+.subheader{
+ display: flex;
+ align-items: center;
+ margin: 0px;
+}
+
+.whatsup-logo{
+ height:60px;
+ border-radius: 8px;
+ flex:0 60px;
+ margin-right: 15px;
+}
+
+.logout{
+ flex:1;
+ justify-content: flex-end;
+ padding:15px;
+ display: none;
+}
+
+.logout a{
+ color:#fff;
+ text-decoration: none;
+}
+
+#signup-form{
+ display: none;
+}
+
+input, textarea{
+ width:100%;
+}
+
+
+section{
+ padding: 0px 15px;
+}
+
+.logo img{
+ height: 35px;
+ padding: 6px;
+ margin-left: 20px;
+}
+
+#updateStatus{
+ display: none;
+}
+
+.members-list{
+ display: none;
+ flex-direction: column;
+}
+
+.me {
+ display: none;
+}
+
+```
+Please try to open the URL **http://localhost:9000** in your browser and the application will load with the basic register or login form with username and status. The output will look like the screenshot below:
+
+![](https://dl.dropboxusercontent.com/s/bwsl0wqek8k22gc/build-users-status-signup-form2.png)
+
+### Step 4: Add app.js basic code
+
+Now we will add our Javascript code to have basic utility elements inside a self invoking function to create a private scope for our app variables. We do not want to pollute JS global scope.
+
+``` javascript
+// Using IIFE for Implementing Module Pattern to keep the Local Space for the JS Variables
+(function() {
+ // Enable pusher logging - don't include this in production
+ Pusher.logToConsole = true;
+
+ var serverUrl = "/",
+ members = [],
+ pusher = new Pusher('73xxxxxxxxxxxxxxxdb', {
+ authEndpoint: '/usersystem/auth',
+ encrypted: true
+ }),
+ channel,
+ userForm = document.getElementById("user-form"),
+ memberTemplateStr = document.getElementById('member-template').innerHTML;
+
+ function showEle(elementId){
+ document.getElementById(elementId).style.display = 'flex';
+ }
+
+ function hideEle(elementId){
+ document.getElementById(elementId).style.display = 'none';
+ }
+
+ function ajax(url, method, payload, successCallback){
+ var xhr = new XMLHttpRequest();
+ xhr.open(method, url, true);
+ xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
+ xhr.onreadystatechange = function () {
+ if (xhr.readyState != 4 || xhr.status != 200) return;
+ successCallback(xhr.responseText);
+ };
+ xhr.send(JSON.stringify(payload));
+ }
+
+ ajax(serverUrl+"isLoggedIn","GET",{},isLoginChecked);
+
+ function isLoginChecked(response){
+ var responseObj = JSON.parse(response);
+ if(responseObj.authenticated){
+ channel = pusher.subscribe('presence-whatsup-members');
+ bindChannelEvents(channel);
+ }
+ updateUserViewState(responseObj.authenticated);
+ }
+
+ function updateUserViewState(isLoggedIn){
+ document.getElementById("loader").style.display = "none";
+ if(isLoggedIn){
+ document.getElementById("logout").style.display = "flex";
+ document.getElementById("signup-form").style.display = "none";
+ }else{
+ document.getElementById("logout").style.display = "none";
+ document.getElementById("signup-form").style.display = "block";
+ }
+ }
+
+ function showLoader(){
+ document.getElementById("loader").style.display = "block";
+ document.getElementById("logout").style.display = "none";
+ document.getElementById("signup-form").style.display = "none";
+ }
+
+ // Adding a new Member Form Submit Event
+ userForm.addEventListener("submit", addNewMember);
+
+
+ function addNewMember(event){
+ event.preventDefault();
+ var newMember = {
+ "username": document.getElementById('display_name').value,
+ "status": document.getElementById('initial_status').value
+ }
+ showLoader();
+ ajax(serverUrl+"register","POST",newMember, onMemberAddSuccess);
+ }
+
+ function onMemberAddSuccess(response){
+ // On Success of registering a new member
+ console.log("Success: " + response);
+ userForm.reset();
+ updateUserViewState(true);
+ // Subscribing to the 'presence-members' Channel
+ channel = pusher.subscribe('presence-whatsup-members');
+ bindChannelEvents(channel);
+ }
+})();
+```
+In the above code, we have referenced all the important variables we will be requiring. We will also initialise the Pusher library using **new Pusher** and passing the api key as the first argument. The second argument contains an optional config object in which we will add the key **authEndpoint** with the custom node api route **/usersystem/auth** and also add the key **encrypted** setting it to value true.
+
+We will create a couple of generic functions to show or hide an element passing its unique id. We have also added a common method named **ajax** to make ajax requests using XMLHttp object in vanilla Javascript.
+
+At the load of the page we make an ajax request to check if the user is logged in or not. If the user is logged in, we will directly use the Pusher instance to subscribe the user to a presence channel named **presence-whatsup-members** , you can have this as the unique chat room or app location where you want to report/track the online members.
+
+We have also written a method above to **addNewMember** using an ajax request to the **register** api route we have built in NodeJS. We will be passing the name and initial status entered into the form.
+
+We also have a method to update the user view state based on the logged in status. This method does nothing but updates the visibility of members list, logout button and signup form. We have used a **bindChannelEvents** method when the user is logged in which we will be implementing later in the blog post.
+
+Please add the following css in **style.css** file to display the **me** element appropriately with the username and the status of the logged in user.
+
+``` css
+
+.me {
+ border:1px solid #aeaeae;
+ padding:10px;
+ margin:10px;
+ border-radius: 10px;
+}
+
+.me img{
+ height: 40px;
+ width: 40px;
+}
+
+.me .status{
+ padding:5px;
+ flex:1;
+}
+
+.me .status .username{
+ font-size:13px;
+ color: #aeaeae;
+ margin-bottom:5px;
+}
+
+.me .status .text{
+ font-size: 15px;
+ width:100%;
+ -webkit-transition: all 1s ease-in 5ms;
+ -moz-transition: all 1s ease-in 5ms;
+ transition: all 1s ease-in 5ms;
+}
+```
+## Step 5: Add code to render the members list and bindChannelEvents
+
+Now, after subscribing to the channel, we need to bind certain events so that we can know whenever a new member is added to the channel or removed from it. We will also bind to a custom event to know whenever someone updates their status.
+
+Add the following code to the **app.js** file:
+
+``` javascript
+// Binding to Pusher Events on our 'presence-whatsup-members' Channel
+
+ function bindChannelEvents(channel){
+ channel.bind('client-status-update',statusUpdated);
+ var reRenderMembers = function(member){
+ renderMembers(channel.members);
+ }
+ channel.bind('pusher:subscription_succeeded', reRenderMembers);
+ channel.bind('pusher:member_added', reRenderMembers);
+ channel.bind('pusher:member_removed', reRenderMembers);
+ }
+
+
+```
+In the above **bindChannelEvents** method, we use the **channel.bind** method to bind event handlers for 3 internal events - **pusher:subscription_succeeded**, **pusher:member_added**, **pusher:member_removed** and 1 custom event - **client-status-update**.
+
+Now we will add the Javascript code to render the list of members. It is important to know that the object which i returned from the **.subscribe** method has a property called **members** which can be used to know the information about the logged in user referred by the key **me** and other members by key **members**. Add the following code to **app.js** file
+
+``` javascript
+
+// Render the list of members with updated data and also render the logged in user component
+
+ function renderMembers(channelMembers){
+ var members = channelMembers.members;
+ var membersListNode = document.createElement('div');
+ showEle('membersList');
+
+ Object.keys(members).map(function(currentMember){
+ if(currentMember !== channelMembers.me.id){
+ var currentMemberHtml = memberTemplateStr;
+ currentMemberHtml = currentMemberHtml.replace('{{username}}',currentMember);
+ currentMemberHtml = currentMemberHtml.replace('{{status}}',members[currentMember].status);
+ currentMemberHtml = currentMemberHtml.replace('{{time}}','');
+ var newMemberNode = document.createElement('div');
+ newMemberNode.classList.add('member');
+ newMemberNode.setAttribute("id","user-"+currentMember);
+ newMemberNode.innerHTML = currentMemberHtml;
+ membersListNode.appendChild(newMemberNode);
+ }
+ });
+ renderMe(channelMembers.me);
+ document.getElementById("membersList").innerHTML = membersListNode.innerHTML;
+ }
+
+
+ function renderMe(myObj){
+ document.getElementById('myusername').innerHTML = myObj.id;
+ document.getElementById('mystatus').innerHTML = myObj.info.status;
+ }
+```
+We have added the event handler for new member add/remove event to re-render the members list so that it remains updated with the online members only. In order to show the members list we need to add the following style into our file **style.css**
+
+``` css
+
+.member{
+ display: flex;
+ border-bottom: 1px solid #aeaeae;
+ margin-bottom: 10px;
+ padding: 10px;
+}
+
+.member .user-icon{
+ flex:0 40px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.member .user-icon img{
+ width:50px;
+ height:50px;
+}
+
+.member .user-info{
+ padding:5px;
+ margin-left:10px;
+}
+
+.member .user-info .name{
+ font-weight: bold;
+ font-size: 16px;
+ padding-bottom:5px;
+}
+
+.member .user-info .status{
+ font-weight: normal;
+ font-size:13px;
+}
+
+.member .user-info .time{
+ font-weight: normal;
+ font-size:10px;
+ color:#aeaeae;
+}
+```
+Now we will write the code, to trigger a client event on our channel to notify all users about the status change of the logged in user. Add the following code to your **app.js** file
+
+``` javascript
+ // On Blur of editting my status update the status by sending Pusher event
+ document.getElementById('mystatus').addEventListener('blur',sendStatusUpdateReq);
+
+ function sendStatusUpdateReq(event){
+ var newStatus = document.getElementById('mystatus').innerHTML;
+ var username = document.getElementById('myusername').innerText;
+ channel.trigger("client-status-update", {
+ username: username,
+ status: newStatus
+ });
+ }
+
+ // New Update Event Handler
+ // We will take the Comment Template, replace placeholders and append to commentsList
+ function statusUpdated(data){
+ var updatedMemberHtml = memberTemplateStr;
+ updatedMemberHtml = updatedMemberHtml.replace('{{username}}',data.username);
+ updatedMemberHtml = updatedMemberHtml.replace('{{status}}',data.status);
+ updatedMemberHtml = updatedMemberHtml.replace('{{time}}','just now');
+ document.getElementById("user-"+data.username).style.color = '#1B8D98';
+ document.getElementById("user-"+data.username).innerHTML=updatedMemberHtml;
+ setTimeout(function(){
+ document.getElementById("user-"+data.username).style.color = '#000';
+ },500);
+ }
+```
+***IMPORTANT***: When we run this code in our browsers, update the status and blur out of the status control, we will get an error in the Javascript console for the Pusher library. To fix this, go to the console at **Pusher.com** website, go to settings and enable sending events from clients directly.
+
+We can only send events from client sdirectly for Presence or Private channels. Link to the official documentation - [https://Pusher.com/docs/client_api_guide/client_events#trigger-events](https://pusher.com/docs/client_api_guide/client_events#trigger-events)
+
+``` javascript
+Pusher : Error : {
+ "type":"WebSocketError",
+ "error":{
+ "type":"PusherError",
+ "data":
+ {
+ "code":null,
+ "message":"To send client events, you must enable this feature in the Settings page of your dashboard."
+ }
+ }
+}
+```
+Link to the Github Repository for reference:
+
+[https://github.com/mappmechanic/whats-up-realtime-status-update](https://github.com/mappmechanic/whats-up-realtime-status-update)
+
+# Conclusion
+
+We have built an application which will display all the online members for a particular presence channel and their statuses. If any of the online user updates their status, every user will be notified about the updated status.
+
+This component or code can be used for developing a social networking section in most of the web apps these days. It is an important use case where the user needs to know about other available participants. For example: an online classroom app can see the other participants and the status can correspond to any question any participant wants to ask the presenter.
+
+![](https://dl.dropboxusercontent.com/s/af8xcqhhpa4v4rf/build-users-status-app-animated.gif)
+
+We have just used **NodeJS** and **Vanilla JS** to implement the above functionality. You can use the Javascript for front end code with any popular framework like **ReactJS** or **AngularJS** etc. The backend can also be **Java** or **Ruby**. Please refer to the Pusher docs for more information on this.
diff --git a/pusher/dockerfile b/pusher/dockerfile
new file mode 100644
index 00000000..3dd59018
--- /dev/null
+++ b/pusher/dockerfile
@@ -0,0 +1,24 @@
+FROM node:carbon
+
+#app 폴더 만들기 - NodeJS 어플리케이션 폴더
+RUN mkdir -p /app
+#winston 등을 사용할떄엔 log 폴더도 생성
+
+#어플리케이션 폴더를 Workdir로 지정 - 서버가동용
+WORKDIR /app
+
+#서버 파일 복사 ADD [어플리케이션파일 위치] [컨테이너내부의 어플리케이션 파일위치]
+#저는 Dockerfile과 서버파일이 같은위치에 있어서 ./입니다
+ADD ./ /app
+
+#패키지파일들 받기
+RUN npm install
+
+#배포버젼으로 설정 - 이 설정으로 환경을 나눌 수 있습니다.
+ENV NODE_ENV=production
+
+EXPOSE 4000
+
+#서버실행
+CMD node server.js
+
diff --git a/pusher/package-lock.json b/pusher/package-lock.json
new file mode 100644
index 00000000..e86925cf
--- /dev/null
+++ b/pusher/package-lock.json
@@ -0,0 +1,977 @@
+{
+ "name": "whats-up",
+ "version": "0.0.1",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "accepts": {
+ "version": "1.3.7",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
+ "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
+ "requires": {
+ "mime-types": "~2.1.24",
+ "negotiator": "0.6.2"
+ }
+ },
+ "ansi-regex": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+ "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
+ },
+ "ansi-styles": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+ "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4="
+ },
+ "array-flatten": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+ "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
+ },
+ "asn1": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
+ "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
+ "requires": {
+ "safer-buffer": "~2.1.0"
+ }
+ },
+ "assert-plus": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz",
+ "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ="
+ },
+ "async": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz",
+ "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==",
+ "requires": {
+ "lodash": "^4.17.11"
+ }
+ },
+ "aws-sign2": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz",
+ "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8="
+ },
+ "aws4": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz",
+ "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ=="
+ },
+ "bcrypt-pbkdf": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
+ "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
+ "requires": {
+ "tweetnacl": "^0.14.3"
+ }
+ },
+ "bl": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/bl/-/bl-1.1.2.tgz",
+ "integrity": "sha1-/cqHGplxOqANGeO7ukHER4emU5g=",
+ "requires": {
+ "readable-stream": "~2.0.5"
+ }
+ },
+ "body-parser": {
+ "version": "1.19.0",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
+ "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
+ "requires": {
+ "bytes": "3.1.0",
+ "content-type": "~1.0.4",
+ "debug": "2.6.9",
+ "depd": "~1.1.2",
+ "http-errors": "1.7.2",
+ "iconv-lite": "0.4.24",
+ "on-finished": "~2.3.0",
+ "qs": "6.7.0",
+ "raw-body": "2.4.0",
+ "type-is": "~1.6.17"
+ }
+ },
+ "boom": {
+ "version": "2.10.1",
+ "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz",
+ "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=",
+ "requires": {
+ "hoek": "2.x.x"
+ }
+ },
+ "bytes": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
+ "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
+ },
+ "caseless": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz",
+ "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c="
+ },
+ "chalk": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+ "requires": {
+ "ansi-styles": "^2.2.1",
+ "escape-string-regexp": "^1.0.2",
+ "has-ansi": "^2.0.0",
+ "strip-ansi": "^3.0.0",
+ "supports-color": "^2.0.0"
+ }
+ },
+ "combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "requires": {
+ "delayed-stream": "~1.0.0"
+ }
+ },
+ "commander": {
+ "version": "2.20.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz",
+ "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ=="
+ },
+ "content-disposition": {
+ "version": "0.5.3",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
+ "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
+ "requires": {
+ "safe-buffer": "5.1.2"
+ }
+ },
+ "content-type": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
+ "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
+ },
+ "cookie": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
+ "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s="
+ },
+ "cookie-parser": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.4.tgz",
+ "integrity": "sha512-lo13tqF3JEtFO7FyA49CqbhaFkskRJ0u/UAiINgrIXeRCY41c88/zxtrECl8AKH3B0hj9q10+h3Kt8I7KlW4tw==",
+ "requires": {
+ "cookie": "0.3.1",
+ "cookie-signature": "1.0.6"
+ }
+ },
+ "cookie-signature": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
+ },
+ "core-util-is": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
+ },
+ "cors": {
+ "version": "2.8.5",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
+ "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
+ "requires": {
+ "object-assign": "^4",
+ "vary": "^1"
+ }
+ },
+ "cryptiles": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz",
+ "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=",
+ "requires": {
+ "boom": "2.x.x"
+ }
+ },
+ "dashdash": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+ "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+ "requires": {
+ "assert-plus": "^1.0.0"
+ },
+ "dependencies": {
+ "assert-plus": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
+ }
+ }
+ },
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
+ },
+ "depd": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
+ "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
+ },
+ "destroy": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
+ "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
+ },
+ "ecc-jsbn": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
+ "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
+ "requires": {
+ "jsbn": "~0.1.0",
+ "safer-buffer": "^2.1.0"
+ }
+ },
+ "ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
+ },
+ "encodeurl": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+ "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
+ },
+ "escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
+ },
+ "escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
+ },
+ "etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
+ },
+ "express": {
+ "version": "4.17.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
+ "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
+ "requires": {
+ "accepts": "~1.3.7",
+ "array-flatten": "1.1.1",
+ "body-parser": "1.19.0",
+ "content-disposition": "0.5.3",
+ "content-type": "~1.0.4",
+ "cookie": "0.4.0",
+ "cookie-signature": "1.0.6",
+ "debug": "2.6.9",
+ "depd": "~1.1.2",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "finalhandler": "~1.1.2",
+ "fresh": "0.5.2",
+ "merge-descriptors": "1.0.1",
+ "methods": "~1.1.2",
+ "on-finished": "~2.3.0",
+ "parseurl": "~1.3.3",
+ "path-to-regexp": "0.1.7",
+ "proxy-addr": "~2.0.5",
+ "qs": "6.7.0",
+ "range-parser": "~1.2.1",
+ "safe-buffer": "5.1.2",
+ "send": "0.17.1",
+ "serve-static": "1.14.1",
+ "setprototypeof": "1.1.1",
+ "statuses": "~1.5.0",
+ "type-is": "~1.6.18",
+ "utils-merge": "1.0.1",
+ "vary": "~1.1.2"
+ },
+ "dependencies": {
+ "cookie": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
+ "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
+ }
+ }
+ },
+ "express-session": {
+ "version": "1.16.2",
+ "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.16.2.tgz",
+ "integrity": "sha512-oy0sRsdw6n93E9wpCNWKRnSsxYnSDX9Dnr9mhZgqUEEorzcq5nshGYSZ4ZReHFhKQ80WI5iVUUSPW7u3GaKauw==",
+ "requires": {
+ "cookie": "0.3.1",
+ "cookie-signature": "1.0.6",
+ "debug": "2.6.9",
+ "depd": "~2.0.0",
+ "on-headers": "~1.0.2",
+ "parseurl": "~1.3.3",
+ "safe-buffer": "5.1.2",
+ "uid-safe": "~2.1.5"
+ },
+ "dependencies": {
+ "depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
+ }
+ }
+ },
+ "extend": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
+ },
+ "extsprintf": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
+ "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
+ },
+ "finalhandler": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
+ "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
+ "requires": {
+ "debug": "2.6.9",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "on-finished": "~2.3.0",
+ "parseurl": "~1.3.3",
+ "statuses": "~1.5.0",
+ "unpipe": "~1.0.0"
+ }
+ },
+ "forever-agent": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+ "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
+ },
+ "form-data": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.1.tgz",
+ "integrity": "sha1-rjFduaSQf6BlUCMEpm13M0de43w=",
+ "requires": {
+ "async": "^2.0.1",
+ "combined-stream": "^1.0.5",
+ "mime-types": "^2.1.11"
+ }
+ },
+ "forwarded": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
+ "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
+ },
+ "fresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
+ },
+ "generate-function": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
+ "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
+ "requires": {
+ "is-property": "^1.0.2"
+ }
+ },
+ "generate-object-property": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz",
+ "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=",
+ "requires": {
+ "is-property": "^1.0.0"
+ }
+ },
+ "getpass": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
+ "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
+ "requires": {
+ "assert-plus": "^1.0.0"
+ },
+ "dependencies": {
+ "assert-plus": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
+ }
+ }
+ },
+ "har-validator": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz",
+ "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=",
+ "requires": {
+ "chalk": "^1.1.1",
+ "commander": "^2.9.0",
+ "is-my-json-valid": "^2.12.4",
+ "pinkie-promise": "^2.0.0"
+ }
+ },
+ "has-ansi": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
+ "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
+ "requires": {
+ "ansi-regex": "^2.0.0"
+ }
+ },
+ "hawk": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz",
+ "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=",
+ "requires": {
+ "boom": "2.x.x",
+ "cryptiles": "2.x.x",
+ "hoek": "2.x.x",
+ "sntp": "1.x.x"
+ }
+ },
+ "hoek": {
+ "version": "2.16.3",
+ "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz",
+ "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0="
+ },
+ "http-errors": {
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
+ "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
+ "requires": {
+ "depd": "~1.1.2",
+ "inherits": "2.0.3",
+ "setprototypeof": "1.1.1",
+ "statuses": ">= 1.5.0 < 2",
+ "toidentifier": "1.0.0"
+ }
+ },
+ "http-signature": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz",
+ "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=",
+ "requires": {
+ "assert-plus": "^0.2.0",
+ "jsprim": "^1.2.2",
+ "sshpk": "^1.7.0"
+ }
+ },
+ "iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "requires": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ }
+ },
+ "inherits": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+ },
+ "ipaddr.js": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz",
+ "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA=="
+ },
+ "is-my-ip-valid": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz",
+ "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ=="
+ },
+ "is-my-json-valid": {
+ "version": "2.20.0",
+ "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.20.0.tgz",
+ "integrity": "sha512-XTHBZSIIxNsIsZXg7XB5l8z/OBFosl1Wao4tXLpeC7eKU4Vm/kdop2azkPqULwnfGQjmeDIyey9g7afMMtdWAA==",
+ "requires": {
+ "generate-function": "^2.0.0",
+ "generate-object-property": "^1.1.0",
+ "is-my-ip-valid": "^1.0.0",
+ "jsonpointer": "^4.0.0",
+ "xtend": "^4.0.0"
+ }
+ },
+ "is-property": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
+ "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ="
+ },
+ "is-typedarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+ "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
+ },
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+ },
+ "isstream": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
+ },
+ "jsbn": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+ "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
+ },
+ "json-schema": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
+ "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
+ },
+ "json-stringify-safe": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
+ },
+ "jsonpointer": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz",
+ "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk="
+ },
+ "jsprim": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
+ "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
+ "requires": {
+ "assert-plus": "1.0.0",
+ "extsprintf": "1.3.0",
+ "json-schema": "0.2.3",
+ "verror": "1.10.0"
+ },
+ "dependencies": {
+ "assert-plus": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
+ }
+ }
+ },
+ "lodash": {
+ "version": "4.17.15",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
+ "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
+ },
+ "media-typer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
+ },
+ "merge-descriptors": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
+ "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
+ },
+ "methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
+ },
+ "mime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="
+ },
+ "mime-db": {
+ "version": "1.40.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz",
+ "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA=="
+ },
+ "mime-types": {
+ "version": "2.1.24",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz",
+ "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==",
+ "requires": {
+ "mime-db": "1.40.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ },
+ "negotiator": {
+ "version": "0.6.2",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
+ "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw=="
+ },
+ "node-uuid": {
+ "version": "1.4.8",
+ "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz",
+ "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc="
+ },
+ "oauth-sign": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
+ "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM="
+ },
+ "object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
+ },
+ "on-finished": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+ "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
+ "requires": {
+ "ee-first": "1.1.1"
+ }
+ },
+ "on-headers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
+ "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA=="
+ },
+ "parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="
+ },
+ "path": {
+ "version": "0.12.7",
+ "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz",
+ "integrity": "sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8=",
+ "requires": {
+ "process": "^0.11.1",
+ "util": "^0.10.3"
+ }
+ },
+ "path-to-regexp": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
+ "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
+ },
+ "pinkie": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
+ "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA="
+ },
+ "pinkie-promise": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
+ "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
+ "requires": {
+ "pinkie": "^2.0.0"
+ }
+ },
+ "process": {
+ "version": "0.11.10",
+ "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
+ "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI="
+ },
+ "process-nextick-args": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
+ "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M="
+ },
+ "proxy-addr": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz",
+ "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==",
+ "requires": {
+ "forwarded": "~0.1.2",
+ "ipaddr.js": "1.9.0"
+ }
+ },
+ "punycode": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+ "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
+ },
+ "pusher": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/pusher/-/pusher-1.5.1.tgz",
+ "integrity": "sha1-gYbPFuWxJLUdpsgwAaTDairUK0Q=",
+ "requires": {
+ "request": "2.74.0"
+ }
+ },
+ "qs": {
+ "version": "6.7.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
+ "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
+ },
+ "random-bytes": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
+ "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs="
+ },
+ "range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
+ },
+ "raw-body": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
+ "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
+ "requires": {
+ "bytes": "3.1.0",
+ "http-errors": "1.7.2",
+ "iconv-lite": "0.4.24",
+ "unpipe": "1.0.0"
+ }
+ },
+ "readable-stream": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz",
+ "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=",
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.1",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~1.0.6",
+ "string_decoder": "~0.10.x",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "request": {
+ "version": "2.74.0",
+ "resolved": "https://registry.npmjs.org/request/-/request-2.74.0.tgz",
+ "integrity": "sha1-dpPKdou7DqXIzgjAhKRe+gW4kqs=",
+ "requires": {
+ "aws-sign2": "~0.6.0",
+ "aws4": "^1.2.1",
+ "bl": "~1.1.2",
+ "caseless": "~0.11.0",
+ "combined-stream": "~1.0.5",
+ "extend": "~3.0.0",
+ "forever-agent": "~0.6.1",
+ "form-data": "~1.0.0-rc4",
+ "har-validator": "~2.0.6",
+ "hawk": "~3.1.3",
+ "http-signature": "~1.1.0",
+ "is-typedarray": "~1.0.0",
+ "isstream": "~0.1.2",
+ "json-stringify-safe": "~5.0.1",
+ "mime-types": "~2.1.7",
+ "node-uuid": "~1.4.7",
+ "oauth-sign": "~0.8.1",
+ "qs": "~6.2.0",
+ "stringstream": "~0.0.4",
+ "tough-cookie": "~2.3.0",
+ "tunnel-agent": "~0.4.1"
+ },
+ "dependencies": {
+ "qs": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.2.3.tgz",
+ "integrity": "sha1-HPyyXBCpsrSDBT/zn138kjOQjP4="
+ }
+ }
+ },
+ "safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+ },
+ "safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+ },
+ "send": {
+ "version": "0.17.1",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
+ "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
+ "requires": {
+ "debug": "2.6.9",
+ "depd": "~1.1.2",
+ "destroy": "~1.0.4",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "0.5.2",
+ "http-errors": "~1.7.2",
+ "mime": "1.6.0",
+ "ms": "2.1.1",
+ "on-finished": "~2.3.0",
+ "range-parser": "~1.2.1",
+ "statuses": "~1.5.0"
+ },
+ "dependencies": {
+ "ms": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+ "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
+ }
+ }
+ },
+ "serve-static": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
+ "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
+ "requires": {
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.3",
+ "send": "0.17.1"
+ }
+ },
+ "setprototypeof": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
+ "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
+ },
+ "sntp": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz",
+ "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=",
+ "requires": {
+ "hoek": "2.x.x"
+ }
+ },
+ "sshpk": {
+ "version": "1.16.1",
+ "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
+ "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
+ "requires": {
+ "asn1": "~0.2.3",
+ "assert-plus": "^1.0.0",
+ "bcrypt-pbkdf": "^1.0.0",
+ "dashdash": "^1.12.0",
+ "ecc-jsbn": "~0.1.1",
+ "getpass": "^0.1.1",
+ "jsbn": "~0.1.0",
+ "safer-buffer": "^2.0.2",
+ "tweetnacl": "~0.14.0"
+ },
+ "dependencies": {
+ "assert-plus": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
+ }
+ }
+ },
+ "statuses": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
+ "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
+ },
+ "string_decoder": {
+ "version": "0.10.31",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+ "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
+ },
+ "stringstream": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz",
+ "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA=="
+ },
+ "strip-ansi": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+ "requires": {
+ "ansi-regex": "^2.0.0"
+ }
+ },
+ "supports-color": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+ "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc="
+ },
+ "toidentifier": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
+ "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
+ },
+ "tough-cookie": {
+ "version": "2.3.4",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz",
+ "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==",
+ "requires": {
+ "punycode": "^1.4.1"
+ }
+ },
+ "tunnel-agent": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz",
+ "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us="
+ },
+ "tweetnacl": {
+ "version": "0.14.5",
+ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+ "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
+ },
+ "type-is": {
+ "version": "1.6.18",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "requires": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.24"
+ }
+ },
+ "uid-safe": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
+ "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
+ "requires": {
+ "random-bytes": "~1.0.0"
+ }
+ },
+ "unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
+ },
+ "util": {
+ "version": "0.10.4",
+ "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz",
+ "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==",
+ "requires": {
+ "inherits": "2.0.3"
+ }
+ },
+ "util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
+ },
+ "utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
+ },
+ "vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
+ },
+ "verror": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
+ "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
+ "requires": {
+ "assert-plus": "^1.0.0",
+ "core-util-is": "1.0.2",
+ "extsprintf": "^1.2.0"
+ },
+ "dependencies": {
+ "assert-plus": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
+ }
+ }
+ },
+ "xtend": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
+ "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68="
+ }
+ }
+}
diff --git a/pusher/package.json b/pusher/package.json
new file mode 100644
index 00000000..3cec1078
--- /dev/null
+++ b/pusher/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "whats-up",
+ "version": "0.0.1",
+ "description": "Whats Up - Vanilla JS Member Management Example with Status Update Visibility using Pusher Realtime APIs",
+ "main": "server.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "mAppMechanic (Rahat Khanna)",
+ "license": "ISC",
+ "dependencies": {
+ "body-parser": "^1.16.1",
+ "cookie-parser": "^1.4.3",
+ "cors": "^2.8.5",
+ "express": "^4.14.1",
+ "express-session": "^1.16.2",
+ "path": "^0.12.7",
+ "pusher": "^1.5.1"
+ }
+}
diff --git a/pusher/server.js b/pusher/server.js
new file mode 100644
index 00000000..a2dd51c8
--- /dev/null
+++ b/pusher/server.js
@@ -0,0 +1,128 @@
+var express = require('express');
+var path = require('path');
+var cors = require('cors');
+var bodyParser = require('body-parser');
+var expressSession = require('express-session');
+var cookieParser = require('cookie-parser');
+const port = process.env.PORT || 4000;
+
+var Pusher = require('pusher');
+
+var pusher = new Pusher({
+ encrypted: true,
+ appId: '791580',
+ key: '33169ca8c59c1f7f97cd',
+ secret: 'fabf1e67ee01ec107f46',
+ cluster: 'ap3'
+});
+
+var app = express();
+
+var corsOptions = {
+ origin: 'http://localhost:8081',
+ credentials: true
+};
+
+app.use(cors(corsOptions));
+app.use(expressSession({
+ key: 'key', // 세션키
+ secret: "secret", // 비밀키
+ resave: true,
+ saveUninitialized: true,
+}))
+;
+
+// must use cookieParser before expressSession
+app.use(cookieParser());
+
+// app.all('/*', function(req, res, next) {
+// res.header("Access-Control-Allow-Origin", "*");
+// res.header("Access-Control-Allow-Headers", "X-Requested-With");
+// next();
+// });
+
+// app.use(expressSession({secret: 'test', ));
+
+app.use(bodyParser.json());
+app.use(bodyParser.urlencoded({extended: false}));
+app.use(express.static(path.join(__dirname, 'public')));
+
+app.listen(port, () => {
+ console.log(`Server started on port ${port}`);
+});
+
+app.post('/register', function (req, res) {
+ res.setHeader('Access-Control-Allow-Credentials', 'true')
+ console.log("userName : " + req.body.userName + " & userId: " + req.body.userId)
+ if (req.body.userName && req.body.userId) {
+ var newMember = {
+ userName: req.body.userName,
+ userId: req.body.userId
+ }
+ req.session.user = newMember;
+ res.json({
+ success: true,
+ error: false
+ });
+ } else {
+ res.json({
+ success: false,
+ error: true,
+ message: 'Incomplete information: username and status are required'
+ });
+ }
+});
+
+app.post('/usersystem/auth', function (req, res) {
+ res.setHeader('Access-Control-Allow-Credentials', 'true')
+ console.log(req.session)
+ var sess = req.session;
+ var socketId = req.body.socket_id;
+ var channel = req.body.channel_name;
+ var currentMember = req.session.user;
+ var presenceData = {
+ user_id: socketId,
+ user_info: {
+ userName: req.session.userName,
+ userId: req.session.userId
+ }
+ };
+ console.log(new Date().toLocaleString() + "Authenticating user: " + socketId + ":" + channel);
+ console.log("With userName = " + req.session.userName + " and userID = " + req.session.userId);
+ var auth = pusher.authenticate(socketId, channel, presenceData);
+ console.log("response: " + '\n' + auth);
+ res.send(auth);
+});
+
+app.get('/isLoggedIn', function (req, res) {
+ if (req.session.user) {
+ res.send({
+ authenticated: true
+ });
+ } else {
+ res.send({authenticated: false});
+ }
+});
+
+app.get('/logout', function (req, res) {
+ if (req.session.user) {
+ req.session.user = null;
+ }
+});
+
+app.post('/paint', (req, res) => {
+ console.log("aa")
+ pusher.trigger('paint', 'draw', req.body);
+ res.json(req.body);
+});
+
+// Error Handler for 404 Pages
+// app.use(function(req, res, next) {
+// var error404 = new Error('Route Not Found');
+// error404.status = 404;
+// next(error404);
+// });
+
+module.exports = app;
+
+
diff --git a/pusher/yarn.lock b/pusher/yarn.lock
new file mode 100644
index 00000000..f6baaeae
--- /dev/null
+++ b/pusher/yarn.lock
@@ -0,0 +1,777 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+accepts@~1.3.3:
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.3.tgz#c3ca7434938648c3e0d9c1e328dd68b622c284ca"
+ dependencies:
+ mime-types "~2.1.11"
+ negotiator "0.6.1"
+
+ansi-regex@^2.0.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
+
+ansi-styles@^2.2.1:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
+
+array-flatten@1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
+
+asn1@~0.2.3:
+ version "0.2.3"
+ resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86"
+
+assert-plus@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234"
+
+assert-plus@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
+
+async@^2.0.1:
+ version "2.1.4"
+ resolved "https://registry.yarnpkg.com/async/-/async-2.1.4.tgz#2d2160c7788032e4dd6cbe2502f1f9a2c8f6cde4"
+ dependencies:
+ lodash "^4.14.0"
+
+aws-sign2@~0.6.0:
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f"
+
+aws4@^1.2.1:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e"
+
+bcrypt-pbkdf@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d"
+ dependencies:
+ tweetnacl "^0.14.3"
+
+bl@~1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/bl/-/bl-1.1.2.tgz#fdca871a99713aa00d19e3bbba41c44787a65398"
+ dependencies:
+ readable-stream "~2.0.5"
+
+body-parser@^1.16.1:
+ version "1.16.1"
+ resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.16.1.tgz#51540d045adfa7a0c6995a014bb6b1ed9b802329"
+ dependencies:
+ bytes "2.4.0"
+ content-type "~1.0.2"
+ debug "2.6.1"
+ depd "~1.1.0"
+ http-errors "~1.5.1"
+ iconv-lite "0.4.15"
+ on-finished "~2.3.0"
+ qs "6.2.1"
+ raw-body "~2.2.0"
+ type-is "~1.6.14"
+
+boom@2.x.x:
+ version "2.10.1"
+ resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f"
+ dependencies:
+ hoek "2.x.x"
+
+bytes@2.4.0:
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.4.0.tgz#7d97196f9d5baf7f6935e25985549edd2a6c2339"
+
+caseless@~0.11.0:
+ version "0.11.0"
+ resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7"
+
+chalk@^1.1.1:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
+ dependencies:
+ ansi-styles "^2.2.1"
+ escape-string-regexp "^1.0.2"
+ has-ansi "^2.0.0"
+ strip-ansi "^3.0.0"
+ supports-color "^2.0.0"
+
+combined-stream@^1.0.5, combined-stream@~1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009"
+ dependencies:
+ delayed-stream "~1.0.0"
+
+commander@^2.9.0:
+ version "2.9.0"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4"
+ dependencies:
+ graceful-readlink ">= 1.0.0"
+
+content-disposition@0.5.2:
+ version "0.5.2"
+ resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4"
+
+content-type@~1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.2.tgz#b7d113aee7a8dd27bd21133c4dc2529df1721eed"
+
+cookie-parser@^1.4.3:
+ version "1.4.4"
+ resolved "https://registry.yarnpkg.com/cookie-parser/-/cookie-parser-1.4.4.tgz#e6363de4ea98c3def9697b93421c09f30cf5d188"
+ integrity sha512-lo13tqF3JEtFO7FyA49CqbhaFkskRJ0u/UAiINgrIXeRCY41c88/zxtrECl8AKH3B0hj9q10+h3Kt8I7KlW4tw==
+ dependencies:
+ cookie "0.3.1"
+ cookie-signature "1.0.6"
+
+cookie-signature@1.0.6:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
+
+cookie@0.3.1:
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
+
+cookie@0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
+ integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
+
+core-util-is@~1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
+
+cors@^2.8.5:
+ version "2.8.5"
+ resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29"
+ integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==
+ dependencies:
+ object-assign "^4"
+ vary "^1"
+
+cryptiles@2.x.x:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8"
+ dependencies:
+ boom "2.x.x"
+
+dashdash@^1.12.0:
+ version "1.14.1"
+ resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
+ dependencies:
+ assert-plus "^1.0.0"
+
+debug@2.6.1:
+ version "2.6.1"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.1.tgz#79855090ba2c4e3115cc7d8769491d58f0491351"
+ dependencies:
+ ms "0.7.2"
+
+debug@2.6.9:
+ version "2.6.9"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
+ integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
+ dependencies:
+ ms "2.0.0"
+
+debug@~2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da"
+ dependencies:
+ ms "0.7.1"
+
+delayed-stream@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
+
+depd@~1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.0.tgz#e1bd82c6aab6ced965b97b88b17ed3e528ca18c3"
+
+depd@~2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
+ integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
+
+destroy@~1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
+
+ecc-jsbn@~0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505"
+ dependencies:
+ jsbn "~0.1.0"
+
+ee-first@1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
+
+encodeurl@~1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20"
+
+escape-html@~1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
+
+escape-string-regexp@^1.0.2:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
+
+etag@~1.7.0:
+ version "1.7.0"
+ resolved "https://registry.yarnpkg.com/etag/-/etag-1.7.0.tgz#03d30b5f67dd6e632d2945d30d6652731a34d5d8"
+
+express-session@^1.16.2:
+ version "1.17.0"
+ resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.17.0.tgz#9b50dbb5e8a03c3537368138f072736150b7f9b3"
+ integrity sha512-t4oX2z7uoSqATbMfsxWMbNjAL0T5zpvcJCk3Z9wnPPN7ibddhnmDZXHfEcoBMG2ojKXZoCyPMc5FbtK+G7SoDg==
+ dependencies:
+ cookie "0.4.0"
+ cookie-signature "1.0.6"
+ debug "2.6.9"
+ depd "~2.0.0"
+ on-headers "~1.0.2"
+ parseurl "~1.3.3"
+ safe-buffer "5.2.0"
+ uid-safe "~2.1.5"
+
+express@^4.14.1:
+ version "4.14.1"
+ resolved "https://registry.yarnpkg.com/express/-/express-4.14.1.tgz#646c237f766f148c2120aff073817b9e4d7e0d33"
+ dependencies:
+ accepts "~1.3.3"
+ array-flatten "1.1.1"
+ content-disposition "0.5.2"
+ content-type "~1.0.2"
+ cookie "0.3.1"
+ cookie-signature "1.0.6"
+ debug "~2.2.0"
+ depd "~1.1.0"
+ encodeurl "~1.0.1"
+ escape-html "~1.0.3"
+ etag "~1.7.0"
+ finalhandler "0.5.1"
+ fresh "0.3.0"
+ merge-descriptors "1.0.1"
+ methods "~1.1.2"
+ on-finished "~2.3.0"
+ parseurl "~1.3.1"
+ path-to-regexp "0.1.7"
+ proxy-addr "~1.1.3"
+ qs "6.2.0"
+ range-parser "~1.2.0"
+ send "0.14.2"
+ serve-static "~1.11.2"
+ type-is "~1.6.14"
+ utils-merge "1.0.0"
+ vary "~1.1.0"
+
+extend@~3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.0.tgz#5a474353b9f3353ddd8176dfd37b91c83a46f1d4"
+
+extsprintf@1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.0.2.tgz#e1080e0658e300b06294990cc70e1502235fd550"
+
+finalhandler@0.5.1:
+ version "0.5.1"
+ resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-0.5.1.tgz#2c400d8d4530935bc232549c5fa385ec07de6fcd"
+ dependencies:
+ debug "~2.2.0"
+ escape-html "~1.0.3"
+ on-finished "~2.3.0"
+ statuses "~1.3.1"
+ unpipe "~1.0.0"
+
+forever-agent@~0.6.1:
+ version "0.6.1"
+ resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
+
+form-data@~1.0.0-rc4:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/form-data/-/form-data-1.0.1.tgz#ae315db9a4907fa065502304a66d7733475ee37c"
+ dependencies:
+ async "^2.0.1"
+ combined-stream "^1.0.5"
+ mime-types "^2.1.11"
+
+forwarded@~0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.0.tgz#19ef9874c4ae1c297bcf078fde63a09b66a84363"
+
+fresh@0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.3.0.tgz#651f838e22424e7566de161d8358caa199f83d4f"
+
+generate-function@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.0.0.tgz#6858fe7c0969b7d4e9093337647ac79f60dfbe74"
+
+generate-object-property@^1.1.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0"
+ dependencies:
+ is-property "^1.0.0"
+
+getpass@^0.1.1:
+ version "0.1.6"
+ resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.6.tgz#283ffd9fc1256840875311c1b60e8c40187110e6"
+ dependencies:
+ assert-plus "^1.0.0"
+
+"graceful-readlink@>= 1.0.0":
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
+
+har-validator@~2.0.6:
+ version "2.0.6"
+ resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-2.0.6.tgz#cdcbc08188265ad119b6a5a7c8ab70eecfb5d27d"
+ dependencies:
+ chalk "^1.1.1"
+ commander "^2.9.0"
+ is-my-json-valid "^2.12.4"
+ pinkie-promise "^2.0.0"
+
+has-ansi@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
+ dependencies:
+ ansi-regex "^2.0.0"
+
+hawk@~3.1.3:
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4"
+ dependencies:
+ boom "2.x.x"
+ cryptiles "2.x.x"
+ hoek "2.x.x"
+ sntp "1.x.x"
+
+hoek@2.x.x:
+ version "2.16.3"
+ resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed"
+
+http-errors@~1.5.1:
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.5.1.tgz#788c0d2c1de2c81b9e6e8c01843b6b97eb920750"
+ dependencies:
+ inherits "2.0.3"
+ setprototypeof "1.0.2"
+ statuses ">= 1.3.1 < 2"
+
+http-signature@~1.1.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf"
+ dependencies:
+ assert-plus "^0.2.0"
+ jsprim "^1.2.2"
+ sshpk "^1.7.0"
+
+iconv-lite@0.4.15:
+ version "0.4.15"
+ resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb"
+
+inherits@2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1"
+
+inherits@2.0.3, inherits@~2.0.1:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
+
+ipaddr.js@1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.2.0.tgz#8aba49c9192799585bdd643e0ccb50e8ae777ba4"
+
+is-my-json-valid@^2.12.4:
+ version "2.15.0"
+ resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.15.0.tgz#936edda3ca3c211fd98f3b2d3e08da43f7b2915b"
+ dependencies:
+ generate-function "^2.0.0"
+ generate-object-property "^1.1.0"
+ jsonpointer "^4.0.0"
+ xtend "^4.0.0"
+
+is-property@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84"
+
+is-typedarray@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
+
+isarray@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
+
+isstream@~0.1.2:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
+
+jodid25519@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/jodid25519/-/jodid25519-1.0.2.tgz#06d4912255093419477d425633606e0e90782967"
+ dependencies:
+ jsbn "~0.1.0"
+
+jsbn@~0.1.0:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
+
+json-schema@0.2.3:
+ version "0.2.3"
+ resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
+
+json-stringify-safe@~5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
+
+jsonpointer@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9"
+
+jsprim@^1.2.2:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.3.1.tgz#2a7256f70412a29ee3670aaca625994c4dcff252"
+ dependencies:
+ extsprintf "1.0.2"
+ json-schema "0.2.3"
+ verror "1.3.6"
+
+lodash@^4.14.0:
+ version "4.17.15"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
+ integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
+
+media-typer@0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
+
+merge-descriptors@1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
+
+methods@~1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
+
+mime-db@~1.26.0:
+ version "1.26.0"
+ resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.26.0.tgz#eaffcd0e4fc6935cf8134da246e2e6c35305adff"
+
+mime-types@^2.1.11, mime-types@~2.1.11, mime-types@~2.1.13, mime-types@~2.1.7:
+ version "2.1.14"
+ resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.14.tgz#f7ef7d97583fcaf3b7d282b6f8b5679dab1e94ee"
+ dependencies:
+ mime-db "~1.26.0"
+
+mime@1.3.4:
+ version "1.3.4"
+ resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53"
+
+ms@0.7.1:
+ version "0.7.1"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098"
+
+ms@0.7.2:
+ version "0.7.2"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765"
+
+ms@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
+ integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
+
+negotiator@0.6.1:
+ version "0.6.1"
+ resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
+
+node-uuid@~1.4.7:
+ version "1.4.7"
+ resolved "https://registry.yarnpkg.com/node-uuid/-/node-uuid-1.4.7.tgz#6da5a17668c4b3dd59623bda11cf7fa4c1f60a6f"
+
+oauth-sign@~0.8.1:
+ version "0.8.2"
+ resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
+
+object-assign@^4:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
+ integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
+
+on-finished@~2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
+ dependencies:
+ ee-first "1.1.1"
+
+on-headers@~1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f"
+ integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==
+
+parseurl@~1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.1.tgz#c8ab8c9223ba34888aa64a297b28853bec18da56"
+
+parseurl@~1.3.3:
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
+ integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
+
+path-to-regexp@0.1.7:
+ version "0.1.7"
+ resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
+
+path@^0.12.7:
+ version "0.12.7"
+ resolved "https://registry.yarnpkg.com/path/-/path-0.12.7.tgz#d4dc2a506c4ce2197eb481ebfcd5b36c0140b10f"
+ dependencies:
+ process "^0.11.1"
+ util "^0.10.3"
+
+pinkie-promise@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
+ dependencies:
+ pinkie "^2.0.0"
+
+pinkie@^2.0.0:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
+
+process-nextick-args@~1.0.6:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3"
+
+process@^0.11.1:
+ version "0.11.9"
+ resolved "https://registry.yarnpkg.com/process/-/process-0.11.9.tgz#7bd5ad21aa6253e7da8682264f1e11d11c0318c1"
+
+proxy-addr@~1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-1.1.3.tgz#dc97502f5722e888467b3fa2297a7b1ff47df074"
+ dependencies:
+ forwarded "~0.1.0"
+ ipaddr.js "1.2.0"
+
+punycode@^1.4.1:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
+
+pusher@^1.5.1:
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/pusher/-/pusher-1.5.1.tgz#8186cf16e5b124b51da6c83001a4c36a2ad42b44"
+ dependencies:
+ request "2.74.0"
+
+qs@6.2.0:
+ version "6.2.0"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.0.tgz#3b7848c03c2dece69a9522b0fae8c4126d745f3b"
+
+qs@6.2.1, qs@~6.2.0:
+ version "6.2.1"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.1.tgz#ce03c5ff0935bc1d9d69a9f14cbd18e568d67625"
+
+random-bytes@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b"
+ integrity sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=
+
+range-parser@~1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e"
+
+raw-body@~2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.2.0.tgz#994976cf6a5096a41162840492f0bdc5d6e7fb96"
+ dependencies:
+ bytes "2.4.0"
+ iconv-lite "0.4.15"
+ unpipe "1.0.0"
+
+readable-stream@~2.0.5:
+ version "2.0.6"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e"
+ dependencies:
+ core-util-is "~1.0.0"
+ inherits "~2.0.1"
+ isarray "~1.0.0"
+ process-nextick-args "~1.0.6"
+ string_decoder "~0.10.x"
+ util-deprecate "~1.0.1"
+
+request@2.74.0:
+ version "2.74.0"
+ resolved "https://registry.yarnpkg.com/request/-/request-2.74.0.tgz#7693ca768bbb0ea5c8ce08c084a45efa05b892ab"
+ dependencies:
+ aws-sign2 "~0.6.0"
+ aws4 "^1.2.1"
+ bl "~1.1.2"
+ caseless "~0.11.0"
+ combined-stream "~1.0.5"
+ extend "~3.0.0"
+ forever-agent "~0.6.1"
+ form-data "~1.0.0-rc4"
+ har-validator "~2.0.6"
+ hawk "~3.1.3"
+ http-signature "~1.1.0"
+ is-typedarray "~1.0.0"
+ isstream "~0.1.2"
+ json-stringify-safe "~5.0.1"
+ mime-types "~2.1.7"
+ node-uuid "~1.4.7"
+ oauth-sign "~0.8.1"
+ qs "~6.2.0"
+ stringstream "~0.0.4"
+ tough-cookie "~2.3.0"
+ tunnel-agent "~0.4.1"
+
+safe-buffer@5.2.0:
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519"
+ integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==
+
+send@0.14.2:
+ version "0.14.2"
+ resolved "https://registry.yarnpkg.com/send/-/send-0.14.2.tgz#39b0438b3f510be5dc6f667a11f71689368cdeef"
+ dependencies:
+ debug "~2.2.0"
+ depd "~1.1.0"
+ destroy "~1.0.4"
+ encodeurl "~1.0.1"
+ escape-html "~1.0.3"
+ etag "~1.7.0"
+ fresh "0.3.0"
+ http-errors "~1.5.1"
+ mime "1.3.4"
+ ms "0.7.2"
+ on-finished "~2.3.0"
+ range-parser "~1.2.0"
+ statuses "~1.3.1"
+
+serve-static@~1.11.2:
+ version "1.11.2"
+ resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.11.2.tgz#2cf9889bd4435a320cc36895c9aa57bd662e6ac7"
+ dependencies:
+ encodeurl "~1.0.1"
+ escape-html "~1.0.3"
+ parseurl "~1.3.1"
+ send "0.14.2"
+
+setprototypeof@1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.2.tgz#81a552141ec104b88e89ce383103ad5c66564d08"
+
+sntp@1.x.x:
+ version "1.0.9"
+ resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198"
+ dependencies:
+ hoek "2.x.x"
+
+sshpk@^1.7.0:
+ version "1.10.2"
+ resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.10.2.tgz#d5a804ce22695515638e798dbe23273de070a5fa"
+ dependencies:
+ asn1 "~0.2.3"
+ assert-plus "^1.0.0"
+ dashdash "^1.12.0"
+ getpass "^0.1.1"
+ optionalDependencies:
+ bcrypt-pbkdf "^1.0.0"
+ ecc-jsbn "~0.1.1"
+ jodid25519 "^1.0.0"
+ jsbn "~0.1.0"
+ tweetnacl "~0.14.0"
+
+"statuses@>= 1.3.1 < 2", statuses@~1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e"
+
+string_decoder@~0.10.x:
+ version "0.10.31"
+ resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
+
+stringstream@~0.0.4:
+ version "0.0.5"
+ resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878"
+
+strip-ansi@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
+ dependencies:
+ ansi-regex "^2.0.0"
+
+supports-color@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
+
+tough-cookie@~2.3.0:
+ version "2.3.2"
+ resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.2.tgz#f081f76e4c85720e6c37a5faced737150d84072a"
+ dependencies:
+ punycode "^1.4.1"
+
+tunnel-agent@~0.4.1:
+ version "0.4.3"
+ resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb"
+
+tweetnacl@^0.14.3, tweetnacl@~0.14.0:
+ version "0.14.5"
+ resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
+
+type-is@~1.6.14:
+ version "1.6.14"
+ resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.14.tgz#e219639c17ded1ca0789092dd54a03826b817cb2"
+ dependencies:
+ media-typer "0.3.0"
+ mime-types "~2.1.13"
+
+uid-safe@~2.1.5:
+ version "2.1.5"
+ resolved "https://registry.yarnpkg.com/uid-safe/-/uid-safe-2.1.5.tgz#2b3d5c7240e8fc2e58f8aa269e5ee49c0857bd3a"
+ integrity sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==
+ dependencies:
+ random-bytes "~1.0.0"
+
+unpipe@1.0.0, unpipe@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
+
+util-deprecate@~1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
+
+util@^0.10.3:
+ version "0.10.3"
+ resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9"
+ dependencies:
+ inherits "2.0.1"
+
+utils-merge@1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.0.tgz#0294fb922bb9375153541c4f7096231f287c8af8"
+
+vary@^1:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
+ integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
+
+vary@~1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.0.tgz#e1e5affbbd16ae768dd2674394b9ad3022653140"
+
+verror@1.3.6:
+ version "1.3.6"
+ resolved "https://registry.yarnpkg.com/verror/-/verror-1.3.6.tgz#cff5df12946d297d2baaefaa2689e25be01c005c"
+ dependencies:
+ extsprintf "1.0.2"
+
+xtend@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
diff --git a/run.sh b/run.sh
new file mode 100644
index 00000000..cf89d87c
--- /dev/null
+++ b/run.sh
@@ -0,0 +1,2 @@
+env > /opt/www/static/env.txt
+http-server /opt/www -p 8080
diff --git a/server.js b/server.js
new file mode 100644
index 00000000..9cee0baf
--- /dev/null
+++ b/server.js
@@ -0,0 +1,34 @@
+require('dotenv').config();
+const express = require('express');
+const bodyParser = require('body-parser');
+const Pusher = require('pusher');
+
+const app = express();
+const port = process.env.PORT || 4000;
+const pusher = new Pusher({
+ appId: process.env.PUSHER_APP_ID,
+ key: process.env.PUSHER_KEY,
+ secret: process.env.PUSHER_SECRET,
+ cluster: 'ap3',
+});
+
+app.use(bodyParser.json());
+app.use(bodyParser.urlencoded({extended: false}));
+app.use((req, res, next) => {
+ res.header('Access-Control-Allow-Origin', '*');
+ res.header(
+ 'Access-Control-Allow-Headers',
+ 'Origin, X-Requested-With, Content-Type, Accept'
+ );
+ next();
+});
+
+
+app.listen(port, () => {
+ console.log(`Server started on port ${port}`);
+});
+
+app.post('/paint', (req, res) => {
+ pusher.trigger('painting', 'draw', req.body);
+ res.json(req.body);
+});
diff --git a/src/App.vue b/src/App.vue
new file mode 100644
index 00000000..2c6104b4
--- /dev/null
+++ b/src/App.vue
@@ -0,0 +1,348 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ item.icon }}
+
+
+
+ {{ item.text }}
+
+
+
+
+
+
+
+
+
+
+
+ by uEngine
+
+
+
+
+ info
+
+
+ Logout
+
+
+ Login
+
+
+
+
+
+ settings
+
+
+
+
+
+
+
+
+
+
+
+<<<<<<< HEAD
+ >>>>>> 30502b93b64cce4918c9208ba91b7a7c48a35b56
+ >
+
+ mdi-close
+
+
+
+
+
+
+
+ How to use EventStorming-tool?
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ close
+
+ Settings
+
+
+ Save
+
+
+
+ Connection Setting
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Click Setting & Insert Infomation
+
+ Close
+
+
+
+
+
+
+
+
diff --git a/src/assets/event.png b/src/assets/event.png
new file mode 100644
index 00000000..8ce40ebd
Binary files /dev/null and b/src/assets/event.png differ
diff --git a/src/assets/logo.png b/src/assets/logo.png
new file mode 100644
index 00000000..f3d2503f
Binary files /dev/null and b/src/assets/logo.png differ
diff --git a/src/assets/logo.svg b/src/assets/logo.svg
new file mode 100644
index 00000000..145b6d13
--- /dev/null
+++ b/src/assets/logo.svg
@@ -0,0 +1 @@
+
diff --git a/src/components/DashBoard.vue b/src/components/DashBoard.vue
new file mode 100644
index 00000000..52391f7b
--- /dev/null
+++ b/src/components/DashBoard.vue
@@ -0,0 +1,1036 @@
+
+
+
+
+
+ pods
+
+
+ deployment
+
+
+ service
+
+
+ add
+
+
+
+
+
+
+
+ ADD
+
+
+
+
+
+
+
+
+
+ |
+ {{ props.item.name }}
+ |
+ {{ props.item.namespace }} |
+
+ {{ props.item.status }} |
+
+ {{ props.item.createTimeStamp }}
+ |
+
+
+ {{ props.item.statusReadyReplicas }}
+ |
+
+ {{ props.item.statusReplicas }}
+ |
+
+ {{ props.item.statusUpdateReplicas }}
+ |
+
+ {{ props.item.statusAvailableReplicas }}
+ |
+
+ {{ props.item.createTimeStamp }}
+ |
+
+
+ {{ props.item.specType }}
+ |
+
+ {{ props.item.specClusterIp }}
+ |
+
+ {{ props.item.ingressIp }}
+ |
+
+ {{ props.item.hostname }}
+ |
+
+
+ |
+
+
+ {{ portData.port }}:{{ portData.nodePort }}/{{ portData.protocol }}
+
+ |
+
+
+
+
+ |
+
+ {{ props.item.createTimeStamp }}
+ |
+
+
+ show_chart
+
+
+ edit
+
+
+ delete
+
+ |
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+ close
+
+
+ {{ selectedRow.item.name }} LOG
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ items.item.dateTime
+ }}
+ |
+ {{ items.item.status }}
+ |
+ {{ items.item.message }} |
+
+
+ {{ items.item.dateTime
+ }}
+ |
+ {{ items.item.status }}
+ |
+ {{ items.item.message }} |
+
+
+ {{ items.item.dateTime
+ }}
+ |
+ {{ items.item.status }}
+ |
+ {{ items.item.message }} |
+
+
+
+
+ Sorry, nothing to display here :(
+
+
+
+
+
+
+
+
+
+
+
+
+ close
+
+
+ {{ selectedRow.item.name }} Log
+
+
+
+
+
+
+
+ {{ keyitem }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{
+ items.item.dateTime }}
+ |
+ {{
+ items.item.status }}
+ |
+ {{ items.item.message }} |
+
+
+ {{
+ items.item.dateTime }}
+ |
+ {{
+ items.item.status }}
+ |
+ {{ items.item.message }} |
+
+
+ {{
+ items.item.dateTime }}
+ |
+ {{
+ items.item.status }}
+ |
+ {{ items.item.message }} |
+
+
+
+
+ Sorry, nothing to display here :(
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{types.toUpperCase()}} Editor
+
+
+
+
+
+ clear
+
+
+
+
+
+
+
+
+
+
+
+
+
삭제 안내
+
{{ deleteName }}를 삭제하시겠습니까?
+
+
+
+
+ Close
+ Confirm
+
+
+
+
+
+ {{ snackbar.text }}
+
+ Close
+
+
+
+
+
+
+
+
+
diff --git a/src/components/EventStormingList/EventStormingListPage.vue b/src/components/EventStormingList/EventStormingListPage.vue
new file mode 100644
index 00000000..7198a492
--- /dev/null
+++ b/src/components/EventStormingList/EventStormingListPage.vue
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+
+ {{item.name}}
+
+
+ Edit
+
+
+ Explore
+
+
+
+
+
+
+ 추가
+
+
+
+
+
+
+
+ 추가
+
+
+
+
+
+
+
+
+
diff --git a/src/components/EventStormingList/Introduce.vue b/src/components/EventStormingList/Introduce.vue
new file mode 100644
index 00000000..29977513
--- /dev/null
+++ b/src/components/EventStormingList/Introduce.vue
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
diff --git a/src/components/HelloWorld.vue b/src/components/HelloWorld.vue
new file mode 100644
index 00000000..7dca9d2f
--- /dev/null
+++ b/src/components/HelloWorld.vue
@@ -0,0 +1,142 @@
+
+
+
+
+
+
+
+
+
+ Welcome to Vuetify
+
+
+ For help and collaboration with other Vuetify developers,
+
please join our online
+ Discord Community
+
+
+
+
+ What's next?
+
+
+
+ {{ next.text }}
+
+
+
+
+
+ Important Links
+
+
+
+ {{ link.text }}
+
+
+
+
+
+ Ecosystem
+
+
+
+ {{ eco.text }}
+
+
+
+
+
+
+
+
diff --git a/src/components/designer/CodeViewer.vue b/src/components/designer/CodeViewer.vue
new file mode 100644
index 00000000..697ec528
--- /dev/null
+++ b/src/components/designer/CodeViewer.vue
@@ -0,0 +1,181 @@
+
+
+
+
+
+ {{value[0][0].name}}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/designer/DefinitionList.vue b/src/components/designer/DefinitionList.vue
new file mode 100755
index 00000000..99253e03
--- /dev/null
+++ b/src/components/designer/DefinitionList.vue
@@ -0,0 +1,725 @@
+
+
+
+
+ restore
+
+
+ Version Manager
+
+
+
+
+
+
+ Close
+
+
+
+
+
+
+ {{selectedCard.name}}을 삭제하시겠습니까?
+ 삭제하시겠습니까?
+
+
+
+ 예
+
+ 아니요
+
+
+
+
+
+
+
+
+ add
+ add
+
+
+
+ New Process
+ label
+
+
+ New Class
+ device_hub
+
+
+ New Practice
+ slideshow
+
+
+ New Folder
+ folder
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t("message['title.desinger.package']") }}
+
+
+
+
+
+
+
+
+
+ folder
+ {{ item.name }}
+
+
+
+
+
+ {{ $t("message['button.rename']") }}
+ edit
+
+
+ {{ $t("message['button.move']") }}
+ folder_open
+
+
+ {{ $t("message['button.delete']") }}
+ delete
+
+
+
+
+
+
+
+
+
+
+
+
{{ $t("message['title.desinger.process']") }}
+
+
+
+
+
+
+
+
+
+
+ {{card.name.split(".")[0]}}
+
+
+
+ {{card.desc}}
+
+
+
+
+ {{ $t("message['button.activate']") }}
+ {{ $t("message['button.edit']") }}
+
+
+
+ {{ $t("message['button.move']") }}
+
+ {{
+ $t("message['button.delete']") }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t("message['title.desinger.package.move']") }}
+
+
+
+
+
+
+
+
+
+
+
+
+ Home
+
+ {{currentPath.substring(currentPath.lastIndexOf("/")+1, currentPath.length)}}
+
+
+
+
+
+ {{package.name}}
+
+
+
+
+
+ {{ $t("message['button.move']")
+ }}
+
+ {{ $t("message['button.close']") }}
+
+
+
+
+
+
+ {{ $t("message['title.desinger.new.package']") }}
+
+
+
+
+
+
+
+ {{
+ $t("message['button.create']") }}
+
+ {{ $t("message['button.close']") }}
+
+
+
+
+
+
+ {{ $t("message['title.desinger.rename.package']") }}
+
+
+
+
+
+
+
+ {{
+ $t("message['button.rename']") }}
+
+ {{ $t("message['button.close']") }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/designer/ModelerImageGenerator.vue b/src/components/designer/ModelerImageGenerator.vue
new file mode 100755
index 00000000..cf858267
--- /dev/null
+++ b/src/components/designer/ModelerImageGenerator.vue
@@ -0,0 +1,76 @@
+
+
+
+
+
diff --git a/src/components/designer/ModelerRouter.vue b/src/components/designer/ModelerRouter.vue
new file mode 100755
index 00000000..e1135c65
--- /dev/null
+++ b/src/components/designer/ModelerRouter.vue
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
diff --git a/src/components/designer/class-modeling/ClassModeler.vue b/src/components/designer/class-modeling/ClassModeler.vue
new file mode 100644
index 00000000..d9e72d68
--- /dev/null
+++ b/src/components/designer/class-modeling/ClassModeler.vue
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/components/designer/class-modeling/EventModeler.vue b/src/components/designer/class-modeling/EventModeler.vue
new file mode 100644
index 00000000..bd524a52
--- /dev/null
+++ b/src/components/designer/class-modeling/EventModeler.vue
@@ -0,0 +1,105 @@
+
+
+
+
+
+
+
+
diff --git a/src/components/designer/class-modeling/UMLelements/classDefinition b/src/components/designer/class-modeling/UMLelements/classDefinition
new file mode 100644
index 00000000..e69de29b
diff --git a/src/components/designer/class-modeling/elements/Actor.vue b/src/components/designer/class-modeling/elements/Actor.vue
new file mode 100755
index 00000000..f0d19719
--- /dev/null
+++ b/src/components/designer/class-modeling/elements/Actor.vue
@@ -0,0 +1,175 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/designer/class-modeling/elements/AggregateDefinition.vue b/src/components/designer/class-modeling/elements/AggregateDefinition.vue
new file mode 100755
index 00000000..bec16794
--- /dev/null
+++ b/src/components/designer/class-modeling/elements/AggregateDefinition.vue
@@ -0,0 +1,290 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/designer/class-modeling/elements/BoundedContext.vue b/src/components/designer/class-modeling/elements/BoundedContext.vue
new file mode 100755
index 00000000..8f6377fe
--- /dev/null
+++ b/src/components/designer/class-modeling/elements/BoundedContext.vue
@@ -0,0 +1,149 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/designer/class-modeling/elements/ClassDefinition.vue b/src/components/designer/class-modeling/elements/ClassDefinition.vue
new file mode 100755
index 00000000..390119ca
--- /dev/null
+++ b/src/components/designer/class-modeling/elements/ClassDefinition.vue
@@ -0,0 +1,279 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Reference from other model (Bounded Context)
+
+
+
+
+
+
+
+ pk
+
+
+
+
+
+
+
+
+
+
+
+ String
+ Long
+ Double
+ Date
+ Boolean
+
+
+ delete
+
+
+
+
+
+ ADD ATTRIBUTE
+
+
+
+ Update class info
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/designer/class-modeling/elements/ClassRelation.vue b/src/components/designer/class-modeling/elements/ClassRelation.vue
new file mode 100755
index 00000000..6edba2e4
--- /dev/null
+++ b/src/components/designer/class-modeling/elements/ClassRelation.vue
@@ -0,0 +1,130 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/designer/class-modeling/elements/CommandDefinition.vue b/src/components/designer/class-modeling/elements/CommandDefinition.vue
new file mode 100755
index 00000000..7d90e166
--- /dev/null
+++ b/src/components/designer/class-modeling/elements/CommandDefinition.vue
@@ -0,0 +1,165 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/designer/class-modeling/elements/DomainEventDefinition.vue b/src/components/designer/class-modeling/elements/DomainEventDefinition.vue
new file mode 100644
index 00000000..84df71ff
--- /dev/null
+++ b/src/components/designer/class-modeling/elements/DomainEventDefinition.vue
@@ -0,0 +1,235 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/designer/class-modeling/elements/ExternalDefinition.vue b/src/components/designer/class-modeling/elements/ExternalDefinition.vue
new file mode 100755
index 00000000..d70554cc
--- /dev/null
+++ b/src/components/designer/class-modeling/elements/ExternalDefinition.vue
@@ -0,0 +1,129 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/designer/class-modeling/elements/PolicyDefinition.vue b/src/components/designer/class-modeling/elements/PolicyDefinition.vue
new file mode 100755
index 00000000..30c4cfae
--- /dev/null
+++ b/src/components/designer/class-modeling/elements/PolicyDefinition.vue
@@ -0,0 +1,188 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/designer/class-modeling/elements/ViewDefinition.vue b/src/components/designer/class-modeling/elements/ViewDefinition.vue
new file mode 100755
index 00000000..a6a64f6f
--- /dev/null
+++ b/src/components/designer/class-modeling/elements/ViewDefinition.vue
@@ -0,0 +1,142 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/designer/class-modeling/index.js b/src/components/designer/class-modeling/index.js
new file mode 100755
index 00000000..60ea016f
--- /dev/null
+++ b/src/components/designer/class-modeling/index.js
@@ -0,0 +1,32 @@
+const ClassModeling = {
+ install (Vue, opts = {}) {
+
+ const files = require.context('.', true, /\.vue$/);
+ const components = {}
+ files.keys().forEach((key) => {
+ if (key === './index.js') {
+ return;
+ }
+ components[key.replace(/(\.\/|\.vue)/g, '')] = files(key);
+ });
+
+ if(Vue._components==null) Vue._components = {};
+
+ for (var key in components) {
+ Vue.component(components[key].default.name, components[key].default);
+ Vue._components[components[key].default.name] = components[key].default;
+ }
+
+ //bpmn 컴포넌트 검색용
+ Vue.classModelingComponents = components;
+
+ //윈도우 전역변수 등록 (다른 인스톨 플러그인에서 거진 하긴 해주지만 혹시 모르니...)
+ if (window && !window.Vue) {
+ window.Vue = Vue;
+ }
+ }
+}
+
+export default ClassModeling
+
+
diff --git a/src/components/designer/modeling/Element.vue b/src/components/designer/modeling/Element.vue
new file mode 100755
index 00000000..8c344689
--- /dev/null
+++ b/src/components/designer/modeling/Element.vue
@@ -0,0 +1,245 @@
+
+
+
+
+
+
+
+
diff --git a/src/components/designer/modeling/ModelingDesigner.vue b/src/components/designer/modeling/ModelingDesigner.vue
new file mode 100755
index 00000000..f4de6d08
--- /dev/null
+++ b/src/components/designer/modeling/ModelingDesigner.vue
@@ -0,0 +1,2015 @@
+
+
+
+
+
+
+
+
+
+
+ Code Preview
+
+
+
+
+
+
+
+
+ {{ open ? 'mdi-folder-open' : 'mdi-folder' }}
+
+
+ {{ files[item.file] }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ code
+ Preview
+
+ Download Archive
+
+
+
+
+
+ SAVE
+
+
+
+
+
+
+ Hands
+
+
+
+
+
+
+
+ {{item.label}}
+
+
+
+
+
+
+ {{ text }}
+
+ Close
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/designer/modeling/ModelingPropertyPanel.vue b/src/components/designer/modeling/ModelingPropertyPanel.vue
new file mode 100755
index 00000000..d5210f9b
--- /dev/null
+++ b/src/components/designer/modeling/ModelingPropertyPanel.vue
@@ -0,0 +1,637 @@
+
+
+
+
+
+
+
+
+
+ delete
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ADD Attribute
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ titleName }}
+
+
+
+
+
+
+
+
+
+ Type
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{titleName}} Name
+
+
+
+
+ 추천 단어 : {{ translateText }}
+
+
+ 선택시 변경 됩니다.
+
+
+
+
+ Attributes
+
+
+
+
+
+
+ delete
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ADD ATTRIBUTE
+
+
+
+
+
+
+
+
+
+
+
+ Associated Aggregate
+
+
+
+
+
+
+
+
+ Edit Domain Model by UML
+
+
+ Trigger
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/designer/modeling/ModelingRelation.vue b/src/components/designer/modeling/ModelingRelation.vue
new file mode 100755
index 00000000..81191ba8
--- /dev/null
+++ b/src/components/designer/modeling/ModelingRelation.vue
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/designer/modeling/UMLDesigner.vue b/src/components/designer/modeling/UMLDesigner.vue
new file mode 100755
index 00000000..736010a5
--- /dev/null
+++ b/src/components/designer/modeling/UMLDesigner.vue
@@ -0,0 +1,937 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SAVE
+
+
+
+
+
+
+
+ Hands
+
+
+
+
+
+
+
+ {{item.label}}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/designer/modeling/index.js b/src/components/designer/modeling/index.js
new file mode 100755
index 00000000..e1fffca9
--- /dev/null
+++ b/src/components/designer/modeling/index.js
@@ -0,0 +1,30 @@
+const Modeling = {
+ install (Vue, opts = {}) {
+
+ const files = require.context('.', true, /\.vue$/);
+ const ModelingComponents = {}
+ files.keys().forEach((key) => {
+ if (key === './index.js') {
+ return;
+ }
+ ModelingComponents[key.replace(/(\.\/|\.vue)/g, '')] = files(key);
+ });
+
+ if(Vue._components==null) Vue._components = {};
+
+ for (var key in ModelingComponents) {
+ Vue.component(ModelingComponents[key].default.name, ModelingComponents[key].default);
+ // Vue._components[ModelingComponents[key].default.name] = ModelingComponents[key].default;
+ }
+
+ Vue.modelingComponents = ModelingComponents;
+
+ if (window && !window.Vue) {
+ window.Vue = Vue;
+ }
+ }
+}
+
+export default Modeling
+
+
diff --git a/src/components/designer/process/CustomizedProcessDesigner.vue b/src/components/designer/process/CustomizedProcessDesigner.vue
new file mode 100755
index 00000000..2fe489bf
--- /dev/null
+++ b/src/components/designer/process/CustomizedProcessDesigner.vue
@@ -0,0 +1,267 @@
+
+
+
+
+
+
+
+ Customized.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{item.label}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ save
+
+
+
+
+
+ Locale
+ English
+ Korean
+
+ Process Variable
+
+ Defintion Settings
+
+
+
+
+ 담당자 변경
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/designer/process/ProcessDesigner.vue b/src/components/designer/process/ProcessDesigner.vue
new file mode 100755
index 00000000..a27016d1
--- /dev/null
+++ b/src/components/designer/process/ProcessDesigner.vue
@@ -0,0 +1,1053 @@
+
+
+
+
+
+ Mark Production (rev
+ {{id.substring(id.indexOf('@') + 1)}})
+
+
+ this is production (rev. {{id.substring(id.indexOf('@') + 1)}})
+
+
+
+ latest version
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Drag On/Off
+
+
+ {{item.label}}
+
+
+
+
+ Undo
+
+
+ Redo
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ rev. {{version.ver}}
+ production
+
+
+
+
+
+
+
+
+
+ settings
+ Settings
+
+
+
+
+
+
+ sort_by_alpha
+ Vars
+
+
+
+
+
+
+
+ Korean
+ English
+
+
+
+
+
+
+
+ play_arrow
+
+
+ save
+ history
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Variables
+
+
+ Role Mappings
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/edityamlpage.vue b/src/components/edityamlpage.vue
new file mode 100644
index 00000000..f2d6399a
--- /dev/null
+++ b/src/components/edityamlpage.vue
@@ -0,0 +1,678 @@
+
+
+
+
+
+
+
+
+
+
+
+ LocalFile
+
+
+
+
+
+ Download
+
+
+ Deploy
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/opengraph/Opengraph.vue b/src/components/opengraph/Opengraph.vue
new file mode 100755
index 00000000..7c4354ee
--- /dev/null
+++ b/src/components/opengraph/Opengraph.vue
@@ -0,0 +1,1290 @@
+
+
+
+
+
+
+
diff --git a/src/components/opengraph/geometry/BezierCurve.vue b/src/components/opengraph/geometry/BezierCurve.vue
new file mode 100755
index 00000000..b6f92b94
--- /dev/null
+++ b/src/components/opengraph/geometry/BezierCurve.vue
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/components/opengraph/geometry/Circle.vue b/src/components/opengraph/geometry/Circle.vue
new file mode 100755
index 00000000..aed50265
--- /dev/null
+++ b/src/components/opengraph/geometry/Circle.vue
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/components/opengraph/geometry/Curve.vue b/src/components/opengraph/geometry/Curve.vue
new file mode 100755
index 00000000..9a98b7eb
--- /dev/null
+++ b/src/components/opengraph/geometry/Curve.vue
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/components/opengraph/geometry/Ellipse.vue b/src/components/opengraph/geometry/Ellipse.vue
new file mode 100755
index 00000000..f56f3f44
--- /dev/null
+++ b/src/components/opengraph/geometry/Ellipse.vue
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/components/opengraph/geometry/Geometry.vue b/src/components/opengraph/geometry/Geometry.vue
new file mode 100755
index 00000000..f347a55d
--- /dev/null
+++ b/src/components/opengraph/geometry/Geometry.vue
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/components/opengraph/geometry/Line.vue b/src/components/opengraph/geometry/Line.vue
new file mode 100755
index 00000000..14cb036e
--- /dev/null
+++ b/src/components/opengraph/geometry/Line.vue
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/components/opengraph/geometry/Point.vue b/src/components/opengraph/geometry/Point.vue
new file mode 100755
index 00000000..59dd9268
--- /dev/null
+++ b/src/components/opengraph/geometry/Point.vue
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/components/opengraph/geometry/PolyLine.vue b/src/components/opengraph/geometry/PolyLine.vue
new file mode 100755
index 00000000..d78b2cf1
--- /dev/null
+++ b/src/components/opengraph/geometry/PolyLine.vue
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/components/opengraph/geometry/Polygon.vue b/src/components/opengraph/geometry/Polygon.vue
new file mode 100755
index 00000000..f759da58
--- /dev/null
+++ b/src/components/opengraph/geometry/Polygon.vue
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/components/opengraph/geometry/Rectangle.vue b/src/components/opengraph/geometry/Rectangle.vue
new file mode 100755
index 00000000..fbd1a206
--- /dev/null
+++ b/src/components/opengraph/geometry/Rectangle.vue
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/components/opengraph/index.js b/src/components/opengraph/index.js
new file mode 100755
index 00000000..d669fa54
--- /dev/null
+++ b/src/components/opengraph/index.js
@@ -0,0 +1,35 @@
+const Opengraph = {
+ install (Vue, opts = {}) {
+
+ const files = require.context('.', true, /\.vue$/);
+ const OGComponents = {}
+ files.keys().forEach((key) => {
+ if (key === './index.js') {
+ return;
+ }
+ OGComponents[key.replace(/(\.\/|\.vue)/g, '')] = files(key);
+ });
+ for (var key in OGComponents) {
+ Vue.component(OGComponents[key].default.name, OGComponents[key].default);
+ console.log("installed : " + OGComponents[key].default.name)
+ }
+
+ //opengraph 컴포넌트 검색용
+ Vue.OGComponents = OGComponents;
+
+ //opengraph 용 이벤트 버스
+ Vue.OGBus = new Vue();
+
+ //opengraph 활성 컴포넌트 저장소
+ Vue.OGLiveComponents = {};
+
+ //윈도우 전역변수 등록 (다른 인스톨 플러그인에서 거진 하긴 해주지만 혹시 모르니...)
+ if (window && !window.Vue) {
+ window.Vue = Vue;
+ }
+ }
+}
+
+export default Opengraph
+
+
diff --git a/src/components/opengraph/shape/CircleElement.vue b/src/components/opengraph/shape/CircleElement.vue
new file mode 100755
index 00000000..7cd648ae
--- /dev/null
+++ b/src/components/opengraph/shape/CircleElement.vue
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/opengraph/shape/EdgeElement.vue b/src/components/opengraph/shape/EdgeElement.vue
new file mode 100755
index 00000000..94a4beb3
--- /dev/null
+++ b/src/components/opengraph/shape/EdgeElement.vue
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/opengraph/shape/EllipseElement.vue b/src/components/opengraph/shape/EllipseElement.vue
new file mode 100755
index 00000000..9b16c19e
--- /dev/null
+++ b/src/components/opengraph/shape/EllipseElement.vue
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/opengraph/shape/GeometryElement.vue b/src/components/opengraph/shape/GeometryElement.vue
new file mode 100755
index 00000000..7d7f770c
--- /dev/null
+++ b/src/components/opengraph/shape/GeometryElement.vue
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/opengraph/shape/GroupElement.vue b/src/components/opengraph/shape/GroupElement.vue
new file mode 100755
index 00000000..b95a95cb
--- /dev/null
+++ b/src/components/opengraph/shape/GroupElement.vue
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/opengraph/shape/HorizontalLaneElement.vue b/src/components/opengraph/shape/HorizontalLaneElement.vue
new file mode 100755
index 00000000..59e388d3
--- /dev/null
+++ b/src/components/opengraph/shape/HorizontalLaneElement.vue
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/opengraph/shape/HorizontalPoolElement.vue b/src/components/opengraph/shape/HorizontalPoolElement.vue
new file mode 100755
index 00000000..b323cea0
--- /dev/null
+++ b/src/components/opengraph/shape/HorizontalPoolElement.vue
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/opengraph/shape/HtmlElement.vue b/src/components/opengraph/shape/HtmlElement.vue
new file mode 100755
index 00000000..c9cae148
--- /dev/null
+++ b/src/components/opengraph/shape/HtmlElement.vue
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/opengraph/shape/ImageElement.vue b/src/components/opengraph/shape/ImageElement.vue
new file mode 100755
index 00000000..3b7254b1
--- /dev/null
+++ b/src/components/opengraph/shape/ImageElement.vue
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/opengraph/shape/OpengraphElement.vue b/src/components/opengraph/shape/OpengraphElement.vue
new file mode 100755
index 00000000..f31c1a50
--- /dev/null
+++ b/src/components/opengraph/shape/OpengraphElement.vue
@@ -0,0 +1,1050 @@
+
+
+
+
+
+
+
+
diff --git a/src/components/opengraph/shape/RectangleElement.vue b/src/components/opengraph/shape/RectangleElement.vue
new file mode 100755
index 00000000..3633c806
--- /dev/null
+++ b/src/components/opengraph/shape/RectangleElement.vue
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/opengraph/shape/SubController.vue b/src/components/opengraph/shape/SubController.vue
new file mode 100755
index 00000000..db6b0633
--- /dev/null
+++ b/src/components/opengraph/shape/SubController.vue
@@ -0,0 +1,108 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/opengraph/shape/SubElements.vue b/src/components/opengraph/shape/SubElements.vue
new file mode 100755
index 00000000..81a8b0dd
--- /dev/null
+++ b/src/components/opengraph/shape/SubElements.vue
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/opengraph/shape/SvgElement.vue b/src/components/opengraph/shape/SvgElement.vue
new file mode 100755
index 00000000..67f0b54d
--- /dev/null
+++ b/src/components/opengraph/shape/SvgElement.vue
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/opengraph/shape/TextElement.vue b/src/components/opengraph/shape/TextElement.vue
new file mode 100755
index 00000000..87cc1e95
--- /dev/null
+++ b/src/components/opengraph/shape/TextElement.vue
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/opengraph/shape/VerticalLaneElement.vue b/src/components/opengraph/shape/VerticalLaneElement.vue
new file mode 100755
index 00000000..398f9f6e
--- /dev/null
+++ b/src/components/opengraph/shape/VerticalLaneElement.vue
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/opengraph/shape/VerticalPoolElement.vue b/src/components/opengraph/shape/VerticalPoolElement.vue
new file mode 100755
index 00000000..121dd736
--- /dev/null
+++ b/src/components/opengraph/shape/VerticalPoolElement.vue
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/yaml.vue b/src/components/yaml.vue
new file mode 100644
index 00000000..420e99ed
--- /dev/null
+++ b/src/components/yaml.vue
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main.js b/src/main.js
new file mode 100644
index 00000000..6f28060d
--- /dev/null
+++ b/src/main.js
@@ -0,0 +1,59 @@
+import Vue from 'vue'
+import './plugins/vuetify'
+import 'vuetify/dist/vuetify.min.css'
+import App from './App.vue'
+import router from './router'
+import store from './store'
+import './registerServiceWorker'
+import VueJWT from 'vuejs-jwt'
+import VModal from 'vue-js-modal'
+import EditYaml from './components/edityamlpage.vue'
+import textReader from './components/yaml.vue'
+import Opengraph from './components/opengraph'
+import ClassModeling from './components/designer/class-modeling'
+import Modeling from './components/designer/modeling'
+import Mustache from 'mustache'
+import CodeMirror from 'vue-codemirror'
+import VueYouTubeEmbed from 'vue-youtube-embed'
+
+Vue.use(Mustache)
+Vue.use(CodeMirror)
+Vue.use(Opengraph);
+Vue.use(ClassModeling);
+Vue.use(Modeling);
+// import Metaworks4 from '../node_modules/metaworks4'
+import vuetify from './plugins/vuetify';
+//
+// Vue.use(Metaworks4);
+
+Vue.use(VModal)
+Vue.component('EditYaml', EditYaml)
+Vue.component('text-reader', textReader)
+
+var options = {'keyName' : 'accessToken'};
+
+Vue.use(VueJWT, options)
+Vue.use(VueYouTubeEmbed, { global: true, componentId: "youtube-media" })
+process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
+
+Vue.prototype.$EventBus = new Vue()
+Vue.prototype.$ModelingBus = new Vue()
+window.$Mustache = Mustache
+
+if( process.env.NODE_ENV == "development" ){
+ window.API_HOST = "localhost:8080";
+}else{
+ window.API_HOST = process.env.VUE_APP_API_HOST
+}
+
+// const TRANSLATE_KEY =`${process.env.VUE_APP_TRANSLATE_KEY}`
+// console.log(TRANSLATE_KEY)
+
+Vue.config.productionTip = false
+
+new Vue({
+ router,
+ store,
+ vuetify,
+ render: function (h) { return h(App) }
+}).$mount('#app')
diff --git a/src/plugins/vuetify.js b/src/plugins/vuetify.js
new file mode 100644
index 00000000..2a4bab2b
--- /dev/null
+++ b/src/plugins/vuetify.js
@@ -0,0 +1,10 @@
+import Vue from 'vue';
+import Vuetify from 'vuetify/lib';
+
+Vue.use(Vuetify);
+
+export default new Vuetify({
+ icons: {
+ iconfont: 'mdi',
+ },
+});
diff --git a/src/registerServiceWorker.js b/src/registerServiceWorker.js
new file mode 100644
index 00000000..76cede07
--- /dev/null
+++ b/src/registerServiceWorker.js
@@ -0,0 +1,32 @@
+/* eslint-disable no-console */
+
+import { register } from 'register-service-worker'
+
+if (process.env.NODE_ENV === 'production') {
+ register(`${process.env.BASE_URL}service-worker.js`, {
+ ready () {
+ console.log(
+ 'App is being served from cache by a service worker.\n' +
+ 'For more details, visit https://goo.gl/AFskqB'
+ )
+ },
+ registered () {
+ console.log('Service worker has been registered.')
+ },
+ cached () {
+ console.log('Content has been cached for offline use.')
+ },
+ updatefound () {
+ console.log('New content is downloading.')
+ },
+ updated () {
+ console.log('New content is available; please refresh.')
+ },
+ offline () {
+ console.log('No internet connection found. App is running in offline mode.')
+ },
+ error (error) {
+ console.error('Error during service worker registration:', error)
+ }
+ })
+}
diff --git a/src/router.js b/src/router.js
new file mode 100644
index 00000000..328bbf23
--- /dev/null
+++ b/src/router.js
@@ -0,0 +1,53 @@
+import Vue from 'vue'
+import Router from 'vue-router'
+// import Dashboard from './views/dashboardpage.vue'
+import EventStormingListPage from './components/EventStormingList/EventStormingListPage.vue'
+import Introduce from './components/EventStormingList/Introduce.vue'
+
+
+
+Vue.use(Router)
+
+import ModelerRouter from './components/designer/ModelerRouter'
+import ProcessDesigner from './components/designer/process/ProcessDesigner'
+import EventModeler from './components/designer/class-modeling/EventModeler'
+import ClassModeler from './components/designer/class-modeling/ClassModeler'
+import ModelerImageGenerator from './components/designer/ModelerImageGenerator'
+
+Vue.component('modeler-router', ModelerRouter);
+Vue.component('modeler-image-generator', ModelerImageGenerator);
+Vue.component('process-designer', ProcessDesigner);
+Vue.component('event-modeler', EventModeler)
+Vue.component('class-modeler', ClassModeler)
+
+
+export default new Router({
+ base: process.env.BASE_URL,
+ routes: [
+ // {
+ // path: '/',
+ // name: 'Introduce',
+ // component: Introduce
+ // },
+ // {
+ // path: '/event',
+ // name: 'EventStorming',
+ // component: EventStormingListPage
+ // },
+ {
+ path: '/',
+ name: 'EventStorming',
+ component: EventModeler
+ },
+ // {
+ // path: '/about',
+ // name: 'about',
+ // // route level code-splitting
+ // // this generates a separate chunk (about.[hash].js) for this route
+ // // which is lazy-loaded when the route is visited.
+ // component: function () {
+ // return import(/* webpackChunkName: "about" */ './views/About.vue')
+ // }
+ // }
+ ]
+})
diff --git a/src/store.js b/src/store.js
new file mode 100644
index 00000000..d448712f
--- /dev/null
+++ b/src/store.js
@@ -0,0 +1,50 @@
+import Vue from 'vue'
+import Vuex from 'vuex'
+import axios from 'axios'
+
+Vue.prototype.$http = axios
+Vue.use(Vuex)
+
+export default new Vuex.Store({
+ state: {
+ accessToken: null,
+ kubeHost: '',
+ kubeToken: '',
+ username:'',
+ storeAuthorized: false
+ },
+ getters: {
+ getAuth(state) {
+ return state.storeAuthorized
+ }
+ },
+ mutations: {
+ LOGIN (state, data) {
+ state.kubeHost = data.kubeHost;
+ state.kubeToken = data.kubeToken;
+ state.username = data.userName;
+ state.storeAuthorized = true;
+
+ axios.defaults.headers.common['kubehost'] = state.kubeHost;
+ axios.defaults.headers.common['kubetoken'] = state.kubeToken;
+ },
+ LOGOUT (state) {
+ state.kubeHost = ''
+ state.kubeToken = ''
+ state.storeAuthorized = false;
+ }
+ },
+ actions: {
+ LOGIN ({ commit }, data) {
+ // console.log(data)
+ commit('LOGIN', data)
+
+ // commit('LOGIN', {accessToken, host})
+ // Vue.prototype.$http.defaults.baseURL = 'http://localhost:8080';
+ // Vue.prototype.$http.defaults.headers.common['Authorization'] = `Bearer ${accessToken}`;
+ },
+ LOGOUT ({commit}) {
+ commit('LOGOUT')
+ },
+ }
+})
diff --git a/src/views/About.vue b/src/views/About.vue
new file mode 100644
index 00000000..3fa28070
--- /dev/null
+++ b/src/views/About.vue
@@ -0,0 +1,5 @@
+
+
+
This is an about page
+
+
diff --git a/src/views/Home.vue b/src/views/Home.vue
new file mode 100644
index 00000000..47595541
--- /dev/null
+++ b/src/views/Home.vue
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/src/views/dashboardpage.vue b/src/views/dashboardpage.vue
new file mode 100644
index 00000000..033a1e4b
--- /dev/null
+++ b/src/views/dashboardpage.vue
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vue.config.js b/vue.config.js
new file mode 100644
index 00000000..ef6e86b2
--- /dev/null
+++ b/vue.config.js
@@ -0,0 +1,5 @@
+module.exports = {
+ "transpileDependencies": [
+ "vuetify"
+ ]
+}
\ No newline at end of file
diff --git a/webpack.config.js b/webpack.config.js
new file mode 100644
index 00000000..ed10c7c7
--- /dev/null
+++ b/webpack.config.js
@@ -0,0 +1,16 @@
+module.exports = {
+ module: {
+ rules: [
+ {
+
+ test: /\.css$/,
+ use: [ 'style-loader', 'css-loader', 'cssimportant-loader' ]
+ }
+ ]
+ },
+ devServer: {
+ disableHostCheck : true,
+ public: 'mydomain.com:8080',
+ port: 8081
+ },
+}