├── .github
└── stale.yml
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── bower.json
├── jstreegrid.js
├── package.json
├── publish
├── tests
├── README.md
├── components
│ ├── api-hide-show-test.html
│ ├── api-select-test.html
│ ├── chrome-header-test.html
│ ├── click-sort-test.html
│ ├── clicked-on-show.html
│ ├── dnd-test.html
│ ├── dynamic-root-node-test.html
│ ├── fixed-widths-resize-test.html
│ ├── fnvalue-test.html
│ ├── grid-api-test.html
│ ├── large-dataset-test.html
│ ├── minwidth-maxwidth-test.html
│ ├── odd-named-id-test.html
│ ├── rename-dynamic-node-test.html
│ ├── rename_id_test.html
│ ├── render_cell-event-test.html
│ ├── resize-event-test.html
│ ├── search-test.html
│ ├── select-child-test.html
│ ├── select-prevent-test.html
│ ├── sort-without-resizable-test.html
│ └── widths-test.html
├── test-list.json
└── test-master.html
└── treegrid.html
/.github/stale.yml:
--------------------------------------------------------------------------------
1 | # Configuration for probot-stale - https://github.com/probot/stale
2 |
3 | # Number of days of inactivity before an Issue or Pull Request becomes stale
4 | daysUntilStale: 60
5 |
6 | # Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
7 | # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
8 | daysUntilClose: 7
9 |
10 | # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
11 | exemptLabels:
12 | - pinned
13 | - "[Status] Maybe Later"
14 |
15 | # Set to true to ignore issues in a project (defaults to false)
16 | exemptProjects: false
17 |
18 | # Set to true to ignore issues in a milestone (defaults to false)
19 | exemptMilestones: false
20 |
21 | # Set to true to ignore issues with an assignee (defaults to false)
22 | exemptAssignees: false
23 |
24 | # Label to use when marking as stale
25 | staleLabel: wontfix
26 |
27 | # Comment to post when marking as stale. Set to `false` to disable
28 | markComment: >
29 | This issue has been automatically marked as stale because it has not had
30 | recent activity. It will be closed if no further activity occurs. Thank you
31 | for your contributions.
32 |
33 | # Comment to post when removing the stale label.
34 | # unmarkComment: >
35 | # Your comment here.
36 |
37 | # Comment to post when closing a stale Issue or Pull Request.
38 | # closeComment: >
39 | # Your comment here.
40 |
41 | # Limit the number of actions per hour, from 1-30. Default is 30
42 | limitPerRun: 30
43 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | lib/**
2 | oneoff/**
3 | /node_modules
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | lib/**
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Avi Deitcher
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # jsTreeGrid
2 | [](https://cdnjs.com/libraries/jstreegrid)
3 |
4 | UNSUPPORTED: You are welcome to find issues, and submit Pull Requests to fix. Beyond that, there really isn't time to support it.
5 |
6 | ## Overview
7 | Plugin for the jstree www.jstree.com tree component that provides a grid extension to the tree.
8 |
9 | Allows any number of columns, and can use any property of the node to display data in the grid
10 |
11 | For significant changes to v3 compared to v1, see the end of this document.
12 |
13 | **Note:** treegrid may have issues when using theme autoloading as in:
14 |
15 | ````JavaScript
16 | core: {
17 | themes: {url: 'http://some.url/theme'}
18 | }
19 | ````
20 |
21 | In any case, theme autoloading is strongly discouraged in jstree v3.
22 |
23 | ## License and Support
24 | jstree-grid is released under the very permissive MIT License, albeit with no warranty or guarantee. See the [MIT License](./LICENSE) for full details. Use it as you will, and use it well.
25 |
26 | If you need commercial support, general assistance or have any questions, please contact [info@atomicinc.com](mailto:info@atomicinc.com).
27 |
28 | jstree-grid can be made a native component for Angular, Angular2, Polymer, Aurelia, and other frameworks. If you have commercial need of a tree grid those or any other framework, please contact [info@atomicinc.com](mailto:info@atomicinc.com).
29 |
30 |
31 |
32 | ## Usage
33 |
34 | 1. Include jquery (>= 1.4.2) and jstree in your page, as usual
35 | 2. Inclue jstree **>= 3.3.0**
36 | 3. Include jstreegrid.js v3 or later
37 | 4. Include grid as a plugin
38 | 5. Include relevant parameters.
39 |
40 | ````HTML
41 |
42 |
43 | ````
44 |
45 |
46 | ````JavaScript
47 | $("div#id").jstree({
48 | // include grid as a plugin
49 | plugins: ["core","ui",...,"grid"],
50 | // include relevant parameters
51 | grid: {
52 | columns: [{},{},...,{}],
53 | width: 25
54 | },
55 | core: {
56 | data: [...]
57 | }
58 | });
59 | ````
60 |
61 | As of 3.0.0-beta5, jstree-grid supports AMD, thanks https://github.com/jochenberger
62 |
63 | As of 3.1.0-beta1, jstree-grid uses a wrapping table, rather than inserted `div`s in the tree. This does a much better job with widths, alignment, editing, etc. etc.
64 |
65 | As of 3.10.2, jstree-grid supports CommonJS, thanks to https://github.com/YarnSeemannsgarn
66 |
67 | ### Structure
68 |
69 | The grid is built by adding divs `
` to each `
` entry for a row in the tree. Inside the `
` is a `` with the data.
70 | Thus, an entry is likely to look like
71 |
72 | ````HTML
73 |
$5.00
74 | ````
75 |
76 | We use the div to control the entire height and width, and the span to get access to the actual data itself.
77 |
78 | ### Options
79 |
80 | #### The options are as follows:
81 |
82 | * `width`: width for the entire jstree-grid. If no width is given, automatically fills the entire viewport (`width: 100%;`)
83 | * `height`: height for the entire jstree-grid. If no height is given, height will reflect the amount of content.
84 | * `fixedHeader`: true/false. If true, then when the tree is scrolled the column headers will remain visible. Defaults to true.
85 | * `columnWidth`: default width for a column for which no width is given. If no width is given, the default is `auto`.
86 | * `columns`: an array of columns to create, on order. Each entry is an object with the following parameters:
87 | * `tree`: boolean, whether the jstree should be placed in this column. Only the first `true` is accepted. If no column is set to `tree:true`, then the first column is used.
88 | * `width`: width of the column in pixels. If no width is given, the default is `auto` **except for the last column**. In the last column, if no width is given, it is treated as 'auto' and fills the entire rest of the grid to the right.
89 | * `minWidth`: Minimum width of the column when width is set to `auto`. Does **not** limit manual column resizing.
90 | * `maxWidth`: Maximum width of the column when width is set to `auto`. Does **not** limit manual column resizing.
91 | * `header`: string to use as a header for the column.
92 | * `headerClass`: a CSS class to add to the header cell in this column
93 | * `headerTitle`: a title to add to the header cell in this column, shown as a tooltip
94 | * `columnClass`: a CSS class to add to the header cell and the column cell
95 | * `cellClass`: a CSS class to add to each cell in this column (except for the header) - added to the ``
96 | * `wideCellClass`: a CSS class to add to each cell in this column (except for the header) - added to the `
`
97 | * `value`: the attribute on the node to use as the value for this cell - entered as the `` text. Can be a string or a function.
98 | * `valueClass`: the attribute on the node to use as a class on this cell - added to the ``
99 | * `valueClassPrefix`: a prefix to add to the valueClass to use as a class on this cell
100 | * `wideValueClass`: the attribute on the node to use as a class on this cell - added to the `
`
101 | * `wideValueClassPrefix`: a prefix to add to the wideValueClass to use as a class on this cell
102 | * `search_callback`: sets the search callback function used when using the `searchColumn` function. The function is passed with four arguments: the `search string`, the `column value`, the `jstree node` corresponding to the column and the `column`. returns true for a match and false if it doesn't match. If not set a default string search function is used (this is similar to [https://www.jstree.com/api/#/?q=search&f=$.jstree.defaults.search.search_callback](https://www.jstree.com/api/#/?q=search&f=$.jstree.defaults.search.search_callback))
103 | * `resizable`: true/false if the columns should be resizable. Defaults to false.
104 | * `draggable`: true/false if the columns should be draggable (requires jQuery UI with sortable plugin). Defaults to false.
105 | * `stateful`: true/false. If true, then whenever a column width is resized, it will store it in html5 localStorage, if available. Defaults to false.
106 | * `headerAsTitle`: true/false. If true, the header is used as `headerTitle`. If false, no title is added, but in case the option `headerTitle` is set, a title is added. Defaults to false.
107 | * `contextmenu`: true/false whether or not a context menu for editing the cells should be shown on right-click. Defaults to false.
108 | * `gridcontextmenu`: function to create context menu items; see context menu of jstree for format. In addition, if `false` or not set, no special context menu. If `true`, creates a default menu to edit the cell entry.
109 | * `caseInsensitive`: true/false whether or not the column sort should be case insensitive. Default value is false (the default sort is case sensitive)
110 |
111 | The reason for both `valueClass` and `wideValueClass` is to give you the ability to control both the narrow part of the text, and the entire width of the cell. For example, if the cell is 56px wide, but the text in it is "OK" and thus only 20px wide.
112 | Suppose you have a class "important" which backgrounds in red, and a class "clickable" which changes the cursor to a pointer.
113 | You want the entire width of the cell to be red, but just the word "OK" to be clickable.
114 | You would ensure that "clickable" is applied to the span, but important to the div.
115 |
116 | Value is one of:
117 |
118 | * the name of the property of the node data whose content will be used; you can choose which once for the entire grid.
119 | * a function, which will be passed the node's data given by `tree.get_node(node)` for the individual tree item. If you want your custom data, access it via `node.data`
120 |
121 | Thus, if you have a node whose data is given by:
122 |
123 | ````JavaScript
124 | {text: "My Node", data: {price: "$10"}}
125 | ````
126 |
127 |
128 | and we want the price value ($10) to be in column 1, then we have a config of:
129 |
130 | ````JavaScript
131 | grid: {
132 | columns: [
133 | {width: 50, header: "Nodes"},
134 | {width: 30, header: "Price", value: "price"}
135 | ]
136 | }
137 | ````
138 |
139 | Or, in a function:
140 |
141 | ````JavaScript
142 | grid: {
143 | columns: [
144 | {width: 50, header: "Nodes"},
145 | {width: 30, header: "Price", value: function(node){return(node.data.price);}}
146 | ]
147 | }
148 | ````
149 |
150 | Using a function allows you to calculate things, or make conditions:
151 |
152 | ````JavaScript
153 | grid: {
154 | columns: [
155 | {width: 50, header: "Nodes"},
156 | {width: 30, header: "Price", value: function(node){return("$"+(node.data.price*2));}}
157 | ]
158 | }
159 | ````
160 |
161 |
162 |
163 | Thanks to ChrisRaven for the metadata support (deprecated as of v3), resizable columns, and cell click events.
164 |
165 | ValueClass is the name of the attribute that will be added as a class to this cell. Thus, if you have a node whose data is given by:
166 |
167 | ````JavaScript
168 | {text: "My Node", data: {price: "$10", scale: "expensive"}}
169 | ````
170 |
171 | and we want the cell to have the class "expensive", then we have a config of:
172 |
173 | ````JavaScript
174 | grid: {
175 | columns: [
176 | {width: 50, header: "Nodes"},
177 | {width: 30, header: "Price", value:"price", valueClass: "scale"}
178 | ]
179 | }
180 | ````
181 |
182 | The result would be:
183 |
184 | ````HTML
185 |
$10
186 | ````
187 |
188 | Conversely, if we want it to be "price-expensive", we would have a config of:
189 |
190 | ````JavaScript
191 | grid: {
192 | columns: [
193 | {width: 50, header: "Nodes"},
194 | {width: 30, header: "Price", value: "price", valueClass: "scale", valueClassPrefix: "price-"}
195 | ]
196 | }
197 | ````
198 |
199 | The result would be:
200 |
201 | ````HTML
202 |
$10
203 | ````
204 |
205 | You can add a tooltip to each element in the grid by adding the name of it to the title, with the HTML stripped out. For example:
206 |
207 | ````JavaScript
208 | grid: {
209 | columns: [
210 | {width: 50, header: "Nodes"},
211 | {width: 30, header: "Price", value: "price", title: "price"}
212 | ]
213 | }
214 | ````
215 |
216 | The result would be:
217 |
218 | ````HTML
219 |
$10
220 | ````
221 |
222 | This includes the actual tree node, not just the added grid cells:
223 |
224 | ````JavaScript
225 | grid: {
226 | columns: [
227 | {width: 50, header: "Nodes", title:"price"},
228 | {width: 30, header: "Price", value: "price", title: "price"}
229 | ]
230 | }
231 | ````
232 |
233 | You also can have the contents of the tree node (value of tree.get_text()) itself made into the tooltip by using the special title keyword
234 | "_DATA_".
235 |
236 | ````JavaScript
237 | grid: {
238 | columns: [
239 | {width: 50, header: "Nodes", title:"_DATA_"},
240 | {width: 30, header: "Price", value: "price", title: "price"}
241 | ]
242 | }
243 | ````
244 |
245 | Finally, you can change a node contents on the fly using "change_node.jstree". You change the `data` attribute of the node, then trigger the event,
246 | for example:
247 |
248 | ````JavaScript
249 | var tree = $("#jstree").jstree(true), node = tree.get_node("my_node");
250 | node.data.value = 25;
251 | tree.trigger("change_node.jstree",node);
252 | ````
253 |
254 | ### HTML
255 |
256 | Note that the data in each cell is treated as HTML content for the span, rather than raw text. You can use HTML in any cell, except for the
257 | base tree node cell, which follows jstree rules.
258 |
259 | ### Heights
260 | The height of the entire div in which the tree is rendered is given by you. If you wish the tree to have a max-height of 40px, you need to set it as part of the standard HTML/CSS.
261 |
262 | ````HTML
263 |
268 |
269 | ````
270 |
271 | In the above example, the tree itself, but *not* the headers, will be limited to 40px, not by jstree or jstreegrid, but by straight CSS. However, jstreegrid will structure the tree and header in such a way that if the total tree is greater than 40px in height, then the tree will have a vertical scrollbar, but the header will remain fixed.
272 |
273 |
274 | ### Themeroller
275 | The themeroller plugin for jstree is supported as of tag 0.9.1 (29 November 2011). Thanks to ChrisRaven for the support.
276 |
277 |
278 | ### API
279 | The following methods can be called on the jstree:
280 |
281 | * `grid_hide_column(column)`: Hide column number `column`. Column numbers begin with `0`. If the column already is hidden, has no effect.
282 | * `grid_show_column(column)`: Show column number `column`. Column numbers begin with `0`. If the column already is shown, has no effect.
283 | * `search(str)`: the [jstree search](https://www.jstree.com/api/#/?q=search) has been extended to search the entire grid. to search a specific column use `searchColumn`.
284 | * `searchColumn(searchObject)`: Search one or more columns for specific strings. `searchObject` is an object with the column number as key, beginning with `0`, and the searchString as value. Multiple column values are joined together with logical `AND`. Uses the jstree search settings to hide/show nodes. Examples:
285 | * `searchColumn({0:'My Node'})`: search for rows wherein the first column has text `'My Node'`
286 | * `searchColumn({0:'My Node', 1:'$10'})`: search for rows wherein the first column has text `'My Node'` *AND* second column has text `'$10'`
287 |
288 | ### Events
289 | * `loaded.jstree`: When the tree is done loading, as usual, it fires a "loaded.jstree" event on the div to which you added jstree. jsTreeGrid uses this event to start its own load process.
290 | * `loaded_grid.jstree`: When jsTreeGrid is done, it fires a "loaded_grid.jstree" event on the same div. If you need to run some
291 | code after the jsTreeGrid is done loading, just listen for that event. An example is in the treegrid.HTML sample page.
292 | * `select_cell.jstree-grid`: If you click in any individual cell, the jstreegrid will fire a "select_cell.jstree_grid" event on the tree. See below for "cell selection".
293 | * `update_cell.jstree-grid`: If you right-click a cell and edit it, when the edit is complete, and if the value has changed, the jstreegrid will fire a `update_cell.jstree-grid` event on the jstree.
294 | * `render_cell.jstree-grid`: Called each time a cell is rendered. Is not a one-time call, as cells are rendered when their container node is opened and destroyed each time is closed, so you can get multiple `render_cell.jstree-grid` calls for the same cell.
295 | * `resize_column.jstree-grid`: When a column is resized, whether from dragging the resizer or double-clicking it, this event will be fired.
296 |
297 | #### Cell Selection
298 | When you select a cell by clicking on it, two things happen:
299 |
300 | 1. jstree-grid fires off a `select_cell.jstree-grid` event on the tree, to which you can listen.
301 | 2. jstree-grid selects the row, leading in turn to the normal jstree row selection event handlers.
302 |
303 | If you wish to prevent the second part - entire row selection by the jstree - for example, if you have input HTML rendered in the cell and do not want to lose input focus, you can prevent the jstree selection.
304 |
305 | To do so, create a handler for the `select_cell.jstree-grid` event and prevent its handling:
306 |
307 | ``` js
308 | mytree.on("select_cell.jstree-grid", function(event, data) {
309 | if (/* some logic, if you want */) {
310 | event.preventDefault();
311 | }
312 | });
313 | ```
314 |
315 | If you still want to prevent the automatic selection of the row, but do it yourself, you can do it manually:
316 |
317 | ``` js
318 | mytree.on("select_cell.jstree-grid", function(event, data) {
319 | event.preventDefault();
320 | mytree.jstree("deselect_all");
321 | mytree.jstree("select_node", data.node);
322 | });
323 | ```
324 |
325 |
326 | The signature for the select_cell.jstree-grid handler is:
327 |
328 | ````JavaScript
329 | function(event,{value:value,header:header,node:node,grid:grid,sourceName:sourceName})
330 | ````
331 |
332 | where:
333 |
334 | * value: value of the data element that rendered this cell
335 | * column: header for the column
336 | * node: reference to the <li> element in the tree that starts the row with the clicked cell
337 | * grid: reference to the <div> element in the grid that was clicked
338 | * sourceName: name of the element in the original data that contained this value, as provided by the config in the columns "value" for this column
339 |
340 |
341 | #### Cell Update
342 |
343 | The signature for the update_cell.jstree-grid handler is:
344 |
345 | ````JavaScript
346 | function(event,{node:node, col:column, value:newvalue,old:oldvalue})
347 | ````
348 |
349 | where:
350 |
351 | * node: the edited node in the data structure
352 | * col: name of the column, e.g. "price"
353 | * value: new value for the cell
354 | * old: the original value before it was changed
355 |
356 |
357 | The signature for the resize_column.jstree-grid handler is:
358 |
359 | ````JavaScript
360 | function(event,columnNumber,newWidth)
361 | ````
362 |
363 | where:
364 |
365 | * columnNumber: the number of the column being resized, with the first column being 0
366 | * newWidth: the new width of the column in pixels
367 |
368 | #### Cell Render
369 |
370 | The signature for the render_cell.jstree-grid handler is:
371 |
372 | ````JavaScript
373 | function(event,{value:cellvalue, column:columnheader, node:node, sourceName:datakeyname})
374 | ````
375 |
376 | where:
377 |
378 | * value: The value of the cell
379 | * column: The column header
380 | * node: Reference to the <li> element in the tree where this cell is rendered
381 | * sourceName: The key name in the data structure that gives the value to this cell
382 |
383 | ### Width
384 |
385 | The width of the table and its columns can change depending on the selected options. As described above, there are three options that affect the width:
386 |
387 | * `width`: in the root of the config, determines the width **for the entire jstree-grid**. If you set this `width` to `200`, the entire jstree-grid will occupy a fixed `200px` of screen width. The default is `100%`, i.e. fill the enclosing element or the viewport.
388 | * `columnWidth`: in the root of the config, determines the default width of a column for which a width is not provided.
389 | * `width`: in a particular column, determines the width of that individual column.
390 |
391 | A number of combinations and permutations is possible using the above.
392 |
393 | #### fixed `width` defined for entire grid
394 | If a fixed `width` is not defined for the entire grid, the behaviour depends on what happens in each column. In all cases, the entire jstree-grid will be the given fixed width.
395 |
396 | * If all columns have a fixed width defined, the columns are given those widths.
397 | * If the sum of all columns is *greater* than the width defined on the entire grid, the second column onwards will be inside a scrolling element.
398 | * If the sum of all columns is *less* than the width defined on the entire grid, the gap will be filled by whitespace after the last column.
399 | * If one or more columns is defined as `auto`, these will automatically be resized to fill to the entire grid.
400 | * If one or more columns does not have a `width` defined, it will be treated as the root value of `columnWidth`, else as `auto`
401 |
402 | Here are examples:
403 |
404 | ````JavaScript
405 | // all columns and grid defined widths, will have blank space after 3rd column
406 | {
407 | "width": "500px",
408 | "columns": [
409 | {"width": "100px"},
410 | {"width": "100px"},
411 | {"width": "150px"}
412 | ]
413 | }
414 |
415 | // all columns and grid defined widths, will scroll 2nd through last column
416 | {
417 | "width": "500px",
418 | "columns": [
419 | {"width": "200px"},
420 | {"width": "200px"},
421 | {"width": "400px"}
422 | ]
423 | }
424 |
425 | // grid defined width, some columns defined, some "auto", will automatically fill grid, no scrolling or whitespace
426 | {
427 | "width": "500px",
428 | "columns": [
429 | {"width": "200px"},
430 | {"width": "200px"},
431 | {"width": "auto"}
432 | ]
433 | }
434 |
435 | // grid defined width, some columns defined, some not, columnWidth fixed, will scroll
436 | // since 200 + 200 + 120 = 520 > 500
437 | // col0 + col1 + col2(from default) = 520 > grid width
438 | {
439 | "width": "500px",
440 | "columnWidth": "120px",
441 | "columns": [
442 | {"width": "200px"},
443 | {"width": "200px"},
444 | {}
445 | ]
446 | }
447 |
448 | // grid defined width, some columns defined, some not, columnWidth auto or not defined, will fill
449 | // since col2 inherits columnWidth = "auto", will default to filling at 100px
450 | {
451 | "width": "500px",
452 | "columns": [
453 | {"width": "200px"},
454 | {"width": "200px"},
455 | {}
456 | ]
457 | }
458 | ````
459 |
460 | #### fixed `width` not defined for entire grid
461 | If a fixed `width` is not defined for the entire grid, then the grid fills the entire containing element or viewport, i.e. it is treated as `width:100%;`. In that case, the widths of the columns are as follows:
462 |
463 | * If all columns have a fixed width defined, the columns are given those widths.
464 | * If the sum of all columns is *greater* than the calculated width of the entire grid, the second column onwards will be inside a scrolling element.
465 | * If the sum of all columns is *less* than the calculated width of the entire grid, the gap will be filled by whitespace after the last column.
466 | * If one or more columns is defined as `auto`, these will automatically be resized to fill to the entire grid.
467 | * If one or more columns does not have a `width` defined, it will be treated as the root value of `columnWidth`, else as `auto`
468 |
469 | In other words, the behaviour of columns does *not* change based on the `width` definition of the entire grid. It only affects the defined or calculated width of the grid, which in turn affects if the columns scroll (columns > grid), have whitespace (columns < grid), or fill (one or more columns is `auto`)
470 |
471 |
472 | ### V3 Changes
473 | jsTree v3 has created significant non-backwards-compatible changes to jsTree. To make jsTreeGrid compatible with jsTree3, jsTreeGrid v3 has changed as well, and is no longer backwards compatible. However, the changes required to support v3 are minimal.
474 |
475 | This section lists significant changes between pre-v3 and v3.
476 |
477 | * jstree v3 no longer stores its data in the DOM, rather inside JS. As such, jsTreeGrid no longer stores any data in the DOM. For example, in pre-v3 jstree, `attr` on a node's source data would store the actual data on the DOM as attributes using `jQuery.attr()`. This is no longer true in v3. jsTreeGrid similarly no longer looks for its source data on the DOM. All data to be passed to the grid should be stored in the `data` property of the node's JSON source.
478 | * `metadata` is no longer an option, since it is no longer necessary to use it to avoid storing data on the DOM.
479 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jstreegrid",
3 | "description": "grid plugin for jstree",
4 | "main": "jstreegrid.js",
5 | "authors": [
6 | {
7 | "name": "Avi Deitcher",
8 | "url": "https://github.com/deitch"
9 | }
10 | ],
11 | "license": "MIT",
12 | "keywords": [
13 | "jquery",
14 | "jstree",
15 | "grid",
16 | "javascript"
17 | ],
18 | "homepage": "https://github.com/deitch/jstree-grid",
19 | "ignore": [
20 | "**/.*",
21 | "node_modules",
22 | "bower_components",
23 | "test",
24 | "tests"
25 | ],
26 | "dependencies": {
27 | "jstree": "^3.3.1",
28 | "jquery": "^1.12.4"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/jstreegrid.js:
--------------------------------------------------------------------------------
1 | /*
2 | * http://github.com/deitch/jstree-grid
3 | *
4 | * This plugin handles adding a grid to a tree to display additional data
5 | *
6 | * Licensed under the MIT license:
7 | * http://www.opensource.org/licenses/mit-license.php
8 | *
9 | * Works only with jstree version >= 3.3.0
10 | *
11 | * $Date: 2017-04-19 $
12 | * $Revision: 3.8.2 $
13 | */
14 |
15 | /*jslint nomen:true */
16 | /*jshint unused:vars */
17 | /*global console, navigator, document, jQuery, define, localStorage */
18 |
19 | /* AMD support added by jochenberger per https://github.com/deitch/jstree-grid/pull/49
20 | *
21 | */
22 | (function (factory) {
23 | if (typeof define === 'function' && define.amd) {
24 | // AMD. Register as an anonymous module.
25 | define(['jquery', 'jstree'], factory);
26 | } else if(typeof module !== 'undefined' && module.exports) {
27 | module.exports = factory(require('jquery'));
28 | } else {
29 | // Browser globals
30 | factory(jQuery);
31 | }
32 | }(function ($) {
33 | var BLANKRE = /^\s*$/g,
34 | IDREGEX = /[\\:&!^|()\[\]<>@*'+~#";,= \/${}%]/g, escapeId = function (id) {
35 | return (id||"").replace(IDREGEX,'\\$&');
36 | }, NODE_DATA_ATTR = "data-jstreegrid", COL_DATA_ATTR = "data-jstreegrid-column",
37 | SEARCHCLASS = "jstree-search",
38 | SPECIAL_TITLE = "_DATA_", LEVELINDENT = 24, styled = false,
39 | MINCOLWIDTH = 10,
40 | generateCellId = function (tree,id) {
41 | return("jstree_"+tree+"_grid_"+escapeId(id)+"_col");
42 | },
43 | getIds = function (nodes) {
44 | return $.makeArray(nodes.map(function(){return this.id;}));
45 | },
46 | findDataCell = function (uniq, ids, col, scope) {
47 | if (scope == undefined) { scope = $(); };
48 | if (ids === null || ids === undefined || ids.length === 0) {
49 | return scope;
50 | }
51 | var ret = $(), columns = [].concat(col), cellId;
52 | if (typeof (ids) === "string") {
53 | cellId = generateCellId(uniq,ids);
54 | ret = columns.map(function (col) {
55 | return "#"+cellId+col;
56 | }).join(", ");
57 | } else {
58 | ret = []
59 | ids.forEach(function (elm,i) {
60 | var cellId = generateCellId(uniq,elm);
61 | ret = ret.concat(columns.map(function (col) {
62 | return "#"+cellId+col;
63 | }));
64 | });
65 | ret = ret.join(", ");
66 | }
67 | return columns.length ==1 ? scope.find(ret) : $(ret);
68 | },
69 | isClickedSep = false, toResize = null, oldMouseX = 0, newMouseX = 0,
70 |
71 | /*jslint regexp:true */
72 | htmlstripre = /<\/?[^>]+>/gi,
73 | /*jslint regexp:false */
74 |
75 | getIndent = function(node,tree) {
76 | var div, i, li, width;
77 |
78 | // did we already save it for this tree?
79 | tree._gridSettings = tree._gridSettings || {};
80 | if (tree._gridSettings.indent > 0) {
81 | width = tree._gridSettings.indent;
82 | } else {
83 | // create a new div on the DOM but not visible on the page
84 | div = $("");
85 | i = node.prev("i");
86 | li = i.parent();
87 | // add to that div all of the classes on the tree root
88 | div.addClass(tree.get_node("#",true).attr("class"));
89 |
90 | // move the li to the temporary div root
91 | li.appendTo(div);
92 |
93 | // attach to the body quickly
94 | div.appendTo($("body"));
95 |
96 | // get the width
97 | width = i.width() || LEVELINDENT;
98 |
99 | // detach the li from the new div and destroy the new div
100 | li.detach();
101 | div.remove();
102 |
103 | // save it for the future
104 | tree._gridSettings.indent = width;
105 | }
106 |
107 |
108 | return(width);
109 |
110 | },
111 |
112 | copyData = function (fromtree,from,totree,to,recurse) {
113 | var i, j;
114 | to.data = $.extend(true, {}, from.data);
115 | if (from && from.children_d && recurse) {
116 | for(i = 0, j = from.children_d.length; i < j; i++) {
117 | copyData(fromtree,fromtree.get_node(from.children_d[i]),totree,totree.get_node(to.children_d[i]),recurse);
118 | }
119 | }
120 | },
121 |
122 | findLastClosedNode = function (tree,id) {
123 | // first get our node
124 | var ret, node = tree.get_node(id), children = node.children;
125 | // is it closed?
126 | if (!children || children.length <= 0 || !node.state.opened) {
127 | ret = id;
128 | } else {
129 | ret = findLastClosedNode(tree,children[children.length-1]);
130 | }
131 | return(ret);
132 | },
133 |
134 | renderAWidth = function(node,tree) {
135 | var depth, width,
136 | fullWidth = parseInt(tree.settings.grid.columns[0].width,10) + parseInt(tree._gridSettings.treeWidthDiff,10);
137 | // need to use a selector in jquery 1.4.4+
138 | depth = tree.get_node(node).parents.length;
139 | width = fullWidth - depth*getIndent(node,tree);
140 | // the following line is no longer needed, since we are doing this inside a
141 | //a.css({"vertical-align": "top", "overflow":"hidden"});
142 | return(fullWidth);
143 | },
144 | renderATitle = function(node,t,tree) {
145 | var a = node.hasClass("jstree-anchor") ? node : node.children("[class~='jstree-anchor']"), title, col = tree.settings.grid.columns[0];
146 | // get the title
147 | title = "";
148 | if (col.title) {
149 | if (col.title === SPECIAL_TITLE) {
150 | title = tree.get_text(t);
151 | } else if (t.attr(col.title)) {
152 | title = t.attr(col.title);
153 | }
154 | }
155 | // strip out HTML
156 | title = title.replace(htmlstripre, '');
157 | if (title) {
158 | a.attr("title",title);
159 | }
160 | },
161 | getCellData = function (value,data) {
162 | var val;
163 | // get the contents of the cell - value could be a string or a function
164 | if (value !== undefined && value !== null) {
165 | if (typeof(value) === "function") {
166 | val = value(data);
167 | } else if (data.data !== null && data.data !== undefined && data.data[value] !== undefined) {
168 | val = data.data[value];
169 | } else {
170 | val = "";
171 | }
172 | } else {
173 | val = "";
174 | }
175 | return val;
176 | };
177 |
178 | $.jstree.defaults.grid = {
179 | width: 'auto'
180 | };
181 |
182 | $.jstree.plugins.grid = function(options,parent) {
183 | this._initialize = function () {
184 | if (!this._initialized) {
185 | var s = this.settings.grid || {}, styles, container = this.element, i,
186 | gs = this._gridSettings = {
187 | columns : s.columns || [],
188 | treeClass : "jstree-grid-col-0",
189 | context: s.contextmenu || false,
190 | columnWidth : s.columnWidth,
191 | defaultConf : {"*display":"inline","*+display":"inline"},
192 | isThemeroller : !!this._data.themeroller,
193 | treeWidthDiff : 0,
194 | resizable : s.resizable,
195 | draggable : s.draggable,
196 | stateful: s.stateful,
197 | headerAsTitle: s.headerAsTitle || false,
198 | indent: 0,
199 | sortOrder: 'text',
200 | sortAsc: true,
201 | caseInsensitive: s.caseInsensitive,
202 | fixedHeader: s.fixedHeader !== false,
203 | width: s.width,
204 | height: s.height,
205 | gridcontextmenu : s.gridcontextmenu,
206 | treecol: 0,
207 | gridcols: []
208 | }, cols = gs.columns, treecol = 0, columnSearch = false;
209 | if(gs.gridcontextmenu === true) {
210 | gs.gridcontextmenu = function (grid,tree,node,val,col,t,target) {
211 | return {
212 | "edit": {
213 | label: "Edit",
214 | "action": function (data) {
215 | var obj = t.get_node(node);
216 | grid._edit(obj,col,target);
217 | }
218 | }
219 | }
220 | }
221 | } else if (gs.gridcontextmenu === false) {
222 | gs.gridcontextmenu = false;
223 | }
224 | // find which column our tree shuld go in
225 | for (var i = 0, len = s.columns.length;i'+styles.join("\n")+'').appendTo("head");
269 | }
270 | this.gridWrapper = $("").addClass("jstree-grid-wrapper").insertAfter(container);
271 | this.midWrapper = $("").addClass("jstree-grid-midwrapper").appendTo(this.gridWrapper);
272 | // set the wrapper width
273 | if (s.width) {
274 | this.gridWrapper.width(s.width);
275 | }
276 | if (s.height) {
277 | this.gridWrapper.height(s.height);
278 | }
279 | // create the data columns
280 | for (var i = 0, len = cols.length;i").addClass("jstree-default jstree-grid-column jstree-grid-column-"+i+" jstree-grid-column-root-"+this.rootid).appendTo(this.midWrapper);
283 | }
284 | this.midWrapper.children("div:eq("+treecol+")").append(container);
285 | container.addClass("jstree-grid-cell");
286 |
287 | //move header with scroll
288 | if (gs.fixedHeader) {
289 | this.gridWrapper.scroll(function() {
290 | $(this).find('.jstree-grid-header').css('top', $(this).scrollTop());
291 | });
292 | }
293 |
294 | // copy original sort function
295 | var defaultSort = $.proxy(this.settings.sort, this);
296 |
297 | // override sort function
298 | this.settings.sort = function (a, b) {
299 | var bigger, colrefs = this.colrefs;
300 |
301 | if (gs.sortOrder==='text') {
302 | var caseInsensitiveSort = this.get_text(a).toLowerCase().localeCompare(this.get_text(b).toLowerCase());
303 | bigger = gs.caseInsensitive ? (caseInsensitiveSort === 1) : (defaultSort(a, b) === 1);
304 | } else {
305 | // gs.sortOrder just refers to the unique random name for this column
306 | // we need to get the correct value
307 | var nodeA = this.get_node(a), nodeB = this.get_node(b),
308 | value = colrefs[gs.sortOrder].value,
309 | valueA = typeof(value) === 'function' ? value(nodeA) : nodeA.data[value],
310 | valueB = typeof(value) === 'function' ? value(nodeB) : nodeB.data[value];
311 | if(typeof(valueA) && typeof(valueB) !== 'undefined') {
312 | bigger = gs.caseInsensitive ? valueA.toLowerCase() > valueB.toLowerCase(): valueA > valueB ;
313 | }
314 | }
315 |
316 | if (!gs.sortAsc)
317 | bigger = !bigger;
318 |
319 | return bigger ? 1 : -1;
320 | };
321 |
322 | // sortable columns when jQuery UI is available
323 | if (gs.draggable) {
324 | if (!$.ui || !$.ui.sortable) {
325 | console.warn('[jstree-grid] draggable option requires jQuery UI');
326 | } else {
327 | var from, to;
328 |
329 | $(this.midWrapper).sortable({
330 | axis: "x",
331 | handle: ".jstree-grid-header",
332 | cancel: ".jstree-grid-separator",
333 | start: function (event, ui) {
334 | from = ui.item.index();
335 | },
336 | stop: function (event, ui) {
337 | to = ui.item.index();
338 | gs.columns.splice(to, 0, gs.columns.splice(from, 1)[0]);
339 | }
340 | });
341 | }
342 | }
343 |
344 | //public function. validate searchObject keys, set columnSearch flag, calls jstree search and reset columnSearch flag
345 | this.searchColumn = function (searchObj) {
346 | var validatedSearchObj = {};
347 |
348 | if(typeof searchObj == 'object') {
349 | for(var columnIndex in searchObj) {
350 | if(searchObj.hasOwnProperty(columnIndex)) {
351 | // keys should be the index of a column. This means the following:
352 | // only integers and smaller than the number of columns and bigger or equal to 0
353 | // (possilbe idea for in the future: ability to set key as a more human readable term like the column header and then map it here to an index)
354 | if (columnIndex % 1 === 0 && columnIndex < cols.length && columnIndex >= 0) {
355 | validatedSearchObj[columnIndex] = searchObj[columnIndex];
356 | }
357 | }
358 | }
359 | }
360 | columnSearch = validatedSearchObj;
361 |
362 | if(Object.keys(validatedSearchObj).length !== 0){
363 | //the search string doesn't matter. we'll use the search string in the columnSearch object!
364 | this.search('someValue');
365 | } else { // nothing to search so reset jstree's search by passing an empty string
366 | this.search('');
367 | }
368 | columnSearch = false;
369 | }
370 |
371 |
372 | // set default search for each column with no user defined search function (used when doing a columnSearch)
373 | for (var i = 0, len = cols.length; idiv.jstree-grid-cell-root-'+this.rootid+' {line-height: '+anchorHeight+'px; height: '+anchorHeight+'px;}').appendTo("head");
498 |
499 | // add container classes to the wrapper - EXCEPT those that are added by jstree, i.e. "jstree" and "jstree-*"
500 | q = cls.split(/\s+/).map(function(i){
501 | var match = i.match(/^jstree(-|$)/);
502 | return (match ? "" : i);
503 | });
504 | this.gridWrapper.addClass(q.join(" "));
505 |
506 | },this))
507 | .on("move_node.jstree",$.proxy(function(e,data){
508 | var node = data.new_instance.element;
509 | //renderAWidth(node,this);
510 | // check all the children, because we could drag a tree over
511 | node.find("li > a").each($.proxy(function(i,elm){
512 | //renderAWidth($(elm),this);
513 | },this));
514 |
515 | },this))
516 | .on("hover_node.jstree",$.proxy(function(node,selected,event){
517 | var id = selected.node.id;
518 | if (this._hover_node !== null && this._hover_node !== undefined) {
519 | findDataCell(this.uniq,this._hover_node,this._gridSettings.gridcols).removeClass("jstree-hovered");
520 | }
521 | this._hover_node = id;
522 | findDataCell(this.uniq,id,this._gridSettings.gridcols).addClass("jstree-hovered");
523 | },this))
524 | .on("dehover_node.jstree",$.proxy(function(node,selected,event){
525 | var id = selected.node.id;
526 | this._hover_node = null;
527 | findDataCell(this.uniq,id,this._gridSettings.gridcols).removeClass("jstree-hovered");
528 | },this))
529 | .on("select_node.jstree",$.proxy(function(node,selected,event){
530 | var id = selected.node.id;
531 | findDataCell(this.uniq,id,this._gridSettings.gridcols).addClass("jstree-clicked");
532 | this.get_node(selected.node.id,true).children("div.jstree-grid-cell").addClass("jstree-clicked");
533 | },this))
534 | .on("deselect_node.jstree",$.proxy(function(node,selected,event){
535 | var id = selected.node.id;
536 | findDataCell(this.uniq,id,this._gridSettings.gridcols).removeClass("jstree-clicked");
537 | },this))
538 | .on("deselect_all.jstree",$.proxy(function(node,selected,event){
539 | // get all of the ids that were unselected
540 | var ids = selected.node || [], i;
541 | findDataCell(this.uniq,ids,this._gridSettings.gridcols).removeClass("jstree-clicked");
542 | },this))
543 | .on("search.jstree", $.proxy(function (e, data) {
544 | // search sometimes filters, so we need to hide all of the appropriate grid cells as well, and show only the matches
545 | var grid = this.gridWrapper, that = this, nodesToShow, startTime = new Date().getTime(),
546 | ids = getIds(data.nodes.filter(".jstree-node")), endTime;
547 | this.holdingCells = {};
548 | if (data.nodes.length) {
549 | var id = _guid();
550 | // save the cells we will hide
551 | var cells = grid.find('div.jstree-grid-cell-regular');
552 | this._detachColumns(id);
553 | if(this._data.search.som) {
554 | // create the list of nodes we want to look at
555 | if(this._data.search.smc) {
556 | nodesToShow = data.nodes.add(data.nodes.find('.jstree-node'));
557 | }
558 | nodesToShow = (nodesToShow || data.nodes).add(data.nodes.parentsUntil(".jstree"));
559 |
560 | // hide all of the grid cells
561 | cells.hide();
562 | // show only those that match
563 | nodesToShow.filter(".jstree-node").each(function (i,node) {
564 | var id = node.id;
565 | if (id) {
566 | that._prepare_grid(node);
567 | for (var i = 0, len = that._gridSettings.gridcols.length; i < len; i++) {
568 | if (i === that._gridSettings.treecol) { continue; }
569 | findDataCell(that.uniq, id, that._gridSettings.gridcols[i], $(that._domManipulation.columns[i])).show();
570 | }
571 | }
572 | });
573 | }
574 |
575 | for (var i = 0, len = this._gridSettings.gridcols.length; i < len; i++) {
576 | if (i === this._gridSettings.treecol) { continue; }
577 | findDataCell(that.uniq, ids, this._gridSettings.gridcols[i], $(this._domManipulation.columns[i])).addClass(SEARCHCLASS);
578 | }
579 | this._reattachColumns(id);
580 | endTime = new Date().getTime();
581 | this.element.trigger("search-complete.jstree-grid", [{time:endTime-startTime}]);
582 | }
583 | return true;
584 | }, this))
585 | .on("clear_search.jstree", $.proxy(function (e, data) {
586 | // search has been cleared, so we need to show all rows
587 | var grid = this.gridWrapper, ids = getIds(data.nodes.filter(".jstree-node"));
588 | grid.find('div.jstree-grid-cell').show();
589 | findDataCell(this.uniq,ids,this._gridSettings.gridcols).removeClass(SEARCHCLASS);
590 | return true;
591 | }, this))
592 | .on("copy_node.jstree", function (e, data) {
593 | var newtree = data.new_instance, oldtree = data.old_instance, obj = newtree.get_node(data.node,true);
594 | copyData(oldtree, data.original, newtree, data.node, true);
595 | newtree._detachColumns(obj.id);
596 | newtree._prepare_grid(obj);
597 | newtree._reattachColumns(obj.id);
598 | return true;
599 | })
600 | .on("show_ellipsis.jstree", $.proxy(function (e, data) {
601 | this.gridWrapper.find(".jstree-grid-cell").add(".jstree-grid-header", this.gridWrapper).addClass("jstree-grid-ellipsis");
602 | return true;
603 | }, this))
604 | .on("hide_ellipsis.jstree", $.proxy(function (e, data) {
605 | this.gridWrapper.find(".jstree-grid-cell").add(".jstree-grid-header", this.gridWrapper).removeClass("jstree-grid-ellipsis");
606 | return true;
607 | }, this))
608 | .on("enable_node.jstree", $.proxy(function (e, data) {
609 | var id = data.node.id;
610 | findDataCell(this.uniq,id,this._gridSettings.gridcols).removeClass("jstree-disabled");
611 | this.get_node(data.node.id,true).children("div.jstree-grid-cell").removeClass("jstree-disabled");
612 | }, this))
613 | .on("disable_node.jstree", $.proxy(function (e, data) {
614 | var id = data.node.id;
615 | findDataCell(this.uniq,id,this._gridSettings.gridcols).addClass("jstree-disabled");
616 | this.get_node(data.node.id,true).children("div.jstree-grid-cell").addClass("jstree-disabled");
617 | }, this))
618 | ;
619 | if (this._gridSettings.isThemeroller) {
620 | this.element
621 | .on("select_node.jstree",$.proxy(function(e,data){
622 | data.rslt.obj.children("[class~='jstree-anchor']").nextAll("div").addClass("ui-state-active");
623 | },this))
624 | .on("deselect_node.jstree deselect_all.jstree",$.proxy(function(e,data){
625 | data.rslt.obj.children("[class~='jstree-anchor']").nextAll("div").removeClass("ui-state-active");
626 | },this))
627 | .on("hover_node.jstree",$.proxy(function(e,data){
628 | data.rslt.obj.children("[class~='jstree-anchor']").nextAll("div").addClass("ui-state-hover");
629 | },this))
630 | .on("dehover_node.jstree",$.proxy(function(e,data){
631 | data.rslt.obj.children("[class~='jstree-anchor']").nextAll("div").removeClass("ui-state-hover");
632 | },this));
633 | }
634 |
635 | if (this._gridSettings.stateful) {
636 | this.element
637 | .on("resize_column.jstree-grid",$.proxy(function(e,col,width){
638 | localStorage['jstree-root-'+this.rootid+'-column-'+col] = width;
639 | },this));
640 | }
641 | };
642 |
643 | // tear down the tree entirely
644 | this.teardown = function() {
645 | var gw = this.gridWrapper, container = this.element, gridparent = gw.parent();
646 | container.detach();
647 | gw.remove();
648 | gridparent.append(container);
649 | parent.teardown.call(this);
650 | };
651 | // clean the grid in case of redraw or refresh entire tree
652 | this._clean_grid = function (target,id) {
653 | var grid = this.gridWrapper;
654 | if (target) {
655 | findDataCell(this.uniq,id,this._gridSettings.gridcols).remove();
656 | } else {
657 | // get all of the `div` children in all of the `td` in dataRow except for :first (that is the tree itself) and remove
658 | grid.find("div.jstree-grid-cell-regular").remove();
659 | }
660 | };
661 | // prepare the headers
662 | this._prepare_headers = function() {
663 | var header, i, col, _this = this, gs = this._gridSettings,cols = gs.columns || [], width, defaultWidth = gs.columnWidth, resizable = gs.resizable || false,
664 | cl, ccl, val, name, last, tr = gs.isThemeroller, classAdd = (tr?"themeroller":"regular"), puller,
665 | hasHeaders = false, gridparent = this.gridparent, rootid = this.rootid,
666 | conf = gs.defaultConf, coluuid,
667 | borPadWidth = 0, totalWidth = 0;
668 |
669 | // save the original parent so we can reparent on destroy
670 | this.parent = gridparent;
671 |
672 | // save the references to columns by unique ID
673 | this.colrefs = {};
674 |
675 |
676 | // create the headers
677 | for (var i = 0, len = cols.length;i");
679 | //col.appendTo(colgroup);
680 | cl = cols[i].headerClass || "";
681 | ccl = cols[i].columnClass || "";
682 | val = cols[i].header || "";
683 | headerTitle = cols[i].headerTitle || "";
684 | do {
685 | coluuid = String(Math.floor(Math.random()*10000));
686 | } while(this.colrefs[coluuid] !== undefined);
687 | // create a unique name for this column
688 | name = cols[i].value ? coluuid : "text";
689 | this.colrefs[name] = cols[i];
690 |
691 | if (val) {hasHeaders = true;}
692 | if(gs.stateful && localStorage['jstree-root-'+rootid+'-column-'+i])
693 | width = localStorage['jstree-root-'+rootid+'-column-'+i];
694 | else
695 | width = cols[i].width || defaultWidth;
696 |
697 | var minWidth = cols[i].minWidth || width;
698 | var maxWidth = cols[i].maxWidth || width;
699 |
700 | // we only deal with borders if width is not auto and not percentages
701 | borPadWidth = tr ? 1+6 : 2+8; // account for the borders and padding
702 | if (width !== 'auto' && typeof(width) !== "string") {
703 | width -= borPadWidth;
704 | }
705 | col = this.midWrapper.children("div.jstree-grid-column-"+i);
706 | last = $("").css(conf).addClass("jstree-grid-div-"+this.uniq+"-"+i+" "+(tr?"ui-widget-header ":"")+" jstree-grid-header jstree-grid-header-cell jstree-grid-header-"+classAdd+" "+cl+" "+ccl).html(val);
707 | last.addClass((tr?"ui-widget-header ":"")+"jstree-grid-header jstree-grid-header-"+classAdd);
708 | if (this.settings.core.themes.ellipsis === true){
709 | last.addClass('jstree-grid-ellipsis');
710 | }
711 |
712 | // add title and strip out HTML
713 | var title = gs.headerAsTitle && !headerTitle ? val : (headerTitle ? headerTitle : "");
714 | title = title.replace(htmlstripre, '');
715 | if (title) {
716 | last.attr("title",title);
717 | }
718 |
719 | last.prependTo(col);
720 | last.attr(COL_DATA_ATTR, name);
721 | totalWidth += last.outerWidth();
722 | puller = $("
").appendTo(last);
723 | col.width(width);
724 | col.css("min-width", minWidth);
725 | col.css("max-width", maxWidth);
726 | }
727 |
728 | last.addClass((tr?"ui-widget-header ":"")+"jstree-grid-header jstree-grid-header-last jstree-grid-header-"+classAdd);
729 | // if there is no width given for the last column, do it via automatic
730 | if (cols[cols.length-1].width === undefined) {
731 | totalWidth -= width;
732 | col.css({width:"auto"});
733 | last.addClass("jstree-grid-width-auto").next(".jstree-grid-separator").remove();
734 | }
735 | if (hasHeaders) {
736 | // save the offset of the div from the body
737 | //gs.divOffset = header.parent().offset().left;
738 | gs.header = header;
739 | } else {
740 | $("div.jstree-grid-header").hide();
741 | }
742 |
743 | if (!this.bound && resizable) {
744 | this.bound = true;
745 | $(document).mouseup(function () {
746 | var ref, cols, width, headers, currentTree, colNum;
747 | if (isClickedSep) {
748 | colNum = toResize.prevAll(".jstree-grid-column").length;
749 | currentTree = toResize.closest(".jstree-grid-wrapper").find(".jstree");
750 | ref = $.jstree.reference(currentTree);
751 | cols = ref.settings.grid.columns;
752 | headers = toResize.parent().children("div.jstree-grid-column");
753 | if (isNaN(colNum) || colNum < 0) { ref._gridSettings.treeWidthDiff = currentTree.find("ins:eq(0)").width() + currentTree.find("[class~='jstree-anchor']:eq(0)").width() - ref._gridSettings.columns[0].width; }
754 | width = ref._gridSettings.columns[colNum].width = parseFloat(toResize.css("width"));
755 | isClickedSep = false;
756 | toResize = null;
757 |
758 | currentTree.trigger("resize_column.jstree-grid", [colNum,width]);
759 | }
760 | }).mousemove(function (e) {
761 | if (isClickedSep) {
762 | newMouseX = e.pageX;
763 | var diff = newMouseX - oldMouseX,
764 | oldPrevHeaderInner,
765 | oldPrevColWidth, newPrevColWidth;
766 |
767 | if (diff !== 0){
768 | oldPrevHeaderInner = toResize.width();
769 | oldPrevColWidth = parseFloat(toResize.css("width"));
770 |
771 | // handle a Chrome issue with columns set to auto
772 | // thanks to Brabus https://github.com/side-by-side
773 | if (!oldPrevColWidth) {oldPrevColWidth = toResize.innerWidth();}
774 |
775 | // make sure that diff cannot be beyond the left/right limits
776 | diff = diff < 0 ? Math.max(diff,-oldPrevHeaderInner) : diff;
777 | newPrevColWidth = oldPrevColWidth+diff;
778 |
779 | // only do this if we are not shrinking past 0 on left - and limit it to that amount
780 | if ((diff > 0 || oldPrevHeaderInner > 0) && newPrevColWidth > MINCOLWIDTH) {
781 | toResize.width(newPrevColWidth+"px");
782 | toResize.css("min-width",newPrevColWidth+"px");
783 | toResize.css("max-width",newPrevColWidth+"px");
784 | oldMouseX = newMouseX;
785 | }
786 | }
787 | }
788 | });
789 | this.gridWrapper.on("selectstart", ".jstree-grid-resizable-separator", function () {
790 | return false;
791 | }).on("mousedown", ".jstree-grid-resizable-separator", function (e) {
792 | isClickedSep = true;
793 | oldMouseX = e.pageX;
794 | toResize = $(this).closest("div.jstree-grid-column");
795 | // the max rightmost position we will allow is the right-most of the wrapper minus a buffer (10)
796 | return false;
797 | })
798 | .on("dblclick", ".jstree-grid-resizable-separator", function (e) {
799 | var clickedSep = $(this), col = clickedSep.closest("div.jstree-grid-column"),
800 | oldPrevColWidth = parseFloat(col.css("width")), newWidth = 0, diff,
801 | colNum = col.prevAll(".jstree-grid-column").length,
802 | oldPrevHeaderInner = col.width(), newPrevColWidth;
803 |
804 |
805 | //find largest width
806 | col.find(".jstree-grid-cell").each(function() {
807 | var item = $(this), width;
808 | item.css("position", "absolute");
809 | item.css("width", "auto");
810 | width = item.outerWidth();
811 | item.css("position", "relative");
812 |
813 | if (width>newWidth) {
814 | newWidth = width;
815 | }
816 | });
817 |
818 | diff = newWidth-oldPrevColWidth;
819 |
820 | // make sure that diff cannot be beyond the left limits
821 | diff = diff < 0 ? Math.max(diff,-oldPrevHeaderInner) : diff;
822 | newPrevColWidth = (oldPrevColWidth+diff)+"px";
823 |
824 | col.width(newPrevColWidth);
825 | col.css("min-width",newPrevColWidth);
826 | col.css("max-width",newPrevColWidth);
827 |
828 | $(this).closest(".jstree-grid-wrapper").find(".jstree").trigger("resize_column.jstree-grid",[colNum,newPrevColWidth]);
829 | })
830 | .on("click", ".jstree-grid-separator", function (e) {
831 | // don't sort after resize
832 | e.stopPropagation();
833 | });
834 | }
835 | this.gridWrapper.on("click", ".jstree-grid-header-cell", function (e) {
836 | if (!_this.sort) {
837 | return;
838 | }
839 |
840 | // get column
841 | var name = $(this).attr(COL_DATA_ATTR);
842 |
843 | // sort order
844 | var symbol;
845 | if (gs.sortOrder === name && gs.sortAsc === true) {
846 | gs.sortAsc = false;
847 | symbol = "↓";
848 | } else {
849 | gs.sortOrder = name;
850 | gs.sortAsc = true;
851 | symbol = "↑";
852 | }
853 |
854 | // add sort arrow
855 | $(this.closest('.jstree-grid-wrapper')).find(".jstree-grid-sort-icon").remove();
856 | $("").addClass("jstree-grid-sort-icon").appendTo($(this)).html(symbol);
857 |
858 | // sort by column
859 | var rootNode = _this.get_node('#');
860 | _this.sort(rootNode, true);
861 | _this.redraw_node(rootNode, true);
862 | });
863 |
864 | };
865 |
866 | this._domManipulation = null; // We'll store the column nodes in this object and an id for the grid-node that started the manipulation { id: "id of the node that started the manipulation", columns: { Key-Value-Pair col-No: Column }}
867 |
868 | function _guid() {
869 | function s4() {
870 | return Math.floor((1 + Math.random()) * 0x10000)
871 | .toString(16)
872 | .substring(1);
873 | }
874 | return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
875 | s4() + '-' + s4() + s4() + s4();
876 | }
877 | /*
878 | * Trys to detach the tree columns on massive dom manipulations
879 | */
880 | this._detachColumns = function (id) {
881 | // if the columns are not detached, then detach them
882 | if (this._domManipulation == null) {
883 | var cols = this._gridSettings.columns || [], treecol = this._gridSettings.treecol, mw = this.midWrapper;
884 | this._domManipulation = { id: id, oldActiveElement: document.activeElement, columns: {} };
885 | for (var i = 0, len = cols.length; i < len; i++) {
886 | //if (treecol === i) {
887 | // continue;
888 | //}
889 | this._domManipulation.columns[i] = mw.children(".jstree-grid-column-" + i)[0];
890 | this._domManipulation.columns[i].parentNode.removeChild(this._domManipulation.columns[i]);
891 | }
892 | }
893 | return this._domManipulation;
894 | }
895 |
896 | this._reattachColumns = function (id) {
897 | if (this._domManipulation == null) { return false; }
898 | if (this._domManipulation.id === id) {
899 | var cols = this._gridSettings.columns || [], treecol = this._gridSettings.treecol, mw = this.midWrapper;
900 | for (var i = 0, len = cols.length; i < len; i++) {
901 | //if (treecol === i) {
902 | // continue;
903 | //}
904 | mw[0].appendChild(this._domManipulation.columns[i]);
905 | }
906 | if (this._domManipulation.oldActiveElement !== document.activeElement) { $(this._domManipulation.oldActiveElement).focus(); }
907 | this._domManipulation = null;
908 | }
909 | return true;
910 | }
911 |
912 | /*
913 | * Override open_node to detach the columns before redrawing child-nodes, and do reattach them afterwarts
914 | */
915 | this.open_node = function (obj, callback, animation) {
916 | var isArray = $.isArray(obj);
917 | var node = null;
918 | if (!isArray) {
919 | node = this.get_node(obj);
920 | if (node.id === "#") { return; } // wtf??? we ar in the root and do not need a open!
921 | }
922 | var id = isArray ? _guid() : node.id;
923 | this._detachColumns(id);
924 | var ret = parent.open_node.call(this, obj, callback, animation);
925 | this._reattachColumns(id);
926 | return ret;
927 | }
928 |
929 | /*
930 | * Override redraw_node to correctly insert the grid
931 | */
932 | this.redraw_node = function (obj, deep, is_callback, force_render) {
933 | var id = $.isArray(obj) ? _guid() : this.get_node(obj).id;
934 | // we detach the columns once
935 | this._detachColumns(id);
936 | // first allow the parent to redraw the node
937 | obj = parent.redraw_node.call(this, obj, deep, is_callback, force_render);
938 | // next prepare the grid for a redrawn node - but only if ths node is not hidden (search does that)
939 | if (obj) {
940 | this._prepare_grid(obj);
941 | }
942 | // don't forget to reattach
943 | this._reattachColumns(id);
944 | return obj;
945 | };
946 | this.refresh = function () {
947 | this._clean_grid();
948 | return parent.refresh.apply(this,arguments);
949 | };
950 | /*
951 | * Override set_id to update cell attributes
952 | */
953 | this.set_id = function (obj, id) {
954 | var old, uniq = this.uniq;
955 | if(obj) {
956 | old = obj.id;
957 | }
958 | var result = parent.set_id.apply(this,arguments);
959 | if(result) {
960 | if (old !== undefined) {
961 | var grid = this.gridWrapper, oldNodes = [old], i;
962 | // get children
963 | if (obj && obj.children_d) {
964 | oldNodes = oldNodes.concat(obj.children_d);
965 | }
966 | // update id in children
967 | findDataCell(uniq,oldNodes,this._gridSettings.gridcols)
968 | .attr(NODE_DATA_ATTR, obj.id)
969 | .removeClass(generateCellId(uniq,old))
970 | .addClass(generateCellId(uniq,obj.id))
971 | .each(function(i,node) {
972 | $(node).attr('id', generateCellId(uniq,obj.id)+(i+1));
973 | });
974 | }
975 | }
976 | return result;
977 | };
978 |
979 | this._hideOrShowTree = function(node, hide) {
980 | //Hides or shows a tree
981 | this._detachColumns(node.id);
982 | // show cells in each detachted column
983 | this._hideOrShowNode(node, hide, this._gridSettings.columns || [], this._gridSettings.treecol);
984 | this._reattachColumns(node.id);
985 | }
986 | this._hideOrShowNode = function(node, hide, cols, treecol) {
987 | //Hides or shows a node with recursive calls to all open child-nodes
988 | for (var i = 0, len = cols.length; i < len; i++) {
989 | if (i === treecol) { continue; }
990 | var cells = findDataCell(this.uniq, node.id, i, $(this._domManipulation.columns[i]));
991 | if (hide) {
992 | cells.addClass("jstree-grid-hidden");
993 | } else {
994 | cells.removeClass("jstree-grid-hidden");
995 | }
996 | }
997 | if (node.state.opened && node.children) {
998 | for (var i = 0, len = node.children.length; i < len; i++) {
999 | this._hideOrShowNode(this.get_node(node.children[i]), hide, cols, treecol);
1000 | }
1001 | }
1002 | }
1003 | this._hide_grid = function (node) {
1004 | if (!node) { return true; }
1005 | this._detachColumns(node.id);
1006 | var children = node.children ? node.children : [], cols = this._gridSettings.columns || [], treecol = this._gridSettings.treecol;
1007 | // try to remove all children
1008 | for (var i = 0, len = children.length; i < len; i++) {
1009 | var child = this.get_node(children[i]);
1010 | // go through each column, remove all children with the correct ID name
1011 | for (var j = 0, lenj = cols.length; j < lenj; j++) {
1012 | if (j === treecol) { continue; }
1013 | findDataCell(this.uniq, child.id, j, $(this._domManipulation.columns[j])).remove();
1014 | }
1015 | if (child.state.opened) { this._hide_grid(child);}
1016 | }
1017 |
1018 |
1019 | this._reattachColumns(node.id);
1020 | };
1021 | this.holdingCells = {};
1022 | this.getHoldingCells = function (obj, col, hc) {
1023 | if (obj.state.hidden || !obj.state.opened) { return $(); }
1024 | var ret = $(), children = obj.children || [], child, i, uniq = this.uniq;
1025 | // run through each child, render it, and then render its children recursively
1026 | for (i = 0; i < children.length; i++) {
1027 | child = generateCellId(uniq, children[i]) + col;
1028 | if (hc[child]) {
1029 | ret = ret.add(hc[child]).add(this.getHoldingCells(this.get_node(children[i]), col, hc));
1030 | //delete hc[child];
1031 | }
1032 | }
1033 | return (ret);
1034 | };
1035 |
1036 | /**
1037 | * put a grid cell in edit mode (input field to edit the data)
1038 | * @name edit(obj, col)
1039 | * @param {mixed} obj
1040 | * @param {obj} col definition
1041 | * @param {element} cell element, either span or wrapping div
1042 | */
1043 | this._edit = function (obj, col, element) {
1044 | if(!obj) { return false; }
1045 | if (!obj.data) {obj.data = {};}
1046 | if (element) {
1047 | element = $(element);
1048 | if (element.prop("tagName").toLowerCase() === "div") {
1049 | element = element.children("span:first");
1050 | }
1051 | } else {
1052 | // need to find the element - later
1053 | return false;
1054 | }
1055 | var rtl = this._data.core.rtl,
1056 | w = this.element.width(),
1057 | t = obj.data[col.value],
1058 | h1 = $("<"+"div />", { css : { "position" : "absolute", "top" : "-200px", "left" : (rtl ? "0px" : "-1000px"), "visibility" : "hidden" } }).appendTo("body"),
1059 | h2 = $("<"+"input />", {
1060 | "value" : t,
1061 | "class" : "jstree-rename-input",
1062 | "css" : {
1063 | "padding" : "0",
1064 | "border" : "1px solid silver",
1065 | "box-sizing" : "border-box",
1066 | "display" : "inline-block",
1067 | "height" : (this._data.core.li_height) + "px",
1068 | "lineHeight" : (this._data.core.li_height) + "px",
1069 | "width" : "150px" // will be set a bit further down
1070 | },
1071 | "blur" : $.proxy(function () {
1072 | var v = h2.val();
1073 | // save the value if changed
1074 | if(v === "" || v === t) {
1075 | v = t;
1076 | } else {
1077 | obj.data[col.value] = v;
1078 | this.element.trigger('update_cell.jstree-grid', { node: obj, col: col.value, value: v, old: t });
1079 | var id = _guid();
1080 | this._detachColumns(id);
1081 | this._prepare_grid(this.get_node(obj, true));
1082 | this._reattachColumns(id);
1083 | }
1084 | h2.remove();
1085 | element.show();
1086 | }, this),
1087 | "keydown" : function (event) {
1088 | var key = event.which;
1089 | if(key === 27) {
1090 | this.value = t;
1091 | }
1092 | if(key === 27 || key === 13 || key === 37 || key === 38 || key === 39 || key === 40 || key === 32) {
1093 | event.stopImmediatePropagation();
1094 | }
1095 | if(key === 27 || key === 13) {
1096 | event.preventDefault();
1097 | this.blur();
1098 | }
1099 | },
1100 | "click" : function (e) { e.stopImmediatePropagation(); },
1101 | "mousedown" : function (e) { e.stopImmediatePropagation(); },
1102 | "keyup" : function (event) {
1103 | h2.width(Math.min(h1.text("pW" + this.value).width(),w));
1104 | },
1105 | "keypress" : function(event) {
1106 | if(event.which === 13) { return false; }
1107 | }
1108 | }),
1109 | fn = {
1110 | fontFamily : element.css('fontFamily') || '',
1111 | fontSize : element.css('fontSize') || '',
1112 | fontWeight : element.css('fontWeight') || '',
1113 | fontStyle : element.css('fontStyle') || '',
1114 | fontStretch : element.css('fontStretch') || '',
1115 | fontVariant : element.css('fontVariant') || '',
1116 | letterSpacing : element.css('letterSpacing') || '',
1117 | wordSpacing : element.css('wordSpacing') || ''
1118 | };
1119 | element.hide();
1120 | element.parent().append(h2);
1121 | h2.css(fn).width(Math.min(h1.text("pW" + h2[0].value).width(),w))[0].select();
1122 | };
1123 |
1124 | this.grid_hide_column = function (col) {
1125 | this.midWrapper.find(".jstree-grid-column-"+col).hide();
1126 | };
1127 | this.grid_show_column = function (col) {
1128 | this.midWrapper.find(".jstree-grid-column-"+col).show();
1129 | };
1130 |
1131 | this._prepare_grid = function (obj) {
1132 | var gs = this._gridSettings, c = gs.treeClass, _this = this, t,
1133 | cols = gs.columns || [], width, tr = gs.isThemeroller, uniq = this.uniq,
1134 | treecol = gs.treecol,
1135 | tree = this.element, rootid = this.rootid,
1136 | classAdd = (tr?"themeroller":"regular"), img, objData = this.get_node(obj),
1137 | defaultWidth = gs.columnWidth, conf = gs.defaultConf, cellClickHandler = function (tree,node,val,col,t) {
1138 | return function(e) {
1139 | //node = tree.find("#"+node.attr("id"));
1140 | var event = jQuery.Event("select_cell.jstree-grid");
1141 | tree.trigger(event, [{value: val,column: col.header,node: node,grid:$(this),sourceName: col.value}]);
1142 | if (!event.isDefaultPrevented()) {
1143 | node.children(".jstree-anchor").trigger("click.jstree",e);
1144 | }
1145 | };
1146 | }, cellRightClickHandler = function (tree,node,val,col,t) {
1147 | return function (e) {
1148 | if (gs.gridcontextmenu) {
1149 | e.preventDefault();
1150 | $.vakata.context.show(this,{ 'x' : e.pageX, 'y' : e.pageY }, gs.gridcontextmenu(_this,tree,node,val,col,t,e.target));
1151 | }
1152 | };
1153 | },
1154 | hoverInHandler = function (node, jsTreeInstance) {
1155 | return function() { jsTreeInstance.hover_node(node); };
1156 | },
1157 | hoverOutHandler = function (node, jsTreeInstance) {
1158 | return function() { jsTreeInstance.dehover_node(node); };
1159 | },
1160 | i, val, cl, wcl, ccl, a, last, valClass, wideValClass, span, paddingleft, title, gridCellName, gridCellParentId, gridCellParent,
1161 | gridCellPrev, gridCellPrevId, gridCellNext, gridCellNextId, gridCellChild, gridCellChildId,
1162 | col, content, tmpWidth, mw = this.midWrapper, column, lid = objData.id,
1163 | highlightSearch, isClicked,
1164 | peers = this.get_node(objData.parent).children,
1165 | // find my position in the list of peers. "peers" is the list of everyone at my level under my parent, in order
1166 | pos = $.inArray(lid,peers),
1167 | hc = this.holdingCells, rendered = false, closed;
1168 | // get our column definition
1169 | t = $(obj);
1170 |
1171 | // find the a children
1172 | a = t.children("[class~='jstree-anchor']");
1173 | highlightSearch = a.hasClass(SEARCHCLASS);
1174 | isClicked = a.hasClass("jstree-clicked");
1175 |
1176 | if (a.length === 1) {
1177 | closed = !objData.state.opened;
1178 | gridCellName = generateCellId(uniq,lid);
1179 | gridCellParentId = objData.parent === "#" ? null : objData.parent;
1180 | a.addClass(c);
1181 | //renderAWidth(a,_this);
1182 | renderATitle(a,t,_this);
1183 | last = a;
1184 |
1185 | // calculate position ids once
1186 | gridCellPrevId = pos <= 0 ? objData.parent : findLastClosedNode(this, peers[pos - 1]);
1187 | gridCellNextId = pos >= peers.length - 1 ? "NULL" : peers[pos + 1];
1188 | gridCellChildId = objData.children && objData.children.length > 0 ? objData.children[0] : "NULL";
1189 |
1190 | // find which column our tree shuld go in
1191 | var s = this.settings.grid;
1192 | for (var i = 0, len = cols.length;i' : '';}
1218 | } else { content = val; }
1219 |
1220 | // content cannot be blank, or it messes up heights
1221 | if (content === undefined || content === null || BLANKRE.test(content)) {
1222 | content = " ";
1223 | }
1224 |
1225 | // get the valueClass
1226 | valClass = col.valueClass && objData.data !== null && objData.data !== undefined ? objData.data[col.valueClass] || "" : "";
1227 | if (valClass && col.valueClassPrefix && col.valueClassPrefix !== "") {
1228 | valClass = col.valueClassPrefix + valClass;
1229 | }
1230 | // get the wideValueClass
1231 | wideValClass = col.wideValueClass && objData.data !== null && objData.data !== undefined ? objData.data[col.wideValueClass] || "" : "";
1232 | if (wideValClass && col.wideValueClassPrefix && col.wideValueClassPrefix !== "") {
1233 | wideValClass = col.wideValueClassPrefix + wideValClass;
1234 | }
1235 | // get the title
1236 | title = col.title && objData.data !== null && objData.data !== undefined ? objData.data[col.title] || "" : "";
1237 | // strip out HTML
1238 | title = title.replace(htmlstripre, '');
1239 |
1240 | // get the width
1241 | paddingleft = 7;
1242 | width = col.width || defaultWidth;
1243 | if (width !== 'auto') {
1244 | width = tmpWidth || (width - paddingleft);
1245 | }
1246 |
1247 | last = findDataCell(uniq, lid, i, column);
1248 | if (!last || last.length < 1) {
1249 | last = $("");
1250 | $("").appendTo(last);
1251 | last.attr("id",gridCellName+i);
1252 | last.addClass(gridCellName);
1253 | last.attr(NODE_DATA_ATTR,lid);
1254 | if (highlightSearch) {
1255 | last.addClass(SEARCHCLASS);
1256 | } else {
1257 | last.removeClass(SEARCHCLASS);
1258 | }
1259 | if (isClicked) {
1260 | last.addClass("jstree-clicked");
1261 | } else {
1262 | last.removeClass("jstree-clicked");
1263 | }
1264 | if (this.settings.core.themes.ellipsis === true && i !== treecol) {
1265 | last.addClass('jstree-grid-ellipsis');
1266 | }
1267 |
1268 | }
1269 |
1270 | // we need to check the hidden-state and see if we need to hide the node
1271 | if (objData.state.hidden) {
1272 | last.addClass("jstree-grid-hidden");
1273 | } else {
1274 | last.removeClass("jstree-grid-hidden");
1275 | }
1276 |
1277 | // ditto for the disabled-state and disabling the node
1278 | if (objData.state.disabled) {
1279 | last.addClass('jstree-disabled');
1280 | } else {
1281 | last.removeClass('jstree-disabled');
1282 | }
1283 | // we need to put it in the dataCell - after the parent, but the position matters
1284 | // if we have no parent, then we are one of the root nodes, but still need to look at peers
1285 |
1286 |
1287 | // if we are first, i.e. pos === 0, we go right after the parent;
1288 | // if we are not first, and our previous peer (one before us) is closed, we go right after the previous peer cell
1289 | // if we are not first, and our previous peer is opened, then we have to find its youngest & lowest closed child (incl. leaf)
1290 | //
1291 | // probably be much easier to go *before* our next one
1292 | // but that one might not be drawn yet
1293 | // here is the logic for jstree drawing:
1294 | // it draws peers from first to last or from last to first
1295 | // it draws children before a parent
1296 | //
1297 | // so I can rely on my *parent* not being drawn, but I cannot rely on my previous peer or my next peer being drawn
1298 |
1299 | // so we do the following:
1300 | // 1- We are the first child: install after the parent
1301 | // 2- Our previous peer is already drawn: install after the previous peer
1302 | // 3- Our previous peer is not drawn, we have a child that is drawn: install right before our first child
1303 | // 4- Our previous peer is not drawn, we have no child that is drawn, our next peer is drawn: install right before our next peer
1304 | // 5- Our previous peer is not drawn, we have no child that is drawn, our next peer is not drawn: install right after parent
1305 | gridCellPrev = findDataCell(uniq, gridCellPrevId, i, column);
1306 | gridCellNext = findDataCell(uniq, gridCellNextId, i, column);
1307 | gridCellChild = findDataCell(uniq, gridCellChildId, i, column);
1308 | gridCellParent = findDataCell(uniq, gridCellParentId, i, column);
1309 |
1310 |
1311 | // if our parent is already drawn, then we put this in the right order under our parent
1312 | if (gridCellParentId) {
1313 | if (gridCellParent && gridCellParent.length > 0) {
1314 | if (gridCellPrev && gridCellPrev.length > 0) {
1315 | last.insertAfter(gridCellPrev);
1316 | } else if (gridCellChild && gridCellChild.length > 0) {
1317 | last.insertBefore(gridCellChild);
1318 | } else if (gridCellNext && gridCellNext.length > 0) {
1319 | last.insertBefore(gridCellNext);
1320 | } else {
1321 | last.insertAfter(gridCellParent);
1322 | }
1323 | rendered = true;
1324 | } else {
1325 | rendered = false;
1326 | }
1327 | // always put it in the holding cells, and then sort when the parent comes in, in case parent is (re)drawn later
1328 | hc[gridCellName+i] = last;
1329 | } else {
1330 | if (gridCellPrev && gridCellPrev.length > 0) {
1331 | last.insertAfter(gridCellPrev);
1332 | } else if (gridCellChild && gridCellChild.length > 0) {
1333 | last.insertBefore(gridCellChild);
1334 | } else if (gridCellNext && gridCellNext.length > 0) {
1335 | last.insertBefore(gridCellNext);
1336 | } else {
1337 | last.appendTo(column);
1338 | }
1339 | rendered = true;
1340 | }
1341 | // do we have any children waiting for this cell? walk down through the children/grandchildren/etc tree
1342 | if (rendered) {
1343 | var toRen = this.getHoldingCells(objData,i,hc);
1344 | last.after(toRen);
1345 | }
1346 | // need to make the height of this match the line height of the tree. How?
1347 | span = last.children("span");
1348 |
1349 | // create a span inside the div, so we can control what happens in the whole div versus inside just the text/background
1350 | span.addClass(cl+" "+valClass).html(content);
1351 | last = last.css(conf).addClass("jstree-grid-cell jstree-grid-cell-regular jstree-grid-cell-root-"+rootid+" jstree-grid-cell-"+classAdd+" "+wcl+ " " + wideValClass + (tr?" ui-state-default":"")).addClass("jstree-grid-col-"+i).addClass("jstree-animated");
1352 | // add click handler for clicking inside a grid cell
1353 | last.click(cellClickHandler(tree,t,val,col,this));
1354 | last.on("contextmenu",cellRightClickHandler(tree,t,val,col,this));
1355 | last.hover(hoverInHandler(t, this), hoverOutHandler(t, this));
1356 |
1357 | if (title) {
1358 | span.attr("title",title);
1359 | }
1360 |
1361 | tree.trigger("render_cell.jstree-grid", [{value: val, column: col.header, node: t, sourceName: col.value}]);
1362 | }
1363 | last.addClass("jstree-grid-cell-last"+(tr?" ui-state-default":""));
1364 | // if there is no width given for the last column, do it via automatic
1365 | if (cols[cols.length-1].width === undefined) {
1366 | last.addClass("jstree-grid-width-auto").next(".jstree-grid-separator").remove();
1367 | }
1368 | }
1369 | this.element.css({'overflow-y':'auto !important'});
1370 | };
1371 | // clean up holding cells
1372 | this.holdingCells = {};
1373 |
1374 | // need to do alternating background colors or borders
1375 | };
1376 | }));
1377 |
1378 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jstreegrid",
3 | "description": "grid plugin for jstree",
4 | "version": "3.10.2",
5 | "url": "https://github.com/deitch/jstree-grid",
6 | "author": {
7 | "name": "Avi Deitcher",
8 | "url": "https://github.com/deitch"
9 | },
10 | "main": "jstreegrid.js",
11 | "license": "MIT",
12 | "contributors": [
13 | {
14 | "name": "jochenberger",
15 | "url": "https://github.com/jochenberger"
16 | },
17 | {
18 | "name": "Tobias Zanke",
19 | "url": "https://github.com/TZanke"
20 | },
21 | {
22 | "name": "ChrisRaven",
23 | "url": "https://github.com/ChrisRaven"
24 | },
25 | {
26 | "name": "Brabus",
27 | "url": "https://github.com/side-by-side"
28 | },
29 | {
30 | "name": "Adam Jimenez",
31 | "url": "https://github.com/adamjimenez"
32 | },
33 | {
34 | "name": "QueroBartk",
35 | "url": "https://github.com/QueroBartK"
36 | },
37 | {
38 | "name": "OgreTransporter",
39 | "url": "https://github.com/OgreTransporter"
40 | },
41 | {
42 | "name": "LlorX",
43 | "url": "https://github.com/Llorx"
44 | },
45 | {
46 | "name": "PetzeltA",
47 | "url": "https://github.com/petzelta/"
48 | },
49 | {
50 | "name": "yarn",
51 | "url": "https://github.com/YarnSeemannsgarn"
52 | }
53 | ],
54 | "dependencies": {
55 | "jstree": ">=3.0.0"
56 | },
57 | "keywords": [
58 | "jquery",
59 | "jstree",
60 | "grid",
61 | "javascript"
62 | ],
63 | "repository": {
64 | "type": "git",
65 | "url": "http://github.com/deitch/jstree-grid.git"
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/publish:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | ######
4 | # Simple script to run npm publish
5 | ######
6 |
7 | # what version number are we in git tag and in package.json
8 | NPMVERSION=$(awk '/\Wversion\W/ {print $2}' package.json | sed 's/[\"\,]//g')
9 | GITTAG=$(git describe --abbrev=0)
10 |
11 | # we *should* be checking if this last one changed, and then publish, but later
12 | npm publish
13 |
14 |
--------------------------------------------------------------------------------
/tests/README.md:
--------------------------------------------------------------------------------
1 | # jstreegrid tests
2 |
3 | This directory contains the tests for jstreegrid. In order to avoid a lot of duplication,
4 | the tests have been created in a modular fashion. There is a single master file,
5 | `test-master.html`, which provides an interface to loading and running any tests.
6 |
7 | All tests should exist as html partial files in `components/` named for the test, e.g. `my-unique-test.html`. Any `script` tags in the file will be executed. The html partial will be loaded into a `
`
8 |
9 | To add a new test:
10 |
11 | 1. Create the test partial `.html` in `components/`, e.g. `my-unique-test.html`
12 | 2. Add the correct partial html and JS scripts to the `my-unique-test.html`
13 | 3. Add the test file name to the `test-list.json` array
14 |
15 | **About ES6/CJS:** Yes, we definitely could use ES6 modules or CommonJS modules (like node),
16 | but for the purpose of clean tests, we stick with native JS understood by the browser,
17 | i.e. no Babel/Traceur transpiling.
18 |
--------------------------------------------------------------------------------
/tests/components/api-hide-show-test.html:
--------------------------------------------------------------------------------
1 |
114 |
76 | There is an issue with Chrome/Chromium wherein if a column size is set to 'auto', calling $(col).css("width") returns 0, rather than the actual width. This tests whether or not it works.
77 |
78 |
79 | The bug hits primarily during resizing. To test this, try resizing a column in Firefox and then in Chrome.
80 |
48 | Should display how long it takes to perform the search. Note that it does it in a crude fashion using a custom event. For true testing, use a profiler.
49 | Will only search when you press the "Search" button.
50 |
51 |
52 | Time for search: milliseconds
53 |
54 |
55 |
56 |
57 |
40 |
Test Runner
41 |
42 | This is a single file to run tests. You can pick which tests you want to run, and they will load and show towards the bottom of this page.
43 |
The columns here all have width=auto, minWidth=100 and maxWidth=200. You can resize them, but when auto-rendering, none should be less than 100 or bigger than 200.
63 | Of particular interest is the minWidth of the second column (which has very few characters) and the maxWidth of the third column (which has many).
64 |
104 | The single search box will search all three trees simultaneously. Each tree is configured slightly differently in terms of search parameters.
105 |
106 |
107 |
108 | The following search boxes will search in their respective column. This will use a logical AND
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 | The following search boxes will search in their respective column. Each input will start a new search independent of the others
118 |