├── img
├── add.png
├── edit.png
├── male.png
├── delete.png
├── female.png
└── invite.png
├── people
├── dina.jpg
├── inna.jpg
├── ivo.jpg
├── maks.jpg
├── oleg.jpg
├── antija.jpg
├── artur.jpg
├── bokta.jpg
├── dasha.jpg
├── elisej.jpg
├── julja.jpg
├── linda.jpg
├── nastja.jpg
├── pavel.jpg
├── plehs.jpg
├── silin.jpg
├── trafim.jpg
├── kochuga.jpg
├── kurchik.jpg
├── migunov.jpg
├── tarasik.jpg
├── vitalik.jpg
└── aleksandra.jpg
├── style
├── img
│ ├── arrow.png
│ └── loader.gif
├── fonts
│ ├── DejaVuSans.ttf
│ ├── DejaVuSerif.ttf
│ ├── DejaVuSansMono.ttf
│ ├── DejaVuSans-Bold.ttf
│ ├── DejaVuSerif-Bold.ttf
│ ├── DejaVuSans-Oblique.ttf
│ ├── DejaVuSansCondensed.ttf
│ ├── DejaVuSansMono-Bold.ttf
│ ├── DejaVuSerif-Italic.ttf
│ ├── DejaVuSans-ExtraLight.ttf
│ ├── DejaVuSerifCondensed.ttf
│ ├── DejaVuSans-BoldOblique.ttf
│ ├── DejaVuSansCondensed-Bold.ttf
│ ├── DejaVuSansMono-Oblique.ttf
│ ├── DejaVuSerif-BoldItalic.ttf
│ ├── DejaVuSansMono-BoldOblique.ttf
│ ├── DejaVuSerifCondensed-Bold.ttf
│ ├── DejaVuSansCondensed-Oblique.ttf
│ ├── DejaVuSerifCondensed-Italic.ttf
│ ├── DejaVuSansCondensed-BoldOblique.ttf
│ └── DejaVuSerifCondensed-BoldItalic.ttf
├── .sass-cache
│ ├── 494beb6c04c81dce77689ef2365b1f1840dff9f2
│ │ └── fskytree.scssc
│ ├── 67ea5eef83c20a3e455f760e03e531dfa1521739
│ │ └── fskytree.scssc
│ ├── d7dbb3fa464cccd7ea48668644e6f77a1aaec454
│ │ └── fskytree.scssc
│ └── ffd5020e99c36ac2e6bc041fc1b38a7dea00aace
│ │ └── fskytree.scssc
├── css
│ └── fskytree.css
└── scss
│ └── fskytree.scss
├── index.php
├── tree3.json
├── index.html
├── main.js
├── tree2.json
├── fskytree
├── component
│ ├── geometry.js
│ ├── builder.js
│ ├── loader.js
│ ├── scrolling.js
│ ├── renderer.js
│ └── calculator.js
├── plugin
│ ├── overlay.js
│ ├── history.js
│ ├── actions.js
│ └── camomile.js
├── tree.js
└── view
│ ├── family.js
│ ├── node.js
│ └── member.js
├── vendor
├── raphael.ext.js
├── scrollto.js
├── require.js
└── kinetic.js
└── tree.json
/img/add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connected/familytree/master/img/add.png
--------------------------------------------------------------------------------
/img/edit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connected/familytree/master/img/edit.png
--------------------------------------------------------------------------------
/img/male.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connected/familytree/master/img/male.png
--------------------------------------------------------------------------------
/img/delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connected/familytree/master/img/delete.png
--------------------------------------------------------------------------------
/img/female.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connected/familytree/master/img/female.png
--------------------------------------------------------------------------------
/img/invite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connected/familytree/master/img/invite.png
--------------------------------------------------------------------------------
/people/dina.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connected/familytree/master/people/dina.jpg
--------------------------------------------------------------------------------
/people/inna.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connected/familytree/master/people/inna.jpg
--------------------------------------------------------------------------------
/people/ivo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connected/familytree/master/people/ivo.jpg
--------------------------------------------------------------------------------
/people/maks.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connected/familytree/master/people/maks.jpg
--------------------------------------------------------------------------------
/people/oleg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connected/familytree/master/people/oleg.jpg
--------------------------------------------------------------------------------
/people/antija.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connected/familytree/master/people/antija.jpg
--------------------------------------------------------------------------------
/people/artur.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connected/familytree/master/people/artur.jpg
--------------------------------------------------------------------------------
/people/bokta.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connected/familytree/master/people/bokta.jpg
--------------------------------------------------------------------------------
/people/dasha.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connected/familytree/master/people/dasha.jpg
--------------------------------------------------------------------------------
/people/elisej.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connected/familytree/master/people/elisej.jpg
--------------------------------------------------------------------------------
/people/julja.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connected/familytree/master/people/julja.jpg
--------------------------------------------------------------------------------
/people/linda.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connected/familytree/master/people/linda.jpg
--------------------------------------------------------------------------------
/people/nastja.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connected/familytree/master/people/nastja.jpg
--------------------------------------------------------------------------------
/people/pavel.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connected/familytree/master/people/pavel.jpg
--------------------------------------------------------------------------------
/people/plehs.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connected/familytree/master/people/plehs.jpg
--------------------------------------------------------------------------------
/people/silin.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connected/familytree/master/people/silin.jpg
--------------------------------------------------------------------------------
/people/trafim.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connected/familytree/master/people/trafim.jpg
--------------------------------------------------------------------------------
/people/kochuga.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connected/familytree/master/people/kochuga.jpg
--------------------------------------------------------------------------------
/people/kurchik.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connected/familytree/master/people/kurchik.jpg
--------------------------------------------------------------------------------
/people/migunov.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connected/familytree/master/people/migunov.jpg
--------------------------------------------------------------------------------
/people/tarasik.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connected/familytree/master/people/tarasik.jpg
--------------------------------------------------------------------------------
/people/vitalik.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connected/familytree/master/people/vitalik.jpg
--------------------------------------------------------------------------------
/style/img/arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connected/familytree/master/style/img/arrow.png
--------------------------------------------------------------------------------
/style/img/loader.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connected/familytree/master/style/img/loader.gif
--------------------------------------------------------------------------------
/people/aleksandra.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connected/familytree/master/people/aleksandra.jpg
--------------------------------------------------------------------------------
/style/fonts/DejaVuSans.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connected/familytree/master/style/fonts/DejaVuSans.ttf
--------------------------------------------------------------------------------
/style/fonts/DejaVuSerif.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connected/familytree/master/style/fonts/DejaVuSerif.ttf
--------------------------------------------------------------------------------
/style/fonts/DejaVuSansMono.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connected/familytree/master/style/fonts/DejaVuSansMono.ttf
--------------------------------------------------------------------------------
/style/fonts/DejaVuSans-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connected/familytree/master/style/fonts/DejaVuSans-Bold.ttf
--------------------------------------------------------------------------------
/style/fonts/DejaVuSerif-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/connected/familytree/master/style/fonts/DejaVuSerif-Bold.ttf
--------------------------------------------------------------------------------
/index.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Raphael
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
Andrejs Silins
19 |
20 |
21 |
22 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/main.js:
--------------------------------------------------------------------------------
1 | require.config({
2 | paths: {
3 | 'jquery': 'vendor/jquery',
4 | 'raphael': '//cdnjs.cloudflare.com/ajax/libs/raphael/2.1.2/raphael-min'
5 | }
6 | });
7 |
8 |
9 | require([
10 | 'fskytree/tree',
11 | 'fskytree/plugin/actions',
12 | 'fskytree/plugin/overlay',
13 | 'fskytree/plugin/camomile',
14 | 'fskytree/plugin/history'
15 | ], function(FskyTree, Actions, Overlay, Camomile, History) {
16 |
17 | var fskyTree = new FskyTree();
18 |
19 | fskyTree.plugin(Actions);
20 | fskyTree.plugin(Overlay);
21 | fskyTree.plugin(Camomile);
22 | fskyTree.plugin(History, {handlerUrl: 'tree2.json'});
23 |
24 | fskyTree.load('tree.json');
25 |
26 | $(fskyTree).on('tree/ready', function(){
27 | this.render();
28 | });
29 |
30 | $(fskyTree).on('tree/error', function(e, error){
31 | alert('Tree error!');
32 | });
33 |
34 | $(fskyTree).on('tree/member/add/son', function(e, member){
35 | alert(member.lastname);
36 | });
37 |
38 | });
39 |
--------------------------------------------------------------------------------
/tree2.json:
--------------------------------------------------------------------------------
1 | {
2 | "member": {
3 | "id": "6ej775223r3",
4 | "firstname": "Linda",
5 | "lastname": "Zaikovska",
6 | "sex": 2,
7 | "image": "people/linda.jpg"
8 | },
9 | "children": [
10 | {
11 | "member": {
12 | "id": "6ej774t23r3",
13 | "firstname": "Jūlija",
14 | "lastname": "Belova",
15 | "current": true,
16 | "sex": 2,
17 | "image": "people/julja.jpg"
18 | },
19 | "partner": {
20 | "id": "sdf4fsh544",
21 | "firstname": "Oļegs",
22 | "lastname": "Jaroševičs",
23 | "sex": 1,
24 | "image": "people/oleg.jpg",
25 | "relatives": 4
26 | }
27 | },
28 | {
29 | "member": {
30 | "id": "6ej774t23r3",
31 | "firstname": "Nataļja",
32 | "lastname": "Kočjuga",
33 | "sex": 2,
34 | "image": "people/kochuga.jpg"
35 | }
36 | },
37 | {
38 | "member": {
39 | "id": "gg57h5623r3",
40 | "firstname": "Sergejs",
41 | "lastname": "Trofimovs",
42 | "sex": 1,
43 | "image": "people/trafim.jpg"
44 | }
45 | }
46 | ]
47 | }
48 |
--------------------------------------------------------------------------------
/fskytree/component/geometry.js:
--------------------------------------------------------------------------------
1 | define(function(require, exports, module){
2 |
3 | var $ = require('jquery');
4 |
5 | /**
6 | * @class Geometry
7 | * @version 1.0
8 | */
9 | var Geometry = new function()
10 | {
11 | var _self;
12 |
13 | /**
14 | * Public API
15 | */
16 | this.Point = Point;
17 |
18 | /**
19 | * Calculate sinus value for specified angle.
20 | *
21 | * @param {number} Angle value
22 | * @returns {Number}
23 | */
24 | this.sin = function(angle)
25 | {
26 | return Math.sin(_self.degrad(angle));
27 | };
28 |
29 | /**
30 | * Calculate cosinus value for specified angle.
31 | *
32 | * @param {number} Angle value
33 | * @returns {Number}
34 | */
35 | this.cos = function(angle)
36 | {
37 | return Math.cos(_self.degrad(angle));
38 | };
39 |
40 | /**
41 | * Calculate cirlce point position using radius and angle.
42 | *
43 | * @param {number} Angle value(usually from 0 to 360)
44 | * @param {number} Radius value
45 | * @param {Point} [offset] Center point
46 | * @returns {Point}
47 | */
48 | this.circlePoint = function(angle, radius, offset)
49 | {
50 | if (typeof(offset) != 'object' || !offset instanceof Point) {
51 | offset = new Point();
52 | }
53 |
54 | return new Point(
55 | (_self.cos(angle)) * radius + offset.x,
56 | (_self.sin(angle)) * radius + offset.y
57 | );
58 | };
59 |
60 | /**
61 | * Convert degrees to radians.
62 | *
63 | * @param {number} Angle value
64 | * @returns {Number}
65 | */
66 | this.degrad = function(angle)
67 | {
68 | return -angle / 180 * Math.PI;
69 | };
70 |
71 | /**
72 | * @class Point
73 | * @classdesc Class that represents 2D geometry point.
74 | */
75 | function Point(x, y)
76 | {
77 | this.x = x || 0;
78 | this.y = y || 0;
79 | };
80 |
81 | //Make self reference
82 | _self = this;
83 | };
84 |
85 | return Geometry;
86 |
87 | });
88 |
--------------------------------------------------------------------------------
/fskytree/component/builder.js:
--------------------------------------------------------------------------------
1 | define(function(require, exports, module){
2 |
3 | var $ = require('jquery'),
4 | Node = require('fskytree/view/node');
5 |
6 | /**
7 | * @class Builder
8 | * @classdesc Tree builder "class" is responsible for parsing JSON tree data.
9 | */
10 | var Builder = function(tree, options)
11 | {
12 | /**
13 | * @type {Builder}
14 | */
15 | var _self;
16 |
17 | /**
18 | * @type {Object} Object containing component setting values
19 | */
20 | this.settings = {
21 |
22 | };
23 |
24 | /**
25 | * Constructor.
26 | *
27 | * @constructs Builder
28 | */
29 | function construct(options)
30 | {
31 | //Extend loader settings with specified options
32 | $.extend(_self.settings, options);
33 |
34 | //Build tree root node after tree data is loaded
35 | $(tree).on('tree/load', _self.build);
36 | }
37 |
38 | /**
39 | * Build root node from JSON tree data.
40 | *
41 | * @param {jQuery.Event} Event instance
42 | * @param {Object} data JSON tree data
43 | * @return {Builder} Builder instance
44 | */
45 | this.build = function(e, data)
46 | {
47 | tree.root = _self.walk(data);
48 |
49 | //Notify that root tree node structure has been built
50 | $(tree).trigger('tree/built', [tree.root]);
51 |
52 | return this;
53 | };
54 |
55 | /**
56 | * Recursively walk JSON data.
57 | *
58 | * @param {Object} data JSON node data
59 | * @return {Node} Tree node instance
60 | */
61 | this.walk = function(data)
62 | {
63 | var node = new Node(tree, data);
64 |
65 | if (data.children) {
66 | for (var i=0;i ' + _self.settings.closeText + '');
103 | var overlay = $('');
104 |
105 | close.click(_self.hide); //Bind close event handler
106 | close.appendTo(overlay); //Append button to verlay
107 |
108 | return overlay;
109 | }
110 |
111 | //Make self reference
112 | _self = this;
113 |
114 | //Initialize object
115 | construct(options);
116 | };
117 |
118 | /**
119 | * Class constants.
120 | */
121 | Overlay.PLUGIN_NAME = 'overlay';
122 |
123 | return Overlay;
124 |
125 | });
126 |
--------------------------------------------------------------------------------
/style/scss/fskytree.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * TEMP
3 | */
4 | @font-face { font-family: DejaVuSans; src: url('../fonts/DejaVuSans.ttf'); }
5 | @font-face { font-family: DejaVuSans; font-weight: bold; src: url('../fonts/DejaVuSans-Bold.ttf'); }
6 | @font-face { font-family: DejaVuSans-ExtraLight; font-weight: bold; src: url('../fonts/DejaVuSans-ExtraLight.ttf'); }
7 | @font-face { font-family: DejaVuSans-Mono; font-weight: bold; src: url('../fonts/DejaVuSansMono.ttf'); }
8 |
9 | * {
10 | margin: 0px;
11 | padding: 0px;
12 | }
13 |
14 | html, body {
15 | width: 100%;
16 | height: 100%;
17 | }
18 |
19 | body {
20 | font-family: "DejaVuSans", sans-serif;
21 | }
22 |
23 |
24 | /**
25 | * FSKYTREE HISTORY
26 | */
27 | #fskytree-history {
28 |
29 | font-family: "DejaVuSans-ExtraLight", sans-serif;
30 | position: absolute;
31 | top: 0px;
32 | left: 0px;
33 | max-width: 200px;
34 | z-index: 1;
35 |
36 | .title {
37 | font-size: 25px;
38 | font-weight: lighter;
39 | color: #044d85;
40 | text-align: left;
41 | margin-bottom: 20px;
42 | }
43 |
44 | .history-list {
45 | list-style: none;
46 | }
47 |
48 | .history-list .list-item {
49 | font-size: 0px;
50 | text-align: center;
51 |
52 | a {
53 | white-space: nowrap;
54 | display: inline-block;
55 | font-size: 16px;
56 | color: #415478;
57 | text-align: center;
58 | text-decoration: none;
59 | padding: 5px 20px;
60 | }
61 |
62 | &.active a {
63 | background: #E3E7F3;
64 | }
65 | }
66 |
67 | .history-list .list-item:before {
68 | content: "";
69 | display: block;
70 | margin: 5px auto;
71 | width: 16px;
72 | height: 16px;
73 | background: url("../img/arrow.png") no-repeat center center;
74 | }
75 |
76 | .history-list .list-item:first-child:before {
77 | display: none;
78 | }
79 | }
80 |
81 |
82 | /**
83 | * FSKYTREE
84 | */
85 | #fskytree-wrapper {
86 | position: absolute;
87 | top: 0px;
88 | left: 0px;
89 | right: 0px;
90 | bottom: 0px;
91 | overflow: hidden;
92 |
93 | &.loading:after {
94 | content: "";
95 | display: block;
96 | width: 64px;
97 | height: 64px;
98 | background: url("../img/loader.gif") no-repeat center center;
99 | position: absolute;
100 | top: 50%;
101 | left: 50%;
102 | margin-left: -32px;
103 | margin-top: -32px;
104 | }
105 |
106 | .fskytree-container {
107 | width: 100%;
108 | height: 100%;
109 |
110 | svg {
111 | display: block;
112 | }
113 | }
114 | }
115 |
116 |
117 | /**
118 | * FSKYTREE OVERLAY
119 | */
120 | #fskytree-overlay {
121 | display: none;
122 | position: absolute;
123 | top: 0px;
124 | right: 0px;
125 | bottom: 0px;
126 | left: 0px;
127 | z-index: 100;
128 | background-color: rgba(255,255,255, .9);
129 |
130 | svg {
131 | display: block;
132 | }
133 |
134 | .close {
135 | color: #FFF;
136 | font-size: 14px;
137 | min-width: 100px;
138 | padding: 10px 15px;
139 | position: absolute;
140 | right: 20px;
141 | top: 20px;
142 | background-color: rgba(111, 139, 202, .8);
143 | text-decoration: none;
144 | text-align: center;
145 | text-transform: uppercase;
146 | }
147 |
148 | .close:after {
149 | content: "×";
150 | display: inline-block;
151 | font-size: 16px;
152 | margin-left: 5px;
153 | }
154 |
155 | .close:hover {
156 | background-color: rgba(111, 139, 202, 1);
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/fskytree/tree.js:
--------------------------------------------------------------------------------
1 | define(function(require, exports, module){
2 |
3 | var $ = require('jquery');
4 | Loader = require('fskytree/component/loader'),
5 | Builder = require('fskytree/component/builder'),
6 | Calculator = require('fskytree/component/calculator'),
7 | Scrolling = require('fskytree/component/scrolling'),
8 | Renderer = require('fskytree/component/renderer');
9 |
10 | /**
11 | * @class FskyTree
12 | * @classdesc Core fsky tree class.
13 | */
14 | var FskyTree = function(options){
15 |
16 | /**
17 | * @type {FskyTree}
18 | */
19 | var _self;
20 |
21 | /**
22 | * @type {Object} Object containing main tree setting values.
23 | */
24 | this.settings = {
25 | id: 'fskytree-wrapper',
26 | containerClass: 'fskytree-container',
27 | handler: 'tree.json',
28 | debug: false
29 | };
30 |
31 | /**
32 | * @type {Object} Object containing connected plugins
33 | */
34 | this.plugin = {};
35 |
36 | /**
37 | * @type {Node} Tree root node instance
38 | */
39 | this.root;
40 |
41 | /**
42 | * @type {Node} Currently selected node
43 | */
44 | this.current;
45 |
46 | /**
47 | * @type {Calculator} Tree calculator component instance
48 | */
49 | this.calculator;
50 |
51 | /**
52 | * @type {Loader} Tree loader component instance
53 | */
54 | this.loader;
55 |
56 | /**
57 | * @type {Renderer} Tree renderer component instance
58 | */
59 | this.renderer;
60 |
61 | /**
62 | * @type {Scrolling} Tree scrolling component instance
63 | */
64 | this.scrolling;
65 |
66 | /**
67 | * @type {jQuery} Tree wrapper dom element
68 | */
69 | this.wrapper;
70 |
71 | /**
72 | * @type {jQuery} Tree container dom element
73 | */
74 | this.container;
75 |
76 | /**
77 | * Constructor.
78 | *
79 | * @constructs Tree
80 | */
81 | function construct(options)
82 | {
83 | //Extend core settings with specified options
84 | $.extend(_self.settings, options);
85 |
86 | //Set tree viewport dom elements
87 | _self.wrapper = $('#' + _self.settings.id);
88 | _self.container = _self.wrapper.find('.' + _self.settings.containerClass);
89 |
90 | //Init. core components
91 | _self.loader = new Loader(_self);
92 | _self.builder = new Builder(_self);
93 | _self.calculator = new Calculator(_self);
94 | _self.scrolling = new Scrolling(_self);
95 | _self.renderer = new Renderer(_self);
96 | }
97 |
98 | /**
99 | * Load tree data file.
100 | *
101 | * @param {Function} plugin Plugin "class" function
102 | * @param {Object} options Plugin options
103 | * @returns {Tree} Tree instance
104 | */
105 | this.load = function(url, data)
106 | {
107 | _self.loader.load(url, data);
108 |
109 | return this;
110 | };
111 |
112 | /**
113 | * Draw tree on canvas.
114 | *
115 | * @returns {Tree} Tree instance
116 | */
117 | this.render = function()
118 | {
119 | _self.renderer.render();
120 |
121 | return this;
122 | };
123 |
124 | /**
125 | * Attaches a plugin to the tree instance.
126 | *
127 | * @param {Function} plugin Plugin "class" function
128 | * @param {Object} options Plugin options
129 | * @returns {Tree} Tree instance
130 | */
131 | this.plugin = function(plugin, options)
132 | {
133 | _self.plugin[plugin.PLUGIN_NAME] = new plugin(_self, options);
134 |
135 | return this;
136 | };
137 |
138 | //Make self reference
139 | _self = this;
140 |
141 | //Initialize object
142 | construct(options);
143 |
144 | };
145 |
146 | return FskyTree;
147 |
148 | });
149 |
--------------------------------------------------------------------------------
/fskytree/plugin/history.js:
--------------------------------------------------------------------------------
1 | define(function(require, exports, module){
2 |
3 | var $ = require('jquery');
4 |
5 | /**
6 | * @class History
7 | * @classdesc This "class" is responsible for managing tree history.
8 | */
9 | var History = function(tree, options)
10 | {
11 | /**
12 | * @type {History} History plugin instance
13 | */
14 | var _self;
15 |
16 | /**
17 | * @type {jQuery} History container element
18 | */
19 | this.container;
20 |
21 | /**
22 | * @type {jQuery} History list element
23 | */
24 | this.list;
25 |
26 | /**
27 | * @type {jQuery} Currently selected history list item element
28 | */
29 | this.activeItem;
30 |
31 | /**
32 | * @type {Object} Object containing plugin setting values
33 | */
34 | this.settings = {
35 | id: 'fskytree-history',
36 | listClass: 'history-list',
37 | itemClass: 'list-item',
38 | activeClass: 'active',
39 | handlerUrl: null
40 | };
41 |
42 | /**
43 | * Constructor.
44 | *
45 | * @constructs History
46 | */
47 | function construct(options)
48 | {
49 | //Extend loader settings with specified options
50 | $.extend(_self.settings, options);
51 |
52 | //Initialize plugin dom elements
53 | _self.initContainer();
54 |
55 | //Load new member tree when user clicks on "relatives" button
56 | $(tree).on('tree/member/relatives', function(e, member){
57 | _self.pushState(member);
58 | });
59 |
60 | //Bind history list item events
61 | _self.list.on('click', '.' + _self.settings.itemClass, _self.loadState);
62 | }
63 |
64 | /**
65 | * Set plugin dom element variables.
66 | *
67 | * @returns {History} History plugin instance
68 | */
69 | this.initContainer = function()
70 | {
71 | _self.container = $('#' + _self.settings.id);
72 | _self.list = _self.container.find('.' + _self.settings.listClass);
73 | _self.activeItem = _self.list.find('.' + _self.settings.activeClass);
74 |
75 | return this;
76 | };
77 |
78 | /**
79 | * ...
80 | */
81 | this.pushState = function(member)
82 | {
83 | //Remove any previous list items
84 | _self.removePreviousItems();
85 |
86 | //Add target member as new list item
87 | _self.addListItem(member);
88 |
89 | //Set current member id as location hash value
90 | _self.setHash(member.id);
91 |
92 | //Load new tree by member id
93 | tree.load(_self.settings.handlerUrl, {id: member.id});
94 |
95 | return this;
96 | };
97 |
98 | /**
99 | * ...
100 | */
101 | this.loadState = function(e)
102 | {
103 | //Set clicked list item as currently active
104 | _self.setActiveItem($(this));
105 |
106 | //Load new tree
107 | tree.load(_self.settings.handlerUrl, {id: $(this).data('id')});
108 |
109 | return this;
110 | };
111 |
112 | /**
113 | * Set window location hash value.
114 | *
115 | * @param {String} Hash value
116 | * @returns {History} History plugin instance
117 | */
118 | this.setHash = function(hash)
119 | {
120 | location.hash = hash;
121 |
122 | return this;
123 | }
124 |
125 | /**
126 | * Set active history list item.
127 | *
128 | * @param {jQuery} jQuery wrapped list item dom element
129 | */
130 | this.setActiveItem = function(item)
131 | {
132 | /**
133 | * Remove active class from previous active item element, set new active item element
134 | * and add "active" class to new active element.
135 | */
136 | _self.activeItem.removeClass(_self.settings.activeClass);
137 | _self.activeItem = item;
138 | _self.activeItem.addClass(_self.settings.activeClass);
139 | }
140 |
141 | /**
142 | * Create list item element for history entry.
143 | *
144 | * @param {Member} Target member view instance
145 | * @returns {jQuery} jQuery wrapped list item DOM element
146 | */
147 | this.createListItem = function(member)
148 | {
149 | return $('' + member.lastname + '');
150 | }
151 |
152 | /**
153 | * ...
154 | */
155 | this.addListItem = function(member)
156 | {
157 | //Create new history list item from tempalte
158 | var listItem = _self.createListItem(member);
159 |
160 | //Append to list
161 | listItem.appendTo(_self.list);
162 |
163 | //Set new list item as currently active
164 | _self.setActiveItem(listItem);
165 | }
166 |
167 | /**
168 | * Remove all elements after currently active element
169 | * in history list, see _self.list.
170 | *
171 | * @returns {History} History plugin instance
172 | */
173 | this.removePreviousItems = function()
174 | {
175 | _self.activeItem.nextAll().remove();
176 |
177 | return this;
178 | }
179 |
180 | //Make self reference
181 | _self = this;
182 |
183 | //Initialize object
184 | construct(options);
185 | }
186 |
187 | /**
188 | * Class constants.
189 | */
190 | History.PLUGIN_NAME = 'history';
191 |
192 | return History;
193 |
194 | });
195 |
--------------------------------------------------------------------------------
/fskytree/component/calculator.js:
--------------------------------------------------------------------------------
1 | define(function(require, exports, module){
2 |
3 | var $ = require('jquery'),
4 | Member = require('fskytree/view/member');
5 | Node = require('fskytree/view/node');
6 |
7 | /**
8 | * @class Calculator
9 | * @classdesc Tree dimensions and position calculator.
10 | */
11 | var Calculator = function(tree, options)
12 | {
13 | /**
14 | * ...
15 | * @type {FskyTree}
16 | */
17 | var _self;
18 |
19 | /**
20 | * @type {Object} Object containing component setting values
21 | */
22 | this.settings = {
23 |
24 | };
25 |
26 | /**
27 | * Constructor.
28 | *
29 | * @constructs Calculator
30 | */
31 | function construct(options)
32 | {
33 | //Extend loader settings with specified options
34 | $.extend(_self.settings, options);
35 |
36 | //Calculate nodes position and dimensions
37 | $(tree).on('tree/built', _self.calculate);
38 | }
39 |
40 | /**
41 | * Calculate and populate node and its siblings
42 | * dimension and position values.
43 | *
44 | * @param {jQuery.Event} node Event instance
45 | * @param {Node} node Node instance
46 | */
47 | this.calculate = function(e, node)
48 | {
49 | _self.calcDimension(node);
50 |
51 | //Notify tree that root node dimensions successfuly calculated.
52 | $(tree).trigger('tree/calculate/dimension');
53 |
54 | //Do your magic...
55 | _self.calcRootPosition(node);
56 | _self.calcPosition(node);
57 |
58 | //Notify tree that root node position is calulcated and ready to be rendered
59 | $(tree).trigger('tree/ready', [node]);
60 | };
61 |
62 | /**
63 | * Calculate and populate node and it siblings width and height properties.
64 | * These values will later be used to calcaulate node position.
65 | *
66 | * @return {Object} Object containing width and hight properties
67 | */
68 | this.calcDimension = function(node)
69 | {
70 | var width = 0;
71 | var cwidth = 0;
72 | var heights = [0];
73 |
74 | if (node.hasChildren()) {
75 |
76 | for (var i=0;i 1) {
86 | _self.drawGroupLine(canvas);
87 | }
88 |
89 | if (node.hasChildren()) {
90 | _self.drawChildrenLine(canvas);
91 | }
92 |
93 | return this;
94 | };
95 |
96 | /**
97 | * Get member width, which can later be used to calculate
98 | * owner node position on canvas.
99 | *
100 | * @returns {Number} Node width in pixels
101 | */
102 | this.getWidth = function()
103 | {
104 | if (node.hasChildren() && node.hasParent()) {
105 | return _self.settings.eWidth;
106 | }
107 |
108 | return _self.settings.dWidth;
109 | };
110 |
111 | /**
112 | * Get node view middle point position.
113 | * This value will later be used to position node view elements.
114 | *
115 | * @returns {Geometry.Point} Object containing x, y properties
116 | */
117 | this.getMPoint = function()
118 | {
119 | return _self.member.getMPoint();
120 | };
121 |
122 | /**
123 | * Draw family children group line.
124 | *
125 | * @param {Raphael.Paper} Raphael paper instance
126 | * @returns {Raphael.Element} Raphael path element
127 | */
128 | this.drawGroupLine = function(canvas)
129 | {
130 | var cPoint1 = node.children[0].getCPoint();
131 | var cPoint2 = node.children.slice(-1)[0].getCPoint();
132 |
133 | var line = canvas.path();
134 |
135 | line.attr({
136 | 'stroke': '#2B3F74',
137 | 'stroke-width': 2,
138 | 'path':
139 | 'M' + (cPoint1.x - 1) + ' ' + cPoint1.y +
140 | 'L' + (cPoint2.x + 1) + ' ' + cPoint2.y
141 | });
142 |
143 | return line;
144 | }
145 |
146 | /**
147 | * Draw family children line.
148 | *
149 | * @param {Raphael.Paper} Raphael paper instance
150 | * @returns {Raphael.Element} Raphael path element
151 | */
152 | this.drawChildrenLine = function(canvas)
153 | {
154 | var line = canvas.path();
155 |
156 | //Get family members image set bounding box
157 | var box = canvas
158 | .set([_self.member.imageEl, _self.partner.imageEl])
159 | .getBBox();
160 |
161 | //Calculate arc point offsets
162 | var offset = {
163 | x: box.x,
164 | y: box.y + box.height,
165 | z: box.x + box.width
166 | };
167 |
168 | line.attr({
169 | 'stroke': '#2B3F74',
170 | 'stroke-width': 2,
171 | 'path':
172 | 'M'
173 | + offset.x + ' '
174 | + offset.y +
175 | 'C'
176 | + (offset.x + 40) + ' '
177 | + (offset.y + 30) + ','
178 | + (offset.z - 40) + ' ' + (offset.y + 30) + ','
179 | + (offset.x + box.width) + ' ' + offset.y +
180 | 'M'
181 | + node.x + ' '
182 | + (offset.y + 22) +
183 | 'L'
184 | + node.x + ' '
185 | + (node.y + node.getHeight())
186 | });
187 |
188 | //Prevent arc from overlaping member "relatives" circle element.
189 | line.toBack();
190 |
191 | return line;
192 | }
193 |
194 | //Make self reference
195 | _self = this;
196 |
197 | //Initialize object
198 | construct(data, options);
199 | };
200 |
201 | return Family;
202 |
203 | });
204 |
--------------------------------------------------------------------------------
/tree.json:
--------------------------------------------------------------------------------
1 | {
2 | "member": {
3 | "id": "34sdt348f7",
4 | "firstname": "Natālija",
5 | "lastname": "Kurčanova",
6 | "sex": 2,
7 | "image": "people/kurchik.jpg"
8 | },
9 | "children": [
10 | {
11 | "member": {
12 | "id": "34kit348f7",
13 | "firstname": "Andrejs",
14 | "lastname": "Jeļisejevs",
15 | "sex": 1,
16 | "image": "people/elisej.jpg"
17 | },
18 | "children": [
19 | {
20 | "member": {
21 | "id": "11kit348f1",
22 | "firstname": "Natālija",
23 | "lastname": "Tarasova",
24 | "sex": 2,
25 | "image": "people/tarasik.jpg"
26 | }
27 | },
28 | {
29 | "member": {
30 | "id": "dgkit548f1",
31 | "firstname": "Inna",
32 | "lastname": "Adameņa",
33 | "sex": 2,
34 | "image": "people/inna.jpg"
35 | }
36 | }
37 | ]
38 | },
39 | {
40 | "member": {
41 | "id": "666it548f1",
42 | "firstname": "Darja",
43 | "lastname": "Petrušina",
44 | "sex": 2,
45 | "current": true,
46 | "image": "people/dasha.jpg"
47 | },
48 | "partner": {
49 | "id": "4389t548f1",
50 | "firstname": "Pāvels",
51 | "lastname": "Degterenko",
52 | "sex": 1,
53 | "image": "people/pavel.jpg",
54 | "relatives": 2
55 | },
56 | "children": [
57 | {
58 | "member": {
59 | "id": "sdf4fsh544",
60 | "firstname": "Oļegs",
61 | "lastname": "Jaroševičs",
62 | "sex": 1,
63 | "image": "people/oleg.jpg"
64 | },
65 | "partner": {
66 | "id": "6ej774t23r3",
67 | "firstname": "Jūlija",
68 | "lastname": "Belova",
69 | "sex": 2,
70 | "image": "people/julja.jpg",
71 | "relatives": 4
72 | },
73 | "children": [
74 | {
75 | "member": {
76 | "id": "d5745fg5f1",
77 | "firstname": "Andrejs",
78 | "lastname": "Silins",
79 | "sex": 1,
80 | "image": "people/silin.jpg"
81 | }
82 | },
83 | {
84 | "member": {
85 | "id": "435dfdf23",
86 | "firstname": "Dmitrijs",
87 | "lastname": "Migunovs",
88 | "sex": 1,
89 | "image": "people/migunov.jpg"
90 | }
91 | },
92 | {
93 | "member": {
94 | "id": "dasdfdgkdf1",
95 | "firstname": "Eduards",
96 | "lastname": "Plehs",
97 | "sex": 1,
98 | "image": "people/plehs.jpg"
99 | }
100 | },
101 | {
102 | "member": {
103 | "id": "3434sdf548f1",
104 | "firstname": "Ivo",
105 | "lastname": "Azirjans",
106 | "sex": 1,
107 | "image": "people/ivo.jpg"
108 | }
109 | }
110 | ]
111 | }
112 | ]
113 | },
114 | {
115 | "member": {
116 | "id": "dere343er34",
117 | "firstname": "Edgars",
118 | "lastname": "Bokta",
119 | "sex": 1,
120 | "image": "people/bokta.jpg"
121 | },
122 | "children": [
123 | {
124 | "member": {
125 | "id": "d45465448f1",
126 | "firstname": "Anastasija",
127 | "lastname": "Jevdokimova",
128 | "sex": 2,
129 | "image": "people/nastja.jpg"
130 | },
131 | "children": [
132 | {
133 | "member": {
134 | "id": "uu3245ef48f1",
135 | "firstname": "Antija",
136 | "lastname": "Janševska",
137 | "sex": 2,
138 | "image": "people/antija.jpg"
139 | }
140 | },
141 | {
142 | "member": {
143 | "id": "32fdf43t548f1",
144 | "firstname": "Dina",
145 | "lastname": "Konopļova",
146 | "sex": 2,
147 | "image": "people/dina.jpg"
148 | }
149 | }
150 | ]
151 | },
152 | {
153 | "member": {
154 | "id": "23ffkit5484",
155 | "firstname": "Maksims",
156 | "lastname": "Kondratjevs",
157 | "sex": 1,
158 | "image": "people/maks.jpg",
159 | "alive": false
160 | }
161 | },
162 | {
163 | "member": {
164 | "id": "123it5648f1",
165 | "firstname": "Vitālijs",
166 | "lastname": "Silins",
167 | "sex": 1,
168 | "image": "people/vitalik.jpg"
169 | }
170 | }
171 | ]
172 | }
173 | ]
174 | }
175 |
--------------------------------------------------------------------------------
/fskytree/plugin/actions.js:
--------------------------------------------------------------------------------
1 | define(function(require, exports, module){
2 |
3 | var $ = require('jquery'),
4 | Member = require('fskytree/view/member'),
5 | Node = require('fskytree/view/node'),
6 | Geometry = require('fskytree/component/geometry');
7 |
8 | /**
9 | * @class Actions
10 | * @classdesc Actions "class" is responsible for displaying actions layer.
11 | */
12 | var Actions = function(tree, options)
13 | {
14 | /**
15 | * ...
16 | * @type {Actions} Actions plugin instance
17 | */
18 | var _self;
19 |
20 | /**
21 | * @type {Raphael.Set} Action elements set
22 | */
23 | this.elSet;
24 |
25 | /**
26 | * @type {Object} Object containing plugin setting values
27 | */
28 | this.settings = {
29 | debug: false,
30 | distance: 30,
31 | imagePath: 'img/'
32 | };
33 |
34 | /**
35 | * @type {Array} Array of action objects
36 | */
37 | this.actions = [
38 | {icon: 'add.png', event: 'tree/member/add'},
39 | {icon: 'edit.png', event: 'tree/member/edit'},
40 | {icon: 'delete.png', event: 'tree/member/delete'}
41 | ];
42 |
43 | /**
44 | * Constructor.
45 | *
46 | * @constructs Loader
47 | */
48 | function construct(options)
49 | {
50 | //Extend loader settings with specified options
51 | $.extend(_self.settings, options);
52 |
53 | //Render actions on node member hover event
54 | $(tree).on('tree/member/hover', function(e, member){
55 | _self.render(tree.renderer.canvas, member);
56 | });
57 |
58 | /**
59 | * Draw transparent background
60 | * when canvas is ready.
61 | */
62 | $(tree).on('tree/render', function(e, renderer){
63 | _self.drawBackground(tree.renderer.canvas);
64 | });
65 | }
66 |
67 | /**
68 | * Draw actions.
69 | *
70 | * @param {Raphael.Paper} Raphael paper instance
71 | * @return {Actions} Actions plugin instance
72 | */
73 | this.render = function(canvas, member)
74 | {
75 | /**
76 | * Before rendering new action elements remove
77 | * previous ones left from other member view.
78 | */
79 | _self.hide();
80 |
81 | //From this point, store all rendered elements in a set
82 | canvas.setStart();
83 |
84 | //Draw overlay
85 | this.drawOverlay(canvas, member);
86 |
87 | //Draw all available action buttons
88 | this.drawActions(canvas, member);
89 |
90 | /**
91 | * This set will later be used to remove all action elements
92 | * from canvas at once, using set.remove() method.
93 | */
94 | _self.elSet = canvas.setFinish();
95 |
96 | return this;
97 | };
98 |
99 | /**
100 | * Remove action elements from tree canvas.
101 | *
102 | * @returns {Actions} Actions plugin instance
103 | */
104 | this.hide = function()
105 | {
106 | if (_self.elSet) {
107 | _self.elSet.remove();
108 | }
109 |
110 | return this;
111 | };
112 |
113 | /**
114 | * Draw actions background overlay.
115 | *
116 | * @param {Raphael.Paper} Raphael paper instance
117 | * @param {Member} Member view instance
118 | * @return {Raphael.Element} Raphael rect. element
119 | */
120 | this.drawOverlay = function(canvas, member)
121 | {
122 | var mPoint = member.getMPoint();
123 | var overlay = canvas.circle();
124 |
125 | var sex = member.sex;
126 |
127 | if (member.node.getType() == Node.TYPE_MEMBER) {
128 | sex = Member.SEX_FEMALE;
129 | }
130 |
131 | overlay.attr({
132 | 'stroke': (_self.settings.debug)? 2 : 'none',
133 | 'fill': '#FFF',
134 | 'fill-opacity': 0,
135 | 'cx': (sex == Member.SEX_MALE)? mPoint.x - 25 : mPoint.x + 25,
136 | 'cy': mPoint.y - 25,
137 | 'r': 90
138 | });
139 |
140 | overlay.insertBefore(member.imageEl);
141 |
142 | return overlay;
143 | }
144 |
145 | /**
146 | * Draw all action elements(buttons).
147 | *
148 | * @param {Raphael.Paper} Raphael paper instance
149 | * @param {Member} Member view instance
150 | */
151 | this.drawActions = function(canvas, member)
152 | {
153 | var mPoint = member.getMPoint();
154 | var sex = member.sex;
155 |
156 | if (member.node.getType() == Node.TYPE_MEMBER) {
157 | sex = Member.SEX_FEMALE;
158 | }
159 |
160 | /**
161 | * Draw all available action buttons.
162 | */
163 | for (var i=0;i<_self.actions.length;i++) {
164 |
165 | //Calculate button position
166 | var point = Geometry.circlePoint(
167 | (sex == Member.SEX_MALE)? 180 - i * 45 : i * 45,
168 | member.settings.imageRadius + _self.settings.distance,
169 | new Geometry.Point(mPoint.x, mPoint.y)
170 | );
171 |
172 | /**
173 | * Draw action background circle.
174 | */
175 | var background = canvas.circle();
176 |
177 | background.attr({
178 | 'cursor': 'pointer',
179 | 'cx': point.x,
180 | 'cy': point.y,
181 | 'r': 22,
182 | 'fill': '#6F8BCA',
183 | 'stroke': 'none'
184 | });
185 |
186 | /**
187 | * Draw action icon.
188 | */
189 | var icon = canvas.image(_self.settings.imagePath + _self.actions[i].icon);
190 |
191 | icon.attr({
192 | 'cursor': 'pointer',
193 | 'x': point.x - 10,
194 | 'y': point.y - 10,
195 | 'width': 21,
196 | 'height': 21,
197 | 'opacity': 0.5
198 | });
199 |
200 | /**
201 | * Bind events
202 | */
203 | var set = canvas.set([background, icon]);
204 |
205 | //Set hover state
206 | set.hover({'opacity': 1}, icon);
207 |
208 | //Bind click event
209 | set.click($.proxy(function(index){
210 | $(tree).trigger(_self.actions[index].event, [member]);
211 | }, set, i));
212 | }
213 | }
214 |
215 | /**
216 | * Draw transaprent background rect. which later
217 | * can be used to correctly handle actions overlay mouseout event.
218 | *
219 | * @param {Raphael.Paper} Raphael paper instance
220 | * @return {Raphael.Element} Raphael rect. element
221 | */
222 | this.drawBackground = function(canvas)
223 | {
224 | var background = canvas.rect();
225 |
226 | background.attr({
227 | 'stroke': 'none',
228 | 'fill': '#FFF',
229 | 'fill-opacity': 0,
230 | 'width': tree.container.width(),
231 | 'height': tree.container.height()
232 | });
233 |
234 | //Prevent background from overlaping tree elements
235 | background.toBack();
236 |
237 | //Bind events
238 | background.mouseover(function(){
239 | _self.hide();
240 | });
241 |
242 | return background;
243 | };
244 |
245 | //Make self reference
246 | _self = this;
247 |
248 | //Initialize object
249 | construct(options);
250 | };
251 |
252 | /**
253 | * Class constants.
254 | */
255 | Actions.PLUGIN_NAME = 'actions';
256 |
257 | return Actions;
258 |
259 | });
260 |
--------------------------------------------------------------------------------
/fskytree/view/node.js:
--------------------------------------------------------------------------------
1 | define(function(require, exports, module){
2 |
3 | var $ = require('jquery'),
4 | Family = require('fskytree/view/family'),
5 | Member = require('fskytree/view/member');
6 | Node = require('fskytree/view/node');
7 | Geometry = require('fskytree/component/geometry');
8 |
9 | /**
10 | * @class Node
11 | * @classdesc Family/Member encapsulation "class".
12 | */
13 | var Node = function(tree, data, options)
14 | {
15 | /**
16 | * ...
17 | * @type {Node}
18 | */
19 | var _self;
20 |
21 | /**
22 | * @type {Object} Object containing component setting values
23 | */
24 | this.settings = {
25 | debug: false,
26 | height: 180
27 | };
28 |
29 | /**
30 | * @type {Number} Node x position on canvas
31 | */
32 | this.x = 0;
33 |
34 | /**
35 | * @type {Number} Node y position on canvas
36 | */
37 | this.y = 0;
38 |
39 | /**
40 | * @type {Number} Node width
41 | */
42 | this.width;
43 |
44 | /**
45 | * @type {Number} Node height
46 | */
47 | this.height;
48 |
49 | /**
50 | * @type {Number} Children nodes total width
51 | */
52 | this.cwidth;
53 |
54 | /**
55 | * @type {Member|Family} Member or Family view
56 | */
57 | this.view;
58 |
59 | /**
60 | * @type {Node} Parent node
61 | */
62 | this.parent;
63 |
64 | /**
65 | * @type {Array} Child nodes
66 | */
67 | this.children = [];
68 |
69 | /**
70 | * @type {Geometry.Point} Node connection point
71 | */
72 | this.cPoint;
73 |
74 | /**
75 | * Constructor.
76 | *
77 | * @constructs Node
78 | */
79 | function construct(data, options)
80 | {
81 | //Extend loader settings with specified options
82 | $.extend(_self.settings, options);
83 |
84 | /**
85 | * Create family view if node data has "children" or "partner" property is set,
86 | * otherwise create single member node.
87 | */
88 | if (data.children || data.partner) {
89 | _self.view = new Family(_self, data);
90 | } else {
91 | _self.view = new Member(_self, data.member);
92 | }
93 | }
94 |
95 | /**
96 | * Get node width which can be used to calculate
97 | * node position on canvas.
98 | *
99 | * @returns {Number} Node width in pixels
100 | */
101 | this.getWidth = function()
102 | {
103 | return _self.view.getWidth();
104 | };
105 |
106 | /**
107 | * Get node height which can be used to calculate
108 | * node position on canvas.
109 | *
110 | * @returns {Number} Node height in pixels
111 | */
112 | this.getHeight = function()
113 | {
114 | return _self.settings.height;
115 | };
116 |
117 | /**
118 | * Get node connection point position.
119 | * This value will later be used to draw children group line.
120 | *
121 | * @returns {Geometry.Point} Object containing x, y properties
122 | */
123 | this.getCPoint = function(node)
124 | {
125 | //Get view middle point
126 | var mPoint = _self.view.getMPoint();
127 |
128 | return new Geometry.Point(
129 | mPoint.x,
130 | _self.y
131 | );
132 | };
133 |
134 | /**
135 | * Get tree instance.
136 | *
137 | * @returns {Tree} Tree instance
138 | */
139 | this.getTree = function()
140 | {
141 | return tree;
142 | };
143 |
144 | /**
145 | * Set current node parent.
146 | *
147 | * @param {Node} parent Parent node instance
148 | * @returns {Node} Current node instance
149 | */
150 | this.setParent = function(parent)
151 | {
152 | _self.parent = parent;
153 |
154 | return this;
155 | };
156 |
157 | /**
158 | * Add child node to current node children.
159 | *
160 | * @param {Node} child Child node instance
161 | * @returns {Node} Current node instance
162 | */
163 | this.addChild = function(child)
164 | {
165 | child.setParent(_self);
166 |
167 | _self.children.push(child);
168 |
169 | return this;
170 | };
171 |
172 | /**
173 | * Check whether current node has any children.
174 | *
175 | * @returns {Boolean} Whether current node has any children
176 | */
177 | this.hasChildren = function()
178 | {
179 | return _self.children.length > 0;
180 | };
181 |
182 | /**
183 | * Check whether current node has parent.
184 | *
185 | * @returns {Boolean} Whether current node has parent
186 | */
187 | this.hasParent = function()
188 | {
189 | return _self.parent != undefined;
190 | };
191 |
192 | /**
193 | * Get node type by view instance.
194 | *
195 | * @returns {Number} Node type
196 | */
197 | this.getType = function()
198 | {
199 | return (_self.view instanceof Family)? Node.TYPE_FAMILY : Node.TYPE_MEMBER;
200 | };
201 |
202 | /**
203 | * Render current node view.
204 | *
205 | * @param {Raphael.Paper} Canvas instance
206 | * @return {Node} Node instance
207 | */
208 | this.render = function(canvas)
209 | {
210 | //Draw parents line
211 | if (_self.hasParent()) {
212 | _self.drawParentsLine(canvas);
213 | }
214 |
215 | //Draw debug bounding box
216 | if (_self.settings.debug) {
217 | _self.drawBBox(canvas);
218 | }
219 |
220 | //Render node view elements
221 | _self.view.render(canvas);
222 |
223 | return this;
224 | };
225 |
226 | /**
227 | * Draw node bounding box.
228 | * This is used for debug purposes only.
229 | *
230 | * @param {Raphael.Paper} Raphael paper instance
231 | * @returns {Raphael.Element} Raphael path element
232 | */
233 | this.drawBBox = function(canvas)
234 | {
235 | var box = canvas.rect();
236 | var width = _self.getWidth();
237 | var height = _self.getHeight();
238 |
239 | box.attr({
240 | 'stroke-dasharray': '- ',
241 | 'x': _self.x + 0.5 - (width / 2),
242 | 'y': _self.y + 0.5,
243 | 'width': width,
244 | 'height': height,
245 | 'stroke': (_self.getType() == Node.TYPE_MEMBER)? '#000' : '#CECECE'
246 | });
247 |
248 | var label = canvas.text();
249 |
250 | label.attr({
251 | 'text-anchor': 'start',
252 | 'font-size': 9,
253 | 'fill': '#000',
254 | 'text': 'x:' + _self.x + '\ny:' + _self.y + '\nw:' + width,
255 | 'x': _self.x - (width / 2) + 5,
256 | 'y': _self.y + 20
257 | });
258 |
259 | return box;
260 | };
261 |
262 | /**
263 | * Draw parents connector line.
264 | *
265 | * @param {Raphael.Paper} Raphael paper instance
266 | * @returns {Raphael.Element} Raphael path element
267 | */
268 | this.drawParentsLine = function(canvas)
269 | {
270 | var line = canvas.path();
271 |
272 | var mPoint = _self.view.getMPoint();
273 | var cPoint = _self.getCPoint();
274 |
275 | line.attr({
276 | 'path': 'M' + mPoint.x + ' ' + mPoint.y + ', ' + cPoint.x + ' ' + cPoint.y,
277 | 'stroke': '#2B3F74',
278 | 'stroke-width': 2
279 | });
280 |
281 | return line;
282 | }
283 |
284 | //Make self reference
285 | _self = this;
286 |
287 | //Initialize object
288 | construct(data, options);
289 | };
290 |
291 | /**
292 | * Class constants.
293 | */
294 | Node.TYPE_MEMBER = 1;
295 | Node.TYPE_FAMILY = 2;
296 |
297 | return Node;
298 |
299 | });
300 |
--------------------------------------------------------------------------------
/vendor/scrollto.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * jQuery.ScrollTo
3 | * Copyright (c) 2007-2012 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
4 | * Dual licensed under MIT and GPL.
5 | * Date: 4/09/2012
6 | *
7 | * @projectDescription Easy element scrolling using jQuery.
8 | * http://flesler.blogspot.com/2007/10/jqueryscrollto.html
9 | * @author Ariel Flesler
10 | * @version 1.4.3.1
11 | *
12 | * @id jQuery.scrollTo
13 | * @id jQuery.fn.scrollTo
14 | * @param {String, Number, DOMElement, jQuery, Object} target Where to scroll the matched elements.
15 | * The different options for target are:
16 | * - A number position (will be applied to all axes).
17 | * - A string position ('44', '100px', '+=90', etc ) will be applied to all axes
18 | * - A jQuery/DOM element ( logically, child of the element to scroll )
19 | * - A string selector, that will be relative to the element to scroll ( 'li:eq(2)', etc )
20 | * - A hash { top:x, left:y }, x and y can be any kind of number/string like above.
21 | * - A percentage of the container's dimension/s, for example: 50% to go to the middle.
22 | * - The string 'max' for go-to-end.
23 | * @param {Number, Function} duration The OVERALL length of the animation, this argument can be the settings object instead.
24 | * @param {Object,Function} settings Optional set of settings or the onAfter callback.
25 | * @option {String} axis Which axis must be scrolled, use 'x', 'y', 'xy' or 'yx'.
26 | * @option {Number, Function} duration The OVERALL length of the animation.
27 | * @option {String} easing The easing method for the animation.
28 | * @option {Boolean} margin If true, the margin of the target element will be deducted from the final position.
29 | * @option {Object, Number} offset Add/deduct from the end position. One number for both axes or { top:x, left:y }.
30 | * @option {Object, Number} over Add/deduct the height/width multiplied by 'over', can be { top:x, left:y } when using both axes.
31 | * @option {Boolean} queue If true, and both axis are given, the 2nd axis will only be animated after the first one ends.
32 | * @option {Function} onAfter Function to be called after the scrolling ends.
33 | * @option {Function} onAfterFirst If queuing is activated, this function will be called after the first scrolling ends.
34 | * @return {jQuery} Returns the same jQuery object, for chaining.
35 | *
36 | * @desc Scroll to a fixed position
37 | * @example $('div').scrollTo( 340 );
38 | *
39 | * @desc Scroll relatively to the actual position
40 | * @example $('div').scrollTo( '+=340px', { axis:'y' } );
41 | *
42 | * @desc Scroll using a selector (relative to the scrolled element)
43 | * @example $('div').scrollTo( 'p.paragraph:eq(2)', 500, { easing:'swing', queue:true, axis:'xy' } );
44 | *
45 | * @desc Scroll to a DOM element (same for jQuery object)
46 | * @example var second_child = document.getElementById('container').firstChild.nextSibling;
47 | * $('#container').scrollTo( second_child, { duration:500, axis:'x', onAfter:function(){
48 | * alert('scrolled!!');
49 | * }});
50 | *
51 | * @desc Scroll on both axes, to different values
52 | * @example $('div').scrollTo( { top: 300, left:'+=200' }, { axis:'xy', offset:-20 } );
53 | */
54 | define(function(require, exports, module){
55 |
56 | var jQuery = require('jquery');
57 |
58 | ;(function( $ ){
59 |
60 | var $scrollTo = $.scrollTo = function( target, duration, settings ){
61 | $(window).scrollTo( target, duration, settings );
62 | };
63 |
64 | $scrollTo.defaults = {
65 | axis:'xy',
66 | duration: parseFloat($.fn.jquery) >= 1.3 ? 0 : 1,
67 | limit:true
68 | };
69 |
70 | // Returns the element that needs to be animated to scroll the window.
71 | // Kept for backwards compatibility (specially for localScroll & serialScroll)
72 | $scrollTo.window = function( scope ){
73 | return $(window)._scrollable();
74 | };
75 |
76 | // Hack, hack, hack :)
77 | // Returns the real elements to scroll (supports window/iframes, documents and regular nodes)
78 | $.fn._scrollable = function(){
79 | return this.map(function(){
80 | var elem = this,
81 | isWin = !elem.nodeName || $.inArray( elem.nodeName.toLowerCase(), ['iframe','#document','html','body'] ) != -1;
82 |
83 | if( !isWin )
84 | return elem;
85 |
86 | var doc = (elem.contentWindow || elem).document || elem.ownerDocument || elem;
87 |
88 | return /webkit/i.test(navigator.userAgent) || doc.compatMode == 'BackCompat' ?
89 | doc.body :
90 | doc.documentElement;
91 | });
92 | };
93 |
94 | $.fn.scrollTo = function( target, duration, settings ){
95 | if( typeof duration == 'object' ){
96 | settings = duration;
97 | duration = 0;
98 | }
99 | if( typeof settings == 'function' )
100 | settings = { onAfter:settings };
101 |
102 | if( target == 'max' )
103 | target = 9e9;
104 |
105 | settings = $.extend( {}, $scrollTo.defaults, settings );
106 | // Speed is still recognized for backwards compatibility
107 | duration = duration || settings.duration;
108 | // Make sure the settings are given right
109 | settings.queue = settings.queue && settings.axis.length > 1;
110 |
111 | if( settings.queue )
112 | // Let's keep the overall duration
113 | duration /= 2;
114 | settings.offset = both( settings.offset );
115 | settings.over = both( settings.over );
116 |
117 | return this._scrollable().each(function(){
118 | // Null target yields nothing, just like jQuery does
119 | if (target == null) return;
120 |
121 | var elem = this,
122 | $elem = $(elem),
123 | targ = target, toff, attr = {},
124 | win = $elem.is('html,body');
125 |
126 | switch( typeof targ ){
127 | // A number will pass the regex
128 | case 'number':
129 | case 'string':
130 | if( /^([+-]=)?\d+(\.\d+)?(px|%)?$/.test(targ) ){
131 | targ = both( targ );
132 | // We are done
133 | break;
134 | }
135 | // Relative selector, no break!
136 | targ = $(targ,this);
137 | if (!targ.length) return;
138 | case 'object':
139 | // DOMElement / jQuery
140 | if( targ.is || targ.style )
141 | // Get the real position of the target
142 | toff = (targ = $(targ)).offset();
143 | }
144 | $.each( settings.axis.split(''), function( i, axis ){
145 | var Pos = axis == 'x' ? 'Left' : 'Top',
146 | pos = Pos.toLowerCase(),
147 | key = 'scroll' + Pos,
148 | old = elem[key],
149 | max = $scrollTo.max(elem, axis);
150 |
151 | if( toff ){// jQuery / DOMElement
152 | attr[key] = toff[pos] + ( win ? 0 : old - $elem.offset()[pos] );
153 |
154 | // If it's a dom element, reduce the margin
155 | if( settings.margin ){
156 | attr[key] -= parseInt(targ.css('margin'+Pos)) || 0;
157 | attr[key] -= parseInt(targ.css('border'+Pos+'Width')) || 0;
158 | }
159 |
160 | attr[key] += settings.offset[pos] || 0;
161 |
162 | if( settings.over[pos] )
163 | // Scroll to a fraction of its width/height
164 | attr[key] += targ[axis=='x'?'width':'height']() * settings.over[pos];
165 | }else{
166 | var val = targ[pos];
167 | // Handle percentage values
168 | attr[key] = val.slice && val.slice(-1) == '%' ?
169 | parseFloat(val) / 100 * max
170 | : val;
171 | }
172 |
173 | // Number or 'number'
174 | if( settings.limit && /^\d+$/.test(attr[key]) )
175 | // Check the limits
176 | attr[key] = attr[key] <= 0 ? 0 : Math.min( attr[key], max );
177 |
178 | // Queueing axes
179 | if( !i && settings.queue ){
180 | // Don't waste time animating, if there's no need.
181 | if( old != attr[key] )
182 | // Intermediate animation
183 | animate( settings.onAfterFirst );
184 | // Don't animate this axis again in the next iteration.
185 | delete attr[key];
186 | }
187 | });
188 |
189 | animate( settings.onAfter );
190 |
191 | function animate( callback ){
192 | $elem.animate( attr, duration, settings.easing, callback && function(){
193 | callback.call(this, target, settings);
194 | });
195 | };
196 |
197 | }).end();
198 | };
199 |
200 | // Max scrolling position, works on quirks mode
201 | // It only fails (not too badly) on IE, quirks mode.
202 | $scrollTo.max = function( elem, axis ){
203 | var Dim = axis == 'x' ? 'Width' : 'Height',
204 | scroll = 'scroll'+Dim;
205 |
206 | if( !$(elem).is('html,body') )
207 | return elem[scroll] - $(elem)[Dim.toLowerCase()]();
208 |
209 | var size = 'client' + Dim,
210 | html = elem.ownerDocument.documentElement,
211 | body = elem.ownerDocument.body;
212 |
213 | return Math.max( html[scroll], body[scroll] )
214 | - Math.min( html[size] , body[size] );
215 | };
216 |
217 | function both( val ){
218 | return typeof val == 'object' ? val : { top:val, left:val };
219 | };
220 |
221 | })( jQuery );
222 |
223 | return jQuery;
224 | });
225 |
--------------------------------------------------------------------------------
/fskytree/plugin/camomile.js:
--------------------------------------------------------------------------------
1 | define(function(require, exports, module){
2 |
3 | var $ = require('jquery'),
4 | Member = require('fskytree/view/member'),
5 | Node = require('fskytree/view/node'),
6 | Geometry = require('fskytree/component/geometry');
7 |
8 | /**
9 | * @class Camomile
10 | * @classdesc Camomile "class" is responsible for adding new tree members.
11 | */
12 | var Camomile = function(tree, options)
13 | {
14 | /**
15 | * ...
16 | * @type {Camomile} Camomile plugin instance
17 | */
18 | var _self;
19 |
20 | /**
21 | * @type {Object} Object containing plugin setting values
22 | */
23 | this.settings = {
24 | imageRadius: 70
25 | };
26 |
27 | /**
28 | * @type {Member} Target member view instance
29 | */
30 | this.member;
31 |
32 | /**
33 | * @type {Raphael.Paper} Camomile overlay canvas
34 | */
35 | this.canvas;
36 |
37 | /**
38 | * @type {Array} Array of available actions
39 | */
40 | this.actions;
41 |
42 | /**
43 | * @type {Geometry.Point} Camomile view middle point
44 | */
45 | this.mPoint;
46 |
47 | /**
48 | * Constructor.
49 | *
50 | * @constructs Loader
51 | */
52 | function construct(options)
53 | {
54 | //Extend loader settings with specified options
55 | $.extend(_self.settings, options);
56 |
57 | /**
58 | * Subscribe to tree "add member" event.
59 | * @todo If current member has a partner(wife/husband), switch to his tree before adding his relatives.
60 | */
61 | $(tree).on('tree/member/add', function(e, member){
62 | _self.setMember(member); //Set target member
63 | _self.initActions(); //Set available member actions
64 | _self.initCanvas(); //Create canvas
65 | _self.show(); //Show overlay
66 | });
67 |
68 | //Remove camomile when overlay is hidden
69 | $(tree.plugin.overlay).on('overlay/hide', function(){
70 | _self.canvas.remove();
71 | });
72 | }
73 |
74 | /**
75 | * Create camomile canvas.
76 | * This canvas will be used to draw camomile view itself.
77 | *
78 | * @return {Camomile} Camomile plugin instance
79 | */
80 | this.initCanvas = function()
81 | {
82 | _self.canvas = Raphael(tree.plugin.overlay.container[0], '100%', '100%');
83 |
84 | return this;
85 | };
86 |
87 | /**
88 | * Set all available camomile actions for current member.
89 | * Each member can have different actions combination, depending on
90 | * member sex, whether it has parents and so on...
91 | *
92 | * @return {Camomile} Camomile plugin instance
93 | */
94 | this.initActions = function()
95 | {
96 | //Define all available actions
97 | var wife = {label: 'добавить\nжену', event: 'tree/member/add/wife'};
98 | var husband = {label: 'добавить\nмужа', event: 'tree/member/add/husband'};
99 |
100 | var mother = {label: 'добавить\nмать', event: 'tree/member/add/mother'};
101 | var father = {label: 'добавить\nотца', event: 'tree/member/add/father'};
102 |
103 | var brother = {label: 'добавить\nбрата', event: 'tree/member/add/brother'};
104 | var sister = {label: 'добавить\nсестру', event: 'tree/member/add/sister'};
105 |
106 | var son = {label: 'добавить\nсына', event: 'tree/member/add/son'};
107 | var dughter = {label: 'добавить\nдочь', event: 'tree/member/add/daughter'};
108 |
109 |
110 | //Add son and daughter actions
111 | _self.actions = [dughter,son];
112 |
113 |
114 | //Check if member has parents
115 | if (_self.member.node.hasParent()) {
116 |
117 | //Add brother and sister actions
118 | _self.actions.push(brother, sister);
119 |
120 | //Add mother or father actions depending on parent partner sex
121 | if (_self.member.node.parent.view.partner.status == Member.STATUS_INACTIVE) {
122 | _self.actions.push((_self.member.node.parent.view.member.sex == Member.SEX_MALE)? mother : father);
123 | }
124 |
125 | } else {
126 | //Add both parents actions
127 | _self.actions.push(mother, father);
128 | }
129 |
130 |
131 | //@todo This is way to fucking long statement, make it shorter!
132 | if ((_self.member.node.view.partner && _self.member.node.view.partner.status == Member.STATUS_INACTIVE) || _self.member.node.getType() == Node.TYPE_MEMBER) {
133 | _self.actions.push((_self.member.sex == Member.SEX_MALE)? wife : husband);
134 | }
135 |
136 |
137 | return this;
138 | };
139 |
140 | /**
141 | * Set current member.
142 | *
143 | * @return {Camomile} Camomile plugin instance
144 | */
145 | this.setMember = function(member)
146 | {
147 | _self.member = member;
148 |
149 | return this;
150 | };
151 |
152 | /**
153 | * Get camomile view middle point.
154 | * This value will later be used to position camomile view elements.
155 | *
156 | * @returns {Geometry.Point} Object containing x, y properties
157 | */
158 | this.getMPoint = function()
159 | {
160 | //Cache middle point
161 | if (!_self.mPoint) {
162 |
163 | var offset = tree.wrapper.offset();
164 |
165 | _self.mPoint = new Geometry.Point(
166 | offset.left + (tree.wrapper.width() / 2),
167 | offset.top + (tree.wrapper.height() / 2)
168 | );
169 | }
170 |
171 | return _self.mPoint;
172 | };
173 |
174 | /**
175 | * Show camomile layer.
176 | *
177 | * @return {Camomile} Camomile plugin instance
178 | */
179 | this.show = function()
180 | {
181 | /**
182 | * Scroll to target member first, then
183 | * show overlay & render camomile elements.
184 | */
185 | tree.scrolling.scrollTo(_self.member.node, function(){
186 | tree.plugin.overlay.show();
187 | _self.render(_self.canvas);
188 | });
189 |
190 | return this;
191 | };
192 |
193 | /**
194 | * Hide camomile layer.
195 | *
196 | * @return {Camomile} Camomile plugin instance
197 | */
198 | this.hide = function()
199 | {
200 | //Hide overlay
201 | tree.plugin.overlay.hide();
202 |
203 | return this;
204 | };
205 |
206 | /**
207 | * Draw camomile on canvas.
208 | *
209 | * @param {Raphael.Paper} Raphael paper instance
210 | * @return {Camomile} Camomile plugin instance
211 | */
212 | this.render = function(canvas)
213 | {
214 | //Draw member image
215 | $(_self)
216 | .clearQueue()
217 | .queue(_self.drawImage)
218 | .queue(_self.drawArc)
219 | .queue(_self.drawCamomile);
220 |
221 | return this;
222 | };
223 |
224 | /**
225 | * Draw member image.
226 | *
227 | * @param {Function} Call this function to fire next callback in queue
228 | * @returns {Raphael.Element} Raphael circle element
229 | */
230 | this.drawImage = function(next)
231 | {
232 | /**
233 | * Draw image.
234 | */
235 | var mPoint = _self.getMPoint();
236 | var image = _self.canvas.circle();
237 |
238 | image.attr({
239 | 'r': _self.member.settings.imageRadius,
240 | 'stroke': 'none',
241 | 'fill': 'url("' + _self.member.getImage() + '")',
242 | 'cx': mPoint.x,
243 | 'cy': mPoint.y
244 | });
245 |
246 | image.animate({'r': _self.settings.imageRadius}, 300, null, next);
247 |
248 | return image;
249 | }
250 |
251 | /**
252 | * Draw member image arc.
253 | *
254 | * @param {Function} Call this function to fire next callback in queue
255 | * @returns {Raphael.Element} Raphael path element
256 | */
257 | this.drawArc = function(next)
258 | {
259 | /**
260 | * Draw arc.
261 | */
262 | var mPoint = _self.getMPoint();
263 | var angle = 360 / _self.actions.length;
264 |
265 | var arc = _self.canvas.arc(mPoint.x, mPoint.y, _self.settings.imageRadius + 15, angle);
266 |
267 | arc.attr({
268 | 'stroke-width': 3,
269 | 'stroke': '#6F8BCA',
270 | 'opacity': 0
271 | });
272 |
273 | arc.rotate(angle / 2);
274 | arc.animate({'opacity': 1}, 200, null, next);
275 |
276 | return arc;
277 | }
278 |
279 | /**
280 | * Draw camomile actions(leafs).
281 | *
282 | * @param {Function} Call this function to fire next callback in queue
283 | */
284 | this.drawCamomile = function(next)
285 | {
286 | var p1, p2;
287 |
288 | var leafSet = _self.canvas.set();
289 | var mPoint = _self.getMPoint();
290 | var angle = 360 / _self.actions.length;
291 |
292 | //Draw all available actions
293 | for (var i=0;i<_self.actions.length;i++) {
294 |
295 | p1 = Geometry.circlePoint((angle * i) + (angle / 2), 89, new Geometry.Point(mPoint.x, mPoint.y));
296 | p2 = Geometry.circlePoint((angle * i) + (angle / 2), 170, new Geometry.Point(mPoint.x, mPoint.y));
297 |
298 | /**
299 | * Draw action(leaf) connection line.
300 | */
301 | var line = _self.canvas.path();
302 |
303 | line.attr({
304 | 'path': 'M' + p1.x + ' ' + p1.y + 'L' + p2.x + ' ' + p2.y,
305 | 'stroke-dasharray': '.',
306 | 'stroke-width': 2,
307 | 'stroke': '#6F8BCA',
308 | 'opacity': 0
309 | });
310 |
311 | /**
312 | * Draw camomile action(leaf).
313 | */
314 | var leaf = _self.canvas.circle();
315 |
316 | leaf.attr({
317 | 'cursor': 'pointer',
318 | 'cx': p2.x,
319 | 'cy': p2.y,
320 | 'r': 45,
321 | 'fill': '#6F8BCA',
322 | 'stroke': 'none',
323 | 'opacity': 0
324 | });
325 |
326 | /**
327 | * Draw camomile action(leaf) label.
328 | */
329 | var label = _self.canvas.text();
330 |
331 | label.attr({
332 | 'cursor': 'pointer',
333 | 'text': _self.actions[i].label,
334 | 'x': p2.x,
335 | 'y': p2.y,
336 | 'fill': '#FFF',
337 | 'font-size': 13,
338 | 'opacity': 0
339 | });
340 |
341 | //Add leaf elements to set
342 | leafSet.push(leaf);
343 | leafSet.push(label);
344 | leafSet.push(line);
345 |
346 | /**
347 | * Bind events.
348 | */
349 | var set = _self.canvas.set([leaf, label]);
350 |
351 | //Set hover state
352 | set.hover({'fill': '#2B3F74'}, leaf);
353 |
354 | //Bind click event
355 | set.click($.proxy(function(index){
356 | $(tree).trigger(_self.actions[index].event, [_self.member]);
357 | }, set, i));
358 | }
359 |
360 | //Fade in camomile actions
361 | leafSet.animate({'opacity': 1}, 200, null, next);
362 | }
363 |
364 | //Make self reference
365 | _self = this;
366 |
367 | //Initialize object
368 | construct(options);
369 | };
370 |
371 | /**
372 | * Class constants.
373 | */
374 | Camomile.PLUGIN_NAME = 'camomile';
375 |
376 | return Camomile;
377 |
378 | });
379 |
--------------------------------------------------------------------------------
/fskytree/view/member.js:
--------------------------------------------------------------------------------
1 | define(function(require, exports, module){
2 |
3 | var $ = require('jquery'),
4 | Member = require('fskytree/view/member');
5 | Node = require('fskytree/view/node');
6 | Geometry = require('fskytree/component/geometry');
7 |
8 | /**
9 | * @class Member
10 | * @classdesc This "class" function contains methods for rendering member node.
11 | */
12 | var Member = function(node, data, options)
13 | {
14 | /**
15 | * ...
16 | * @type {Member}
17 | */
18 | var _self;
19 |
20 | /**
21 | * @type {Object} Object containing current object setting values
22 | */
23 | this.settings = {
24 | imagePath: 'img/',
25 | yearChar: 'г',
26 | width: 160,
27 | imageRadius: 45
28 | };
29 |
30 | /**
31 | * ...
32 | * @type {String} Member id
33 | */
34 | this.id;
35 |
36 | /**
37 | * ...
38 | * @type {String} Member image/photo
39 | */
40 | this.image;
41 |
42 | /**
43 | * @type {String} Member firstname
44 | */
45 | this.firstname;
46 |
47 | /**
48 | * @type {String} Member lastname
49 | */
50 | this.lastname;
51 |
52 | /**
53 | * @type {Number} Member birthyear
54 | */
55 | this.birthyear;
56 |
57 | /**
58 | * @type {Number} Member deathyear
59 | */
60 | this.deathyear;
61 |
62 | /**
63 | * @type {Number} Member relatives count
64 | */
65 | this.relatives = 0;
66 |
67 | /**
68 | * @type {Number} Member status
69 | */
70 | this.status = Member.STATUS_ACTIVE;
71 |
72 | /**
73 | * @type {Number} Member sex
74 | */
75 | this.sex = Member.SEX_MALE;
76 |
77 | /**
78 | * @type {Boolean} Is member alive?
79 | */
80 | this.alive = true;
81 |
82 | /**
83 | * @type {Boolean} Is this member currently selected?
84 | */
85 | this.current = false;
86 |
87 | /**
88 | * @type {Geometry.Point} Member view middle point
89 | */
90 | this.mPoint;
91 |
92 | /**
93 | * @type {Raphael.Element} Raphael element instance
94 | */
95 | this.imageEl;
96 |
97 | /**
98 | * @type {Node} Node instance
99 | */
100 | this.node = node;
101 |
102 | /**
103 | * Constructor.
104 | *
105 | * @constructs Member
106 | */
107 | function construct(data, options)
108 | {
109 | //Extend member settings with specified options
110 | $.extend(_self.settings, options);
111 |
112 | //Populate public properites
113 | $.extend(_self, data);
114 | }
115 |
116 | /**
117 | * Draw current view on canvas.
118 | *
119 | * @param {Raphael.Paper} Raphael paper instance
120 | * @return {Member} Member view instance
121 | */
122 | this.render = function(canvas)
123 | {
124 | /**
125 | * Draw and set image element.
126 | * It will later be used to calculate and draw family view children line.
127 | */
128 | _self.imageEl = _self.drawImage(canvas);
129 |
130 | //Draw member label
131 | _self.drawLabel(canvas);
132 |
133 | //Draw overlay indicating that member is dead
134 | if (!_self.alive) {
135 | _self.drawDeathSector(canvas);
136 | }
137 |
138 | //Draw relatives count label
139 | if (_self.relatives > 0) {
140 | _self.drawRelatives(canvas);
141 | }
142 |
143 | return this;
144 | };
145 |
146 | /**
147 | * Get member width, which can later be used to calculate
148 | * owner node position on canvas.
149 | *
150 | * @returns {Number} Node width in pixels
151 | */
152 | this.getWidth = function()
153 | {
154 | return _self.settings.width;
155 | };
156 |
157 | /**
158 | * Get node view middle point position.
159 | * This value will later be used to position node view elements.
160 | *
161 | * @returns {Geometry.Point} Object containing x, y properties
162 | */
163 | this.getMPoint = function()
164 | {
165 | //Cache mPoint value for better performance
166 | if (!_self.mPoint) {
167 |
168 | var x = node.x;
169 | var y = node.y + _self.settings.imageRadius + 30;
170 |
171 | if (node.getType() == Node.TYPE_FAMILY) {
172 | x = x + 40 * ((_self.sex == Member.SEX_MALE)? -1 : 1);
173 | }
174 |
175 | _self.mPoint = new Geometry.Point(x, y);
176 | }
177 |
178 | return _self.mPoint;
179 | };
180 |
181 | /**
182 | * Get member biography string, including:
183 | * firstname, lastname and life years.
184 | *
185 | * @returns {String} Biography string
186 | */
187 | this.getBio = function()
188 | {
189 | return [
190 | _self.firstname,
191 | _self.lastname,
192 | _self.getLifeYears()
193 | ].join('\n');
194 | };
195 |
196 | /**
197 | * Get member full life years.
198 | * If deayth year is undefined then only bithyear will be returned.
199 | *
200 | * @returns {String} Life years string
201 | */
202 | this.getLifeYears = function()
203 | {
204 | var lifeyears = null;
205 |
206 | if (_self.birthyear != undefined) {
207 | lifeyears = _self.birthyear + _self.settings.yearChar + '.'
208 | }
209 |
210 | if (_self.deathyear != undefined) {
211 | lifeyears + ' - ' + _self.deathyear + _self.settings.yearChar + '.';
212 | }
213 |
214 | return lifeyears;
215 | };
216 |
217 | /**
218 | * Get member image/photo.
219 | *
220 | * @returns {String} Image path
221 | */
222 | this.getImage = function()
223 | {
224 | if (_self.status == Member.STATUS_INACTIVE) {
225 | return _self.settings.imagePath + 'invite.png';
226 | }
227 |
228 | return _self.image;
229 | };
230 |
231 | /**
232 | * Draw member image shape.
233 | *
234 | * @param {Raphael.Paper} Raphael paper instance
235 | * @returns {Raphael.Element} Raphael circle element
236 | */
237 | this.drawImage = function(canvas)
238 | {
239 | var image = canvas.circle();
240 | var mPoint = _self.getMPoint();
241 |
242 | /**
243 | * Draw image element.
244 | */
245 | image.attr({
246 | 'cursor': 'pointer',
247 | 'stroke-width': 3,
248 | 'stroke': (!_self.alive)? '#404040' : 'none',
249 | 'fill': 'url("' + _self.getImage() + '")',
250 | 'r': _self.settings.imageRadius,
251 | 'cx': mPoint.x,
252 | 'cy': mPoint.y
253 | });
254 |
255 | if (_self.current) {
256 | image.attr({
257 | 'stroke': '#6F8BCA',
258 | 'r': _self.settings.imageRadius - 1
259 | });
260 | }
261 |
262 | /**
263 | * Bind events.
264 | */
265 | if (_self.status == Member.STATUS_INACTIVE) {
266 |
267 | //Set hover attributes
268 | image.attr({'fill-opacity': 0.8});
269 | image.hover({'fill-opacity': 1});
270 | }
271 |
272 | /**
273 | * Fetch tree reference
274 | * and pass upcoming image events to tree.
275 | */
276 | var tree = node.getTree();
277 |
278 | image.click(function(){
279 | $(tree).trigger('tree/member/click', [_self]);
280 | });
281 |
282 | //Bind hover event only on active members
283 | if (_self.status == Member.STATUS_ACTIVE) {
284 | image.mouseover(function(){
285 | $(tree).trigger('tree/member/hover', [_self]);
286 | });
287 | }
288 |
289 | return image;
290 | };
291 |
292 | /**
293 | * Draw death sector.
294 | *
295 | * @param {Raphael.Paper} Raphael paper instance
296 | * @returns {Raphael.Element} Raphael path element
297 | */
298 | this.drawDeathSector = function(canvas)
299 | {
300 | var sector = canvas.path();
301 |
302 | var radius = _self.settings.imageRadius;
303 | var mPoint = _self.getMPoint();
304 |
305 | sector.attr({
306 | 'path': 'M '
307 | + (mPoint.x + (radius - 2)) + ','
308 | + (mPoint.y + (radius / 3) - 7)
309 | + ' A ' + (radius - 2) +',' + (radius - 2) + ' 0 0,1 '
310 | + (mPoint.x - (radius / 2)) + ','
311 | + (mPoint.y + (radius - 7)) + ' z',
312 | 'stroke': 'none',
313 | 'fill': '#000',
314 | 'opacity': 0.75
315 | });
316 |
317 | return sector;
318 | }
319 |
320 | /**
321 | * Draw member label.
322 | *
323 | * @param {Raphael.Paper} Raphael paper instance
324 | * @returns {Raphael.Element} Raphael text element
325 | */
326 | this.drawLabel = function(canvas)
327 | {
328 | var label = canvas.text();
329 | var mPoint = _self.getMPoint();
330 |
331 | //Crete default label under the node
332 | label.attr({
333 | //'font-family': 'DejaVu Sans Light',
334 | 'font-size': 11,
335 | 'fill': '#465b86',
336 | 'text': _self.getBio()
337 | });
338 |
339 | //Verticaly align label by top
340 | label.attr({
341 | 'x': mPoint.x,
342 | 'y': mPoint.y + _self.settings.imageRadius + (label.getBBox().height / 2) + 10
343 | });
344 |
345 | //Change label position if node has children
346 | if (node.hasChildren()) {
347 |
348 | /**
349 | * Put label on top of the node if member doesn't have parents,
350 | * otherwise put it aside depending on member sex.
351 | */
352 | if (node.hasParent()) {
353 | label.attr({
354 | 'x': mPoint.x + (_self.settings.imageRadius + (label.getBBox().width / 2) + 10) * ((_self.sex == Member.SEX_MALE)? -1 : 1),
355 | 'y': mPoint.y
356 | });
357 | } else {
358 | label.attr({
359 | 'y': mPoint.y - _self.settings.imageRadius + (label.getBBox().height / 2) - 30 - 10
360 | });
361 | }
362 | }
363 |
364 | //Set black color for label if member is dead
365 | if (!_self.alive) {
366 | label.attr('fill', '#000');
367 | }
368 |
369 | return label;
370 | }
371 |
372 | /**
373 | * Draw member relatives label.
374 | *
375 | * @param {Raphael.Paper} Raphael paper instance
376 | * @returns {Raphael.Element} Raphael text element
377 | */
378 | this.drawRelatives = function(canvas)
379 | {
380 | /**
381 | * Draw relative background circle.
382 | */
383 | var wrapper = canvas.circle();
384 | var mPoint = _self.getMPoint();
385 |
386 | wrapper.attr({
387 | 'stroke': 'none',
388 | 'fill': '#6F8BCA',
389 | 'cursor': 'pointer',
390 | 'cx': mPoint.x + (_self.settings.imageRadius / 1.2) * ((_self.sex == Member.SEX_MALE)? -1 : 1),
391 | 'cy': mPoint.y + (_self.settings.imageRadius / 1.2),
392 | 'r': 22
393 | });
394 |
395 | /**
396 | * Draw relatives count number.
397 | */
398 | var label = canvas.text();
399 |
400 | label.attr({
401 | 'fill': '#FFF',
402 | 'cursor': 'pointer',
403 | 'font-size': 18,
404 | 'font-weight': 'bold',
405 | 'font-family': 'Verdana',
406 | 'x': wrapper.attr('cx'),
407 | 'y': wrapper.attr('cy'),
408 | 'text': '+' + _self.relatives
409 | });
410 |
411 | /**
412 | * Bind events.
413 | */
414 | var set = canvas.set([wrapper, label])
415 |
416 | set.hover({
417 | 'fill': '#2B3F74',
418 | 'r': 24
419 | }, wrapper);
420 |
421 | /**
422 | * Fetch tree reference
423 | * and pass upcoming image events to tree.
424 | */
425 | var tree = node.getTree();
426 |
427 | set.click(function(){
428 | $(tree).trigger('tree/member/relatives', [_self]);
429 | });
430 |
431 |
432 | return wrapper;
433 | }
434 |
435 | //Make self reference
436 | _self = this;
437 |
438 | //Initialize object
439 | construct(data, options);
440 | };
441 |
442 | /**
443 | * Class constants.
444 | */
445 | Member.SEX_MALE = 1;
446 | Member.SEX_FEMALE = 2;
447 |
448 | Member.STATUS_ACTIVE = 1;
449 | Member.STATUS_INACTIVE = 2;
450 |
451 | return Member;
452 |
453 | });
454 |
--------------------------------------------------------------------------------
/vendor/require.js:
--------------------------------------------------------------------------------
1 | /*
2 | RequireJS 2.1.9 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
3 | Available via the MIT or new BSD license.
4 | see: http://github.com/jrburke/requirejs for details
5 | */
6 | var requirejs,require,define;
7 | (function(Z){function H(b){return"[object Function]"===L.call(b)}function I(b){return"[object Array]"===L.call(b)}function y(b,c){if(b){var e;for(e=0;ethis.depCount&&!this.defined){if(H(m)){if(this.events.error&&this.map.isDefine||j.onError!==aa)try{d=i.execCb(c,m,b,d)}catch(e){a=e}else d=i.execCb(c,m,b,d);this.map.isDefine&&((b=this.module)&&void 0!==b.exports&&b.exports!==
19 | this.exports?d=b.exports:void 0===d&&this.usingExports&&(d=this.exports));if(a)return a.requireMap=this.map,a.requireModules=this.map.isDefine?[this.map.id]:null,a.requireType=this.map.isDefine?"define":"require",v(this.error=a)}else d=m;this.exports=d;if(this.map.isDefine&&!this.ignore&&(r[c]=d,j.onResourceLoad))j.onResourceLoad(i,this.map,this.depMaps);x(c);this.defined=!0}this.defining=!1;this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=
20 | !0)}}else this.fetch()}},callPlugin:function(){var a=this.map,b=a.id,e=n(a.prefix);this.depMaps.push(e);s(e,"defined",u(this,function(d){var m,e;e=this.map.name;var g=this.map.parentMap?this.map.parentMap.name:null,h=i.makeRequire(a.parentMap,{enableBuildCallback:!0});if(this.map.unnormalized){if(d.normalize&&(e=d.normalize(e,function(a){return c(a,g,!0)})||""),d=n(a.prefix+"!"+e,this.map.parentMap),s(d,"defined",u(this,function(a){this.init([],function(){return a},null,{enabled:!0,ignore:!0})})),
21 | e=l(p,d.id)){this.depMaps.push(d);if(this.events.error)e.on("error",u(this,function(a){this.emit("error",a)}));e.enable()}}else m=u(this,function(a){this.init([],function(){return a},null,{enabled:!0})}),m.error=u(this,function(a){this.inited=!0;this.error=a;a.requireModules=[b];F(p,function(a){0===a.map.id.indexOf(b+"_unnormalized")&&x(a.map.id)});v(a)}),m.fromText=u(this,function(d,c){var e=a.name,g=n(e),B=O;c&&(d=c);B&&(O=!1);q(g);t(k.config,b)&&(k.config[e]=k.config[b]);try{j.exec(d)}catch(ca){return v(A("fromtexteval",
22 | "fromText eval for "+b+" failed: "+ca,ca,[b]))}B&&(O=!0);this.depMaps.push(g);i.completeLoad(e);h([e],m)}),d.load(a.name,h,m,k)}));i.enable(e,this);this.pluginMaps[e.id]=e},enable:function(){T[this.map.id]=this;this.enabling=this.enabled=!0;y(this.depMaps,u(this,function(a,b){var c,d;if("string"===typeof a){a=n(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap);this.depMaps[b]=a;if(c=l(N,a.id)){this.depExports[b]=c(this);return}this.depCount+=1;s(a,"defined",u(this,function(a){this.defineDep(b,
23 | a);this.check()}));this.errback&&s(a,"error",u(this,this.errback))}c=a.id;d=p[c];!t(N,c)&&(d&&!d.enabled)&&i.enable(a,this)}));F(this.pluginMaps,u(this,function(a){var b=l(p,a.id);b&&!b.enabled&&i.enable(a,this)}));this.enabling=!1;this.check()},on:function(a,b){var c=this.events[a];c||(c=this.events[a]=[]);c.push(b)},emit:function(a,b){y(this.events[a],function(a){a(b)});"error"===a&&delete this.events[a]}};i={config:k,contextName:b,registry:p,defined:r,urlFetched:S,defQueue:G,Module:X,makeModuleMap:n,
24 | nextTick:j.nextTick,onError:v,configure:function(a){a.baseUrl&&"/"!==a.baseUrl.charAt(a.baseUrl.length-1)&&(a.baseUrl+="/");var b=k.pkgs,c=k.shim,d={paths:!0,config:!0,map:!0};F(a,function(a,b){d[b]?"map"===b?(k.map||(k.map={}),Q(k[b],a,!0,!0)):Q(k[b],a,!0):k[b]=a});a.shim&&(F(a.shim,function(a,b){I(a)&&(a={deps:a});if((a.exports||a.init)&&!a.exportsFn)a.exportsFn=i.makeShimExports(a);c[b]=a}),k.shim=c);a.packages&&(y(a.packages,function(a){a="string"===typeof a?{name:a}:a;b[a.name]={name:a.name,
25 | location:a.location||a.name,main:(a.main||"main").replace(ja,"").replace(ea,"")}}),k.pkgs=b);F(p,function(a,b){!a.inited&&!a.map.unnormalized&&(a.map=n(b))});if(a.deps||a.callback)i.require(a.deps||[],a.callback)},makeShimExports:function(a){return function(){var b;a.init&&(b=a.init.apply(Z,arguments));return b||a.exports&&ba(a.exports)}},makeRequire:function(a,f){function h(d,c,e){var g,k;f.enableBuildCallback&&(c&&H(c))&&(c.__requireJsBuild=!0);if("string"===typeof d){if(H(c))return v(A("requireargs",
26 | "Invalid require call"),e);if(a&&t(N,d))return N[d](p[a.id]);if(j.get)return j.get(i,d,a,h);g=n(d,a,!1,!0);g=g.id;return!t(r,g)?v(A("notloaded",'Module name "'+g+'" has not been loaded yet for context: '+b+(a?"":". Use require([])"))):r[g]}K();i.nextTick(function(){K();k=q(n(null,a));k.skipMap=f.skipMap;k.init(d,c,e,{enabled:!0});C()});return h}f=f||{};Q(h,{isBrowser:z,toUrl:function(b){var f,e=b.lastIndexOf("."),g=b.split("/")[0];if(-1!==e&&(!("."===g||".."===g)||1h.attachEvent.toString().indexOf("[native code"))&&!W?(O=!0,h.attachEvent("onreadystatechange",b.onScriptLoad)):(h.addEventListener("load",b.onScriptLoad,!1),h.addEventListener("error",
34 | b.onScriptError,!1)),h.src=e,K=h,C?x.insertBefore(h,C):x.appendChild(h),K=null,h;if(da)try{importScripts(e),b.completeLoad(c)}catch(l){b.onError(A("importscripts","importScripts failed for "+c+" at "+e,l,[c]))}};z&&!s.skipDataMain&&M(document.getElementsByTagName("script"),function(b){x||(x=b.parentNode);if(J=b.getAttribute("data-main"))return q=J,s.baseUrl||(D=q.split("/"),q=D.pop(),fa=D.length?D.join("/")+"/":"./",s.baseUrl=fa),q=q.replace(ea,""),j.jsExtRegExp.test(q)&&(q=J),s.deps=s.deps?s.deps.concat(q):
35 | [q],!0});define=function(b,c,e){var h,j;"string"!==typeof b&&(e=c,c=b,b=null);I(c)||(e=c,c=null);!c&&H(e)&&(c=[],e.length&&(e.toString().replace(la,"").replace(ma,function(b,e){c.push(e)}),c=(1===e.length?["require"]:["require","exports","module"]).concat(c)));if(O){if(!(h=K))P&&"interactive"===P.readyState||M(document.getElementsByTagName("script"),function(b){if("interactive"===b.readyState)return P=b}),h=P;h&&(b||(b=h.getAttribute("data-requiremodule")),j=E[h.getAttribute("data-requirecontext")])}(j?
36 | j.defQueue:R).push([b,c,e])};define.amd={jQuery:!0};j.exec=function(b){return eval(b)};j(s)}})(this);
37 |
--------------------------------------------------------------------------------
/vendor/kinetic.js:
--------------------------------------------------------------------------------
1 | define([
2 | 'jquery'
3 | ], function($) {
4 | return function($) {
5 | 'use strict';
6 |
7 | var DEFAULT_SETTINGS = {
8 | cursor: 'move',
9 | decelerate: true,
10 | triggerHardware: false,
11 | y: true,
12 | x: true,
13 | slowdown: 0.9,
14 | maxvelocity: 40,
15 | throttleFPS: 60,
16 | movingClass: {
17 | up: 'kinetic-moving-up',
18 | down: 'kinetic-moving-down',
19 | left: 'kinetic-moving-left',
20 | right: 'kinetic-moving-right'
21 | },
22 | deceleratingClass: {
23 | up: 'kinetic-decelerating-up',
24 | down: 'kinetic-decelerating-down',
25 | left: 'kinetic-decelerating-left',
26 | right: 'kinetic-decelerating-right'
27 | }
28 | },
29 | SETTINGS_KEY = 'kinetic-settings',
30 | ACTIVE_CLASS = 'kinetic-active';
31 | /**
32 | * Provides requestAnimationFrame in a cross browser way.
33 | * http://paulirish.com/2011/requestanimationframe-for-smart-animating/
34 | */
35 | if ( !window.requestAnimationFrame ) {
36 |
37 | window.requestAnimationFrame = ( function() {
38 |
39 | return window.webkitRequestAnimationFrame ||
40 | window.mozRequestAnimationFrame ||
41 | window.oRequestAnimationFrame ||
42 | window.msRequestAnimationFrame ||
43 | function( /* function FrameRequestCallback */ callback, /* DOMElement Element */ element ) {
44 | window.setTimeout( callback, 1000 / 60 );
45 | };
46 |
47 | }());
48 |
49 | }
50 |
51 | // add touch checker to jQuery.support
52 | $.support = $.support || {};
53 | $.extend($.support, {
54 | touch: "ontouchend" in document
55 | });
56 | var selectStart = function() { return false; };
57 |
58 | var decelerateVelocity = function(velocity, slowdown) {
59 | return Math.floor(Math.abs(velocity)) === 0 ? 0 // is velocity less than 1?
60 | : velocity * slowdown; // reduce slowdown
61 | };
62 |
63 | var capVelocity = function(velocity, max) {
64 | var newVelocity = velocity;
65 | if (velocity > 0) {
66 | if (velocity > max) {
67 | newVelocity = max;
68 | }
69 | } else {
70 | if (velocity < (0 - max)) {
71 | newVelocity = (0 - max);
72 | }
73 | }
74 | return newVelocity;
75 | };
76 |
77 | var setMoveClasses = function(settings, classes) {
78 | this.removeClass(settings.movingClass.up)
79 | .removeClass(settings.movingClass.down)
80 | .removeClass(settings.movingClass.left)
81 | .removeClass(settings.movingClass.right)
82 | .removeClass(settings.deceleratingClass.up)
83 | .removeClass(settings.deceleratingClass.down)
84 | .removeClass(settings.deceleratingClass.left)
85 | .removeClass(settings.deceleratingClass.right);
86 |
87 | if (settings.velocity > 0) {
88 | this.addClass(classes.right);
89 | }
90 | if (settings.velocity < 0) {
91 | this.addClass(classes.left);
92 | }
93 | if (settings.velocityY > 0) {
94 | this.addClass(classes.down);
95 | }
96 | if (settings.velocityY < 0) {
97 | this.addClass(classes.up);
98 | }
99 |
100 | };
101 |
102 | var stop = function($scroller, settings) {
103 | settings.velocity = 0;
104 | settings.velocityY = 0;
105 | settings.decelerate = true;
106 | if (typeof settings.stopped === 'function') {
107 | settings.stopped.call($scroller, settings);
108 | }
109 | };
110 |
111 | /** do the actual kinetic movement */
112 | var move = function($scroller, settings) {
113 | var scroller = $scroller[0];
114 | // set scrollLeft
115 | if (settings.x && scroller.scrollWidth > 0){
116 | scroller.scrollLeft = settings.scrollLeft = scroller.scrollLeft + settings.velocity;
117 | if (Math.abs(settings.velocity) > 0) {
118 | settings.velocity = settings.decelerate ?
119 | decelerateVelocity(settings.velocity, settings.slowdown) : settings.velocity;
120 | }
121 | } else {
122 | settings.velocity = 0;
123 | }
124 |
125 | // set scrollTop
126 | if (settings.y && scroller.scrollHeight > 0){
127 | scroller.scrollTop = settings.scrollTop = scroller.scrollTop + settings.velocityY;
128 | if (Math.abs(settings.velocityY) > 0) {
129 | settings.velocityY = settings.decelerate ?
130 | decelerateVelocity(settings.velocityY, settings.slowdown) : settings.velocityY;
131 | }
132 | } else {
133 | settings.velocityY = 0;
134 | }
135 |
136 | setMoveClasses.call($scroller, settings, settings.deceleratingClass);
137 |
138 | if (typeof settings.moved === 'function') {
139 | settings.moved.call($scroller, settings);
140 | }
141 |
142 | if (Math.abs(settings.velocity) > 0 || Math.abs(settings.velocityY) > 0) {
143 | // tick for next movement
144 | window.requestAnimationFrame(function(){ move($scroller, settings); });
145 | } else {
146 | stop($scroller, settings);
147 | }
148 | };
149 |
150 | var callOption = function(method, options) {
151 | var methodFn = $.kinetic.callMethods[method],
152 | args = Array.prototype.slice.call(arguments)
153 | ;
154 | if (methodFn) {
155 | this.each(function(){
156 | var opts = args.slice(1), settings = $(this).data(SETTINGS_KEY);
157 | opts.unshift(settings);
158 | methodFn.apply(this, opts);
159 | });
160 | }
161 | };
162 |
163 | var attachListeners = function($this, settings) {
164 | var element = $this[0];
165 | if ($.support.touch) {
166 | $this.bind('touchstart', settings.events.touchStart)
167 | .bind('touchend', settings.events.inputEnd)
168 | .bind('touchmove', settings.events.touchMove)
169 | ;
170 | } else {
171 | $this
172 | .mousedown(settings.events.inputDown)
173 | .mouseup(settings.events.inputEnd)
174 | .mousemove(settings.events.inputMove)
175 | ;
176 | }
177 | $this
178 | .click(settings.events.inputClick)
179 | .scroll(settings.events.scroll)
180 | .bind("selectstart", selectStart) // prevent selection when dragging
181 | .bind('dragstart', settings.events.dragStart);
182 | };
183 | var detachListeners = function($this, settings) {
184 | var element = $this[0];
185 | if ($.support.touch) {
186 | $this.unbind('touchstart', settings.events.touchStart)
187 | .unbind('touchend', settings.events.inputEnd)
188 | .unbind('touchmove', settings.events.touchMove);
189 | } else {
190 | $this
191 | .unbind('mousedown', settings.events.inputDown)
192 | .unbind('mouseup', settings.events.inputEnd)
193 | .unbind('mousemove', settings.events.inputMove)
194 | .unbind('scroll', settings.events.scroll);
195 | }
196 | $this.unbind('click', settings.events.inputClick)
197 | .unbind("selectstart", selectStart); // prevent selection when dragging
198 | $this.unbind('dragstart', settings.events.dragStart);
199 | };
200 |
201 | var initElements = function(options) {
202 | this
203 | .addClass(ACTIVE_CLASS)
204 | .each(function(){
205 |
206 | var self = this,
207 | $this = $(this);
208 |
209 | if ($this.data(SETTINGS_KEY)){
210 | return;
211 | }
212 |
213 | var settings = $.extend({}, DEFAULT_SETTINGS, options),
214 | xpos,
215 | prevXPos = false,
216 | ypos,
217 | prevYPos = false,
218 | mouseDown = false,
219 | scrollLeft,
220 | scrollTop,
221 | throttleTimeout = 1000 / settings.throttleFPS,
222 | lastMove,
223 | elementFocused
224 | ;
225 |
226 | settings.velocity = 0;
227 | settings.velocityY = 0;
228 |
229 | // make sure we reset everything when mouse up
230 | var resetMouse = function() {
231 | xpos = false;
232 | ypos = false;
233 | mouseDown = false;
234 | };
235 | $(document).mouseup(resetMouse).click(resetMouse);
236 |
237 | var calculateVelocities = function() {
238 | settings.velocity = capVelocity(prevXPos - xpos, settings.maxvelocity);
239 | settings.velocityY = capVelocity((prevYPos - ypos), settings.maxvelocity); // Changed to reverse direction
240 | };
241 | var useTarget = function(target) {
242 | if ($.isFunction(settings.filterTarget)) {
243 | return settings.filterTarget.call(self, target) !== false;
244 | }
245 | return true;
246 | };
247 | var start = function(clientX, clientY) {
248 | mouseDown = true;
249 | settings.velocity = prevXPos = 0;
250 | settings.velocityY = prevYPos = 0;
251 | xpos = clientX;
252 | ypos = clientY;
253 | };
254 | var end = function() {
255 | if (xpos && prevXPos && settings.decelerate === false) {
256 | settings.decelerate = true;
257 | calculateVelocities();
258 | xpos = prevXPos = mouseDown = false;
259 | move($this, settings);
260 | }
261 | };
262 |
263 | var inputmove = function(clientX, clientY) {
264 | if (!lastMove || new Date() > new Date(lastMove.getTime() + throttleTimeout)) {
265 | lastMove = new Date();
266 |
267 | if (mouseDown && (xpos || ypos)) {
268 | if (elementFocused) {
269 | $(elementFocused).blur();
270 | elementFocused = null;
271 | $this.focus();
272 | }
273 | settings.decelerate = false;
274 | settings.velocity = settings.velocityY = 0;
275 | $this[0].scrollLeft = settings.scrollLeft = settings.x ? $this[0].scrollLeft - (clientX - xpos) : $this[0].scrollLeft;
276 | $this[0].scrollTop = settings.scrollTop = settings.y ? $this[0].scrollTop - ((clientY - ypos)) : $this[0].scrollTop; // Changed to reverse directions
277 |
278 | prevXPos = xpos;
279 | prevYPos = ypos;
280 | xpos = clientX;
281 | ypos = clientY;
282 |
283 | calculateVelocities();
284 | setMoveClasses.call($this, settings, settings.movingClass);
285 |
286 | if (typeof settings.moved === 'function') {
287 | settings.moved.call($this, settings);
288 | }
289 | }
290 | }
291 | };
292 |
293 | // Events
294 | settings.events = {
295 | touchStart: function(e){
296 | var touch;
297 | if (useTarget(e.target)) {
298 | touch = e.originalEvent.touches[0];
299 | start(touch.clientX, touch.clientY);
300 | e.stopPropagation();
301 | }
302 | },
303 | touchMove: function(e){
304 | var touch;
305 | if (mouseDown) {
306 | touch = e.originalEvent.touches[0];
307 | inputmove(touch.clientX, touch.clientY);
308 | if (e.preventDefault) {e.preventDefault();}
309 | }
310 | },
311 | inputDown: function(e){
312 | if (useTarget(e.target)) {
313 | start(e.clientX, e.clientY);
314 | elementFocused = e.target;
315 | if (e.target.nodeName === 'IMG'){
316 | e.preventDefault();
317 | }
318 | e.stopPropagation();
319 | }
320 | },
321 | inputEnd: function(e){
322 | end();
323 | elementFocused = null;
324 | if (e.preventDefault) {e.preventDefault();}
325 | },
326 | inputMove: function(e) {
327 | if (mouseDown){
328 | inputmove(e.clientX, e.clientY);
329 | if (e.preventDefault) {e.preventDefault();}
330 | }
331 | },
332 | scroll: function(e) {
333 | if (typeof settings.moved === 'function') {
334 | settings.moved.call($this, settings);
335 | }
336 | if (e.preventDefault) {e.preventDefault();}
337 | },
338 | inputClick: function(e){
339 | if (Math.abs(settings.velocity) > 0) {
340 | e.preventDefault();
341 | return false;
342 | }
343 | },
344 | // prevent drag and drop images in ie
345 | dragStart: function(e) {
346 | if (elementFocused) {
347 | return false;
348 | }
349 | }
350 | };
351 |
352 | attachListeners($this, settings);
353 | $this.data(SETTINGS_KEY, settings)
354 | .css("cursor", settings.cursor);
355 |
356 | if (settings.triggerHardware) {
357 | $this.css({
358 | '-webkit-transform': 'translate3d(0,0,0)',
359 | '-webkit-perspective': '1000',
360 | '-webkit-backface-visibility': 'hidden'
361 | });
362 | }
363 | });
364 | };
365 |
366 | $.kinetic = {
367 | settingsKey: SETTINGS_KEY,
368 | callMethods: {
369 | start: function(settings, options){
370 | var $this = $(this);
371 | settings = $.extend(settings, options);
372 | if (settings) {
373 | settings.decelerate = false;
374 | move($this, settings);
375 | }
376 | },
377 | end: function(settings, options){
378 | var $this = $(this);
379 | if (settings) {
380 | settings.decelerate = true;
381 | }
382 | },
383 | stop: function(settings, options){
384 | var $this = $(this);
385 | stop($this, settings);
386 | },
387 | detach: function(settings, options) {
388 | var $this = $(this);
389 | detachListeners($this, settings);
390 | $this
391 | .removeClass(ACTIVE_CLASS)
392 | .css("cursor", "");
393 | },
394 | attach: function(settings, options) {
395 | var $this = $(this);
396 | attachListeners($this, settings);
397 | $this
398 | .addClass(ACTIVE_CLASS)
399 | .css("cursor", "move");
400 | },
401 | jumpTo: function(settings, options) {
402 | $(this).kinetic("stop");
403 | this.scrollLeft = options.x;
404 | this.scrollTop = options.y;
405 | }
406 | }
407 | };
408 | $.fn.kinetic = function(options) {
409 | if (typeof options === 'string') {
410 | callOption.apply(this, arguments);
411 | } else {
412 | initElements.call(this, options);
413 | }
414 | return this;
415 | };
416 |
417 | return $;
418 | }($);
419 | });
420 |
--------------------------------------------------------------------------------