├── pi.ico
├── filedLinker.jpg
├── package.json
├── composer.json
├── LICENCE
├── fieldsLinker.css
├── README.md
├── index.html
└── fieldsLinker.js
/pi.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhilippeMarcMeyer/FieldsLinker/HEAD/pi.ico
--------------------------------------------------------------------------------
/filedLinker.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PhilippeMarcMeyer/FieldsLinker/HEAD/filedLinker.jpg
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fieldslinker",
3 | "version": "1.0.4",
4 | "description": "jquery plugin to match file columns with DB columns by drawing arrows",
5 | "homepage": "https://philippemarcmeyer.github.io/#FieldsLinker",
6 | "repository": {
7 | "type": "git",
8 | "url": "git+https://github.com/PhilippeMarcMeyer/FieldsLinker.git"
9 | },
10 | "funding": "",
11 | "author": "Philippemarcmeyer",
12 | "license": "MIT",
13 | "scripts": {},
14 | "keywords": ["jquery", "plugins", "canvas", "list", "lines", "jquery-plugin"],
15 | "main": "fieldsLinker.js",
16 | "files": [
17 | "fieldsLinker.css",
18 | "fieldsLinker.js"
19 | ],
20 | "devDependencies": {},
21 | "semistandard": {
22 | "ignore": [],
23 | "globals": []
24 | },
25 | "bugs": {
26 | "url": "https://github.com/PhilippeMarcMeyer/FieldsLinker/issues"
27 | }
28 | }
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "philippemarcmeyer/fieldslinker",
3 | "description": "Designed for matching files headers to database fields during the process importing. Allows drawing links between elements of 2 lists.",
4 | "keywords": ["jquery", "plugins", "canvas", "list", "lines", "jquery-plugin"],
5 | "type": "library",
6 | "license": "MIT",
7 | "authors": [
8 | {
9 | "name": "Phillipe Marc Mayer",
10 | "email": "pmg.meyer@gmail.com",
11 | "homepage": "https://philippemarcmeyer.github.io",
12 | "role": "Maintainer"
13 | }
14 | ],
15 | "support": {
16 | "issues": "https://github.com/PhilippeMarcMeyer/FieldsLinker/issues",
17 | "source": "https://github.com/PhilippeMarcMeyer/FieldsLinker",
18 | "docs": "https://github.com/PhilippeMarcMeyer/FieldsLinker"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/LICENCE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 philippe MEYER
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 | © 2020 GitHub, Inc.
23 |
--------------------------------------------------------------------------------
/fieldsLinker.css:
--------------------------------------------------------------------------------
1 | .fieldsLinker div {
2 | font-weight: bold;
3 | }
4 |
5 | .fieldsLinker ul {
6 | list-style: none;
7 | margin-left: 0px;
8 | padding-left: 0px;
9 | font-weight: normal;
10 | }
11 |
12 | tr td {
13 | padding-left: 10px;
14 | }
15 |
16 | tr td:not(:last-child) {
17 | border-right: 1px solid grey;
18 | }
19 |
20 | .fieldsLinker li {
21 | border: solid 1px grey;
22 | padding: 5px 2px 5px 8px;
23 | margin-top: 7px;
24 | margin-bottom: 8px;
25 | user-select: none;
26 | -moz-user-select: none;
27 | -webkit-user-select: none;
28 | -ms-user-select: none;
29 | cursor: pointer;
30 | background-color: #fff;
31 | background: linear-gradient(#ffffff, #e9e9e9);
32 | color: black;
33 | border-radius: 0px;
34 | text-overflow: ellipsis;
35 | overflow: hidden;
36 | }
37 |
38 | .fieldsLinker li.selected {
39 | -webkit-filter: invert(100%);
40 | filter: invert(100%);
41 | }
42 |
43 | .fieldsLinker li[data-mandatory='true'] {
44 | background-color: #D7504C;
45 | background: linear-gradient(#D7504C, #C12F2B);
46 | color: white;
47 | }
48 |
49 | .fieldsLinker li.inactive {
50 | opacity: 0.5;
51 | }
52 |
53 | .fieldsLinker li.linkOver {
54 | border: solid 2px transparent; /*plus 1px*/
55 | padding: 4px 1px 4px 7px; /* minus 1px*/
56 | }
57 |
58 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # FieldsLinker
2 |
3 | Designed for matching files headers to database fields during the process importing.
4 | Allows drawing links between elements of 2 lists (headers of the file on the left, column names on the right)
5 | and getting back the result in a js object
6 |
7 | Given 2 lists : for instance one from a text import, the second listing the fields a db table
8 | the jquery plugin allows you to draw and save links between the 2 lists
9 |
10 | You can link on a one to one basis or on a one to many basis. Fields can be declared as mandatory the result reporting an error in case there are not filled.
11 |
12 | Store previous links and headers to automaticaly recognize files
13 | reload links if you need it with the existingLinks property :
14 |
15 | example :
16 | "existingLinks": [{ "from": "lastName", "to": "last_name" }, { "from": "firstName", "to": "first_name" }, { "from": "role", "to": "jobTitle" }]
17 |
18 | Side note : If FieldsLinker interest you, but you need it for other use case than file importation, check LinksMaker plugin which works the same but can allow more than 2 lists (you then compare lists two by two, by selecting them). this plugin works much the same as Fieldslinker and this one could evolve in any direction that meets your needs.
19 |
20 | ## Usage
21 |
22 | * param 1 : action
23 | * param 2 : input
24 |
25 | ### Available actions :
26 |
27 | * init :
28 |
29 | fieldLinks=$("#bonds").fieldsLinker("init",input);
30 |
31 |
32 | Example of input :
33 |
34 |
35 |
36 | var input = {
37 | "options": {
38 | "lineStyle": "square-ends",
39 | "buttonErase": "Erase Links"
40 | },
41 | "listA": {
42 | "name": "columns in files",
43 | "list": [
44 | "firstName",
45 | "lastName",
46 | "phone",
47 | "email",
48 | "role",
49 | "Birthday",
50 | "Adress",
51 | "Sales"
52 | ]
53 | },
54 | "listB": {
55 | "name": "Fields available",
56 | "list": [
57 | "Id",
58 | "Company",
59 | "jobTitle",
60 | "adress 1",
61 | "adress 2",
62 | "first_name",
63 | "last_name",
64 | "email_adress",
65 | "Phone number"
66 | ]
67 | },
68 | "existingLinks": [{
69 | "from": "lastName",
70 | "to": "last_name"
71 | }, {
72 | "from": "firstName",
73 | "to": "first_name"
74 | }, {
75 | "from": "role",
76 | "to": "jobTitle"
77 | }]
78 |
79 | }
80 |
81 |
82 | Available input.options for "init" action :
83 | * options.className : you may change the default className ('fieldsLinker') by another class you provide
84 | * options.whiteSpace : default is 'nowrap' => you can change it to 'normal' to allow lists element to go to the next line (usefull if you have long names separated by spaces or ][)
85 | * lineStyle : 'straight' or 'square-ends' : will change the way the links are drawn between the items of the 2 lists, default is 'straight'
86 | * associationMode : 'oneToOne' or 'manyToMany' : allow links to be set on a one to one relationship or many to many
87 | * handleColor : your own colors default is '#CF0000,#00AD00,#0000AD,#FF4500,#00ADAD,#AD00AD,#582900,#FFCC00,#000000,#33FFCC'
88 | * mobileClickIt : true or false : simpler mode on mobile device where you click on item A and then click on item B to set the link (mobileClickIt option is automatic on touch devices)
89 |
90 |
91 | * changeParameters :
92 | example :
93 |
94 | fieldLinks.fieldsLinker("changeParameters",{"whiteSpace":"normal"});
95 |
96 | where the second parameter holds an options object to modify the original input.options attribute
97 |
98 | * eraseLinks : drop all links
99 |
100 | * getLinks : returns all the links
101 |
102 | * disable : disables the whole stuff
103 |
104 | * enable : enables it again
105 |
106 |
107 | https://philippemarcmeyer.github.io/FieldsLinker/index.html
108 |
109 | Also available on https://packagist.org/packages/philippemarcmeyer/fieldslinker ++++
110 |
111 | ### v1.04 : Just a wording fix for npm
112 |
113 | ### v1.03 : bug fix : no drag and drop when FieldsLinker is disabled
114 |
115 | ### v1.02 : Improvement : adding an id to the root dom element if not provided
116 |
117 | ### v1.01 : Bug correction : the manual sorting by drap and drop now works
118 |
119 | ### v1.00 : Jquery 3.5 Compatibility
120 |
121 | ### v0.96 : Removing filter option and alternateview
122 | Sorry for the contributors : Fieldlinkers was diverging too much from the original intent and use case : to be a filter between a file to import and a database
123 |
124 | I made another plugin LinksMaker, which works the same but can allow more than 2 lists (you then compare lists two by two, by selecting them)
125 |
126 | this plugin works much the same as Fieldslinker
127 |
128 | this one could evolve in any direction !
129 |
130 | ### v0.95 : rewritten for multiples instances in mind
131 |
132 | ### v0.92
133 |
134 | Introducing new option : whiteSpace
135 | normal,nowrap,pre,pre-wrap,pre-line,break-spaces default => nowrap
136 |
137 | ### v0.91
138 |
139 | Fix `mobileClickIt` option:
140 |
141 | - if set to `true`, will use mobile mode, regardless of touch screen mode.
142 | - fix crash modeB `null` value
143 | - if selected (clicked), use CSS to invert element as visual feedback.
144 | - mobileClickIt option is automatic on touch devices
145 |
146 | ### v0.90
147 | Canvas calculations fixes and other various fixes.
148 | Filter mode (removed in v 0.96)
149 |
150 | ### v0.89
151 |
152 | 
153 |
154 | v 0.89 : Corrected a bug that corrupted the links array of objects detected by flartet on github
155 |
156 | v 0.88 : New display mode : idea by Naveen nsirangu => show links between two "tables" linked by ids like a join in sql. instead of headers names, objects are provided (removed in v 0.96)
157 |
158 | v 0.87 : New option for touch devices {"mobileClickIt":true} : idea by Norman Tomlins => make links more easily on touch devices just by clicking
159 |
160 | v 0.86 : Modifications if Bootstrap is not available : tooltip are basic html titles and the links are horizontally centered
161 |
162 | v 0.85 : Mobile friendly (requested by a user)
163 |
164 | v 0.80 : I've been cleaning code in order to get something much simpler (nearly the same code than LinksMaker) it allowed me to implement drag and drop to reorder the items in the lists, which is convenient expecially if you've got long lists.
165 | options dropped in this version : autoDetect and byName : no more autodetect and the links are given back by name only not by order.
166 | the link over effect has also been dropped. Important : oneToMany:"on|off" becomes associationMode: "oneToOne|manyToMany"
167 | So you don't have one to many relation + one to one, but instead many to many and one to one relations.
168 |
169 | v 0.72 : [Cancelled] New lineStyle : square-ends-dotted : white dots at the beginings and ends of lines on hover
170 |
171 | v 0.60 :
172 | Mandatory fields show a tooltip (mandatoryErrorMessage)
173 | disable/enable : disable/enable everything, the global opacity is set to 0.5
174 |
175 | v 0.45 : Mandatory fields. the css is now in his own file.
176 |
177 | v 0.41 : the colors don't change when links are deleted and the color of the link while drawing is consistent with the final result
178 |
179 | v 0.4 : new option allowing to link one to many
180 |
181 | v 0.3 :
182 | FieldsLinker becomes responsive !
183 |
184 | v 0.2 :
185 | You can choose beetween an output with positions or names
186 | Colours can be re-defined and lines come in 2 flavours
187 | You may input somme links from a previous session
188 | Auto-detect feature helps you find part of the links (optional)
189 | Optional "Erase Links" button
190 | Works in Chrome, Chromium, Opera, Firefox and IE (9+)
191 |
192 | v 0.1 : the lines are drawn while dragging over the canvas and the horizontal middle points of the cells are calculated
193 | no parameters anymore
194 |
195 | Tested on : Chrome, Firefox, Chromium, IE, Opéra : OK for Chrome, Firefox and Opera
196 | todo : horizontal middle points of the cells are wrong on IE
197 | and on chromium the canvas zone (between the 2 lists) is selected during the dragging process which is not aesthetic.
198 |
199 | v0.01 : first commit : todo => parameters should not be necessary cellHeight,List1Width,canvasWidth,List2Width and should be calculated
200 |
201 | https://philippemarcmeyer.github.io/FieldsLinker/index.html
202 |
203 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | FieldsLinker
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
187 |
188 |
189 |
190 |
205 |
206 |
207 |
208 |
209 |
210 |
FieldsLinker v 1.04
211 |
New option for touch devices {"mobileClickIt":true} : idea by Norman Tomlins , make links more easily on touch devices just by clicking
212 |
Allows to link elements of list A to elements of list B and returns the links in a js object
213 |
214 | The elements of a list are sortable by drag and drop for a better lisibility
215 | Colours can be re-defined and links come in 2 flavours
216 |
217 | Straight lines
218 | Lines with squares at the ends
219 |
220 | You may input somme links from a previous session
221 | Mandatory fields (optional array)
222 | Works in Chrome, Chromium, Opera, Firefox and IE (9+)
223 | Result in an object with error true/false plus an array of links
224 | Actions are : init, eraseLinks, getLinks, changeParameters, disable, enable
225 |
226 |
227 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
Save links
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
385 |
386 |
--------------------------------------------------------------------------------
/fieldsLinker.js:
--------------------------------------------------------------------------------
1 | /*
2 | https://github.com/PhilippeMarcMeyer/FieldsLinker v 1.04
3 |
4 | v 1.04 : Just a wording fix for npm
5 | v 1.03 : bug fix : no drag and drop when FieldsLinker is disabled
6 | v 1.02 : Improvement : adding an id to the root dom element if not provided
7 | v 1.01 : Bug correction : the manual sorting by drap and drop now works
8 | v 1.00 : jquery 3.5 compatibility
9 | v 0.96 : Remove filter option and alternateview : theses modes have nothing to do with the original concept
10 | v 0.95 : rewritten for multiples instances in mind
11 | v 0.92 : introducing new option : whiteSpace
12 | v 0.91 : fix mobileClickIt if set, add selected css classes, automatic mobileClickIt on touch devices
13 | v 0.90 : Code beautified by flartet
14 | v 0.89 : Corrected a bug that corrupted the links array of objects detected by flartet on github
15 | v 0.88 : New display mode : idea by Naveen nsirangu => show links between two "tables" linked by ids like a join in sql. instead of headers names, objects ar provided
16 | v 0.87 : New option for touch devices {"mobileClickIt":true} : idea by Norman Tomlins => make links more easily on touch devices just by clicking
17 | */
18 | let fieldsLinkerMemory = [];
19 |
20 | function getRandomInt(max) {
21 | return Math.floor(Math.random() * Math.floor(max));
22 | }
23 |
24 | ;(function($) {
25 | $.fn.fieldsLinker = function (action, input) {
26 | factory = this;
27 | if (action == 'init') {
28 | factory.selector = factory[0];
29 |
30 | if(!factory.selector.id){
31 | factory.selector.id = "FLinkerId_" + getRandomInt(1024000000);
32 | }
33 |
34 | factory.work = new FieldsLinker(factory.selector);
35 | if(fieldsLinkerMemory.length == 0){
36 | fieldsLinkerMemory.push({"selector":factory.selector,"factory":factory});
37 | }else{
38 | let found = false;
39 | fieldsLinkerMemory.forEach(function(x,i){
40 | if(x.selector == factory.selector){
41 | found = true;
42 | fieldsLinkerMemory[i].factory = factory;
43 | }
44 | });
45 | if(!found){
46 | fieldsLinkerMemory.push({"selector":factory.selector,"factory":factory});
47 | }
48 | }
49 | factory.work.init(input);
50 | factory.work.deduplicate();
51 | factory.work.setGlobalRedraw();
52 | factory.work.readUserPreferences();
53 | factory.work.fillChosenLists();
54 | factory.work.makeDropDownForLists();
55 | factory.work.drawColumnsAtLoadTime();
56 | factory.work.drawColumnsContentA();
57 | factory.work.drawColumnsContentB();
58 | factory.work.createCanvas();
59 | factory.work.setListeners();
60 | factory.work.changeSelects();
61 | factory.work.manageExistingLinks();
62 | factory.work.manageResize();
63 | factory.work.draw();
64 |
65 | return factory;
66 |
67 | } else if (action == 'eraseLinks') {
68 | factory.work.eraseLinks();
69 | factory.work.draw();
70 | return factory;
71 |
72 | } else if (action === 'getLinks') {
73 | return factory.work.getLinks();
74 |
75 | } else if (action === 'changeParameters') {
76 | factory.work.changeParameters(input);
77 |
78 | } else if (action == 'disable') {
79 | factory.work.enable(false);
80 | return (factory);
81 |
82 | } else if (action == 'enable') {
83 | factory.work.enable(true);
84 | return (factory);
85 | } else {
86 | console.log(factory.work.errMsg + 'no action parameter provided (param 1)');
87 | }
88 | };
89 | }(jQuery));
90 |
91 |
92 | function FieldsLinker(selector){
93 | this.selector = selector;
94 | this.$root = $(this.selector);
95 | this.FL_Factory_Lists = null;
96 | this.FL_Original_Factory_Lists = null;
97 | this.bootstrap_enabled = (typeof $().modal == 'function');
98 | this.errMsg = 'fieldsLinker error : ';
99 | this.data = {};
100 | this.listsNr = 0;
101 | this.listNames = [];
102 | this.listA = [];
103 | this.listB = [];
104 | this.chosenListA = '';
105 | this.chosenListB = '';
106 | this.keyNameA = '';
107 | this.keyNameB = '';
108 | this.dropDownForLists = null;
109 | this.$leftDiv;
110 | this.$midDiv;
111 | this.$rightDiv;
112 | this.$canvas;
113 | this.$main;
114 | this.$btn;
115 | this.$ulLeft;
116 | this.$ulRight;
117 | this.$filterDiv1;
118 | this.$filterDiv2;
119 | this.canvasId = '';
120 | this.canvasCtx = null;
121 | this.canvasWidth = 0;
122 | this.canvasHeight = 0;
123 | this.canvasPtr = null;
124 | this.mandatoryErrorMessage = 'This field is mandatory';
125 | this.mandatoryTooltips = true;
126 | this.onError = false;
127 | this.className = 'fieldsLinker';
128 | this.linksByName = [];
129 | this.ListHeights1 = [];
130 | this.ListHeights2 = [];
131 | this.move = null;
132 | this.that = null;
133 | this.lineStyle = 'straight'; // straight or square-ends
134 | this.handleColor = '#CF0000,#00AD00,#0000AD,#FF4500,#00ADAD,#AD00AD,#582900,#FFCC00,#000000,#33FFCC'.split(',');
135 | this.lineColor = 'black';
136 | this.associationMode = 'oneToOne';
137 | this.isDisabled = false;
138 | this.globalAlpha = 1;
139 | this.mandatories = [];
140 | this.whiteSpace ="nowrap";
141 | this.hideLink = false;
142 | this.isTouchScreen = is_touch_device();
143 | this.mobileClickIt = false;
144 |
145 | this.draw = function () {
146 | var self = this;
147 | var tablesAB = self.chosenListA + '|' + self.chosenListB; // existingLinks
148 | self.canvasCtx.globalAlpha = self.globalAlpha;
149 | self.canvasCtx.beginPath();
150 | self.canvasCtx.fillStyle = 'white';
151 | self.canvasCtx.strokeStyle = self.lineColor;
152 | self.canvasCtx.clearRect(0, 0, self.canvasWidth, self.canvasHeight);
153 | var links = self.linksByName.filter(function (x) {
154 | return x.tables == tablesAB;
155 | });
156 | links.forEach(function (item, i) {
157 |
158 | var positionA = self.listA.indexOf(item.from);
159 | var positionB = self.listB.indexOf(item.to);
160 |
161 | if (positionB == -1 || positionA == -1) {
162 | console.log('error link names unknown');
163 | return;
164 | }
165 | var Ax = 0;
166 | var Ay = self.ListHeights1[positionA];
167 | var Bx = self.canvasWidth;
168 | var By = self.ListHeights2[positionB];
169 | self.canvasCtx.beginPath();
170 | self.canvasCtx.moveTo(Ax, Ay);
171 | var handleCurrentColor = self.handleColor[i % self.handleColor.length];
172 | if (self.lineStyle == 'square-ends' || self.lineStyle == 'square-ends-dotted') {
173 | self.canvasCtx.fillStyle = handleCurrentColor;
174 | self.canvasCtx.strokeStyle = handleCurrentColor;
175 | self.canvasCtx.rect(Ax, Ay - 4, 8, 8);
176 | self.canvasCtx.rect(Bx - 8, By - 4, 8, 8);
177 | self.canvasCtx.fill();
178 | self.canvasCtx.stroke();
179 | self.canvasCtx.moveTo(Ax + 8, Ay);
180 | self.canvasCtx.lineTo(Ax + 16, Ay);
181 | self.canvasCtx.lineTo(Bx - 16, By);
182 | self.canvasCtx.lineTo(Bx - 8, By);
183 | self.canvasCtx.stroke();
184 | } else {
185 | self.canvasCtx.strokeStyle = handleCurrentColor;
186 | self.canvasCtx.lineTo(Bx, By);
187 | self.canvasCtx.stroke();
188 | }
189 | self.canvasCtx.closePath();
190 | self.canvasCtx.lineWidth = 1;
191 | });
192 | }
193 | this.makeLink = function (infos) {
194 | var self = this;
195 | var tablesAB = self.chosenListA + '|' + self.chosenListB;
196 | var already = false;
197 | var test = self.linksByName.filter(function (x) {
198 | return x.tables == tablesAB && x.to == infos.nameB && x.from == infos.nameA;
199 | });
200 | if (test.length > 0) already = true;
201 | if (!already) {
202 | if (self.associationMode == 'oneToOne') {
203 | for (var i = self.linksByName.length - 1; i >= 0; i--) {
204 | if (self.linksByName[i].tables == tablesAB && self.linksByName[i].to == infos.nameB) {
205 | self.linksByName.splice(i, 1);
206 | }
207 | }
208 |
209 | for (var i = self.linksByName.length - 1; i >= 0; i--) {
210 | if (self.linksByName[i].tables == tablesAB && self.linksByName[i].from == infos.nameA) {
211 | self.linksByName.splice(i, 1);
212 | }
213 | }
214 | }
215 | self.linksByName.push({
216 | 'tables': tablesAB,
217 | 'from': infos.nameA,
218 | 'to': infos.nameB
219 | });
220 | $(self.$root).trigger({
221 | type: 'fieldLinkerUpdate',
222 | what: 'addLink'
223 | });
224 | }
225 | self.draw();
226 | }
227 | this.eraseLinkA = function (nameA) {
228 | var self = this;
229 | var tablesAB = self.chosenListA + '|' + self.chosenListB;
230 | for (var i = self.linksByName.length - 1; i >= 0; i--) {
231 | if (self.linksByName[i].tables == tablesAB && self.linksByName[i].from == nameA) {
232 | self.linksByName.splice(i, 1);
233 | }
234 | }
235 | self.draw();
236 | $(self.$root).trigger({
237 | type: 'fieldLinkerUpdate',
238 | what: 'removeLink'
239 | });
240 | }
241 | this.eraseLinkB = function (nameB) {
242 | var self = this;
243 | var tablesAB = self.chosenListA + '|' + self.chosenListB;
244 | for (var i = self.linksByName.length - 1; i >= 0; i--) {
245 | if (self.linksByName[i].tables == tablesAB && self.linksByName[i].to == nameB) {
246 | self.linksByName.splice(i, 1);
247 | }
248 | }
249 | self.draw();
250 | $(self.$root).trigger({
251 | type: 'fieldLinkerUpdate',
252 | what: 'removeLink'
253 | });
254 | }
255 | this.readUserPreferences = function () {
256 | var self = this;
257 | if (self.data.options.className) {
258 | self.className = self.data.options.className;
259 | }
260 | if(self.data.options.whiteSpace){
261 | self.whiteSpace = self.data.options.whiteSpace;
262 | }
263 | if (self.data.options.lineStyle) {
264 | if (self.data.options.lineStyle == 'square-ends' || self.data.options.lineStyle == 'square-ends-dotted')
265 | self.lineStyle = self.data.options.lineStyle;
266 | }
267 | if (self.data.options.lineColor) {
268 | self.lineColor = self.data.options.lineColor;
269 | }
270 | if (self.data.options.handleColor) {
271 | self.handleColor = self.data.options.handleColor.split(',');
272 | }
273 | if (self.data.options.associationMode) {
274 | self.associationMode = self.data.options.associationMode;
275 | }
276 | if (self.data.options.mobileClickIt != undefined) {
277 | self.mobileClickIt = self.data.options.mobileClickIt;
278 | }
279 | if(self.isTouchScreen){
280 | self.mobileClickIt = true;
281 | }
282 |
283 | }
284 | this.fillChosenLists = function () {
285 | var self = this;
286 | self.listNames = [];
287 | self.listA = [];
288 | self.listB = [];
289 | if (self.chosenListA == '' || self.chosenListB == '') {
290 | self.chosenListA = self.data.Lists[0].name;
291 | self.chosenListB = self.data.Lists[1].name;
292 | }
293 | self.keyNameA = self.data.Lists[0].keyName || '';
294 | self.keyNameB = self.data.Lists[1].keyName || '';
295 |
296 | self.data.Lists.forEach(function (x) {
297 | self.listNames.push(x.name);
298 | if (x.name == self.chosenListA) {
299 | x.list.forEach(function (y) {
300 | self.listA.push(y);
301 | });
302 | }
303 | if (x.name == self.chosenListB) {
304 | if (x.mandatories != undefined) {
305 | self.mandatories = x.mandatories;
306 | }
307 | x.list.forEach(function (y) {
308 | self.listB.push(y);
309 | });
310 | }
311 | });
312 | }
313 | this.makeDropDownForLists = function () {
314 | var self = this;
315 | self.dropDownForLists = $(' ');
316 | self.dropDownForLists
317 | .css('width', '100%');
318 | self.listNames.forEach(function (x) {
319 | var $option = $(' ');
320 | $option
321 | .val(x)
322 | .text(x)
323 | .appendTo(self.dropDownForLists);
324 | });
325 | }
326 | this.drawColumnsAtLoadTime = function () {
327 | var self = this;
328 | self.$root.html('');
329 | self.$main = $('
');
330 | self.$main
331 | .appendTo(self.$root)
332 | .addClass('FL-main ' + self.className)
333 | .css({
334 | 'position': 'relative',
335 | 'width': '100%',
336 | 'text-align': 'left'
337 | });
338 |
339 | self.$leftDiv = $('
');
340 | self.$leftDiv
341 | .appendTo(self.$main)
342 | .addClass('FL-left')
343 | .css({
344 | 'float': 'left',
345 | 'width': '40%',
346 | 'display': 'inline-block',
347 | 'text-align': 'left',
348 | 'white-space': self.whiteSpace
349 | })
350 | .append(self.dropDownForLists.clone());
351 |
352 | self.$leftDiv.find('select')
353 | .attr('id', 'select1')
354 | .val(self.chosenListA)
355 | .on('change', function () {
356 | self.chosenListA = $(this).val();
357 | self.fillChosenLists();
358 | });
359 |
360 | self.$ulLeft = $('');
361 | self.$ulLeft
362 | .appendTo(self.$leftDiv)
363 | .css({
364 | 'text-align': 'left',
365 | 'list-style': 'none'
366 | });
367 |
368 | self.$midDiv = $('
');
369 | self.$midDiv
370 | .appendTo(self.$main)
371 | .addClass('FL-mid')
372 | .css({
373 | 'display': 'inline-block',
374 | 'width': '20%'
375 | });
376 |
377 | self.$rightDiv = $('
');
378 | self.$rightDiv
379 | .appendTo(self.$main)
380 | .addClass('FL-right')
381 | .css({
382 | 'float': 'right',
383 | 'width': '40%',
384 | 'display': 'inline-block',
385 | 'text-align': 'left',
386 | 'white-space': self.whiteSpace
387 | })
388 | .append(self.dropDownForLists.clone());
389 |
390 | if (self.data.options.buttonErase) {
391 | self.$btn = $(' ');
392 | self.$btn
393 | .appendTo(self.$root.find('.FL-main'))
394 | .attr('type', 'button')
395 | .addClass('btn btn-default btn-sm eraseLink')
396 | .attr("style","position:absolute;top:-6px;right:0;opacity:0.9;")
397 | .html(self.data.options.buttonErase);
398 | }
399 |
400 | self.$rightDiv.find('select')
401 | .attr('id', 'select2')
402 | .val(self.chosenListB)
403 | .on('change', function () {
404 | self.chosenListB = $(this).val();
405 | self.fillChosenLists();
406 | });
407 |
408 | self.$ulRight = $('');
409 | self.$ulRight
410 | .appendTo(self.$leftDiv)
411 | .css({
412 | 'text-align': 'left',
413 | 'list-style': 'none'
414 | });
415 | }
416 | this.computeListHeight = function (li) {
417 | // outerHeight(true) adds margins too, full step is simply full outerHeight / 2 between li siblings
418 | if(!$(li).hasClass('hidden')){
419 | var step = Math.ceil($(li).outerHeight(true) / 2);
420 | return Math.floor($(li).position().top + step);
421 | }
422 | }
423 | this.drawColumnsContentA = function () {
424 | var self = this;
425 |
426 | if (self.$ulLeft.length == 1) {
427 | self.$ulLeft.empty();
428 | } else {
429 | self.$ulLeft = $('');
430 | self.$ulLeft.appendTo(self.$leftDiv)
431 | }
432 |
433 | self.$ulLeft
434 | .attr('data-col', self.chosenListA)
435 | .css({
436 | 'text-align': 'left',
437 | 'list-style': 'none'
438 | });
439 |
440 | self.listA.forEach(function (x, i) {
441 | let nrItems = Object.keys(x).length;
442 | if (self.hideLink) {
443 | nrItems--;
444 | }
445 | if (nrItems < 0) {
446 | nrItems = 1;
447 | }
448 | let percent = (100 / nrItems) + '%';
449 | var $li = $(' ');
450 | let item = x;
451 | let id = x;
452 | $li
453 | .appendTo(self.$ulLeft)
454 | .attr('data-offset', i)
455 | .attr('data-name', id)
456 | .css({
457 | 'width': '100%',
458 | 'position': 'relative'
459 | });
460 | var $div = $('
');
461 | $div
462 | .appendTo($li)
463 | .attr('ondrop', 'LM_drop(event)')
464 | .attr('ondragover', 'LM_allowDrop(event)')
465 | .attr('ondragstart', 'LM_drag(event)')
466 | .attr('draggable', 'true')
467 | .css({
468 | 'width': '80%'
469 | })
470 | .html(item);
471 | var $eraseIcon = $(' ');
472 | $eraseIcon
473 | .appendTo($li)
474 | .addClass('fa fa-undo unlink')
475 | .attr('draggable', 'false')
476 | .css({
477 | 'right': '28px',
478 | 'color': '#aaa',
479 | 'position': 'absolute',
480 | 'top': '50%',
481 | 'transform': 'translateY(-50%)'
482 | });
483 | var $pullIcon = $(' ');
484 | $pullIcon
485 | .appendTo($li)
486 | .addClass('fa fa-arrows-alt link')
487 | .attr('draggable', 'false')
488 | .css({
489 | 'right': '8px',
490 | 'color': '#aaa',
491 | 'position': 'absolute',
492 | 'top': '50%',
493 | 'transform': 'translateY(-50%)'
494 | });
495 | });
496 | // Computing the vertical offset of the middle of each cell.
497 | self.ListHeights1 = [];
498 |
499 | $(self.$ulLeft).find('li').each(function (i, li) {
500 | var val = self.computeListHeight(li);
501 | self.ListHeights1.push(val);
502 | });
503 |
504 | if (!self.mobileClickIt) {
505 | $(self.$ulLeft).find('li').off('mousedown').on('mousedown', function (e) {
506 | // we make a move object to keep track of the origine and also remember that we are starting a mouse drag (mouse is down)
507 | if (self.isDisabled) return;
508 | self.move = {};
509 | self.move.offsetA = $(this).data('offset');
510 | self.move.nameA = $(this).data('name');
511 | self.move.offsetB = -1;
512 | self.move.nameB = -1;
513 | });
514 | }
515 |
516 | if (self.isTouchScreen && !self.mobileClickIt) {
517 | // On mousedown in left List :
518 | $(self.$root).find('.link').off('touchstart').on('touchstart', function (e) {
519 |
520 | if (self.isDisabled) return;
521 | self.move = {};
522 | self.move.offsetA = $(this).parent().data('offset');
523 | self.move.nameA = $(this).parent().data('name');
524 | self.move.offsetB = -1;
525 | self.move.nameB = -1;
526 |
527 | var originalEvent = e.originalEvent;
528 | if (originalEvent != null && originalEvent.touches != undefined) {
529 | var touch = originalEvent.touches[0];
530 | if (move != null) {
531 | var mouseEvent = new MouseEvent('mousedown', {
532 | clientX: touch.clientX,
533 | clientY: touch.clientY
534 | });
535 | self.drawImmediate(mouseEvent);
536 | }
537 | }
538 | });
539 |
540 | $(self.$root).find('.link').off('touchmove').on('touchmove', function (e) {
541 | var originalEvent = e.originalEvent;
542 | if (originalEvent != null && originalEvent.touches != undefined) {
543 | var touch = originalEvent.touches[0];
544 | var mouseEvent = new MouseEvent('mousemove', {
545 | clientX: touch.clientX,
546 | clientY: touch.clientY
547 | });
548 |
549 | if (self.move != null) {
550 | self.drawImmediate(mouseEvent);
551 | }
552 |
553 | }
554 | });
555 |
556 | $(self.$root).find('.link').off('touchend').on('touchend', function (e) {
557 | if (self.isDisabled) return;
558 |
559 | var originalEvent = e.originalEvent;
560 | if (originalEvent != null && originalEvent.touches != undefined) {
561 | var touch = originalEvent.changedTouches[0];
562 | var mousePosition = {
563 | x: touch.clientX,
564 | y: touch.clientY
565 | };
566 | if (self.move != null) {
567 | let found = false;
568 | $(self.$ulRight).find('li').each(function (i) {
569 | if (!found) {
570 | var rect = this.getBoundingClientRect();
571 | //left, top, right, bottom, width, height
572 | if (mousePosition.x >= rect.left && mousePosition.x <= rect.right && mousePosition.y >= rect.top && mousePosition.y <= rect.bottom) {
573 | if (self.associationMode == 'oneToOne') {
574 | self.eraseLinkB($(this).data('name')); // we erase an existing link if any
575 | }
576 | self.move.offsetB = $(this).data('offset');
577 | self.move.nameB = $(this).data('name');
578 | var infos = JSON.parse(JSON.stringify(move));
579 | self.move = null;
580 | self.makeLink(infos);
581 | found = true;
582 | }
583 | }
584 | });
585 | if (!found) {
586 | self.draw();
587 | }
588 | }
589 | }
590 | });
591 | }
592 |
593 | if (!self.mobileClickIt) {
594 | $(self.$ulLeft).find('li').off('mouseup').on('mouseup', function (e) {
595 | if (self.isDisabled) return;
596 | // We do a mouse up on the left side : the drag is canceled
597 | self.move = null;
598 | });
599 |
600 | $(self.$ulLeft).find('li').off('click').on('click', function (e) {
601 | if (self.isDisabled) return;
602 | self.eraseLinkA($(this).data('name'));
603 | self.draw();
604 | });
605 | }
606 |
607 | if (self.mobileClickIt) {
608 | $(self.$ulLeft).find('li').off('click').on('click', function (e) {
609 | if (self.isDisabled) return;
610 | let el = $(this);
611 | $(self.$root).find('.selected').removeClass('selected');
612 | el.addClass('selected');
613 | self.move = {};
614 | self.move.offsetA = el.data('offset');
615 | self.move.nameA = el.data('name');
616 | self.move.offsetB = -1;
617 | self.move.nameB = -1;
618 | });
619 | }
620 |
621 | $(self.$root).find('.unlink').off('click').on('click', function (e) {
622 | if (self.isDisabled) return;
623 | self.eraseLinkA($(this).data('name'));
624 | self.draw();
625 | });
626 | }
627 | this.drawColumnsContentB = function () {
628 | var self = this;
629 | if (self.$ulRight.length == 1) {
630 | self.$ulRight.empty();
631 | } else {
632 | self.$ulRight = $('');
633 | self.$ulRight.appendTo(self.$rightDiv)
634 | }
635 | self.$ulRight
636 | .appendTo(self.$rightDiv)
637 | .attr('data-col', self.chosenListB)
638 | .css({
639 | 'text-align': 'left',
640 | 'list-style': 'none'
641 | });
642 |
643 | self.listB.forEach(function (x, i) {
644 | let item = x;
645 | let id = x;
646 | var isMandatory = (self.mandatories.indexOf(x) != -1);
647 | var $li = $(' ');
648 | $li
649 | .appendTo(self.$ulRight)
650 | .attr('data-offset', i)
651 | .attr('data-name', id)
652 | .attr('data-mandatory', isMandatory)
653 | .attr('draggable', 'true');
654 | var $div = $('
');
655 | $div
656 | .appendTo($li)
657 | .attr('ondrop', 'LM_drop(event)')
658 | .attr('ondragover', 'LM_allowDrop(event)')
659 | .attr('ondragstart', 'LM_drag(event)')
660 | .attr('draggable', 'true')
661 | .css({
662 | 'width': '80%'
663 | })
664 | .html(item);
665 | if (isMandatory && self.mandatoryTooltips) {
666 | $li
667 | .attr('data-placement', 'top')
668 | .attr('title', self.mandatoryErrorMessage);
669 | if (self.bootstrap_enabled)
670 | $li.tooltip();
671 | }
672 | });
673 | // Computing the vertical offset of the middle of each cell.
674 | self.ListHeights2 = [];
675 |
676 | $(self.$ulRight).find('li').each(function (i, li) {
677 | var val = self.computeListHeight(li);
678 | self.ListHeights2.push(val);
679 | });
680 | // Mouse up on the right side
681 | $(self.$ulRight).find('li').off('mouseup').on('mouseup', function (e) {
682 | if (self.isDisabled) return;
683 | if (self.move != null) { // no drag
684 | if (self.associationMode == 'oneToOne') {
685 | self.eraseLinkB($(this).data('name')); // we erase an existing link if any
686 | }
687 | self.move.offsetB = $(this).data('offset');
688 | self.move.nameB = $(this).data('name');
689 | var infos = JSON.parse(JSON.stringify(self.move));
690 | self.move = null;
691 | self.makeLink(infos);
692 | }
693 | });
694 |
695 | $(self.$ulRight).find('li').off('dblclick').on('dblclick', function (e) {
696 | if (self.isDisabled) return;
697 | self.eraseLinkB($(this).data('name')); // we erase an existing link if any
698 | self.draw();
699 | });
700 | // mousemove over a right cell
701 | $(self.$ulRight).find('li').off('mousemove').on('mousemove', function (e) {
702 | if (self.isDisabled) return;
703 | if (self.move != null) { // drag occuring
704 | var _from = self.move.offsetA;
705 | var _To = $(this).data('offset');
706 | var Ax = 0;
707 | var Ay = self.ListHeights1[_from];
708 | var Bx = self.canvasWidth;
709 | var By = self.ListHeights2[_To];
710 | self.draw();
711 | self.canvasCtx.beginPath();
712 | var color = self.handleColor[_from % self.handleColor.length];
713 | self.canvasCtx.fillStyle = 'white';
714 | self.canvasCtx.strokeStyle = color;
715 | self.canvasCtx.moveTo(Ax, Ay);
716 | self.canvasCtx.lineTo(Bx, By);
717 | self.canvasCtx.stroke();
718 | }
719 | });
720 |
721 | if (self.mobileClickIt) {
722 | $(self.$ulRight).find('li').off('click').on('click', function (e) {
723 | $(self.$root).find('.selected').removeClass('selected');
724 | if (self.isDisabled || self.move === null) return;
725 | self.move.offsetB = $(this).data('offset');
726 | self.move.nameB = $(this).data('name');
727 | var infos = JSON.parse(JSON.stringify(move));
728 | self.move = null;
729 | self.makeLink(infos);
730 | });
731 | }
732 | }
733 | this.createCanvas = function () {
734 | var self = this;
735 | self.canvasId = 'cnv_' + Date.now();
736 | var w = self.$midDiv.width();
737 | var h2 = self.$rightDiv.height();
738 | var h1 = self.$leftDiv.height();
739 | var h = h1 >= h2 ? h1 : h2;
740 | self.$canvas = $(' ');
741 | self.$canvas
742 | .appendTo(self.$midDiv)
743 | .attr('id', self.canvasId)
744 | .css({
745 | 'width': w + 'px',
746 | 'height': h + 'px'
747 | });
748 | self.canvasWidth = w;
749 | self.canvasHeight = h;
750 | self.canvasPtr = document.getElementById(self.canvasId);
751 | if(self.canvasPtr){
752 | self.canvasPtr.width = self.canvasWidth;
753 | self.canvasPtr.height = self.canvasHeight;
754 | self.canvasCtx = self.canvasPtr.getContext("2d");
755 | }
756 | }
757 | this.getTouchPos = function (e) {
758 | var self = this;
759 | var rect = self.canvasPtr.getBoundingClientRect();
760 | return {
761 | x: e.clientX - rect.left,
762 | y: e.clientY - rect.top
763 | };
764 | }
765 | this.drawImmediate = function (e) {
766 | var self = this;
767 | self.canvasCtx.clearRect(0, 0, self.canvasWidth, self.canvasHeight);
768 | // we redraw all the existing links
769 | self.draw();
770 | self.canvasCtx.beginPath();
771 | // we draw the new would-be link
772 | var _from = self.move.offsetA;
773 | var color = self.handleColor[_from % self.handleColor.length];
774 | self.canvasCtx.fillStyle = 'white';
775 | self.canvasCtx.strokeStyle = color;
776 | var Ax = 0;
777 | var Ay = self.ListHeights1[_from];
778 | // mouse position relative to the canvas
779 | //var Bx = e.offsetX;
780 | //var By = e.offsetY;
781 | var relativePosition = self.getTouchPos(e);
782 | var Bx = relativePosition.x;
783 | var By = relativePosition.y;
784 | self.canvasCtx.moveTo(Ax, Ay);
785 | self.canvasCtx.lineTo(Bx, By);
786 | self.canvasCtx.stroke();
787 |
788 | }
789 | this.setListeners = function () {
790 | var self = this;
791 | if (self.data.options.buttonErase) {
792 | $(self.$root).find('.FL-main .eraseLink').on('click', function (e) {
793 | if (self.isDisabled) return;
794 | self.linksByName.length = 0;
795 | self.draw();
796 | $(self.selector).trigger({
797 | type: 'fieldLinkerUpdate',
798 | what: 'removeLink'
799 | });
800 | });
801 | }
802 | // mousemove over the canvas
803 | $(self.$canvas).on('mousemove', function (e) {
804 | if (self.isDisabled) return;
805 | if (self.move != null) {
806 | self.drawImmediate(e);
807 | }
808 | });
809 | $(self.$main).on('mouseup', function (e) {
810 | if (self.isDisabled) return;
811 | if (self.move != null) {
812 | self.move = null;
813 | self.draw();
814 | }
815 | });
816 | // this is a remnant of LinksMaker : could handle more than 2 columns
817 | // this won't change unluss we trigger it
818 | $(self.$leftDiv).find('select').on('change', function (e) {
819 | if (self.isDisabled) return;
820 | self.chosenListA = $(this).val();
821 | $(self.$rightDiv).find('select option').each(function () {
822 | $(this)
823 | .attr('disabled', $(this).val() == self.chosenListA);
824 | });
825 | self.drawColumnsContentA();
826 | self.draw();
827 | });
828 |
829 | $(self.$rightDiv).find('select').on('change', function (e) {
830 | if (self.isDisabled) return;
831 | self.chosenListB = $(this).val();
832 | $(self.$leftDiv).find('select option').each(function () {
833 | $(this)
834 | .attr('disabled', $(this).val() == this.chosenListB);
835 | });
836 | self.drawColumnsContentB();
837 | self.draw();
838 | });
839 | }
840 | this.setGlobalRedraw = function(){
841 | var self = this;
842 | $(self.selector).on('LM_Message_Redraw', function () {
843 | self.mandatories = [];
844 | self.move = null;
845 | self.listA = [];
846 | self.listB = [];
847 | self.FL_Factory_Lists.Lists.forEach(function (x) {
848 | if (x.name == self.chosenListA) {
849 | let dict = {};
850 | x.list.forEach(function (y) {
851 | if (!dict[y]) {
852 | dict[y] = 1;
853 | } else {
854 | dict[y] + 1;
855 | y += '(' + dict[y] + ')';
856 | }
857 | });
858 | self.listA = x.list;
859 | }
860 | if (x.name == self.chosenListB) {
861 | self.listB = x.list;
862 | if (x.mandatories != undefined) {
863 | self.mandatories = x.mandatories;
864 | }
865 | }
866 | });
867 | self.drawColumnsContentA();
868 | self.drawColumnsContentB();
869 | self.draw();
870 | });
871 | }
872 | this.setError = function(message){
873 | var self = this;
874 | self.onError = true;
875 | throw self.errMsg + message;
876 | }
877 | this.init = function(input,onFilter){
878 |
879 | var self = this;
880 | if (!input) {
881 | setError('no input options provided (param 2)');
882 | }
883 | self.data = JSON.parse(JSON.stringify(input));
884 | self.FL_Factory_Lists = self.data;
885 |
886 | if (!onFilter) {
887 | self.FL_Original_Factory_Lists = JSON.parse(JSON.stringify( self.data ));
888 | }
889 | if (!self.data.Lists || self.data.Lists.length < 2) {
890 | self.setError('provide at least 2 lists');
891 | }
892 | }
893 | this.deduplicate = function(){
894 | var self = this;
895 | self.listsNr = self.data.Lists.length;
896 |
897 | for (let i = 0; i < self.listsNr; i++) {
898 | let dict = {};
899 | for (let j = 0; j < self.data.Lists[i].list.length; j++) {
900 | let val = self.data.Lists[i].list[j];
901 | if (!dict[val]) {
902 | dict[val] = 1;
903 | } else {
904 | dict[val] += 1;
905 | self.data.Lists[i].list[j] += '(' + dict[val] + ')';
906 | }
907 | }
908 | }
909 | }
910 | this.changeSelects = function(){
911 | var self = this;
912 | $(self.selector)
913 | .find('.FL-left select')
914 | .trigger('change')
915 | .css('border', 'none')
916 | .css('appearance', 'none')
917 | .attr('disabled', 'true');
918 |
919 | $(self.selector)
920 | .find('.FL-right select')
921 | .trigger('change')
922 | .css('border', 'none')
923 | .css('appearance', 'none')
924 | .attr('disabled', 'true');
925 | }
926 | this.manageExistingLinks = function(){
927 | var self = this;
928 | if (self.data.existingLinks) {
929 | self.linksByName = self.data.existingLinks;
930 | var tablesAB = self.chosenListA + '|' + self.chosenListB;
931 | self.linksByName.forEach(function (x) {
932 | x.tables = tablesAB;
933 | });
934 | }
935 | }
936 | this.manageResize= function(){
937 | var self = this;
938 | $(window).resize(function () {
939 | self.canvasWidth = $(self.selector).find('.FL-main .FL-mid').width();
940 | self.canvasPtr.width = self.canvasWidth;
941 | $('#' + self.canvasId).css('width', self.canvasWidth + 'px');
942 | self.draw();
943 | });
944 | }
945 | this.eraseLinks = function(){
946 | var self = this;
947 | self.linksByName.length = 0;
948 | self.draw();
949 | }
950 | this.getLinks = function(){
951 | var self = this;
952 | if (!self.onError) {
953 | var isMandatoryError = false;
954 | let links = null;
955 | var errorMessage = self.mandatoryErrorMessage + ' : ';
956 | var fieldInErrorName = '';
957 | self.mandatories.forEach(function (m, i) {
958 | if (!isMandatoryError) {
959 | var match = self.linksByName.filter(function (link) {
960 | return link.to == m;
961 | });
962 | if (match.length == 0) {
963 | isMandatoryError = true;
964 | fieldInErrorName = m;
965 | }
966 | }
967 | });
968 | if (isMandatoryError) {
969 | return {
970 | 'error': true,
971 | 'errorMessage': errorMessage + fieldInErrorName,
972 | 'links': []
973 | };
974 | } else {
975 | links = [];
976 | self.linksByName.forEach(function (x) {
977 | links.push({
978 | from: x.from,
979 | to: x.to
980 | });
981 | });
982 | return {
983 | 'error': false,
984 | 'errorMessage': '',
985 | 'links': links
986 | };
987 | }
988 | } else {
989 | return [];
990 | }
991 | }
992 | this.changeParameters = function(input){
993 | var self = this;
994 | if (!self.onError) {
995 | if (input) {
996 | var options = JSON.parse(JSON.stringify(input));
997 | if (options.className) {
998 | self.className = options.className;
999 | }
1000 | if(options.whiteSpace){
1001 | self.whiteSpace = options.whiteSpace;
1002 |
1003 | self.$leftDiv.css("white-space",self.whiteSpace);
1004 | self.$rightDiv.css("white-space",self.whiteSpace);
1005 |
1006 | self.ListHeights1 = [];
1007 |
1008 | $(self.$ulLeft).find('li').each(function (i, li) {
1009 | var val = self.computeListHeight(li);
1010 | self.ListHeights1.push(val);
1011 | });
1012 |
1013 | self.ListHeights2 = [];
1014 |
1015 | $(self.$ulRight).find('li').each(function (i, li) {
1016 | var val = self.computeListHeight(li);
1017 | self.ListHeights2 .push(val);
1018 | });
1019 |
1020 | }
1021 | if (options.lineStyle) {
1022 | self.lineStyle = options.lineStyle;
1023 | }
1024 | if (options.lineColor) {
1025 | self.lineColor = options.lineColor;
1026 | }
1027 | if (options.handleColor) {
1028 | self.handleColor = options.handleColor.split(',');
1029 | }
1030 | if (options.associationMode) {
1031 | let unicityTokenA = '';
1032 | let unicityTokenB = '';
1033 | let formerAssociation = self.associationMode;
1034 | self.associationMode = options.associationMode;
1035 | if (self.associationMode == 'oneToOne' && formerAssociation == 'manyToMany') {
1036 | let unicityDict = {};
1037 | for (var i = self.linksByName.length - 1; i >= 0; i--) {
1038 | unicityTokenA = self.linksByName[i].tables + '_A_' + self.linksByName[i]['from'];
1039 | unicityTokenB = self.linksByName[i].tables + '_B_' + self.linksByName[i]['to'];
1040 | let doDelete = false;
1041 | if (!unicityDict[unicityTokenA]) {
1042 | unicityDict[unicityTokenA] = true;
1043 | } else {
1044 | doDelete = true;
1045 | }
1046 | if (!unicityDict[unicityTokenB]) {
1047 | unicityDict[unicityTokenB] = true;
1048 | } else {
1049 | doDelete = true;
1050 | }
1051 | if (doDelete) {
1052 | self.linksByName.splice(i, 1);
1053 | }
1054 | }
1055 | }
1056 | }
1057 | }
1058 | self.draw();
1059 | }
1060 | }
1061 | this.enable = function(doEnable){
1062 | var self = this;
1063 | self.isDisabled = !doEnable;
1064 |
1065 | $(self.$root)
1066 | .find('.eraseLink')
1067 | .prop('disabled', self.isDisabled);
1068 |
1069 | if(doEnable) {
1070 | $(self.$root)
1071 | .find('li')
1072 | .removeClass('inactive')
1073 | .find('div')
1074 | .prop('draggable',true);
1075 | }else{
1076 | $(self.$root)
1077 | .find('li')
1078 | .addClass('inactive')
1079 | .find('div')
1080 | .prop('draggable',false);
1081 | }
1082 |
1083 | $(self.$root)
1084 | .find('select')
1085 | .prop('disabled', self.isDisabled);
1086 |
1087 | self.globalAlpha = self.isDisabled ? 0.5 : 1;
1088 |
1089 | self.draw();
1090 |
1091 | }
1092 | }
1093 | // utils
1094 | function LM_allowDrop(ev) {
1095 | // ev.dataTransfer.dropEffect = "none"; // dropping is not allowed
1096 | console.log(ev.currentTarget);
1097 | ev.preventDefault();
1098 | }
1099 |
1100 | function LM_drag(ev) {
1101 | let $target = $(ev.target);
1102 | let data = {};
1103 | data.name = $target.parent().attr('data-name');
1104 | data.col = $target.parent().parent().attr('data-col');
1105 | data.offset = $target.parent().attr('data-offset');
1106 | ev.dataTransfer.setData('text/plain', JSON.stringify({
1107 | 'data': data
1108 | }));
1109 | }
1110 |
1111 | function LM_drop(ev) {
1112 | ev.preventDefault();
1113 | let src = JSON.parse(ev.dataTransfer.getData('text'));
1114 | if (src) {
1115 | src = src.data;
1116 | }
1117 | let $target = $(ev.target);
1118 | let $root = $target.closest(".fieldsLinker").parent();
1119 | let currentDropId = $root.attr("id");
1120 | let factory = null;
1121 | if($root.length==1){
1122 | let oneMemory = fieldsLinkerMemory.filter(function(x,i){
1123 | return x.selector.id == currentDropId;
1124 | });
1125 | if(oneMemory.length >0){
1126 | factory = oneMemory[0].factory;
1127 | }
1128 |
1129 | if (factory != null) {
1130 | let dest = {};
1131 | dest.name = $target.parent().attr('data-name');
1132 | dest.col = $target.parent().parent().attr('data-col');
1133 | dest.offset = $target.parent().attr('data-offset');
1134 | if (src.col == dest.col && src.offset != dest.offset && src.name != dest.name) {
1135 | factory.work.FL_Factory_Lists.Lists.forEach(function (x) {
1136 | if (x.name == src.col) {
1137 | let indexA = x.list.indexOf(src.name);
1138 | let indexB = x.list.indexOf(dest.name);
1139 | if (indexA != -1 && indexB != -1) {
1140 | let temp = x.list[indexA];
1141 | x.list[indexA] = x.list[indexB];
1142 | x.list[indexB] = temp;
1143 | }
1144 | }
1145 | });
1146 | $($root).trigger('LM_Message_Redraw');
1147 | }
1148 | }
1149 | }
1150 |
1151 | }
1152 |
1153 |
1154 | function is_touch_device() { // from bolmaster2 - stackoverflow
1155 | var prefixes = ' -webkit- -moz- -o- -ms- '.split(' ');
1156 | var mq = function (query) {
1157 | return window.matchMedia(query).matches;
1158 | };
1159 |
1160 | if (('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) {
1161 | return true;
1162 | }
1163 |
1164 | // include the 'heartz' as a way to have a non matching MQ to help terminate the join
1165 | // https://git.io/vznFH
1166 | var query = ['(', prefixes.join('touch-enabled),('), 'heartz', ')'].join('');
1167 | return mq(query);
1168 | }
1169 |
--------------------------------------------------------------------------------