diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..2536d66 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +# http://editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 4 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore index 7376bb2..eb4cb83 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,29 @@ -test/javasource/ -test/deployment/ -test/.classpath -test/.project +test_5_21_4/javasource/ +test_5_21_4/deployment/ +test_5_21_4/.classpath +test_5_21_4/.project +test_5_21_4/theme/ +test_5_21_4/userlib/ +test_5_21_4/.classpath +test_5_21_4/.project + +test_6_10_8/javasource/ +test_6_10_8/deployment/ +test_6_10_8/.classpath +test_6_10_8/.project +test_6_10_8/theme/ +test_6_10_8/userlib/ +test_6_10_8/.classpath +test_6_10_8/.project *.launch *.tmp *.lock -test/zip Progress Bar.bat .idea/ + +dist/ + +node_modules/ +.editorconfig +*DS_Store* +.vscode/ +*.bak diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..0de7fb2 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,28 @@ +{ + // Enforcing + "curly" : false, + "forin" : false, + "latedef" : "nofunc", + "newcap" : true, + "quotmark" : "double", + "eqeqeq" : true, + "undef" : true, + "globals" : { + "mendix" : false, + "mx" : false, + "logger" : false, + "mxui" : false + }, + + // Relaxing + "laxbreak" : true, + + // Environments + "browser" : true, + "devel" : true, + "dojo" : true, + "-W083" : true, + "-W084" : true, + "-W030" : true, + "-W116" : true +} diff --git a/Gulpfile.js b/Gulpfile.js new file mode 100644 index 0000000..81ba5d2 --- /dev/null +++ b/Gulpfile.js @@ -0,0 +1,95 @@ +// Generated on 2017-04-04 using generator-mendix 2.1.1 :: git+https://github.com/mendix/generator-mendix.git +/*jshint -W069,-W097*/ +"use strict"; + +// In case you seem to have trouble starting Mendix through `gulp modeler`, you might have to set the path to the Mendix application, otherwise leave both values as they are +var MODELER_PATH = null; +var MODELER_ARGS = "/file:{path}"; + +/******************************************************************************** + * Do not edit anything below, unless you know what you are doing + ********************************************************************************/ +var gulp = require("gulp"), + zip = require("gulp-zip"), + del = require("del"), + newer = require("gulp-newer"), + gutil = require("gulp-util"), + gulpif = require("gulp-if"), + jsonTransform = require("gulp-json-transform"), + intercept = require("gulp-intercept"), + argv = require("yargs").argv, + widgetBuilderHelper = require("widgetbuilder-gulp-helper"), + jsValidate = require("gulp-jsvalidate"); + +var pkg = require("./package.json"), + paths = widgetBuilderHelper.generatePaths(pkg), + xmlversion = widgetBuilderHelper.xmlversion; + +gulp.task("default", function() { + gulp.watch("./src/**/*", ["compress"]); + gulp.watch("./src/**/*.js", ["copy:js"]); + gulp.watch("./src/**/*.html", ["copy:html"]) +}); + +gulp.task("clean", function () { + return del([ + paths.WIDGET_TEST_DEST, + paths.WIDGET_DIST_DEST + ], { force: true }); +}); + +gulp.task("compress", ["clean"], function () { + return gulp.src("src/**/*") + .pipe(zip(pkg.name + ".mpk")) + .pipe(gulp.dest(paths.TEST_WIDGETS_FOLDER)) + .pipe(gulp.dest("dist")); +}); + +gulp.task("copy:js", function () { + return gulp.src(["./src/**/*.js"]) + .pipe(jsValidate()) + .pipe(newer(paths.TEST_WIDGETS_DEPLOYMENT_FOLDER)) + .pipe(gulp.dest(paths.TEST_WIDGETS_DEPLOYMENT_FOLDER)); +}); + +gulp.task("copy:html", function () { + return gulp.src(["./src/**/*.html"]) + .pipe(newer(paths.TEST_WIDGETS_DEPLOYMENT_FOLDER)) + .pipe(gulp.dest(paths.TEST_WIDGETS_DEPLOYMENT_FOLDER)); +}); + +gulp.task("version:xml", function () { + return gulp.src(paths.PACKAGE_XML) + .pipe(xmlversion(argv.n)) + .pipe(gulp.dest("./src/")); +}); + +gulp.task("version:json", function () { + return gulp.src("./package.json") + .pipe(gulpif(typeof argv.n !== "undefined", jsonTransform(function(data) { + data.version = argv.n; + return data; + }, 2))) + .pipe(gulp.dest("./")); +}); + +gulp.task("icon", function (cb) { + var icon = (typeof argv.file !== "undefined") ? argv.file : "./icon.png"; + console.log("\nUsing this file to create a base64 string: " + gutil.colors.cyan(icon)); + gulp.src(icon) + .pipe(intercept(function (file) { + console.log("\nCopy the following to your " + pkg.name + ".xml (after description):\n\n" + gutil.colors.cyan("") + file.contents.toString("base64") + gutil.colors.cyan("<\/icon>") + "\n"); + cb(); + })); +}); + +gulp.task("folders", function () { + paths.showPaths(); return; +}); + +gulp.task("modeler", function (cb) { + widgetBuilderHelper.runmodeler(MODELER_PATH, MODELER_ARGS, paths.TEST_PATH, cb); +}); + +gulp.task("build", ["compress"]); +gulp.task("version", ["version:xml", "version:json"]); diff --git a/README.md b/README.md index c3e9dc0..8aee540 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -#TreeViewAndGridView +# TreeViewAndGridView (DEPRECATED) [![Support](https://img.shields.io/badge/Mendix%20Support%3A-Community-orange.svg)](https://docs.mendix.com/community/app-store/app-store-content-support) + +> **This widget has been removed from the Marketplace and is deprecated. Do not use this widget.** The treeview and gridview widgets provide the highly customizable grid widgets as seen in home.mendix.com. @@ -35,7 +37,7 @@ As a prelude to your treeview setup, you need to mention all the entity types yo The treeview always require a context object (thus; the widget can only be used inside a dataview or templategrid). and can only display objects which are somehow related to your context. If you are in the rare case that you have no such context, see the demo project about how to create a dummy context. The context object should always be part of your entity configuration! Otherwise the treeview won't load at all. -For each type, you can define a view properties; sorting, cacheburst (see the refreshing the tree section) and custom styling for this object type. We recommend to use classes and define them in your custom theme, as they provide a lot of flexibility and possibilities, but for a quick prototype inline styling might work as well. If you use drag and drop, the sort attribute should be an ascending float attribute. See the drag and drop section for more information. +For each type, you can define a view properties; sorting, cache bust (see the refreshing the tree section) and custom styling for this object type. We recommend to use classes and define them in your custom theme, as they provide a lot of flexibility and possibilities, but for a quick prototype inline styling might work as well. If you use drag and drop, the sort attribute should be an ascending float attribute. See the drag and drop section for more information. When you configured all entities you intend to display in the widget, you can define the relations between them. @@ -74,7 +76,7 @@ The content of an item is always refreshed automatically. The children of new it #### Case: add a new child when the reference owner is the child -Sadly, this cannot detected by the widget, as it does not trigger a change on the parent, and the child is not in the treeview yet. If you need this behavior, using a child burst attribute (on the parent entity) is the way to go. Whenever the the treeview receives a new value for this attribute, it reloads the children of an item. The flower list example demonstrates this nicely. +Sadly, this cannot detected by the widget, as it does not trigger a change on the parent, and the child is not in the treeview yet. If you need this behavior, using a cache bust attribute (on the parent entity) is the way to go. Whenever the the treeview receives a new value for this attribute, it reloads the children of an item. The flower list example demonstrates this nicely. #### Case: an object is deleted from the database entirely @@ -82,7 +84,7 @@ The treeview always picks it up when an object is removed from a reference(set). If the owner of the reference is not the object being deleted, just remove the referred object from the reference first and then delete that object (that can be done in the same microflow). -If the owner of the reference is the object being deleted, the parent needs to be refreshed manually, again this can be done by using the case burst attribute of the parent. +If the owner of the reference is the object being deleted, the parent needs to be refreshed manually, again this can be done by using the cache bust attribute of the parent. #### Case: an object is added to a constrained relation @@ -90,7 +92,7 @@ See the next section ## Advanced: Constrained relations -When removing an item from a constrained relation, this is handled in the same way as removing an object from an unconstrained relation. However **adding a child to a constrained relation is never picked up automatically**, as the new object might not pass the given constraint. To avoid unnecessary retrieves from the server, you need to indicate in your model that an object needs to refetch its children by the burst attribute as explained above. +When removing an item from a constrained relation, this is handled in the same way as removing an object from an unconstrained relation. However **adding a child to a constrained relation is never picked up automatically**, as the new object might not pass the given constraint. To avoid unnecessary retrieves from the server, you need to indicate in your model that an object needs to refetch its children by the cache bust attribute as explained above. ## Advanced: Drag and drop Enabling drag and drop on a relation allows items to be both dragged from it and dropped to it. The widget automatically calculates where an item can be dropped, based on the type of the object your are holding, the object or relation you are dragging over and the copy state. diff --git a/dist/TreeView.mpk b/dist/TreeView.mpk new file mode 100644 index 0000000..980a594 Binary files /dev/null and b/dist/TreeView.mpk differ diff --git a/dist/v3.0.1/TreeView.mpk b/dist/v3.0.1/TreeView.mpk deleted file mode 100644 index c716559..0000000 Binary files a/dist/v3.0.1/TreeView.mpk and /dev/null differ diff --git a/dist/v3.0.2/TreeView.mpk b/dist/v3.0.2/TreeView.mpk new file mode 100644 index 0000000..bad91d0 Binary files /dev/null and b/dist/v3.0.2/TreeView.mpk differ diff --git a/dist/v4.0.1/TreeView.mpk b/dist/v4.0.1/TreeView.mpk new file mode 100644 index 0000000..b36b3f7 Binary files /dev/null and b/dist/v4.0.1/TreeView.mpk differ diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..da72cc3 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3364 @@ +{ + "name": "TreeView", + "version": "4.6.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "ansi-gray": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", + "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", + "dev": true + }, + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-differ": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", + "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=", + "dev": true + }, + "array-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", + "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", + "dev": true + }, + "array-slice": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", + "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", + "dev": true + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "1.0.3" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "atob": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.0.3.tgz", + "integrity": "sha1-GcenYEc3dEaPILLS0DNyrX1Mv10=", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "1.0.1", + "class-utils": "0.3.5", + "component-emitter": "1.2.1", + "define-property": "1.0.0", + "isobject": "3.0.1", + "mixin-deep": "1.3.0", + "pascalcase": "0.1.1" + } + }, + "beeper": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.1.tgz", + "integrity": "sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", + "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.0.tgz", + "integrity": "sha512-P4O8UQRdGiMLWSizsApmXVQDBS6KCt7dSexgLKBmH5Hr1CZq7vsnscFh8oR1sP1ab1Zj0uCHCEzZeV6SfUf3rA==", + "dev": true, + "requires": { + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "define-property": "1.0.0", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "repeat-element": "1.1.2", + "snapdragon": "0.8.1", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.1" + } + }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "1.0.0", + "component-emitter": "1.2.1", + "get-value": "2.0.6", + "has-value": "1.0.0", + "isobject": "3.0.1", + "set-value": "2.0.0", + "to-object-path": "0.3.0", + "union-value": "1.0.0", + "unset-value": "1.0.0" + } + }, + "camelcase": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", + "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "class-utils": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.5.tgz", + "integrity": "sha1-F+eTEDdQ+WJ7IXbqNM/RtWWQPIA=", + "dev": true, + "requires": { + "arr-union": "3.1.0", + "define-property": "0.2.5", + "isobject": "3.0.1", + "lazy-cache": "2.0.2", + "static-extend": "0.1.2" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, + "requires": { + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wrap-ansi": "2.1.0" + } + }, + "clone": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.3.tgz", + "integrity": "sha1-KY1+IjFmD0DAA8LtMUDezz9TCF8=", + "dev": true + }, + "clone-stats": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", + "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", + "dev": true + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "1.0.0", + "object-visit": "1.0.1" + } + }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true + }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", + "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", + "dev": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.3", + "typedarray": "0.0.6" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" + } + }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + } + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "dateformat": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", + "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "dev": true, + "requires": { + "clone": "1.0.3" + } + }, + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "1.0.2" + } + }, + "del": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "dev": true, + "requires": { + "globby": "5.0.0", + "is-path-cwd": "1.0.0", + "is-path-in-cwd": "1.0.0", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "rimraf": "2.6.2" + } + }, + "deprecated": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/deprecated/-/deprecated-0.0.1.tgz", + "integrity": "sha1-+cmvVGSvoeepcUWKi97yqpTVuxk=", + "dev": true + }, + "detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", + "dev": true + }, + "duplexer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", + "dev": true + }, + "duplexer2": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", + "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", + "dev": true, + "requires": { + "readable-stream": "1.1.14" + } + }, + "duplexify": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.5.1.tgz", + "integrity": "sha512-j5goxHTwVED1Fpe5hh3q9R93Kip0Bg2KVAt4f8CEYM3UEwYcPSvWbXaUQOzdX/HtiNomipv+gU7ASQPDbV7pGQ==", + "dev": true, + "requires": { + "end-of-stream": "1.4.0", + "inherits": "2.0.3", + "readable-stream": "2.3.3", + "stream-shift": "1.0.0" + }, + "dependencies": { + "end-of-stream": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.0.tgz", + "integrity": "sha1-epDYM+/abPpurA9JSduw+tOmMgY=", + "dev": true, + "requires": { + "once": "1.4.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" + } + }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + } + } + }, + "end-of-stream": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-0.1.5.tgz", + "integrity": "sha1-jhdyBsPICDfYVjLouTWd/osvbq8=", + "dev": true, + "requires": { + "once": "1.3.3" + }, + "dependencies": { + "once": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", + "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=", + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + } + } + }, + "error-ex": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", + "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", + "dev": true, + "requires": { + "is-arrayish": "0.2.1" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esprima": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", + "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", + "dev": true + }, + "event-stream": { + "version": "3.0.20", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.0.20.tgz", + "integrity": "sha1-A4u7LqnqkDhbJvvBhU0LU58qvqM=", + "dev": true, + "requires": { + "duplexer": "0.1.1", + "from": "0.1.7", + "map-stream": "0.0.7", + "pause-stream": "0.0.11", + "split": "0.2.10", + "stream-combiner": "0.0.4", + "through": "2.3.8" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "posix-character-classes": "0.1.1", + "regex-not": "1.0.0", + "snapdragon": "0.8.1", + "to-regex": "3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", + "dev": true, + "requires": { + "homedir-polyfill": "1.0.1" + } + }, + "extend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", + "dev": true + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + }, + "extglob": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.3.tgz", + "integrity": "sha512-AyptZexgu7qppEPq59DtN/XJGZDrLcVxSHai+4hdgMMS9EpF4GBvygcWWApno8lL9qSjVpYt7Raao28qzJX1ww==", + "dev": true, + "requires": { + "array-unique": "0.3.2", + "define-property": "1.0.0", + "expand-brackets": "2.1.4", + "extend-shallow": "2.0.1", + "fragment-cache": "0.2.1", + "regex-not": "1.0.0", + "snapdragon": "0.8.1", + "to-regex": "3.0.1" + } + }, + "fancy-log": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.2.tgz", + "integrity": "sha1-9BEl49hPLn2JpD0G2VjI94vha+E=", + "dev": true, + "requires": { + "ansi-gray": "0.1.1", + "color-support": "1.1.3", + "time-stamp": "1.1.0" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" + } + }, + "find-index": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/find-index/-/find-index-0.1.1.tgz", + "integrity": "sha1-Z101iyyjiS15Whq0cjL4tuLg3eQ=", + "dev": true + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "2.1.0", + "pinkie-promise": "2.0.1" + } + }, + "findup-sync": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", + "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", + "dev": true, + "requires": { + "detect-file": "1.0.0", + "is-glob": "3.1.0", + "micromatch": "3.1.4", + "resolve-dir": "1.0.1" + } + }, + "fined": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fined/-/fined-1.1.0.tgz", + "integrity": "sha1-s33IRLdqL15wgeiE98CuNE8VNHY=", + "dev": true, + "requires": { + "expand-tilde": "2.0.2", + "is-plain-object": "2.0.4", + "object.defaults": "1.1.0", + "object.pick": "1.3.0", + "parse-filepath": "1.0.2" + } + }, + "first-chunk-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz", + "integrity": "sha1-Wb+1DNkF9g18OUzT2ayqtOatk04=", + "dev": true + }, + "flagged-respawn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.0.tgz", + "integrity": "sha1-Tnmumy6zi/hrO7Vr8+ClaqX8q9c=", + "dev": true + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "dev": true, + "requires": { + "for-in": "1.0.2" + } + }, + "fork-stream": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/fork-stream/-/fork-stream-0.0.4.tgz", + "integrity": "sha1-24Sfznf2cIpfjzhq5TOgkHtUrnA=", + "dev": true + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "0.2.2" + } + }, + "from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "gaze": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/gaze/-/gaze-0.5.2.tgz", + "integrity": "sha1-QLcJU30k0dRXZ9takIaJ3+aaxE8=", + "dev": true, + "requires": { + "globule": "0.1.0" + } + }, + "get-caller-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", + "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=", + "dev": true + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "glob-stream": { + "version": "3.1.18", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-3.1.18.tgz", + "integrity": "sha1-kXCl8St5Awb9/lmPMT+PeVT9FDs=", + "dev": true, + "requires": { + "glob": "4.5.3", + "glob2base": "0.0.12", + "minimatch": "2.0.10", + "ordered-read-streams": "0.1.0", + "through2": "0.6.5", + "unique-stream": "1.0.0" + }, + "dependencies": { + "glob": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-4.5.3.tgz", + "integrity": "sha1-xstz0yJsHv7wTePFbQEvAzd+4V8=", + "dev": true, + "requires": { + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "2.0.10", + "once": "1.4.0" + } + }, + "minimatch": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz", + "integrity": "sha1-jQh8OcazjAAbl/ynzm0OHoCvusc=", + "dev": true, + "requires": { + "brace-expansion": "1.1.8" + } + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "through2": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", + "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", + "dev": true, + "requires": { + "readable-stream": "1.0.34", + "xtend": "4.0.1" + } + } + } + }, + "glob-watcher": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-0.0.6.tgz", + "integrity": "sha1-uVtKjfdLOcgymLDAXJeLTZo7cQs=", + "dev": true, + "requires": { + "gaze": "0.5.2" + } + }, + "glob2base": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/glob2base/-/glob2base-0.0.12.tgz", + "integrity": "sha1-nUGbPijxLoOjYhZKJ3BVkiycDVY=", + "dev": true, + "requires": { + "find-index": "0.1.1" + } + }, + "global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dev": true, + "requires": { + "global-prefix": "1.0.2", + "is-windows": "1.0.1", + "resolve-dir": "1.0.1" + } + }, + "global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", + "dev": true, + "requires": { + "expand-tilde": "2.0.2", + "homedir-polyfill": "1.0.1", + "ini": "1.3.5", + "is-windows": "1.0.1", + "which": "1.3.0" + } + }, + "globby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "dev": true, + "requires": { + "array-union": "1.0.2", + "arrify": "1.0.1", + "glob": "7.1.2", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + } + }, + "globule": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/globule/-/globule-0.1.0.tgz", + "integrity": "sha1-2cjt3h2nnRJaFRt5UzuXhnY0auU=", + "dev": true, + "requires": { + "glob": "3.1.21", + "lodash": "1.0.2", + "minimatch": "0.2.14" + }, + "dependencies": { + "glob": { + "version": "3.1.21", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz", + "integrity": "sha1-0p4KBV3qUTj00H7UDomC6DwgZs0=", + "dev": true, + "requires": { + "graceful-fs": "1.2.3", + "inherits": "1.0.2", + "minimatch": "0.2.14" + } + }, + "graceful-fs": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", + "integrity": "sha1-FaSAaldUfLLS2/J/QuiajDRRs2Q=", + "dev": true + }, + "inherits": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", + "integrity": "sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js=", + "dev": true + }, + "minimatch": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", + "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", + "dev": true, + "requires": { + "lru-cache": "2.7.3", + "sigmund": "1.0.1" + } + } + } + }, + "glogg": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.0.tgz", + "integrity": "sha1-f+DxmfV6yQbPUS/urY+Q7kooT8U=", + "dev": true, + "requires": { + "sparkles": "1.0.0" + } + }, + "graceful-fs": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.11.tgz", + "integrity": "sha1-dhPHeKGv6mLyXGMKCG1/Osu92Bg=", + "dev": true, + "requires": { + "natives": "1.1.1" + } + }, + "gulp": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/gulp/-/gulp-3.9.1.tgz", + "integrity": "sha1-VxzkWSjdQK9lFPxAEYZgFsE4RbQ=", + "dev": true, + "requires": { + "archy": "1.0.0", + "chalk": "1.1.3", + "deprecated": "0.0.1", + "gulp-util": "3.0.8", + "interpret": "1.1.0", + "liftoff": "2.5.0", + "minimist": "1.2.0", + "orchestrator": "0.3.8", + "pretty-hrtime": "1.0.3", + "semver": "4.3.6", + "tildify": "1.2.0", + "v8flags": "2.1.1", + "vinyl-fs": "0.3.14" + } + }, + "gulp-if": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/gulp-if/-/gulp-if-2.0.2.tgz", + "integrity": "sha1-pJe351cwBQQcqivIt92jyARE1ik=", + "dev": true, + "requires": { + "gulp-match": "1.0.3", + "ternary-stream": "2.0.1", + "through2": "2.0.3" + } + }, + "gulp-intercept": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/gulp-intercept/-/gulp-intercept-0.1.0.tgz", + "integrity": "sha1-neRcMAFmydnZ/cuO7/hg6XZ8Ft8=", + "dev": true, + "requires": { + "event-stream": "3.0.20" + } + }, + "gulp-json-transform": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/gulp-json-transform/-/gulp-json-transform-0.4.5.tgz", + "integrity": "sha512-kaGUaAhgjxeLgIMNF3IPFFmYCF6AgvzBQwqmVowiIStNADZSoILtPNDisYA4mKfpwMTqSiWLogQt1q5U75+uwA==", + "dev": true, + "requires": { + "gulp-util": "3.0.8", + "promise": "8.0.1", + "through2": "2.0.3" + } + }, + "gulp-jsvalidate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/gulp-jsvalidate/-/gulp-jsvalidate-3.0.0.tgz", + "integrity": "sha1-X6rFJn33byi9Vz8S/1N/Z6gjvhc=", + "dev": true, + "requires": { + "esprima": "3.1.3", + "gulp-util": "3.0.8", + "through2": "2.0.3" + } + }, + "gulp-match": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/gulp-match/-/gulp-match-1.0.3.tgz", + "integrity": "sha1-kcfA1/Kb7NZgbVfYCn+Hdqh6uo4=", + "dev": true, + "requires": { + "minimatch": "3.0.4" + } + }, + "gulp-newer": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/gulp-newer/-/gulp-newer-1.3.0.tgz", + "integrity": "sha1-1Q7Ky7gi7aSStXMkpshaB/2aVcE=", + "dev": true, + "requires": { + "glob": "7.1.2", + "gulp-util": "3.0.8", + "kew": "0.7.0" + } + }, + "gulp-util": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", + "integrity": "sha1-AFTh50RQLifATBh8PsxQXdVLu08=", + "dev": true, + "requires": { + "array-differ": "1.0.0", + "array-uniq": "1.0.3", + "beeper": "1.1.1", + "chalk": "1.1.3", + "dateformat": "2.2.0", + "fancy-log": "1.3.2", + "gulplog": "1.0.0", + "has-gulplog": "0.1.0", + "lodash._reescape": "3.0.0", + "lodash._reevaluate": "3.0.0", + "lodash._reinterpolate": "3.0.0", + "lodash.template": "3.6.2", + "minimist": "1.2.0", + "multipipe": "0.1.2", + "object-assign": "3.0.0", + "replace-ext": "0.0.1", + "through2": "2.0.3", + "vinyl": "0.5.3" + }, + "dependencies": { + "object-assign": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", + "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=", + "dev": true + } + } + }, + "gulp-zip": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/gulp-zip/-/gulp-zip-3.2.0.tgz", + "integrity": "sha1-69GY2ubcLV9E2BRWnI7EIRipPvk=", + "dev": true, + "requires": { + "chalk": "1.1.3", + "concat-stream": "1.6.0", + "gulp-util": "3.0.8", + "through2": "2.0.3", + "yazl": "2.4.3" + } + }, + "gulplog": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", + "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", + "dev": true, + "requires": { + "glogg": "1.0.0" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "has-gulplog": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", + "integrity": "sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=", + "dev": true, + "requires": { + "sparkles": "1.0.0" + } + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "2.0.6", + "has-values": "1.0.0", + "isobject": "3.0.1" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "3.0.0", + "kind-of": "4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "homedir-polyfill": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz", + "integrity": "sha1-TCu8inWJmP7r9e1oWA921GdotLw=", + "dev": true, + "requires": { + "parse-passwd": "1.0.0" + } + }, + "hosted-git-info": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz", + "integrity": "sha512-pNgbURSuab90KbTqvRPsseaTxOJCZBD0a7t+haSN33piP9cCM4l0CqdzAif2hUqm716UovKB2ROmiabGAKVXyg==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true + }, + "interpret": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", + "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=", + "dev": true + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "dev": true + }, + "is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "dev": true, + "requires": { + "is-relative": "1.0.0", + "is-windows": "1.0.1" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-builtin-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "dev": true, + "requires": { + "builtin-modules": "1.1.1" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "2.1.1" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-odd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-odd/-/is-odd-1.0.0.tgz", + "integrity": "sha1-O4qTLrAos3dcObsJ6RdnrM22kIg=", + "dev": true, + "requires": { + "is-number": "3.0.0" + } + }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true + }, + "is-path-in-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz", + "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=", + "dev": true, + "requires": { + "is-path-inside": "1.0.1" + } + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "requires": { + "path-is-inside": "1.0.2" + } + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "3.0.1" + } + }, + "is-relative": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "dev": true, + "requires": { + "is-unc-path": "1.0.0" + } + }, + "is-unc-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "dev": true, + "requires": { + "unc-path-regex": "0.1.2" + } + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "is-windows": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.1.tgz", + "integrity": "sha1-MQ23D3QtJZoWo2kgK1GvhCMzENk=", + "dev": true + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "kew": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/kew/-/kew-0.7.0.tgz", + "integrity": "sha1-edk9LTM2PW/dKXCzNdkUGtWR15s=", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "lazy-cache": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-2.0.2.tgz", + "integrity": "sha1-uRkKT5EzVGlIQIWfio9whNiCImQ=", + "dev": true, + "requires": { + "set-getter": "0.1.0" + } + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "dev": true, + "requires": { + "invert-kv": "1.0.0" + } + }, + "liftoff": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-2.5.0.tgz", + "integrity": "sha1-IAkpG7Mc6oYbvxCnwVooyvdcMew=", + "dev": true, + "requires": { + "extend": "3.0.1", + "findup-sync": "2.0.0", + "fined": "1.1.0", + "flagged-respawn": "1.0.0", + "is-plain-object": "2.0.4", + "object.map": "1.0.1", + "rechoir": "0.6.2", + "resolve": "1.5.0" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "parse-json": "2.2.0", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "strip-bom": "2.0.0" + }, + "dependencies": { + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "0.2.1" + } + } + } + }, + "lodash": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-1.0.2.tgz", + "integrity": "sha1-j1dWDIO1n8JwvT1WG2kAQ0MOJVE=", + "dev": true + }, + "lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", + "dev": true + }, + "lodash._basetostring": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz", + "integrity": "sha1-0YYdh3+CSlL2aYMtyvPuFVZqB9U=", + "dev": true + }, + "lodash._basevalues": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz", + "integrity": "sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc=", + "dev": true + }, + "lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", + "dev": true + }, + "lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", + "dev": true + }, + "lodash._reescape": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz", + "integrity": "sha1-Kx1vXf4HyKNVdT5fJ/rH8c3hYWo=", + "dev": true + }, + "lodash._reevaluate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz", + "integrity": "sha1-WLx0xAZklTrgsSTYBpltrKQx4u0=", + "dev": true + }, + "lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", + "dev": true + }, + "lodash._root": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", + "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=", + "dev": true + }, + "lodash.escape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", + "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", + "dev": true, + "requires": { + "lodash._root": "3.0.1" + } + }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", + "dev": true + }, + "lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", + "dev": true + }, + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "dev": true, + "requires": { + "lodash._getnative": "3.9.1", + "lodash.isarguments": "3.1.0", + "lodash.isarray": "3.0.4" + } + }, + "lodash.restparam": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", + "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=", + "dev": true + }, + "lodash.template": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", + "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", + "dev": true, + "requires": { + "lodash._basecopy": "3.0.1", + "lodash._basetostring": "3.0.1", + "lodash._basevalues": "3.0.0", + "lodash._isiterateecall": "3.0.9", + "lodash._reinterpolate": "3.0.0", + "lodash.escape": "3.2.0", + "lodash.keys": "3.1.2", + "lodash.restparam": "3.6.1", + "lodash.templatesettings": "3.1.1" + } + }, + "lodash.templatesettings": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", + "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=", + "dev": true, + "requires": { + "lodash._reinterpolate": "3.0.0", + "lodash.escape": "3.2.0" + } + }, + "lru-cache": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", + "dev": true + }, + "make-iterator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.0.tgz", + "integrity": "sha1-V7713IXSOSO6I3ZzJNjo+PPZaUs=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", + "integrity": "sha1-ih8HiW2CsQkmvTdEokIACfiJdKg=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "1.0.1" + } + }, + "merge-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", + "integrity": "sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=", + "dev": true, + "requires": { + "readable-stream": "2.3.3" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" + } + }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + } + } + }, + "micromatch": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.4.tgz", + "integrity": "sha512-kFRtviKYoAJT+t7HggMl0tBFGNAKLw/S7N+CO9qfEQyisob1Oy4pao+geRbkyeEd+V9aOkvZ4mhuyPvI/q9Sfg==", + "dev": true, + "requires": { + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.0", + "define-property": "1.0.0", + "extend-shallow": "2.0.1", + "extglob": "2.0.3", + "fragment-cache": "0.2.1", + "kind-of": "6.0.2", + "nanomatch": "1.2.6", + "object.pick": "1.3.0", + "regex-not": "1.0.0", + "snapdragon": "0.8.1", + "to-regex": "3.0.1" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "1.1.8" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "mixin-deep": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.0.tgz", + "integrity": "sha512-dgaCvoh6i1nosAUBKb0l0pfJ78K8+S9fluyIR2YvAeUD/QuMahnFnF3xYty5eYXMjhGSsB0DsW6A0uAZyetoAg==", + "dev": true, + "requires": { + "for-in": "1.0.2", + "is-extendable": "1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "2.0.4" + } + } + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "multipipe": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz", + "integrity": "sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s=", + "dev": true, + "requires": { + "duplexer2": "0.0.2" + } + }, + "nanomatch": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.6.tgz", + "integrity": "sha512-WJ6XTCbvWXUFPbi/bDwKcYkCeOGUHzaJj72KbuPqGn78Ba/F5Vu26Zlo6SuMQbCIst1RGKL1zfWBCOGAlbRLAg==", + "dev": true, + "requires": { + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "define-property": "1.0.0", + "extend-shallow": "2.0.1", + "fragment-cache": "0.2.1", + "is-odd": "1.0.0", + "kind-of": "5.1.0", + "object.pick": "1.3.0", + "regex-not": "1.0.0", + "snapdragon": "0.8.1", + "to-regex": "3.0.1" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "natives": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.1.tgz", + "integrity": "sha512-8eRaxn8u/4wN8tGkhlc2cgwwvOLMLUMUn4IYTexMgWd+LyUDfeXVkk2ygQR0hvIHbJQXgHujia3ieUUDwNGkEA==", + "dev": true + }, + "node-mendix-modeler-path": { + "version": "https://github.com/JelteMX/node-mendix-modeler-path/archive/v1.0.0.tar.gz", + "integrity": "sha1-y4kY7GD/c3Qzc/hdD0H/cOxVAV8=", + "dev": true, + "requires": { + "shelljs": "0.5.3" + }, + "dependencies": { + "shelljs": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.5.3.tgz", + "integrity": "sha1-xUmCuZbHbvDB5rWfvcWCX1txMRM=", + "dev": true + } + } + }, + "normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "dev": true, + "requires": { + "hosted-git-info": "2.5.0", + "is-builtin-module": "1.0.0", + "semver": "4.3.6", + "validate-npm-package-license": "3.0.1" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "0.1.1", + "define-property": "0.2.5", + "kind-of": "3.2.2" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "3.0.1" + } + }, + "object.defaults": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", + "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=", + "dev": true, + "requires": { + "array-each": "1.0.1", + "array-slice": "1.1.0", + "for-own": "1.0.0", + "isobject": "3.0.1" + } + }, + "object.map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", + "integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=", + "dev": true, + "requires": { + "for-own": "1.0.0", + "make-iterator": "1.0.0" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "3.0.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "orchestrator": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/orchestrator/-/orchestrator-0.3.8.tgz", + "integrity": "sha1-FOfp4nZPcxX7rBhOUGx6pt+UrX4=", + "dev": true, + "requires": { + "end-of-stream": "0.1.5", + "sequencify": "0.0.7", + "stream-consume": "0.1.0" + } + }, + "ordered-read-streams": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-0.1.0.tgz", + "integrity": "sha1-/VZamvjrRHO6abbtijQ1LLVS8SY=", + "dev": true + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "dev": true, + "requires": { + "lcid": "1.0.0" + } + }, + "parse-filepath": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", + "integrity": "sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=", + "dev": true, + "requires": { + "is-absolute": "1.0.0", + "map-cache": "0.2.2", + "path-root": "0.1.1" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "1.3.1" + } + }, + "parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "dev": true + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "2.0.1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-parse": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", + "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", + "dev": true + }, + "path-root": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", + "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", + "dev": true, + "requires": { + "path-root-regex": "0.1.2" + } + }, + "path-root-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", + "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", + "dev": true + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + }, + "dependencies": { + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true + } + } + }, + "pause-stream": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", + "dev": true, + "requires": { + "through": "2.3.8" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "2.0.4" + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "pretty-hrtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", + "dev": true + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true + }, + "promise": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-8.0.1.tgz", + "integrity": "sha1-5F1osAoXZHttpxG/he1u1HII9FA=", + "dev": true, + "requires": { + "asap": "2.0.6" + } + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "requires": { + "load-json-file": "1.1.0", + "normalize-package-data": "2.4.0", + "path-type": "1.1.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "requires": { + "find-up": "1.1.2", + "read-pkg": "1.1.0" + } + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "requires": { + "resolve": "1.5.0" + } + }, + "regex-not": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.0.tgz", + "integrity": "sha1-Qvg+OXcWIt+CawKvF2Ul1qXxV/k=", + "dev": true, + "requires": { + "extend-shallow": "2.0.1" + } + }, + "repeat-element": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", + "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "replace-ext": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", + "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=", + "dev": true + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "resolve": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz", + "integrity": "sha512-hgoSGrc3pjzAPHNBg+KnFcK2HwlHTs/YrAGUr6qgTVUZmXv1UEXXl0bZNBKMA9fud6lRYFdPGz0xXxycPzmmiw==", + "dev": true, + "requires": { + "path-parse": "1.0.5" + } + }, + "resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", + "dev": true, + "requires": { + "expand-tilde": "2.0.2", + "global-modules": "1.0.0" + } + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "dev": true, + "requires": { + "glob": "7.1.2" + } + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", + "dev": true + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + }, + "semver": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", + "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=", + "dev": true + }, + "sequencify": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/sequencify/-/sequencify-0.0.7.tgz", + "integrity": "sha1-kM/xnQLgcCf9dn9erT57ldHnOAw=", + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "set-getter": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/set-getter/-/set-getter-0.1.0.tgz", + "integrity": "sha1-12nBgsnVpR9AkUXy+6guXoboA3Y=", + "dev": true, + "requires": { + "to-object-path": "0.3.0" + } + }, + "set-value": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", + "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "dev": true, + "requires": { + "extend-shallow": "2.0.1", + "is-extendable": "0.1.1", + "is-plain-object": "2.0.4", + "split-string": "3.1.0" + } + }, + "shelljs": { + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.8.tgz", + "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=", + "dev": true, + "requires": { + "glob": "7.1.2", + "interpret": "1.1.0", + "rechoir": "0.6.2" + } + }, + "sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", + "dev": true + }, + "snapdragon": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.1.tgz", + "integrity": "sha1-4StUh/re0+PeoKyR6UAL91tAE3A=", + "dev": true, + "requires": { + "base": "0.11.2", + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "map-cache": "0.2.2", + "source-map": "0.5.7", + "source-map-resolve": "0.5.1", + "use": "2.0.2" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "1.0.0", + "isobject": "3.0.1", + "snapdragon-util": "3.0.1" + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-resolve": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.1.tgz", + "integrity": "sha512-0KW2wvzfxm8NCTb30z0LMNyPqWCdDGE2viwzUaucqJdkTRXtZiSY3I+2A6nVAjmdOy0I4gU8DwnVVGsk9jvP2A==", + "dev": true, + "requires": { + "atob": "2.0.3", + "decode-uri-component": "0.2.0", + "resolve-url": "0.2.1", + "source-map-url": "0.4.0", + "urix": "0.1.0" + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, + "sparkles": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.0.tgz", + "integrity": "sha1-Gsu/tZJDbRC76PeFt8xvgoFQEsM=", + "dev": true + }, + "spdx-correct": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", + "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", + "dev": true, + "requires": { + "spdx-license-ids": "1.2.2" + } + }, + "spdx-expression-parse": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz", + "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=", + "dev": true + }, + "spdx-license-ids": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz", + "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=", + "dev": true + }, + "split": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/split/-/split-0.2.10.tgz", + "integrity": "sha1-Zwl8YB1pfOE2j0GPBs0gHPBSGlc=", + "dev": true, + "requires": { + "through": "2.3.8" + } + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "3.0.2" + }, + "dependencies": { + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "1.0.0", + "is-extendable": "1.0.1" + } + }, + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "2.0.4" + } + } + } + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "0.2.5", + "object-copy": "0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "stream-combiner": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", + "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", + "dev": true, + "requires": { + "duplexer": "0.1.1" + } + }, + "stream-consume": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/stream-consume/-/stream-consume-0.1.0.tgz", + "integrity": "sha1-pB6tGm1ggc63n2WwYZAbbY89HQ8=", + "dev": true + }, + "stream-shift": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", + "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", + "dev": true + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-bom": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-1.0.0.tgz", + "integrity": "sha1-hbiGLzhEtabV7IRnqTWYFzo295Q=", + "dev": true, + "requires": { + "first-chunk-stream": "1.0.0", + "is-utf8": "0.2.1" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "ternary-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ternary-stream/-/ternary-stream-2.0.1.tgz", + "integrity": "sha1-Bk5Im0tb9gumpre8fy9cJ07Pgmk=", + "dev": true, + "requires": { + "duplexify": "3.5.1", + "fork-stream": "0.0.4", + "merge-stream": "1.0.1", + "through2": "2.0.3" + } + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "through2": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", + "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", + "dev": true, + "requires": { + "readable-stream": "2.3.3", + "xtend": "4.0.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" + } + }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + } + } + }, + "tildify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tildify/-/tildify-1.2.0.tgz", + "integrity": "sha1-3OwD9V3Km3qj5bBPIYF+tW5jWIo=", + "dev": true, + "requires": { + "os-homedir": "1.0.2" + } + }, + "time-stamp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", + "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=", + "dev": true + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "to-regex": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.1.tgz", + "integrity": "sha1-FTWL7kosg712N3uh3ASdDxiDeq4=", + "dev": true, + "requires": { + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "regex-not": "1.0.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "3.0.0", + "repeat-string": "1.6.1" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "unc-path-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", + "dev": true + }, + "union-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", + "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "dev": true, + "requires": { + "arr-union": "3.1.0", + "get-value": "2.0.6", + "is-extendable": "0.1.1", + "set-value": "0.4.3" + }, + "dependencies": { + "set-value": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", + "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", + "dev": true, + "requires": { + "extend-shallow": "2.0.1", + "is-extendable": "0.1.1", + "is-plain-object": "2.0.4", + "to-object-path": "0.3.0" + } + } + } + }, + "unique-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-1.0.0.tgz", + "integrity": "sha1-1ZpKdUJ0R9mqbJHnAmP40mpLEEs=", + "dev": true + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "0.3.1", + "isobject": "3.0.1" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "2.0.6", + "has-values": "0.1.4", + "isobject": "2.1.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + } + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "use": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/use/-/use-2.0.2.tgz", + "integrity": "sha1-riig1y+TvyJCKhii43mZMRLeyOg=", + "dev": true, + "requires": { + "define-property": "0.2.5", + "isobject": "3.0.1", + "lazy-cache": "2.0.2" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "0.1.6" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "user-home": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz", + "integrity": "sha1-K1viOjK2Onyd640PKNSFcko98ZA=", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "v8flags": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-2.1.1.tgz", + "integrity": "sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ=", + "dev": true, + "requires": { + "user-home": "1.1.1" + } + }, + "validate-npm-package-license": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", + "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=", + "dev": true, + "requires": { + "spdx-correct": "1.0.2", + "spdx-expression-parse": "1.0.4" + } + }, + "vinyl": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", + "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", + "dev": true, + "requires": { + "clone": "1.0.3", + "clone-stats": "0.0.1", + "replace-ext": "0.0.1" + } + }, + "vinyl-fs": { + "version": "0.3.14", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-0.3.14.tgz", + "integrity": "sha1-mmhRzhysHBzqX+hsCTHWIMLPqeY=", + "dev": true, + "requires": { + "defaults": "1.0.3", + "glob-stream": "3.1.18", + "glob-watcher": "0.0.6", + "graceful-fs": "3.0.11", + "mkdirp": "0.5.1", + "strip-bom": "1.0.0", + "through2": "0.6.5", + "vinyl": "0.4.6" + }, + "dependencies": { + "clone": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz", + "integrity": "sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8=", + "dev": true + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "through2": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", + "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", + "dev": true, + "requires": { + "readable-stream": "1.0.34", + "xtend": "4.0.1" + } + }, + "vinyl": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz", + "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", + "dev": true, + "requires": { + "clone": "0.2.0", + "clone-stats": "0.0.1" + } + } + } + }, + "which": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", + "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", + "dev": true, + "requires": { + "isexe": "2.0.0" + } + }, + "which-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", + "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", + "dev": true + }, + "widgetbuilder-gulp-helper": { + "version": "https://github.com/JelteMX/widgetbuilder-gulp-helper/archive/1.0.1.tar.gz", + "integrity": "sha1-1Zh+VPpGDESJL4cFGaQpwm6aemM=", + "dev": true, + "requires": { + "gulp-util": "3.0.8", + "node-mendix-modeler-path": "https://github.com/JelteMX/node-mendix-modeler-path/archive/v1.0.0.tar.gz", + "semver": "5.4.1", + "shelljs": "0.7.8", + "through2": "2.0.3", + "xml2js": "0.4.19" + }, + "dependencies": { + "semver": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", + "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==", + "dev": true + } + } + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "1.0.2", + "strip-ansi": "3.0.1" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "xml2js": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "dev": true, + "requires": { + "sax": "1.2.4", + "xmlbuilder": "9.0.4" + } + }, + "xmlbuilder": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.4.tgz", + "integrity": "sha1-UZy0ymhtAFqEINNJbz8MruzKWA8=", + "dev": true + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "dev": true + }, + "yargs": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz", + "integrity": "sha1-eC7CHvQDNF+DCoCMo9UTr1YGUgg=", + "dev": true, + "requires": { + "camelcase": "3.0.0", + "cliui": "3.2.0", + "decamelize": "1.2.0", + "get-caller-file": "1.0.2", + "os-locale": "1.4.0", + "read-pkg-up": "1.0.1", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "1.0.2", + "which-module": "1.0.0", + "y18n": "3.2.1", + "yargs-parser": "4.2.1" + } + }, + "yargs-parser": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-4.2.1.tgz", + "integrity": "sha1-KczqwNxPA8bIe0qfIX3RjJ90hxw=", + "dev": true, + "requires": { + "camelcase": "3.0.0" + } + }, + "yazl": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.4.3.tgz", + "integrity": "sha1-7CblzIfVYBud+EMtvdPNLlFzoHE=", + "dev": true, + "requires": { + "buffer-crc32": "0.2.13" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..8837125 --- /dev/null +++ b/package.json @@ -0,0 +1,37 @@ +{ + "name": "TreeView", + "version": "4.6.1", + "description": "", + "license": "", + "author": "", + "private": true, + "dependencies": {}, + "devDependencies": { + "del": "^2.2.2", + "gulp": "^3.9.1", + "gulp-if": "^2.0.1", + "gulp-intercept": "^0.1.0", + "gulp-json-transform": "^0.4.2", + "gulp-newer": "^1.3.0", + "gulp-util": "^3.0.7", + "gulp-zip": "^3.2.0", + "gulp-jsvalidate": "^3.0.0", + "widgetbuilder-gulp-helper": "https://github.com/JelteMX/widgetbuilder-gulp-helper/archive/1.0.1.tar.gz", + "yargs": "^6.0.0" + }, + "engines": { + "node": ">=5" + }, + "generatorVersion": "2.1.1", + "paths": { + "testProjectFolder": "./test_5_21_4/", + "testProjectFileName": "TreeviewDemo.mpr" + }, + "scripts": { + "build": "node ./node_modules/gulp/bin/gulp build", + "version": "node ./node_modules/gulp/bin/gulp version", + "icon": "node ./node_modules/gulp/bin/gulp icon", + "folders": "node ./node_modules/gulp/bin/gulp folders", + "modeler": "node ./node_modules/gulp/bin/gulp modeler" + } +} \ No newline at end of file diff --git a/src/TreeView/GridView.xml b/src/TreeView/GridView.xml index e2d0d63..6e443cf 100644 --- a/src/TreeView/GridView.xml +++ b/src/TreeView/GridView.xml @@ -99,13 +99,13 @@ Pmmn04F2ux1MpVI79I4q6jJMfXsbe3iMjYKIcDicKxQKt4qiWFRJi6Joe9iUkWER6C8e48eQCJLC - + @@ -161,13 +161,13 @@ Pmmn04F2ux1MpVI79I4q6jJMfXsbe3iMjYKIcDicKxQKt4qiWFRJi6Joe9iUkWER6C8e48eQCJLC - + @@ -345,7 +345,7 @@ Pmmn04F2ux1MpVI79I4q6jJMfXsbe3iMjYKIcDicKxQKt4qiWFRJi6Joe9iUkWER6C8e48eQCJLC Progress message 1) Basics If set, a progress bar will be shown when running this action - + 1 Order +Example: Domain model: Orderline *---orderline_order-->1 Order -Configuration to show all orderlines within an order (which is the context object): +Configuration to show all orderlines within an order (which is the context object): -Association owner: 'Orderline' -Association (reference): 'orderline_order' -Association direction: 'The association owner is the child' +Association owner: 'Orderline' +Association (reference): 'orderline_order' +Association direction: 'The association owner is the child' It is allowed to add an additional relation, with association direction 'The association owner is the parent', to be able to expand from the orderline back to the order (which might make sense if an orderline would be related to multiple orders). @@ -126,21 +127,21 @@ It is allowed to add an additional relation, with association direction 'The ass Association (reference) 1) Define the relation - The association from owner entity type to the other entity + The association from owner entity type to the other entity Association (referenceSet) 1) Define the relation - The association from owner entity type to the other entity - + The association from owner entity type to the other entity + Association direction 1) Define the relation - This property describes whether the selected defines whether this association is from + This property describes whether the selected defines whether this association is from parent <-- child (default) or - child --> parent + child --> parent The association owner is the child @@ -166,17 +167,17 @@ It is allowed to add an additional relation, with association direction 'The ass Style 2) Set additional options Define CSS properties here. Use newline or '|' to separate CSS properties (';') is not allowed! - + Allow drops 3) Drag and drop options Allow items to be dropped in this association - + Allow drop copies 3) Drag and drop options Allow items to be dropped as a copy in this association (not supported for single references where fromparent is false) - + On drop microflow 3) Drag and drop options @@ -187,7 +188,7 @@ It is allowed to add an additional relation, with association direction 'The ass - + 3) Rendering 1) Tree configuration @@ -226,14 +227,14 @@ It is allowed to add an additional relation, with association direction 'The ass - + - + (Default) Image @@ -244,8 +245,8 @@ It is allowed to add an additional relation, with association direction 'The ass On click action 3) Interaction The name of the action that should be invoked when this item is clicked. The name should refer to the name of an action as defined in step 4) Actions - - + + Class 2) Advanced @@ -255,7 +256,7 @@ It is allowed to add an additional relation, with association direction 'The ass Style 2) Advanced Define CSS properties here. Use newline or '|' to separate CSS properties (';') is not allowed! - + Column date time format 2) Advanced @@ -277,13 +278,13 @@ It is allowed to add an additional relation, with association direction 'The ass 'True' caption 3) Boolean rendering - + 'False' caption 3) Boolean rendering - - + + @@ -311,17 +312,17 @@ It is allowed to add an additional relation, with association direction 'The ass Button caption 2) Button - + Button image 2) Button - + Hide if disabled 2) Button Whether to hide, or only disable the button when it can't be pressed. - + Microflow 1) Basics @@ -333,30 +334,30 @@ It is allowed to add an additional relation, with association direction 'The ass 1) Basics - + Is default action 1) Basics - If true, this action will be used as default action and be invoked if the item is doubleclicked or the return key is pressed. + If true, this action will be used as default action and be invoked if the item is doubleclicked or the return key is pressed. Confirmation message 1) Basics If set, users have to confirm this message before continuing - + Progress message 1) Basics If set, a progress bar will be shown when running this action - - + + Fire on selection change 2) Settings Invoke this action immediately when the current selection changes. For more information about retrieving the current selection, see the 'fine tuning > Selection reference' properties - + + +--> - \ No newline at end of file + diff --git a/src/TreeView/widget/Commons.js b/src/TreeView/widget/Commons.js index 87debd5..7c07205 100644 --- a/src/TreeView/widget/Commons.js +++ b/src/TreeView/widget/Commons.js @@ -1,1666 +1,538 @@ -dojo.provide("TreeView.widget.Commons"); -dojo.require("dojo.data.util.simpleFetch"); -dojo.require("dijit.form.DropDownButton"); //DropDownMenu in dojo 1.7! -dojo.require("dijit.form.ComboBox"); -dojo.require("dijit.form.Button"); -dojo.require("dijit.Menu"); -dojo.require("dijit.MenuItem"); -dojo.require("dijit.form.FilteringSelect"); -dojo.require("dijit.form.Select"); -dojo.require("dijit.Dialog"); //fix for #101262 - -dojo.setObject("TreeView.widget.Commons", (function() { - - - //MX5 hack - function fixObjProps (widget, props) { - var args = {}; - - for (var i = 0, prop; prop = props[i]; i++) { - var arr = widget[prop]; - - for (var j = 0, obj; obj = arr[j]; j++) { - for (var p in obj) { - (args[p] || (args[p] = [])).push(obj[p]); - } - } - } - - for (var a in args) { - widget[a] = args[a].join(";"); - } - } - - - function getEnumMap(classname, attrname) { - var meta = mx.metadata.getMetaEntity(classname); - - if (getAttributeType(classname, attrname) != 'Enum') - throw "Not an enumeration: " + args.join("."); - - return meta.getEnumMap(attrname); - } - - /** - * Renders a label, - * @param {[type]} name [description] - * @param {[type]} close [description] - * @param {[type]} data [datamap object to be attached to the domnode] - * @return {[type]} node [description] - */ - function renderLabel(name, close, data) { - var n = mxui.dom.span( - { 'class' : 'gv_label' }, - mxui.dom.span( - { 'class' : 'gv_label_name' }, - name ? name : "" - ) - ); - - if (close) - dojo.place(mxui.dom.span( - { 'class' : 'gv_label_close' }, - 'x' - ), n); - - mxui.dom.data(n, "data", data); - return n; - } - - function getAttributeType(classnameOrObject, attr) { - var parts = attr.split("/"); - if (parts.length == 3) - return getAttributeType(parts[1], parts[2]) - - if (attr.indexOf("/") == -1) { - if (classnameOrObject.getClass) - classnameOrObject = classnameOrObject.getClass(); - - var meta = mx.metadata.getMetaEntity(classnameOrObject); - return meta.getAttributeClass(attr); - } - - return false; - } - - - function getObjectAttr (object, attr, renderValue) { - if (!object || !attr) - return ""; - - if (attr.indexOf("/") == -1) { - if (renderValue) - return mx.parser && mx.parser.formatAttribute ? mx.parser.formatAttribute(object, attr) : mxui.html.renderValue(object, attr); //mxui.html.rendervalue moved in 5.~7. - return object.getAttribute(attr); - } - var parts = attr.split("/"); - if (parts.length == 3) { - var child = object.get(parts[0]); - - if (!child) - return ""; - - //Fine, we have an object - if (dojo.isObject(child)) { - child = object.getChild(parts[0]); //Get child only works if child was not a guid but object - return getObjectAttr(child, parts[2], renderValue); - } - - //Try to retrieve guid in syc - else { - //..but, there is a guid... - var tmp = null; - mx.processor.get({ guid : child, noCache : false, callback : function(obj) { //async = false option would be nice! - tmp = obj; - }}); - if (tmp != null) //callback was invoked in sync :) - return getObjectAttr(tmp, parts[2], renderValue); - - //console && console.warn && console.warn("Commons.getObjectAttr failed to retrieve " + attr ); - //This happens if no retrieve schema was used :-(. - return ""; - } - - } - - //objects can be returned in X different ways, sometime just a guid, sometimes its an object... - if (parts.length == 2) { - var result = object.getAttribute(parts[0]); //incase of of a get object, return the GUIDs (but sometimes getAttribute gives the object...) - if (!result) - return ""; - if (result.guid) - return result.guid; - if (/\d+/.test(result)) - return result; - } - throw "GridCommons.getObjectAttr: Failed to retrieve attribute '" + attr + "'"; - - } - - - function objectToGuid(thing) { - if (!thing) - return null; - if (thing.guid) - return thing.guid; - if (thing.getGUID) - return thing.getGUID(); - if (/^\d+$/.test(thing)) - return thing; - throw "Does not look like a MxObject: " + thing; - } - - - /** - * Stores data - * @param {[type]} object [object or list of mxobjects to change] - * @param {[type]} attr [attribute or reference to update] - * @param {[type]} value [the new value(s): primitive, guid(s) or mxobject(s)] - * @param {[type]} mode ["add", "rem" or empty / "set"] - * @param {[type]} commit [commit or save?] - * @param {Function} callback [callback function] - * @return {[type]} [description] - */ - function store(object, attr, value, mode, commit, callback) { - var res = false; - - //list of objects - if (dojo.isArray(object)) { - var left = 1; - var cb = function() { - left -= 1; - if (left < 1) - callback && callback(); - } - - for (var i = 0; i < object.length; i++) { - left += 1; - store(object[i], attr, value, mode, commit, cb); - }; - - cb(); - } - - //single object - else { - var parts = attr.split("/"); - attr = parts[0] - if (!object.has(attr)) - throw "Commons.store: Unable to read or write attribute '" + attr + "'. Does the user have sufficient permission and is the attribute retrieved from the server?" - - //simple attribute - if (parts.length == 1 && attr.indexOf(".") == -1) - res = object.set(attr, value) - - //reference - else if (parts.length == 1 || parts.length == 2) { - var isRefSet = object.isObjectReferenceSet(attr); - - //reference set - if (isRefSet) { - var guids; - if (dojo.isArray(value)) - guids = dojo.map(value, objectToGuid); - else if (!value) - guids = []; - else - guids = [objectToGuid(value)] - - - switch(mode) { - case "add" : - res = object.addReferences(attr, guids); - break; - case "rem" : - res = object.removeReferences(attr, guids); - break; - default: - res = object.set(attr, guids); - } - } - - //single reference - else { - if (dojo.isArray(value)) - throw "Commons.store: cannot assign array to reference"; - - var guid = objectToGuid(value); - res = object.set(attr, guid ? guid : "") //client needs '' as empty - } - } - - else - throw "Commons.store: unsupported attribute path: " + attr; - - //check res - if (res === false) //set returns undefined if ok, or false on failure - throw "Commons.store: Unable to update attribute: " + attr; - - mx.processor[commit === true ? 'commit' : 'save']({ - mxobj: object, - error : error, - callback : function() { - callback && callback(); - } - }) - } - } - - - /** - * MWE, since the liveConnect does not work properly with bubbling en event propagion, we implement our own... - * @param {[type]} widget [description] - * @param {[type]} node [description] - * @param {[type]} event [description] - * @param {[type]} map of className -> function(node, event) -> boolean. (If false, further event are stopped) - */ - function liveConnect(widget, node, event, map) { - if (!node) - throw "liveConnect: no node provided" - widget.connect(node, event, function(e) { - var currNode = e.target; - var matched = {}; //we already matched these, don't bubble. - - while(currNode != node && currNode != null){ - for(var clazz in map) { - if (!(clazz in matched) && dojo.hasClass(currNode, clazz)) { - //avoid a second match on the same selector - matched[clazz] = true; - - //call the callback - var res = map[clazz].call(widget, currNode, e); - - //stop the event! - if (res === false) { - e && e.stopPropagation(); // dojo.stopEvent(e); - return; - } - } - } - currNode = currNode.parentNode; - } - - //e && dojo.stopEvent(e); - - }); - } - - /** - * Shows a confirm dialog. If the message is empty, the dialog is skipped altogether, and the callback is invoked inmediately - * @param {[type]} message [description] - * @param {Function} callback [description] - * @return {[type]} [description] - */ - function confirm(message, callback, yescaption, nocaption) { - if (!message) { - callback && callback(); - return; - } - - mx.ui.confirmation({ - content: message, - proceed: yescaption || "Yes", - cancel: nocaption || "Cancel", - handler : callback - }); - } - - function mf (mfname, data, callback, context, mfNeedsList, progressMessage) { - //firing on multiple items? wait for all items to finish - if (dojo.isArray(data) && !mfNeedsList) { - var left = data.length; - var cb = function() { - left -= 1; - if (left < 1 && callback) - callback.call(context || window); - } - dojo.forEach(data, function(dataitem) { - mf(mfname, dataitem, cb, context, false, progressMessage); - }); - - } - - else { - var guids = dojo.map(dojo.isArray(data) ? data : [data], objectToGuid); - - if (guids.length > 1 && !mfNeedsList) - throw "Multiple selection found, but microflow supports only one argument!"; - - mx.ui.action(mfname, { - store : { - caller : context - }, - params : { - applyto : 'selection', - guids : guids - }, - progressMsg : progressMessage, - progress : progressMessage ? "modal" : undefined, - error : function() { - if (error) - error(); - }, - callback : function(_, data) { - if (callback) - callback.call(context || window); - }, - async : !!progressMessage - }); - } - } - - function configError (widget, msg) { - msg = "Configuration error in " + widget.id + ": " + msg; - if (console) - console.error(msg); - widget.domNode.innerHTML = msg; - mx.ui.error(msg) - throw msg; - } - - function error(e) { - console.error(e); - mx.ui.error(e); - throw e; - } - - /** - * splits the given properties up to objects in target per index. First property indicates targetobjects name - */ - function splitPropsTo(widget, propnames, target) { - var props = propnames.split(","); - var rawdata = {}; - - var nameprop = props[0]; - for(var i = 0; i < props.length; i++) - rawdata[props[i]] = widget[props[i]].split(";"); - - //create target objects - for(i = 0; i < rawdata[nameprop].length; i++){ - var obj = {}; - var hasdata = false; - for (var key in rawdata) { - var val = obj[key] = rawdata[key][i]; - if (/^true$/.test(obj[key])) - obj[key] = true; - else if (/^false$/.test(obj[key])) - obj[key] = false; - hasdata = hasdata || (val !== ""); - } - if (hasdata) //if the object does not contain any data at all, skip it - target.push(obj); - } - } - - /** - * Data from contexts might either be a guid or an object. Normalize to an object and invoke callback - * @param {[type]} data [null, guid or mx object] - * @param {[function(object, guid)]} cb to be invoked when resolved [description] - * @return {[type]} [description] - */ - function normalizeContext(data, cb) { - //Nothing - if (data == null) - cb(null, null); - //GUid only - else if (typeof(data) != "object" && /^\d+$/.test(data)) { - mx.processor.get({ - guid: data, - callback : function(mxobj) { - if (mxobj == null) - cb(null, null) - else - cb(mxobj, mxobj.getGUID()) - }, - error : this.showError - }, this); - } - //Context is mxobj object - else { - var guid = data.getGUID(); - cb(data, guid); - } - } - - return { - getObjectAttr : getObjectAttr, - liveConnect : liveConnect, - mf : mf, - configError : configError, - splitPropsTo : splitPropsTo, - error : error, - getAttributeType : getAttributeType, - getEnumMap : getEnumMap, - renderLabel : renderLabel, - normalizeContext : normalizeContext, - objectToGuid : objectToGuid, - store : store, - get : getObjectAttr, - confirm : confirm, - fixObjProps : fixObjProps - } -})(), window); - - -dojo.declare("TreeView.widget.DropDown", null, { - onChange : null, - label : null, - options : null, - value : null, - dataset : null, - className: null, - sticky : true, - - _datasetsub : null, - - constructor : function(args, domNode, owner) { - dojo.mixin(this, args); - this.options = this.options || []; - - if (this.dataset) { - this._datasetsub = dojo.connect(this.dataset, 'onReceiveItems', dojo.hitch(this, this.receiveDatasetItems)); - if (owner) - owner.addSubscription(this._datasetsub); - - } - - this.menu = new dijit.Menu({ - style: "display: none;" - }); - - this.dropdown = new dijit.form.DropDownButton({ - label : this.label, - dropDown : this.menu, - onClick : function(e) { - dojo.stopEvent(e); - } - }); - this.domNode = this.dropdown.domNode; - - dojo.addClass(this.dropdown.dropDown.domNode, 'gv_dropdown_menu ' + this.className); - dojo.addClass(this.dropdown.domNode, 'gv_dropdown ' + this.className); - - dojo.place(this.dropdown.domNode, domNode); - - this.addOptions(this.options); - - if (this.dataset) - this.addOptions(this.dataset.getOptions()); - }, - - receiveDatasetItems : function(items) { - this.clearItems(); - this.addOptions(this.options); - this.addOptions(items); - }, - - addOptions : function(items) { - dojo.forEach(items, function(item) { - this.menu.addChild(this.createOption(item)); - }, this); - }, - - clearItems : function() { - dojo.forEach(this.menu.getChildren(), function(child) { - this.menu.removeChild(child); - }, this); - }, - - createOption : function(item) { - //separator - if (item == null) - return new dijit.MenuSeparator(); - - if (this.sticky && this.value !== null && this.value == item.value) //redraw selection if needed - this.dropdown.set('label', item.label); - - return new dijit.MenuItem({ - label : mxui.dom.escapeHTML(item.label), - value : item.value, - onClick : item.onClick - ? dojo.hitch(item, item.onClick, dojo.hitch(this, this.itemClick)) //pass itemClick as callback to the onClick, so it can be invoked - : dojo.hitch(this, this.itemClick, item) - }); - }, - - itemClick : function(item, e) { - this.onChange.call(null, item.value); - if (this.sticky) { - this.dropdown.set('label', item.label); - this.value = item.value; - } - e && dojo.stopEvent(e); - }, - - /* dojo get & set proxying */ - set : function() { - if (!this.dropdown._destroyed) - return this.dropdown.set.apply(this.dropdown, arguments); - return undefined; - }, - - get : function() { - if (!this.dropdown._destroyed) - return this.dropdown.get.apply(this.dropdown, arguments); - return undefined; - }, - - destroy : function() { - if (this._datasetsub) - dojo.disconnect(this._datasetsub); - this.dropdown.destroy(); - this._destroyed = true; - }, - - free : function() { - this.destroy(); //Free is used by commons, destroy by Mendix widgets - } - -}); - -dojo.declare("TreeView.widget.Checkbox", null, { - onChange : null, - value : null, - className: null, - readOnly : false, - - _clickSubscription: null, - - constructor : function(args, domNode) { - dojo.mixin(this, args); - - this.checkbox = mendix.dom.input({ - type : "checkbox" - }); - - dojo.attr(this.checkbox, "checked", this.value); - dojo.attr(this.checkbox, "readonly", this.readOnly); - dojo.attr(this.checkbox, "disabled", this.readOnly); - - if (!this.readOnly) - this._clickSubscription = dojo.connect(this.checkbox, "onchange", dojo.hitch(this, this.change)); - - dojo.addClass(this.checkbox, 'gv_checkbox ' + this.className); - - dojo.place(this.checkbox, domNode); - }, - - change : function(e) { - this.onChange.call(null, this.checkbox.checked); - e && e.stopPropagation(); - }, - - /* dojo get & set proxying */ - set : function() { - return undefined; - }, - - get : function() { - return undefined; - }, - - destroy : function() { - if (this._clickSubscription) - dojo.disconnect(this._clickSubscription); - } - -}); - -dojo.declare("TreeView.widget.Condition", null, { - condname : '', - condattr : '', - condvalues : '', - condclass : '', - - widget : null, - values : null, - - constructor : function(args, widget) { - dojo.mixin(this, args); - this.widget = widget; - - //always compare strings - this.values = dojo.map(("" + this.condvalues).split("\\n"), function(s) { return dojo.trim(s); }); - }, - - getClass : function() { - return this.condclass; - }, - - appliesTo : function(record) { - var value = TreeView.widget.Commons.get(record.data(), this.condattr); - if (value === null || value === undefined || /^\s*$/.test("" + value)) { - if (dojo.indexOf(this.values, "false") != -1) //This one was suppossed to match on falsy values - return true; - else - return false; - } - return -1 != dojo.indexOf(this.values, "" + value); - } - -}) - -dojo.declare("TreeView.widget.Colrenderer", null, { - - columnname : '', - columnentity : '', - columnrendermode : '', - columnattr : '', - columnimage : '', - columnaction : '', - columnclazz : '', - columnstyle : '', - columndateformat : '', - columntruecaption : '', - columnfalsecaption : '', - columneditdataset : '', - columneditable : false, - columneditautocommit : true, - columnonchangemf: '', - columncondition : '', - columnprefix : '', - columnpostfix : '', - - colindex : -1, - tree : null, - condition : null, - toDestruct : null, - - constructor : function(args, tree, colindex) { - dojo.mixin(this, args); - this.toDestruct = []; - this.columnstyle = this.columnstyle.split(/\||\n/).join(";"); //XXX: modeler does not export ';' separated css attributes correctly. Allow newlines and pipes as separators - - this.tree = tree; - this.colindex = colindex; - - if ((this.columneditable && this.columnattr.indexOf("/") > -1) || (this.columnrendermode == 'dataset')) { - this.dataset = this.tree.dataset[this.columneditdataset]; - if (this.dataset == null) - this.tree.configError("Unknown dataset for editable reference '" + this.columnattr + "': '" + this.columneditdataset + "'"); - } - - if (this.columncondition) { - this.condition = this.tree.conditions[this.columncondition]; - if (!this.condition) - this.tree.configError("Undefined condition '" + this.columncondition + "' for '" + this.columnattr + "'"); - } - - }, - - appliesTo : function(renderNode) { - return renderNode.isA(this.columnentity); - }, - - setupNode : function(parentNode) { - dojo.attr(parentNode.parentNode, "style", this.columnstyle); - dojo.addClass(parentNode.parentNode, this.columnclazz); - - mxui.dom.data(parentNode.parentNode, 'colindex', this.colindex) - }, - - createDefaultImage : function(parentNode) { - if (this.columnimage) { - dojo.place(mxui.dom.img({ - //'class' : 'gg_img ' + this.columnclazz, - //'style' : this.columnstyle, - 'src' : this.columnimage - }), parentNode, 'first'); - } - }, - - invokeAction : function(record) { - if (this.columnaction) - this.tree.actionsByName[this.columnaction].invoke(record); - }, - - applyChange : function(record, newvalue, remove) { - TreeView.widget.Commons.store( - record.data(), - this.dataset ? this.dataset.getAssoc() : this.columnattr, - newvalue, - this.dataset && this.dataset.isRefSet() && remove === true ? "rem" : "add", - this.columneditautocommit && !this.columnonchangemf, //MWE: ignore auto commit setting if onchange is used - dojo.hitch(this, this._fireOnChange, record) - ); - }, - - _fireOnChange : function(record) { - if (this.columnonchangemf) - TreeView.widget.Commons.mf(this.columnonchangemf, record.data(), function() {}, this.tree); - }, - - renderEditable : function(record, domNode, firstTime) { - if (!firstTime) - return; - - var attrtype = TreeView.widget.Commons.getAttributeType(this.columnentity, this.columnattr); - - //dropdown with reference selector dropdown - if (this.columnattr.indexOf('/') > -1) { - this.toDestruct.push(new TreeView.widget.DropDown({ - value : TreeView.widget.Commons.objectToGuid(record.data().get(this.columnattr.split("/")[0])), //can be both guid and nothing - onChange : dojo.hitch(this, this.applyChange, record), - sticky : !this.dataset.isRefSet(), - className: 'gv_columnedit_dropdownmenu', - dataset : this.dataset, - label : this.dataset.rellabel - }, - domNode, - record - )); - } - else if (attrtype == "Enum" || (attrtype == "Boolean" && (this.columntruecaption || this.columnfalsecaption)) ) { - var items=[]; - - //boolean - if (attrtype == "Boolean") - items = [ - { value : true, label : this.columntruecaption || "Yes" }, - { value : false, label : this.columnfalsecaption || "No" } - ] - - //enum map - else { - var em = TreeView.widget.Commons.getEnumMap(this.columnentity, this.columnattr) - for (var i = 0; i < em.length; i++) - items.push({ value : em[i].key, label : em[i].caption }); - } - - //setup dropdown - this.toDestruct.push(new TreeView.widget.DropDown({ - options : items, - value : TreeView.widget.Commons.getObjectAttr(record.data(), this.columnattr, false), - onChange : dojo.hitch(this, this.applyChange, record), - sticky : true, - className: 'gv_columnedit_dropdownmenu' - }, - domNode, - record - )); - } - else if (attrtype == "Boolean") { - new TreeView.widget.Checkbox({ - value : TreeView.widget.Commons.getObjectAttr(record.data(), this.columnattr, false), - onChange : dojo.hitch(this, this.applyChange, record), - className: 'gv_columnedit_checkbox' - }, - domNode - ); - } - else - this.tree.configError("This widget does not currently support edit for property " + this.columnattr + " type: " + attrtype); - }, - - render : function(record, domNode, firstTime) { - if (this.columnaction != '') - dojo.addClass(domNode, 'gg_clickable'); - - if (this.condition && !this.condition.appliesTo(record)) { - dojo.style(domNode.parentNode, 'display', 'none'); - return; //hide - } - - dojo.style(domNode.parentNode, 'display', ''); - - switch (this.columnrendermode) { - case 'attribute': - if (this.columneditable) - this.renderEditable(record, domNode, firstTime) - else { - dojo.empty(domNode); - var attrtype = TreeView.widget.Commons.getAttributeType(this.columnentity, this.columnattr); - - //Boolean value? - if (attrtype == "Boolean" && !(this.columntruecaption || this.columnfalsecaption)) { - this.createDefaultImage(domNode); - new TreeView.widget.Checkbox({ //TODO: MWE, when cleaned up? - - value : TreeView.widget.Commons.getObjectAttr(record.data(), this.columnattr, false), - className: 'gv_columnview_checkbox', - readOnly : true - }, - domNode - ); - } - - //Any other value - else { - var value = this._renderAttr(record); - if (value === null || value === undefined) - value = ""; - - dojo.html.set(domNode, this.columnprefix + mxui.dom.escapeHTML(value).replace(/\n/g,"
") + this.columnpostfix); - dojo.attr(domNode, 'title', value); - - this.createDefaultImage(domNode); - } - } - - break; - case 'caption': - if (firstTime) { - domNode.innerHTML = this.columnprefix + this.columnname + this.columnpostfix; - this.createDefaultImage(domNode); - } - break; - case 'attributehtml': - domNode.innerHTML = this.columnprefix + TreeView.widget.Commons.getObjectAttr(record.data(), this.columnattr, false) + this.columnpostfix; - this.createDefaultImage(domNode); - break; - case 'attributeimage': - dojo.empty(domNode); - - var url = TreeView.widget.Commons.getObjectAttr(record.data(), this.columnattr, false); - if (!url) - url = this.columnimage; - - domNode.appendChild(mxui.dom.img({ - //'class' : 'gg_img ' + this.columnclazz, - //'style' : this.columnstyle, - 'src' : url - })); - break; - case 'image': - if (firstTime === true) - this.createDefaultImage(domNode); - break; - case 'thumbnail' : - dojo.empty(domNode); - - var fileid = TreeView.widget.Commons.getObjectAttr(record.data(), this.columnattr == '' ? 'FileID' : this.columnattr); - var cd = TreeView.widget.Commons.getObjectAttr(record.data(), this.columnattr.replace(/FileID/,'') + 'changedDate'); - domNode.appendChild(mxui.dom.img({ - //'class' : 'gg_img ' + this.columnclazz, - //'style' : this.columnstyle, - 'src' : 'file?thumb=true&target=internal&fileID=' + fileid + '&changedDate='+cd - })); - break; - case 'systemimage' : - dojo.empty(domNode); - - var fileid = TreeView.widget.Commons.getObjectAttr(record.data(), this.columnattr == '' ? 'FileID' : this.columnattr); - var cd = TreeView.widget.Commons.getObjectAttr(record.data(), this.columnattr.replace(/FileID/,'') + 'changedDate'); - - domNode.appendChild(mxui.dom.img({ - //'class' : 'gg_img ' + this.columnclazz, - //'style' : this.columnstyle, - 'src' : 'file?thumb=false&target=internal&fileID=' + fileid + '&changedDate='+cd - })); - break; - case 'dataset': - //only subscribe when the record is new - dojo.empty(domNode) - - if(firstTime === true) { - record.addSubscription(dojo.connect(this.dataset, 'onReceiveItems', dojo.hitch(this, function(items) { - this.render(record, domNode); - }))) - } - - var guids = record.data().getReferences(this.dataset.getAssoc()); - if (this.dataset.hasData) { - dojo.forEach(guids, function(guid) { - var value = this.dataset.getValue(guid); - if (value) { - dojo.place( - TreeView.widget.Commons.renderLabel( - value , - this.columneditable, - { owner: record, guid : guid, dataset: this.columneditdataset, colindex: this.colindex } - ), domNode - ); - } - }, this); - } - break; - default: - this.tree.configError("not implemented columnrendermode: " + this.columnrendermode); - } - }, - - _renderAttr : function(record) { - var object = record.data(); - var attrtype = TreeView.widget.Commons.getAttributeType(object, this.columnattr); - var value = TreeView.widget.Commons.getObjectAttr(object, this.columnattr, attrtype != "DateTime"); - if (attrtype == "DateTime") { - if (!value || "" == value) - return ""; - - return dojo.date.locale.format(new Date(value), { - selector : 'date', - datePattern : this.columndateformat != "" ? this.columndateformat : "EEE dd MMM y" - }); - } - else if (attrtype == "Boolean" && (this.columntruecaption || this.columnfalsecaption)) - return value == "Yes" ? this.columntruecaption : this.columnfalsecaption; - - return value; - }, - - free : function() { - dojo.forEach(this.toDestruct, function(item) { - item.free(); - }); - } -}); - -dojo.declare("TreeView.widget.Action", null, { - //Not functional - actname : '', - actentity : '', - actshowbutton : '', - actclassname : '', - actbuttoncaption : '', - actbuttonimage : '', - actmf : '', - actmultimf : '', - actisdefault : false, - actonselect : false, - actnoselectionmf : '', - actshortcut : '', - actautohide : '', - actconfirmtext : '', - actdataset : '', - actappliestomultiselection : true, - actprogressmsg : '', - //*Not functional - - tree : null, - - constructor : function(args, tree) { - this.tree = tree; - dojo.mixin(this, args); - - this.tree.connect(this.tree, 'onSelect', dojo.hitch(this, this.updateToSelection)); - }, - - assignRefToSelection : function(item) { - if (!this.actmf) - this.configError("No selection microflow defined for association assignment button"); - - TreeView.widget.Commons.store( - //records to objects - dojo.map(this.tree.getSelection(), function(item) { return item.data() }), - - this.dataset.getAssoc(), item, "add", false, - //callback - dojo.hitch(this, function() { - this.invokeOnSelection(); - - this.mxbutton.set('value', null); - }) - ); - - }, - - setup : function(parentNode) { - if (this.actshowbutton) { - if (this.actdataset) { - - this.dataset = this.tree.dataset[this.actdataset]; - if (this.dataset == null) - this.tree.configError("Unknown dataset for action: '" + this.actdataset + "'"); - if (!this.actappliestomultiselection) - this.tree.configError("Reference assignment should be allowed to be applied to multi selections! (see the action allow to multiselection property)"); - - this.mxbutton = new TreeView.widget.DropDown({ - onChange : dojo.hitch(this, this.assignRefToSelection), - sticky : false, - label : this.dataset.rellabel, - dataset : this.dataset, - className : ' gv_action_dropdown ' + this.actclassname - }, - parentNode, - null - ) - } - else { - this.mxbutton = new mxui.widget._Button({ - caption : this.actbuttoncaption, - icon : this.actbuttonimage, - onClick : dojo.hitch(this, this.invokeOnSelection), - type : 'button', - cssclass : this.actclassname, - //title : column.help, //TODO:? - isInactive : false - }); - dojo.place(this.mxbutton.domNode, parentNode); - } - } - - if (this.actonselect) - this.tree.connect(this.tree, 'onSelect', dojo.hitch(this, this.invokeOnSelection)) - }, - - appliesToSelection : function() { - if (this.actnoselectionmf) - return true; - - if (!this.tree.hasSelection() || !this.actmf) - return false; - - if (this.tree.hasMultiSelection() && !this.actappliestomultiselection) - return false; - - return this.appliesTo(this.tree.getSelection()); - }, - - //Check if this action is applicable to the mentioned item or item list - appliesTo : function (item) { - if (!this.actentity) - return true; - - var applies = true; - if (dojo.isArray(item)) { - for(var i = 0; i < item.length; i++) - applies &= this.appliesTo(item[i]); - return applies; - } - - return item.isA(this.actentity); - }, - - //show, hide, enable, disable based on the current selection - updateToSelection : function() { - if (this.actshowbutton) { - var enable = this.appliesToSelection(); - - if (!this.mxbutton._destroyed) {//MWE: wtf? - this.mxbutton.set("disabled", !enable); - if (this.actautohide) - dojo.style(this.mxbutton.domNode, 'display', enable ? 'inline-block' : 'none'); - else - (enable ? dojo.removeClass : dojo.addClass)(this.mxbutton.domNode, 'gv_button_disabled'); - } - } - }, - - //invoke, but on the current selection / context, that is, the button is triggered from the header - invokeOnSelection : function() { - if (this.appliesToSelection()) { - var selection = this.tree.getSelection(); - - //invoke on the data of the selected node - if (selection && (this.actmf || this.actmultimf)) - this.invoke(selection); - - //invoke on the root object - else if (this.actnoselectionmf) { - TreeView.widget.Commons.confirm(this.actconfirmtext, dojo.hitch(this, function() { - TreeView.widget.Commons.mf(this.actnoselectionmf, this.tree.getContextObject(), null, this.tree, false, this.actprogressmsg); - })); - } - } - }, - - invoke : function(selection) { - if ((this.actmf || this.actmultimf) && this.appliesTo(selection)) { //double check applies to, see #15349 - - - - TreeView.widget.Commons.confirm(this.actconfirmtext, dojo.hitch(this, function() { - //if a new item is added, suggest it as new selection - delete this._recordSelectionSuggestion; - this.tree._recordSelectionSuggestion = true; - - //See ticket 9116, we need to invoke the single argument microflow version for a single argument. Multi argument mf will break - if (dojo.isArray(selection) && selection.length > 1 && this.actmultimf) - TreeView.widget.Commons.mf(this.actmultimf, dojo.map(selection, function(item) { return item.data() }), null, this.tree, true, this.actprogressmsg); - else { - var sel = selection == null || selection == [] - ? [] - : dojo.isArray (selection) - ? dojo.map(selection, function(item) { return item.data() }) - : selection.data(); - - TreeView.widget.Commons.mf(this.actmf, sel, null, this.tree, false, this.actprogressmsg); - } - })); - } - }, - - free : function() { - if (this.mxbutton && !this.mxbutton._destroyed) - this.mxbutton.destroy(); - } - -}); - -dojo.declare("TreeView.widget.RelatedDataset", null, { - relname : '', - rellabel : '', - relentity : '', - relcontextassoc : '', - relitemassocref : '', - relitemassocrefset : '', - relnameattr : '', - relconstraint : '', - relnewitemcaption : '', - - widget : null, - contextGUID : null, - hasData : false, - - existingLabels : null, - existingLabelsById: null, - existingOptions : null, - - _fetchingLabels : false, - - constructor : function(args, widget, cb) { - this.widget = widget; - dojo.mixin(this, dojo.data.util.simpleFetch); - dojo.mixin(this, args); - - this.existingOptions = []; - - this.widget.subscribe({ //fetch the new labels if changed - entity : this.relentity, - callback : dojo.hitch(this, this.fetchLabels) - }); - - this.widget.connect(this.widget, "update", dojo.hitch(this, function(data, cb) { - this.contextGUID = data && data.getGUID ? data.getGUID() : data; - this.fetchLabels(); - cb && cb(); - })); - }, - - getAssoc : function() { - return this.relitemassocref != '' ? this.relitemassocref.split("/")[0] : this.relitemassocrefset.split("/")[0]; - }, - - isRefSet : function() { - return this.relitemassocrefset != ''; - }, - - getValue : function(item, _) { - if (typeof (item) == "object") - return item.getAttribute(this.relnameattr); - else if (/^\d+$/.test(item)){ - var obj = this.existingLabelsById[item]; //assuming guid - return obj ? this.getValue(obj) : null; //TODO: warn? - } - else - this.widget.showError("Dataset getValue not valid for: " + item); - return ''; - }, - - getOptions : function() { - return this.existingOptions; - }, - - fetchLabels : function() { - if (this.contextGUID == null || this._fetchingLabels) - return; - - this.hasData = false; - this._fetchingLabels = true; - var xpath = "//" + this.relentity + this.relconstraint + (this.relcontextassoc != '' ? "[" + this.relcontextassoc.split("/")[0] + " = '[%CurrentObject%]']" : ''); - xpath = xpath.replace(/\[\%CurrentObject\%\]/gi, this.contextGUID); - mx.processor.get({ - xpath : xpath, - callback : dojo.hitch(this, this.retrieveLabels), - filter : { - sort : [ [this.relnameattr, "asc" ] ], - attributes : [this.relnameattr] - } - },this); - }, - - retrieveLabels : function(objects) { - this.rawObjects = objects; - this.existingLabels = {}; - this.existingLabelsById = {}; - - this.existingOptions = dojo.map(objects, function(obj) { - var value = obj.getGUID(); - var label = obj.getAttribute(this.relnameattr); - - this.existingLabels[label.toLowerCase()] = obj; - this.existingLabelsById[value] = obj; - - return { value : value, label : label } - }, this); - - this._fetchingLabels = false; - - if (this.relnewitemcaption) - this.existingOptions.splice(0,0, { - value : 'new', - label : this.relnewitemcaption, - onClick : dojo.hitch(this, this.createNewItem) - }, null) //Null = separator - - this.hasData = true; - this.onReceiveItems(this.existingOptions); - }, - - onReceiveItems : function(items) { - //connect stub - }, - - /** - * Given a array of guids, returns a list of captions - * @param {[type]} items [description] - * @return {[type]} [description] - */ - getCaptions : function(items) { - return dojo.map(items, this.getValue, this); - }, - - createNewItem : function(callback) { - var labelname = prompt("Please enter " + this.relnewitemcaption, ""); - if (labelname) { - mx.processor.create({ - entity : this.relentity, - error : this.widget.showError, - callback : dojo.hitch(this, function(label) { - var cb = callback - ? dojo.hitch(this, callback, { - value : label.getGUID(), - label: labelname - }) - : null; - - if (this.relcontextassoc) - TreeView.widget.Commons.store(label, this.relcontextassoc, this.contextGUID); - - TreeView.widget.Commons.store(label, this.relnameattr, dojo.trim(labelname), null, true, cb); - }) - }, this) - } - }, - - /** dojo store compatibility, useful for future display in selects, filtering selects etc. etc.*/ - - /* Identity api */ - getIdentity : function(item) { - return item.getGUID(); - }, - - getIdentityAttributes : function() { - return null; - }, - - fetchItemByIdentity : function(args) { - //TODO: check and error handling - if (!this.existingLabelsById) - args.onItem.call(args.scope, null); - else - args.onItem.call(args.scope, this.existingLabelsById[args.identity]); - }, - - /* Simplefetch api */ - _fetchItems : function(query, resultcallback){ - var results = []; - if (this.existingLabels != null) - for(key in this.existingLabels) - if (key.indexOf(query.query.name.toLowerCase()) == 0) - results.push(this.existingLabels[key]); - - resultcallback(results, query); - } - -}); - -dojo.declare("TreeView.widget.SearchControl", null, { - dataset : null, - - domNode : null, - widget : null, - - searchfilter : "", - searchlabel : null, - realtime : false, - hasDataset : true, - - _isSearching : false, - _searchPending : false, - - constructor : function(args, widget) { - dojo.mixin(this, args); - - this.widget = widget; - - this.setupLayout(); - this.setupSearch(); - - }, - - setupLayout : function() { - this.domNode = mxui.dom.div({ - 'class' : 'gv_searchBar' - }); - - this.labelContainer = mxui.dom.div({ 'class' : 'gv_searchLabelContainer'}); - dojo.place(this.labelContainer, this.domNode); - }, - - setupSearch : function() { - //no dataset? setup stub dataset - if (this.dataset == null) { - this.hasDataset = false; - this.dataset = dojo.mixin({ - _fetchItems : dojo.hitch(this, function(query, resultcallback){ - resultcallback([], query); - }), - - getValue : dojo.hitch(this, function(item, _) { - return null; - }) - }, - dojo.data.util.simpleFetch - ); - } - - this.searchInput = new dijit.form.ComboBox({ - store: this.dataset, //MWE: TODO: works if null? - queryExpr:"${0}", - searchAttr:'name', - searchDelay:0, - tabIndex:0, - hasDownArrow:false, - autoComplete:false, - - onKeyUp : dojo.hitch(this, function(e) { - if (e.keyCode == dojo.keys.DOWN_ARROW && !this.hasDataset && this.widget.selectFirstItem) { //MWE on arrow down, put focus on grid, but only if no labels are used - this.widget.selectFirstItem(); - this.widget.grabFocus(); - } - else if (e.keyCode == dojo.keys.ENTER) { - if (this.searchInput.item!= null) - this.setSearchFilter("", this.searchInput.item); - else { - this.setSearchFilter(this.searchInput.getValue(), null); - } - } - else if (e.keyCode == dojo.keys.TAB) { - if (this.searchInput.item!= null) { //do not tab away if tab is used to select an item - this.setSearchFilter("", this.searchInput.item); - dojo.stopEvent(e); - } - } - else if (e.keyCode == dojo.keys.SPACE) { - var name = dojo.trim(this.searchInput.getValue().toLowerCase()); - for(key in this.existingLabels) //check whether first part is an label, recognize it. - if (name == key) { - this.setSearchFilter("", this.existingLabels[key]); - break; - } - } - else if (this.realtime) { - if (!this._isSearching) - this.setSearchFilter(this.searchInput.getValue(), null); - else - this._searchPending = true; - } - }), - - onChange : dojo.hitch(this, function(e) { - if (this.searchInput.item != null) {//only auto search on blur if label selection was made - this.setSearchFilter("", this.searchInput.item); - } - }) - }); - - this.searchInput.loadDropDown(); - dojo.addClass(this.searchInput.dropDown.domNode, 'gv_search_labeldropdownmenu'); - - var tb = this.searchInput.textbox; - var self = this; - - tb.value = this.searchplaceholder; - dojo.addClass(tb, 'tg_search_placeholder'); - - this.widget.connect(tb, 'onfocus', function() { - if (self.searchplaceholder == tb.value) - tb.value = ''; - dojo.removeClass(tb, 'tg_search_placeholder'); - }); - - this.widget.connect(tb, 'onblur', function() { - if ('' == tb.value && self.searchlabel == null) { - tb.value = self.searchplaceholder; - dojo.addClass(tb, 'tg_search_placeholder'); - } - }); - - dojo.place(this.searchInput.domNode, this.domNode); - - //this.connect(this.searchSubmit, 'onclick', dojo.hitch(this, this.performSearch)); - // this.connect(this.searchReset, 'onclick', dojo.hitch(this, this.resetAndFetchAll)); - this.widget.connect(this.labelContainer, 'onclick', dojo.hitch(this, function(evt) { - if (dojo.hasClass(evt.target, 'gv_label_close')) - this.setSearchFilter(this.searchInput.getValue(), null);//remove the label selection - })); - - - }, - - updateSearchLabel : function(label) { - dojo.empty(this.labelContainer); - if (label != null) { - var labelname = label.getAttribute(this.dataset.relnameattr); - dojo.place(TreeView.widget.Commons.renderLabel(labelname, true), this.labelContainer); - } - }, - - setSearchFilter :function(searchfilter, searchlabel) { - if (this.searchfilter != searchfilter || this.searchlabel != searchlabel) { - this._isSearching = true; - this.searchfilter = searchfilter; - this.searchlabel = searchlabel; - -// this.searchInput.set("value", searchfilter); - this.updateSearchLabel(searchlabel); - - this.widget.curpage = 0; - this.widget.fetchAll(dojo.hitch(this, function() { - this._isSearching = false; - - //There were one ore more searches triggered while we were searching.. - if (this._searchPending) { - this._searchPending = false; - this.setSearchFilter(this.searchInput.getValue(), null);//TODO: how does this relate to this.searchInput.item? - } - })); - } - - }, - - getSearchConstraints : function(searchAttrs) { - //search for term xpath - var xpath = ""; - - if (!searchAttrs.length) - this.widget.configError("No search attributes defined!"); - - if (this.searchfilter) { - var filtervalues = dojo.map(this.searchfilter.split(/\s+/), mxui.html.escapeQuotes); - - //we want every search value to occur at least once! In one of the attributes - xpath += "[(" + dojo.map(filtervalues, function(fv) { - return dojo.map(searchAttrs, function(attr) { - return "contains(" + attr + ",'" + fv + "')"; - }).join(" or "); - }, this).join (") and (") + ")]"; - } - - if (this.searchlabel != null) - xpath += "[" + this.dataset.getAssoc() + " = '" + this.searchlabel.getGUID() + "']"; - - return xpath; - }, - - free : function() { - this.searchInput && this.searchInput.destroy(); - } - -}); - -dojo.declare("TreeView.widget.FilterManager", null, { - widget: null, - domNode : null, - filters : null, - - constructor : function(widget) { - this.widget = widget; - this.filters = []; - - this.menu = new dijit.Menu({ - style: "display: none;" - }); - - this.dropdown = new dijit.form.DropDownButton({ - label : "Filter", - dropDown : this.menu - }); - - dojo.addClass(this.dropdown.dropDown.domNode, 'gv_filter_dropdown_menu'); - - this.domNode = this.dropdown.domNode; - dojo.addClass(this.domNode, 'gv_filter_dropdown'); - }, - - getSearchConstraints : function() { - var cs = dojo.map(this.filters, function(filter) { - return filter.getSearchConstraints().join(" or "); - }).join(" ][ "); - return cs.length > 0 ? "[" + cs + "]" : ""; - }, - - addFilter : function(filter) { - if (this.filters.length > 0) - this.menu.addChild(new dijit.MenuSeparator()); - - this.filters.push(filter); - dojo.forEach(filter.getMenuItems(), function(item) { - this.menu.addChild(item); - }, this); - }, - - free : function() { - dojo.forEach(this.filters, function(filter) { - filter.free(); - }); - - this.dropdown.destroy(); - }, - - applyFilters : function() { - this.widget.curpage = 0; - this.widget.fetchAll(); - } -}); - -dojo.declare("TreeView.widget.Filter", null, { - fm : null, - filterattr : null, - filtertruecaption : null, - filterfalsecaption : null, - filterbooleandefault : null, - - isEnum : false, - trueitem : null, - falseitem : null, - enumStateMap : null, - enumItems : null, - - itemClick : function() { - this.fm.applyFilters(); - }, - - constructor : function(args, fm) { - this.fm = fm; - dojo.mixin(this, args); - - this.isEnum = TreeView.widget.Commons.getAttributeType(fm.widget.entity, this.filterattr) == "Enum"; - - //setup enum menu items - if (this.isEnum) { - this.enumStateMap = {}; - - this.enumItems = dojo.map( - TreeView.widget.Commons.getEnumMap(fm.widget.entity, this.filterattr), - function(enumItem) { - - var mi = new dijit.CheckedMenuItem({ - label : enumItem.caption, - checked : true, - onClick : dojo.hitch(this, this.itemClick) - }); - - this.enumStateMap[enumItem.key] = mi; - return mi; - }, this); - - } - - //setup boolean menu items - else { - if (this.filtertruecaption) - this.trueitem = new dijit.CheckedMenuItem({ - label : this.filtertruecaption, - checked : "all" == this.filterbooleandefault || true == this.filterbooleandefault, - onClick : dojo.hitch(this, this.itemClick) - }); - - if (this.filterfalsecaption) - this.falseitem = new dijit.CheckedMenuItem({ - label : this.filterfalsecaption, - checked : "all" == this.filterbooleandefault || false == this.filterbooleandefault, - onClick : dojo.hitch(this, this.itemClick) - }); - } - - this.fm.addFilter(this); - }, - - getMenuItems : function() { - if (this.isEnum) - return this.enumItems; - - else { - var res = []; - if (this.trueitem) - res.push(this.trueitem); - if (this.falseitem) - res.push(this.falseitem); - return res; - } - }, - - getSearchConstraints : function() { - var res = []; - - //enum? - if (this.isEnum) { - for(var key in this.enumStateMap) - if (this.enumStateMap[key].get("checked") === true) - res.push(this.filterattr + " = '" + key + "'"); - } - - //boolean? - else { - if (this.trueitem && this.trueitem .get("checked") === true) - res.push (this.filterattr + " = true() "); - if (this.falseitem && this.falseitem.get("checked") === true) - res.push (this.filterattr + " = false()"); - - //only one value is defined to filter? Then the other is always true - if (this.falseitem ^ this.trueitem) - res.push(this.filterattr + " = " + (this.falseitem ? "true()" : "false()")); - } - - if (res.length == 0) //filter all out - res.push(this.isEnum ? this.filterattr + " = NULL" : this.filterattr + " = true() and " + this.filterattr + " = false()"); - - return res; - }, - - free : function() { - - } +define([ + "dojo/_base/declare", + "dojo/_base/lang", + "dojo/data/util/simpleFetch", +], function (declare, lang) { + "use strict"; + + var Commons = declare("TreeView.widget.Commons", null, { + //MX5 hack + fixObjProps: function (widget, props) { + logger.debug("TreeView.widget.Commons.fixObjProps"); + + var args = {}; + + for (var i = 0; i < props.length; i++) { + var prop = props[i]; + var arr = widget[prop]; + + for (var j = 0; j < arr.length; j++) { + var obj = arr[j]; + + for (var p in obj) { + (args[p] || (args[p] = [])).push(obj[p]); + } + } + } + + for (var a in args) { + widget[a] = args[a].join(";"); + } + }, + + getEnumMap: function (classname, attrname) { + logger.debug("TreeView.widget.Commons.getEnumMap"); + + var meta = mx.meta.getEntity(classname); + + if (this.getAttributeType(classname, attrname) != "Enum") { + throw "Not an enumeration: " + arguments.join("."); + } + + return meta.getEnumMap(attrname); + }, + + /** + * Renders a label, + * @param {[type]} name [description] + * @param {[type]} close [description] + * @param {[type]} data [datamap object to be attached to the domnode] + * @return {[type]} node [description] + */ + renderLabel: function (name, close, data) { + logger.debug("TreeView.widget.Commons.renderLabel"); + + var n = mxui.dom.create( + "span", + { "class": "gv_label" }, + mxui.dom.create( + "span", + { "class": "gv_label_name" }, + name ? name : "" + ) + ); + + if (close) { + dojo.place(mxui.dom.create( + "span", + { "class": "gv_label_close" }, + "x" + ), n); + } + + mxui.dom.data(n, "data", data); + return n; + }, + + getAttributeType: function (classnameOrObject, attr) { + logger.debug("TreeView.widget.Commons.getAttributeType"); + + var parts = attr.split("/"); + if (parts.length == 3) { + return this.getAttributeType(parts[1], parts[2]); + } + + if (attr.indexOf("/") == -1) { + if (classnameOrObject.getEntity) { + classnameOrObject = classnameOrObject.getEntity(); + } + + var meta = mx.meta.getEntity(classnameOrObject); + return meta.getAttributeType(attr); + } + + return false; + }, + + getObjectAttr: function (object, attr, renderValue) { + logger.debug("TreeView.widget.Commons.getObjectAttr"); + + if (!object || !attr) { + return ""; + } + + if (attr.indexOf("/") == -1) { + if (renderValue) { + return mx.parser && mx.parser.formatAttribute ? mx.parser.formatAttribute(object, attr) : mxui.html.renderValue(object, attr); //mxui.html.rendervalue moved in 5.~7. + } + return object.get(attr); + } + var parts = attr.split("/"); + if (parts.length == 3) { + var child = object.getReference(parts[0]); + + if (!child) { + return ""; + } + + //Fine, we have an object + if (dojo.isObject(child)) { + child = object.getChild(parts[0]); //Get child only works if child was not a guid but object + return this.getObjectAttr(child, parts[2], renderValue); + } + + //Try to retrieve guid in syc + else { + //..but, there is a guid... + var tmp = null; + mx.data.get({ + guid: child, noCache: false, callback: function (obj) { //async = false option would be nice! + tmp = obj; + } + }); + if (tmp != null) {//callback was invoked in sync :) + return this.getObjectAttr(tmp, parts[2], renderValue); + } + + console && console.warn && console.warn("Commons.getObjectAttr failed to retrieve " + attr ); + //This happens if no retrieve schema was used :-(. + return ""; + } + } + + //objects can be returned in X different ways, sometime just a guid, sometimes its an object... + if (parts.length == 2) { + var result = object.getReferences(parts[0]); //incase of of a get object, return the Guids (but sometimes getAttribute gives the object...) + if (!result || result.length == 0) { + return ""; + } + if (result.guid) { + return result.guid; + } + if (/\d+/.test(result)) { + return result; + } + } + throw "GridCommons.getObjectAttr: Failed to retrieve attribute '" + attr + "'"; + }, + + getObjectAttrAsync: function (object, attr, renderValue, cb) { + logger.debug("TreeView.widget.Commons.getObjectAttrAsync"); + + if (!object || !attr) { + return cb(""); + } + + if (attr.indexOf("/") == -1) { + if (renderValue) { + return cb(mx.parser && mx.parser.formatAttribute ? mx.parser.formatAttribute(object, attr) : mxui.html.renderValue(object, attr)); //mxui.html.rendervalue moved in 5.~7. + } + return cb(object.get(attr)); + } + var parts = attr.split("/"); + if (parts.length == 3) { + var child = object.getReference(parts[0]); + + if (!child) { + return cb(""); + } + + //Fine, we have an object + if (dojo.isObject(child)) { + child = object.getChild(parts[0]); //Get child only works if child was not a guid but object + return cb(this.getObjectAttr(child, parts[2], renderValue)); + } + + //Try to retrieve guid in syc + else { + //..but, there is a guid... + mx.data.get({ + guid: child, + noCache: false, + callback: lang.hitch(this, function (obj) { //async = false option would be nice! + if (obj != null) {//callback was invoked in sync :) + return this.getObjectAttrAsync(obj, parts[2], renderValue, cb); + } + + console && console.warn && console.warn("Commons.getObjectAttr failed to retrieve " + attr ); + //This happens if no retrieve schema was used :-(. + return cb(""); + }) + }); + return; + } + } + + //objects can be returned in X different ways, sometime just a guid, sometimes its an object... + if (parts.length == 2) { + var result = object.getReferences(parts[0]); //incase of of a get object, return the Guids (but sometimes getAttribute gives the object...) + if (!result || result.length == 0) { + return cb(""); + } + if (result.guid) { + return cb(result.guid); + } + if (/\d+/.test(result)) { + return cb(result); + } + } + throw "GridCommons.getObjectAttr: Failed to retrieve attribute '" + attr + "'"; + }, + + objectToGuid: function (thing) { + logger.debug("TreeView.widget.Commons.objectToGuid"); + + if (!thing) { + return null; + } else if (thing.guid) { + return thing.guid; + } else if (thing.getGuid) { + return thing.getGuid(); + } else if (/^\d+$/.test(thing)) { + return thing; + } + throw "Does not look like a MxObject: " + thing; + }, + + /** + * Stores data + * @param {[type]} object [object or list of mxobjects to change] + * @param {[type]} attr [attribute or reference to update] + * @param {[type]} value [the new value(s): primitive, guid(s) or mxobject(s)] + * @param {[type]} mode ["add", "rem" or empty / "set"] + * @param {[type]} commit [commit or save?] + * @param {Function} callback [callback function] + * @return {[type]} [description] + */ + store: function (object, attr, value, mode, commit, callback) { + logger.debug("TreeView.widget.Commons.store"); + + var res = false; + + //list of objects + if (dojo.isArray(object)) { + var left = 1; + var cb = function () { + left -= 1; + if (left < 1) { + callback && callback(); + } + }; + + for (var i = 0; i < object.length; i++) { + left += 1; + // TODO: Check scoping, jshint says store does not work. Should it be this.store? + store(object[i], attr, value, mode, commit, cb); + } + + cb(); + } else { //single object + var parts = attr.split("/"); + attr = parts[0]; + if (!object.has(attr)) { + throw "Commons.store: Unable to read or write attribute '" + attr + "'. Does the user have sufficient permission and is the attribute retrieved from the server?"; + } + + //simple attribute + if (parts.length === 1 && attr.indexOf(".") === -1) { + res = object.set(attr, value); + } + + //reference + else if (parts.length === 1 || parts.length === 2) { + var isRefSet = object.isObjectReferenceSet(attr); + + //reference set + if (isRefSet) { + var guids; + if (dojo.isArray(value)) { + guids = dojo.map(value, this.objectToGuid); + } else if (!value) { + guids = []; + } else { + guids = [this.objectToGuid(value)]; + } + + switch (mode) { + case "add": + res = object.addReferences(attr, guids); + break; + case "rem": + res = object.removeReferences(attr, guids); + break; + default: + res = object.set(attr, guids); + } + } else { //single reference + if (dojo.isArray(value)) { + throw "Commons.store: cannot assign array to reference"; + } + + var guid = this.objectToGuid(value); + res = object.set(attr, guid ? guid : ""); //client needs '' as empty + } + } + + else { + throw "Commons.store: unsupported attribute path: " + attr; + } + + //check res + if (res === false) { //set returns undefined if ok, or false on failure + throw "Commons.store: Unable to update attribute: " + attr; + } + + mx.data.commit({ + mxobj: object, + error: this.error, + callback: function () { + callback && callback(); + } + }); + } + }, + + + /** + * MWE, since the liveConnect does not work properly with bubbling en event propagion, we implement our own... + * @param {[type]} widget [description] + * @param {[type]} node [description] + * @param {[type]} event [description] + * @param {[type]} map of className -> function(node, event) -> boolean. (If false, further event are stopped) + */ + liveConnect: function (widget, node, event, map) { + logger.debug("TreeView.widget.Commons.liveConnect"); + + if (!node) { + throw "liveConnect: no node provided"; + } + + widget.connect(node, event, function (e) { + var currNode = e.target; + var matched = {}; //we already matched these, don't bubble. + + while (currNode != node && currNode != null) { + for (var clazz in map) { + if (!(clazz in matched) && dojo.hasClass(currNode, clazz)) { + //avoid a second match on the same selector + matched[clazz] = true; + + //call the callback + var res = map[clazz].call(widget, currNode, e); + + //stop the event! + if (res === false) { + e && e.stopPropagation(); // dojo.stopEvent(e); + return; + } + } + } + currNode = currNode.parentNode; + } + }); + }, + + /** + * Shows a confirm dialog. If the message is empty, the dialog is skipped altogether, and the callback is invoked inmediately + * @param {[type]} message [description] + * @param {Function} callback [description] + */ + confirm: function (message, callback, yescaption, nocaption) { + logger.debug("TreeView.widget.Commons.confirm"); + + if (!message) { + callback && callback(); + return; + } + + mx.ui.confirmation({ + content: message, + proceed: yescaption || "Yes", + cancel: nocaption || "Cancel", + handler: callback + }); + }, + + mf: function (mfname, data, callback, context, mfNeedsList, progressMessage) { + logger.debug("TreeView.widget.Commons.mf"); + + //firing on multiple items? wait for all items to finish + if (dojo.isArray(data) && !mfNeedsList) { + var left = data.length; + var cb = function () { + left -= 1; + if (left < 1 && callback) { + callback.call(context || window); + } + }; + var self = this; + dojo.forEach(data, function (dataitem) { + self.mf(mfname, dataitem, cb, context, false, progressMessage); + }); + } else { + var guids = dojo.map(dojo.isArray(data) ? data : [data], this.objectToGuid); + + if (guids.length > 1 && !mfNeedsList) { + throw "Multiple selection found, but microflow supports only one argument!"; + } + + var mfObject = { + params: { + applyto: "selection", + guids: guids + }, + progressMsg: progressMessage, + progress: progressMessage ? "modal" : undefined, + error: function () { + // TODO: This is useless, error is not defined (this.error?) + // if (error) { + // error(); + // } + }, + callback: function (_, data) { + if (callback) { + callback.call(context || window); + } + }, + async: !!progressMessage + }; + + if (!mx.version || mx.version && parseInt(mx.version.split(".")[0]) < 7) { + // < Mendix 7 + mfObject.store = { + caller: this.mxform + }; + } else { + mfObject.origin = this.mxform; + } + + mx.ui.action(mfname, mfObject, this); + } + }, + + configError: function (widget, msg) { + logger.debug("TreeView.widget.Commons.configError"); + + msg = "Configuration error in " + widget.id + ": " + msg; + if (console) { + console.error(msg); + } + widget.domNode.innerHTML = msg; + throw msg; + }, + + error: function (e) { + logger.debug("TreeView.widget.Commons.error"); + + console.error(e); + throw e; + }, + + /** + * splits the given properties up to objects in target per index. First property indicates targetobjects name + */ + splitPropsTo: function (widget, propnames, target) { + logger.debug("TreeView.widget.Commons.splitPropsTo"); + + var props = propnames.split(","); + var rawdata = {}; + + var nameprop = props[0]; + for (var i = 0; i < props.length; i++) { + rawdata[props[i]] = widget[props[i]] ? widget[props[i]].split(";") : ""; + } + + //create target objects + for (i = 0; i < rawdata[nameprop].length; i++) { + var obj = {}; + var hasdata = false; + for (var key in rawdata) { + var val = obj[key] = rawdata[key][i] || ""; // if undefined make an empty string. + if (/^true$/.test(obj[key])) { + obj[key] = true; + } else if (/^false$/.test(obj[key])) { + obj[key] = false; + } + hasdata = hasdata || (val !== ""); + } + if (hasdata) {//if the object does not contain any data at all, skip it + target.push(obj); + } + } + }, + + /** + * Data from contexts might either be a guid or an object. Normalize to an object and invoke callback + * @param {[type]} data [null, guid or mx object] + * @param {[function(object, guid)]} cb to be invoked when resolved [description] + * @return {[type]} [description] + */ + normalizeContext: function (data, cb) { + logger.debug("TreeView.widget.Commons.normalizeContext"); + + //Nothing + if (data == null) { + cb(null, null); + } else if (typeof (data) != "object" && /^\d+$/.test(data)) { //GUid only + mx.data.get({ + guid: data, + callback: function (mxobj) { + if (mxobj == null) { + cb(null, null); + } else { + cb(mxobj, mxobj.getGuid()); + } + }, + error: this.showError + }, this); + } + //Context is mxobj object + else { + var guid = data.getGuid(); + cb(data, guid); + } + } + }); + + return new Commons(); }); diff --git a/src/TreeView/widget/Commons/Action.js b/src/TreeView/widget/Commons/Action.js new file mode 100644 index 0000000..1161931 --- /dev/null +++ b/src/TreeView/widget/Commons/Action.js @@ -0,0 +1,196 @@ +define([ + "dojo/_base/declare", + "dojo/_base/lang", + "dojo/dom-style", + "TreeView/widget/Commons", + "TreeView/widget/Commons/Dropdown", +], function(declare, lang, domStyle, Commons, DropDown) { + "use strict" + + return declare("TreeView.widget.Commons.Action", null, { + //Not functional + actname : "", + actentity : "", + actshowbutton : "", + actclassname : "", + actbuttoncaption : "", + actbuttonimage : "", + actmf : "", + actmultimf : "", + actisdefault : false, + actonselect : false, + actnoselectionmf : "", + actshortcut : "", + actautohide : "", + actconfirmtext : "", + actdataset : "", + actappliestomultiselection : true, + actprogressmsg : "", + //*Not functional + + tree : null, + + constructor : function(args, tree) { + this.tree = tree; + dojo.mixin(this, args); + + this.tree.connect(this.tree, "onSelect", lang.hitch(this, this.updateToSelection)); + }, + + assignRefToSelection : function(item) { + if (!this.actmf) + this.configError("No selection microflow defined for association assignment button"); + + Commons.store( + //records to objects + dojo.map(this.tree.getSelection(), function(item) { return item.data(); }), + + this.dataset.getAssoc(), item, "add", false, + //callback + lang.hitch(this, function() { + this.invokeOnSelection(); + + this.mxbutton.set("value", null); + }) + ); + + }, + + setup : function(parentNode) { + if (this.actshowbutton) { + if (this.actdataset) { + + this.dataset = this.tree.dataset[this.actdataset]; + if (this.dataset == null){ + this.tree.configError("Unknown dataset for action: '" + this.actdataset + "'"); + } + if (!this.actappliestomultiselection){ + this.tree.configError("Reference assignment should be allowed to be applied to multi selections! (see the action allow to multiselection property)"); + } + + this.mxbutton = new DropDown({ + onChange : lang.hitch(this, this.assignRefToSelection), + sticky : false, + label : this.dataset.rellabel, + dataset : this.dataset, + className : " gv_action_dropdown " + this.actclassname + }, + parentNode, + null + ); + } + else { + this.mxbutton = new mxui.widget._Button({ + caption : this.actbuttoncaption, + iconUrl : this.actbuttonimage, + onClick : lang.hitch(this, this.invokeOnSelection), + type : "button", + cssclass : this.actclassname, + //title : column.help, //TODO:? + isInactive : false + }); + dojo.place(this.mxbutton.domNode, parentNode); + } + } + + if (this.actonselect) { + this.tree.connect(this.tree, "onSelect", lang.hitch(this, this.invokeOnSelection)); + } + }, + + appliesToSelection : function() { + if (this.actnoselectionmf){ + return true; + } + + if ((!this.tree.hasSelection() || !this.actmf) || (this.tree.hasMultiSelection() && !this.actappliestomultiselection)) + return false; + + return this.appliesTo(this.tree.getSelection()); + }, + + //Check if this action is applicable to the mentioned item or item list + appliesTo : function (item) { + if (!this.actentity){ + return true; + } + + var applies = true; + if (dojo.isArray(item)) { + for(var i = 0; i < item.length; i++){ + applies &= this.appliesTo(item[i]); + } + return applies; + } + + return item.isA(this.actentity); + }, + + //show, hide, enable, disable based on the current selection + updateToSelection : function() { + if (this.actshowbutton) { + var enable = this.appliesToSelection(); + + if (!this.mxbutton._destroyed) {//MWE: wtf? + this.mxbutton.set("disabled", !enable); + if (this.actautohide){ + domStyle.set(this.mxbutton.domNode, "display", enable ? "inline-block" : "none"); + } else { + (enable ? dojo.removeClass : dojo.addClass)(this.mxbutton.domNode, "gv_button_disabled"); + } + } + } + }, + + //invoke, but on the current selection / context, that is, the button is triggered from the header + invokeOnSelection : function() { + if (this.appliesToSelection()) { + var selection = this.tree.getSelection(); + + //invoke on the data of the selected node + if (selection && (this.actmf || this.actmultimf)){ + this.invoke(selection); + } + + //invoke on the root object + else if (this.actnoselectionmf) { + Commons.confirm(this.actconfirmtext, lang.hitch(this, function() { + Commons.mf(this.actnoselectionmf, this.tree.getContextObject(), null, this.tree, false, this.actprogressmsg); + })); + } + } + }, + + invoke : function(selection) { + if ((this.actmf || this.actmultimf) && this.appliesTo(selection)) { //double check applies to, see #15349 + + + + Commons.confirm(this.actconfirmtext, lang.hitch(this, function() { + //if a new item is added, suggest it as new selection + delete this._recordSelectionSuggestion; + this.tree._recordSelectionSuggestion = true; + + //See ticket 9116, we need to invoke the single argument microflow version for a single argument. Multi argument mf will break + if (dojo.isArray(selection) && selection.length > 1 && this.actmultimf){ + Commons.mf(this.actmultimf, dojo.map(selection, function(item) { return item.data(); }), null, this.tree, true, this.actprogressmsg); + } else { + var sel = selection == null || selection == [] + ? [] + : dojo.isArray (selection) + ? dojo.map(selection, function(item) { return item.data() }) + : selection.data(); + + Commons.mf(this.actmf, sel, null, this.tree, false, this.actprogressmsg); + } + })); + } + }, + + free : function() { + if (this.mxbutton && !this.mxbutton._destroyed) { + this.mxbutton.destroy(); + } + } + }); +}); diff --git a/src/TreeView/widget/Commons/Checkbox.js b/src/TreeView/widget/Commons/Checkbox.js new file mode 100644 index 0000000..9a4a979 --- /dev/null +++ b/src/TreeView/widget/Commons/Checkbox.js @@ -0,0 +1,57 @@ +define([ + "dojo/_base/declare", + "dojo/_base/lang", + "dojo/dom-attr", +], function(declare, lang, attr) { + "use strict" + + return declare("TreeView.widget.Commons.Checkbox", null, { + onChange: null, + value: null, + className: null, + readOnly: false, + + _clickSubscription: null, + + constructor: function (args, domNode) { + dojo.mixin(this, args); + + this.checkbox = mxui.dom.create("input", { + type: "checkbox" + }); + + attr.set(this.checkbox, "checked", this.value); + attr.set(this.checkbox, "readonly", this.readOnly); + attr.set(this.checkbox, "disabled", this.readOnly); + + if (!this.readOnly){ + this._clickSubscription = dojo.connect(this.checkbox, "onchange", lang.hitch(this, this.change)); + } + + dojo.addClass(this.checkbox, "gv_checkbox " + this.className); + + dojo.place(this.checkbox, domNode); + }, + + change: function (e) { + this.onChange.call(null, this.checkbox.checked); + if (e) { + e.stopPropagation(); + } + }, + + /* dojo get & set proxying */ + set: function () { + return undefined; + }, + + get: function () { + return undefined; + }, + + destroy: function () { + if (this._clickSubscription) + dojo.disconnect(this._clickSubscription); + } + }); +}); diff --git a/src/TreeView/widget/Commons/ColRenderer.js b/src/TreeView/widget/Commons/ColRenderer.js new file mode 100644 index 0000000..a14ded7 --- /dev/null +++ b/src/TreeView/widget/Commons/ColRenderer.js @@ -0,0 +1,353 @@ +define([ + "dojo/_base/declare", + "dojo/_base/lang", + "dojo/html", + "dojo/dom-attr", + "dojo/dom-style", + "TreeView/widget/Commons", + "TreeView/widget/Commons/Checkbox", + "TreeView/widget/Commons/Dropdown" +], function(declare, lang, html, attr, domStyle, Commons, Checkbox, DropDown) { + "use strict"; + + return declare("TreeView.widget.Commons.ColRenderer", null, { + columnname: "", + columnentity: "", + columnrendermode: "", + columnattr: "", + columnimage: "", + columnaction: "", + columnclazz: "", + columnstyle: "", + columndateformat: "", + columntruecaption: "", + columnfalsecaption: "", + columneditdataset: "", + columneditable: false, + columneditautocommit: true, + columnonchangemf: "", + columncondition: "", + columnprefix: "", + columnpostfix: "", + + colindex: -1, + tree: null, + condition: null, + toDestruct: null, + + constructor: function (args, tree, colindex) { + dojo.mixin(this, args); + this.toDestruct = []; + this.columnstyle = this.columnstyle ? this.columnstyle.split(/\||\n/).join(";") : ""; //XXX: modeler does not export ";" separated css attributes correctly. Allow newlines and pipes as separators + + this.tree = tree; + this.colindex = colindex; + + if ((this.columneditable && this.columnattr.indexOf("/") > -1) || (this.columnrendermode === "dataset")) { + this.dataset = this.tree.dataset[this.columneditdataset]; + if (this.dataset === null){ + this.tree.configError("Unknown dataset for editable reference '" + this.columnattr + "': " + this.columneditdataset + "'"); + } + } + + if (this.columncondition) { + this.condition = this.tree.conditions[this.columncondition]; + if (!this.condition) + this.tree.configError("Undefined condition \"" + this.columncondition + "\" for \"" + this.columnattr + "\""); + } + + }, + + appliesTo: function (renderNode) { + return renderNode.isA(this.columnentity); + }, + + setupNode: function (parentNode) { + attr.set(parentNode.parentNode, "style", this.columnstyle); + dojo.addClass(parentNode.parentNode, this.columnclazz); + + mxui.dom.data(parentNode.parentNode, "colindex", this.colindex); + }, + + createDefaultImage: function (parentNode) { + if (this.columnimage) { + dojo.place(mxui.dom.create("img", { + //"class" : "gg_img " + this.columnclazz, + //"style" : this.columnstyle, + "src": this.columnimage + }), parentNode, "first"); + } + }, + + invokeAction: function (record) { + if (this.columnaction) + this.tree.actionsByName[this.columnaction].invoke(record); + }, + + applyChange: function (record, newvalue, remove) { + Commons.store( + record.data(), + this.dataset ? this.dataset.getAssoc() : this.columnattr, + newvalue, + this.dataset && this.dataset.isRefSet() && remove === true ? "rem" : "add", + this.columneditautocommit && !this.columnonchangemf, //MWE: ignore auto commit setting if onchange is used + lang.hitch(this, this._fireOnChange, record) + ); + }, + + _fireOnChange: function (record) { + if (this.columnonchangemf) + Commons.mf(this.columnonchangemf, record.data(), function () { + }, this.tree); + }, + + renderEditable: function (record, domNode, firstTime) { + if (!firstTime) + return; + + var attrtype = Commons.getAttributeType(this.columnentity, this.columnattr); + + //dropdown with reference selector dropdown + if (this.columnattr.indexOf("/") > -1) { + this.toDestruct.push(new DropDown({ + value: Commons.objectToGuid(record.data().get(this.columnattr.split("/")[0])), //can be both guid and nothing + onChange: lang.hitch(this, this.applyChange, record), + sticky: !this.dataset.isRefSet(), + className: "gv_columnedit_dropdownmenu", + dataset: this.dataset, + label: this.dataset.rellabel + }, + domNode, + record + )); + + } else if (attrtype === "Enum" || (attrtype === "Boolean" && (this.columntruecaption || this.columnfalsecaption))) { + var items = []; + + //boolean + if (attrtype === "Boolean"){ + items = [ + {value: true, label: this.columntruecaption || "Yes"}, + {value: false, label: this.columnfalsecaption || "No"} + ]; + + //enum map + } else { + var em = Commons.getEnumMap(this.columnentity, this.columnattr); + for (var i = 0; i < em.length; i++){ + items.push({value: em[i].key, label: em[i].caption}); + } + } + + //setup dropdown + Commons.getObjectAttrAsync(record.data(), this.columnattr, false, lang.hitch(this, function (value) { + this.toDestruct.push(new DropDown({ + options: items, + value: value, + onChange: lang.hitch(this, this.applyChange, record), + sticky: true, + className: "gv_columnedit_dropdownmenu" + }, + domNode, + record + )); + })); + } else if (attrtype === "Boolean") { + Commons.getObjectAttrAsync(record.data(), this.columnattr, false, lang.hitch(this, function (value) { + new Checkbox({ + value: value, + onChange: lang.hitch(this, this.applyChange, record), + className: "gv_columnedit_checkbox" + }, + domNode + ); + })); + } + else { + this.tree.configError("This widget does not currently support edit for property " + this.columnattr + " type: " + attrtype); + } + }, + + render: function (record, domNode, firstTime) { + logger.debug("ColRenderer.render"); + if (this.columnaction !== ""){ + dojo.addClass(domNode, "gg_clickable"); + } + + if (this.condition) { + this.condition.appliesToAsync(record, lang.hitch(this, function (applied) { + if (applied) { + this.renderRecord(record, domNode, firstTime); + } else { + domStyle.set(domNode.parentNode, "display", "none"); + return; //hide + } + })); + } else { + this.renderRecord(record, domNode, firstTime); + } + }, + + renderRecord: function (record, domNode, firstTime) { + logger.debug("ColRenderer.renderRecord"); + domStyle.set(domNode.parentNode, "display", ""); + + switch (this.columnrendermode) { + case "attribute": + if (this.columneditable) { + this.renderEditable(record, domNode, firstTime); + } else { + dojo.empty(domNode); + var attrtype = Commons.getAttributeType(this.columnentity, this.columnattr); + + //Boolean value? + if (attrtype === "Boolean" && !(this.columntruecaption || this.columnfalsecaption)) { + this.createDefaultImage(domNode); + + Commons.getObjectAttrAsync(record.data(), this.columnattr, false, lang.hitch(this, function (value) { + new Checkbox({ //TODO: MWE, when cleaned up? + value: value, + className: "gv_columnview_checkbox", + readOnly: true + }, + domNode + ); + })); + + } else {//Any other value + this._renderAttrAsync(record, lang.hitch(this, function (value) { + if (value === null || value === undefined){ + value = ""; + } + + html.set(domNode, this.columnprefix + mxui.dom.escapeString(value).replace(/\n/g, "
") + this.columnpostfix); + attr.set(domNode, "title", value); + + this.createDefaultImage(domNode); + })); + } + } + + break; + case "caption": + if (firstTime) { + domNode.innerHTML = this.columnprefix + this.columnname + this.columnpostfix; + this.createDefaultImage(domNode); + } + break; + case "attributehtml": + Commons.getObjectAttrAsync(record.data(), this.columnattr, false, lang.hitch(this, function (value) { + domNode.innerHTML = this.columnprefix + value + this.columnpostfix; + this.createDefaultImage(domNode); + })); + break; + case "attributeimage": + dojo.empty(domNode); + + Commons.getObjectAttrAsync(record.data(), this.columnattr, false, lang.hitch(this, function (url) { + if (!url){ + url = this.columnimage; + } + domNode.appendChild(mxui.dom.create("img", { + //"class" : "gg_img " + this.columnclazz, + //"style" : this.columnstyle, + "src": url + })); + })); + + break; + case "image": + if (firstTime === true){ + this.createDefaultImage(domNode); + } + break; + case "thumbnail" : + dojo.empty(domNode); + + Commons.getObjectAttrAsync(record.data(), this.columnattr === "" ? "FileID" : this.columnattr, false, lang.hitch(this, function (fileid) { + Commons.getObjectAttrAsync(record.data(), this.columnattr.replace(/FileID/, "") + "changedDate", false, lang.hitch(this, function (cd) { + domNode.appendChild(mxui.dom.create("img", { + //"class" : "gg_img " + this.columnclazz, + //"style" : this.columnstyle, + "src": "file?thumb=true&target=internal&fileID=" + fileid + "&changedDate=" + cd + })); + })); + })); + + break; + case "systemimage" : + dojo.empty(domNode); + + Commons.getObjectAttrAsync(record.data(), this.columnattr === "" ? "FileID" : this.columnattr, false, lang.hitch(this, function (fileid) { + Commons.getObjectAttrAsync(record.data(), this.columnattr.replace(/FileID/, "") + "changedDate", false, lang.hitch(this, function (cd) { + domNode.appendChild(mxui.dom.create("img", { + //"class" : "gg_img " + this.columnclazz, + //"style" : this.columnstyle, + "src": "file?thumb=true&target=internal&fileID=" + fileid + "&changedDate=" + cd + })); + })); + })); + + break; + case "dataset": + //only subscribe when the record is new + dojo.empty(domNode); + + if (firstTime === true) { + record.addSubscription(dojo.connect(this.dataset, "onReceiveItems", lang.hitch(this, function (items) { + this.render(record, domNode); + }))); + } + + var guids = record.data().getReferences(this.dataset.getAssoc()); + if (this.dataset.hasData) { + dojo.forEach(guids, function (guid) { + var value = this.dataset.getValue(guid); + if (value) { + dojo.place( + Commons.renderLabel( + value, + this.columneditable, + { + owner: record, + guid: guid, + dataset: this.columneditdataset, + colindex: this.colindex + } + ), domNode + ); + } + }, this); + } + break; + default: + this.tree.configError("not implemented columnrendermode: " + this.columnrendermode); + } + }, + + _renderAttrAsync: function (record, cb) { + var object = record.data(); + var attrtype = Commons.getAttributeType(object, this.columnattr); + Commons.getObjectAttrAsync(object, this.columnattr, attrtype !== "DateTime", lang.hitch(this, function (value) { + if (attrtype === "DateTime") { + if (!value || "" === value){ + return cb(""); + } + return cb(dojo.date.locale.format(new Date(value), { + selector: "date", + datePattern: this.columndateformat !== "" ? this.columndateformat : "EEE dd MMM y" + })); + } else if (attrtype === "Boolean" && (this.columntruecaption || this.columnfalsecaption)){ + return cb(value === "Yes" ? this.columntruecaption : this.columnfalsecaption); + } + return cb(value); + })); + }, + + free: function () { + dojo.forEach(this.toDestruct, function (item) { + item.free(); + }); + } + }); +}); diff --git a/src/TreeView/widget/Commons/Condition.js b/src/TreeView/widget/Commons/Condition.js new file mode 100644 index 0000000..117681c --- /dev/null +++ b/src/TreeView/widget/Commons/Condition.js @@ -0,0 +1,58 @@ +define([ + "dojo/_base/declare", + "dojo/_base/lang", + "TreeView/widget/Commons" +], function(declare, lang, Commons) { + "use strict"; + + return declare("TreeView.widget.Commons.Condition", null, { + condname: "", + condattr: "", + condvalues: "", + condclass: "", + + widget: null, + values: null, + + constructor: function (args, widget) { + dojo.mixin(this, args); + this.widget = widget; + + //always compare strings + this.values = dojo.map(("" + this.condvalues).split("\\n"), function (s) { + return dojo.trim(s); + }); + }, + + getClass: function () { + return this.condclass; + }, + + appliesTo: function (record) { + logger.debug("Condition.appliesTo"); + var value = Commons.getObjectAttr(record.data(), this.condattr); + if (value === null || value === undefined || /^\s*$/.test("" + value)) { + if (dojo.indexOf(this.values, "false") != -1){ //This one was suppossed to match on falsy values + return true; + } else { + return false; + } + } + return -1 != dojo.indexOf(this.values, "" + value); + }, + + appliesToAsync: function (record, cb) { + logger.debug("Condition.appliesToAsync"); + Commons.getObjectAttrAsync(record.data(), this.condattr, false, lang.hitch(this, function(value) { + if (value === null || value === undefined || /^\s*$/.test("" + value)) { + if (dojo.indexOf(this.values, "false") != -1){ //This one was suppossed to match on falsy values + return cb(true); + } else { + return cb(false); + } + } + cb(-1 != dojo.indexOf(this.values, "" + value)); + })); + } + }); +}); diff --git a/src/TreeView/widget/Commons/Dropdown.js b/src/TreeView/widget/Commons/Dropdown.js new file mode 100644 index 0000000..ec34b3a --- /dev/null +++ b/src/TreeView/widget/Commons/Dropdown.js @@ -0,0 +1,128 @@ +define([ + "dojo/_base/declare", + "dojo/_base/lang", + "dijit/Menu", + "dijit/MenuItem", + "dijit/MenuSeparator", + "dijit/form/DropDownButton" +], function(declare, lang, Menu, MenuItem, MenuSeparator, DropDownButton) { + "use strict" + + return declare("TreeView.widget.Commons.DropDown", null, { + onChange: null, + label: null, + options: null, + value: null, + dataset: null, + className: null, + sticky: true, + + _datasetsub: null, + + constructor: function (args, domNode, owner) { + dojo.mixin(this, args); + this.options = this.options || []; + + if (this.dataset) { + this._datasetsub = dojo.connect(this.dataset, "onReceiveItems", lang.hitch(this, this.receiveDatasetItems)); + if (owner){ + owner.addSubscription(this._datasetsub); + } + + } + + this.menu = new Menu({ + style: "display: none;" + }); + + this.dropdown = new DropDownButton({ + label: this.label, + dropDown: this.menu, + onClick: function (e) { + dojo.stopEvent(e); + } + }); + this.domNode = this.dropdown.domNode; + + dojo.addClass(this.dropdown.dropDown.domNode, "gv_dropdown_menu " + this.className); + dojo.addClass(this.dropdown.domNode, "gv_dropdown " + this.className); + + dojo.place(this.dropdown.domNode, domNode); + + this.addOptions(this.options); + + if (this.dataset) + this.addOptions(this.dataset.getOptions()); + }, + + receiveDatasetItems: function (items) { + this.clearItems(); + this.addOptions(this.options); + this.addOptions(items); + }, + + addOptions: function (items) { + dojo.forEach(items, function (item) { + this.menu.addChild(this.createOption(item)); + }, this); + }, + + clearItems: function () { + dojo.forEach(this.menu.getChildren(), function (child) { + this.menu.removeChild(child); + }, this); + }, + + createOption: function (item) { + //separator + if (item == null){ + return new MenuSeparator(); + } + + if (this.sticky && this.value !== null && this.value == item.value){ //redraw selection if needed + this.dropdown.set("label", item.label); + } + + return new MenuItem({ + label: mxui.dom.escapeString(item.label), + value: item.value, + onClick: item.onClick + ? lang.hitch(item, item.onClick, lang.hitch(this, this.itemClick)) //pass itemClick as callback to the onClick, so it can be invoked + : lang.hitch(this, this.itemClick, item) + }); + }, + + itemClick: function (item, e) { + this.onChange.call(null, item.value); + if (this.sticky) { + this.dropdown.set("label", item.label); + this.value = item.value; + } + + if (e) { + dojo.stopEvent(e); + } + }, + + /* dojo get & set proxying */ + set: function () { + return !this.dropdown._destroyed ? this.dropdown.set.apply(this.dropdown, arguments) : undefined; + }, + + get: function () { + return !this.dropdown._destroyed ? this.dropdown.get.apply(this.dropdown, arguments) : undefined; + }, + + destroy: function () { + if (this._datasetsub){ + dojo.disconnect(this._datasetsub); + } + this.dropdown.destroy(); + this._destroyed = true; + }, + + free: function () { + this.destroy(); //Free is used by commons, destroy by Mendix widgets + } + }); +}); diff --git a/src/TreeView/widget/Commons/RelatedDataset.js b/src/TreeView/widget/Commons/RelatedDataset.js new file mode 100644 index 0000000..4164af6 --- /dev/null +++ b/src/TreeView/widget/Commons/RelatedDataset.js @@ -0,0 +1,187 @@ +define([ + "dojo/_base/declare", + "dojo/_base/lang", + "TreeView/widget/Commons" +], function (declare, lang, Commons) { + "use strict" + + return declare("TreeView.widget.Commons.RelatedDataset", null, { + relname: "", + rellabel: "", + relentity: "", + relcontextassoc: "", + relitemassocref: "", + relitemassocrefset: "", + relnameattr: "", + relconstraint: "", + relnewitemcaption: "", + + widget: null, + contextGuid: null, + hasData: false, + + existingLabels: null, + existingLabelsById: null, + existingOptions: null, + + _fetchingLabels: false, + + constructor: function (args, widget, cb) { + this.widget = widget; + dojo.mixin(this, dojo.data.util.simpleFetch); + dojo.mixin(this, args); + + this.existingOptions = []; + + this.widget.subscribe({ //fetch the new labels if changed + entity: this.relentity, + callback: lang.hitch(this, this.fetchLabels) + }); + + this.widget.connect(this.widget, "update", lang.hitch(this, function (data, cb) { + this.contextGuid = data && data.getGuid ? data.getGuid() : data; + this.fetchLabels(); + cb && cb(); + })); + }, + + getAssoc: function () { + return (this.relitemassocref && this.relitemassocref != "") ? this.relitemassocref.split("/")[0] : this.relitemassocrefset.split("/")[0]; + }, + + isRefSet: function () { + return (this.relitemassocrefset && this.relitemassocrefset != ""); + }, + + getValue: function (item, _) { + if (typeof (item) == "object") { + return item.get(this.relnameattr); + } else if (/^\d+$/.test(item)) { + var obj = this.existingLabelsById[item]; //assuming guid + return obj ? this.getValue(obj) : null; //TODO: warn? + } else { + this.widget.showError("Dataset getValue not valid for: " + item); + } + return ""; + }, + + getOptions: function () { + return this.existingOptions; + }, + + fetchLabels: function () { + if (this.contextGuid == null || this._fetchingLabels) { + return; + } + this.hasData = false; + this._fetchingLabels = true; + var xpath = "//" + this.relentity + (this.relconstraint ? this.relconstraint : "") + (this.relcontextassoc ? "[" + this.relcontextassoc.split("/")[0] + " = '[%CurrentObject%]']" : ""); + xpath = xpath.replace(/\[\%CurrentObject\%\]/gi, this.contextGuid); + mx.data.get({ + xpath: xpath, + callback: lang.hitch(this, this.retrieveLabels), + filter: { + sort: [[this.relnameattr, "asc"]], + attributes: [this.relnameattr] + } + }, this); + }, + + retrieveLabels: function (objects) { + this.rawObjects = objects; + this.existingLabels = {}; + this.existingLabelsById = {}; + + this.existingOptions = dojo.map(objects, function (obj) { + var value = obj.getGuid(); + var label = obj.get(this.relnameattr); + + this.existingLabels[label.toLowerCase()] = obj; + this.existingLabelsById[value] = obj; + + return { value: value, label: label }; + }, this); + + this._fetchingLabels = false; + + if (this.relnewitemcaption) { + this.existingOptions.splice(0, 0, { + value: "new", + label: this.relnewitemcaption, + onClick: lang.hitch(this, this.createNewItem) + }, null) //Null = separator + } + this.hasData = true; + this.onReceiveItems(this.existingOptions); + }, + + onReceiveItems: function (items) { + //connect stub + }, + + /** + * Given a array of guids, returns a list of captions + * @param {[type]} items [description] + * @return {[type]} [description] + */ + getCaptions: function (items) { + return dojo.map(items, this.getValue, this); + }, + + createNewItem: function (callback) { + var labelname = prompt("Please enter " + this.relnewitemcaption, ""); + if (labelname) { + mx.data.create({ + entity: this.relentity, + error: this.widget.showError, + callback: lang.hitch(this, function (label) { + var cb = callback + ? lang.hitch(this, callback, { + value: label.getGuid(), + label: labelname + }) + : null; + + if (this.relcontextassoc) + Commons.store(label, this.relcontextassoc, this.contextGuid); + + Commons.store(label, this.relnameattr, dojo.trim(labelname), null, true, cb); + }) + }, this); + } + }, + + /** dojo store compatibility, useful for future display in selects, filtering selects etc. etc.*/ + + /* Identity api */ + getIdentity: function (item) { + return item.getGuid(); + }, + + getIdentityAttributes: function () { + return null; + }, + + fetchItemByIdentity: function (args) { + //TODO: check and error handling + if (!this.existingLabelsById) { + args.onItem.call(args.scope, null); + } else { + args.onItem.call(args.scope, this.existingLabelsById[args.identity]); + } + }, + + /* Simplefetch api */ + _fetchItems: function (query, resultcallback) { + var results = []; + if (this.existingLabels != null) { + for (var key in this.existingLabels) { + if (key.indexOf(query.query.name.toLowerCase()) === 0) { + results.push(this.existingLabels[key]); + } + } + } + resultcallback(results, query); + } + }); +}); diff --git a/src/TreeView/widget/Commons/SearchControl.js b/src/TreeView/widget/Commons/SearchControl.js new file mode 100644 index 0000000..478abee --- /dev/null +++ b/src/TreeView/widget/Commons/SearchControl.js @@ -0,0 +1,213 @@ +define([ + "dojo/_base/declare", + "dojo/_base/lang", + "dijit/form/ComboBox", + "TreeView/widget/Commons" +], function(declare, lang, ComboBox, Commons) { + "use strict" + + return declare("TreeView.widget.Commons.SearchControl", null, { + dataset: null, + + domNode: null, + widget: null, + + searchfilter: "", + searchlabel: null, + realtime: false, + hasDataset: true, + + _isSearching: false, + _searchPending: false, + + constructor: function (args, widget) { + dojo.mixin(this, args); + + this.widget = widget; + + this._setupLayout(); + this._setupSearch(); + + }, + + _setupLayout: function () { + this.domNode = mxui.dom.create("div", { + "class": "gv_searchBar" + }); + + this.labelContainer = mxui.dom.create("div", {"class": "gv_searchLabelContainer"}); + dojo.place(this.labelContainer, this.domNode); + }, + + _setupSearch: function () { + //no dataset? setup stub dataset + if (this.dataset == null) { + this.hasDataset = false; + this.dataset = dojo.mixin({ + _fetchItems: lang.hitch(this, function (query, resultcallback) { + resultcallback([], query); + }), + + getValue: lang.hitch(this, function (item, _) { + return null; + }) + }, + dojo.data.util.simpleFetch + ); + } + + this.searchInput = new ComboBox({ + store: this.dataset, //MWE: TODO: works if null? + queryExpr: "${0}", + searchAttr: "name", + searchDelay: 0, + tabIndex: 0, + hasDownArrow: false, + autoComplete: false, + + onKeyUp: lang.hitch(this, function (e) { + if (e.keyCode == dojo.keys.DOWN_ARROW && !this.hasDataset && this.widget.selectFirstItem) { //MWE on arrow down, put focus on grid, but only if no labels are used + this.widget.selectFirstItem(); + this.widget.grabFocus(); + } else if (e.keyCode == dojo.keys.ENTER) { + if (this.searchInput.item != null) + this.setSearchFilter("", this.searchInput.item); + else { + this.setSearchFilter(this.searchInput.get("value"), null); + } + } else if (e.keyCode == dojo.keys.TAB) { + if (this.searchInput.item != null) { //do not tab away if tab is used to select an item + this.setSearchFilter("", this.searchInput.item); + dojo.stopEvent(e); + } + } else if (e.keyCode == dojo.keys.SPACE) { + var name = dojo.trim(this.searchInput.get("value").toLowerCase()); + for (key in this.existingLabels) //check whether first part is an label, recognize it. + if (name == key) { + this.setSearchFilter("", this.existingLabels[key]); + break; + } + } else if (this.realtime) { + if (!this._isSearching) { + this.setSearchFilter(this.searchInput.get("value"), null); + } else { + this._searchPending = true; + } + } + }), + + onChange: lang.hitch(this, function (e) { + if (this.searchInput.item != null) {//only auto search on blur if label selection was made + this.setSearchFilter("", this.searchInput.item); + } + }), + + resize: function () { + + } + }); + + this.searchInput.loadDropDown(); + dojo.addClass(this.searchInput.dropDown.domNode, "gv_search_labeldropdownmenu"); + + var tb = this.searchInput.textbox; + var self = this; + + tb.value = this.searchplaceholder; + dojo.addClass(tb, "tg_search_placeholder"); + + this.widget.connect(tb, "onfocus", function () { + if (self.searchplaceholder == tb.value){ + tb.value = ""; + } + dojo.removeClass(tb, "tg_search_placeholder"); + }); + + this.widget.connect(tb, "onblur", function () { + if ("" == tb.value && self.searchlabel == null) { + tb.value = self.searchplaceholder; + dojo.addClass(tb, "tg_search_placeholder"); + } + }); + + dojo.place(this.searchInput.domNode, this.domNode); + + //this.connect(this.searchSubmit, "onclick", lang.hitch(this, this.performSearch)); + // this.connect(this.searchReset, "onclick", lang.hitch(this, this.resetAndFetchAll)); + this.widget.connect(this.labelContainer, "onclick", lang.hitch(this, function (evt) { + if (dojo.hasClass(evt.target, "gv_label_close")){ + this.setSearchFilter(this.searchInput.get("value"), null);//remove the label selection + } + })); + + + }, + + updateSearchLabel: function (label) { + dojo.empty(this.labelContainer); + if (label != null) { + var labelname = label.get(this.dataset.relnameattr); + dojo.place(Commons.renderLabel(labelname, true), this.labelContainer); + } + }, + + setSearchFilter: function (searchfilter, searchlabel) { + if (this.searchfilter != searchfilter || this.searchlabel != searchlabel) { + this._isSearching = true; + this.searchfilter = searchfilter; + this.searchlabel = searchlabel; + + // this.searchInput.set("value", searchfilter); + this.updateSearchLabel(searchlabel); + + this.widget.curpage = 0; + this.widget.fetchAll(lang.hitch(this, function () { + this._isSearching = false; + + //There were one ore more searches triggered while we were searching.. + if (this._searchPending) { + this._searchPending = false; + this.setSearchFilter(this.searchInput.get("value"), null);//TODO: how does this relate to this.searchInput.item? + } + })); + } + + }, + + getSearchConstraints: function (searchAttrs, limit) { + //search for term xpath + var xpath = ""; + + if (!searchAttrs.length){ + this.widget.configError("No search attributes defined!"); + } + + if (this.searchfilter) { + var filtervalues = dojo.map(this.searchfilter.split(/\s+/), mxui.html.escapeQuotes); + + if (typeof limit !== "undefined" && filtervalues.length > limit) { + filtervalues.splice(limit, filtervalues.length - limit); + } + + //we want every search value to occur at least once! In one of the attributes + xpath += "[(" + dojo.map(filtervalues, function (fv) { + return dojo.map(searchAttrs, function (attr) { + return "contains(" + attr + ",'" + fv + "')"; + }).join(" or "); + }, this).join(") and (") + ")]"; + } + + if (this.searchlabel != null){ + xpath += "[" + this.dataset.getAssoc() + " = '" + this.searchlabel.getGuid() + "']"; + } + + return xpath; + }, + + free: function () { + if (this.searchInput) { + this.searchInput.destroy(); + } + } + }); +}); diff --git a/src/TreeView/widget/Filters/AbstractFilter.js b/src/TreeView/widget/Filters/AbstractFilter.js new file mode 100644 index 0000000..7fadef4 --- /dev/null +++ b/src/TreeView/widget/Filters/AbstractFilter.js @@ -0,0 +1,37 @@ +define([ + "dojo/_base/declare", + "dojo/_base/lang" +], function(declare, lang) { + "use strict"; + + return declare("TreeView.widget.Filters.AbstractFilter", null, { + filterManager: null, + + filterattr: null, + filtertruecaption: null, + filterfalsecaption: null, + filterbooleandefault: null, + + isEnum: false, + trueitem: null, + falseitem: null, + enumStateMap: null, + enumItems: null, + + itemClick: function () { + this.filterManager.applyFilters(); + }, + + constructor: function (args, filterManager) { + this.filterManager = filterManager; + dojo.mixin(this, args); + + }, + + getSearchConstraints: function () { + console.error("ERROR: You must override getSearchConstraints()"); + }, + + free: function () {} + }); +}); diff --git a/src/TreeView/widget/Filters/BoolFilter.js b/src/TreeView/widget/Filters/BoolFilter.js new file mode 100644 index 0000000..8024f2b --- /dev/null +++ b/src/TreeView/widget/Filters/BoolFilter.js @@ -0,0 +1,58 @@ +define([ + "dojo/_base/declare", + "dojo/_base/lang", + "dijit/CheckedMenuItem", + "TreeView/widget/Commons", + "TreeView/widget/Filters/ExclusiveFilter" +], function(declare, lang, CheckedMenuItem, Commons, ExclusiveFilter) { + "use strict"; + + return declare("TreeView.widget.Filters.BoolFilter", ExclusiveFilter, { + + constructor: function (args, fm) { + this.anyitem = new CheckedMenuItem({ + label: this.anylabel, + checked: true, + onChange: lang.hitch(this, this.itemClick, this.anylabel) + }); + this.menuItems.push(this.anyitem); + + if (this.filtertruecaption){ + this.trueitem = new CheckedMenuItem({ + label: this.filtertruecaption, + checked: false, + onChange: lang.hitch(this, this.itemClick, this.filtertruecaption) + }); + this.menuItems.push(this.trueitem); + } + if (this.filterfalsecaption){ + this.falseitem = new CheckedMenuItem({ + label: this.filterfalsecaption, + checked: false, + onChange: lang.hitch(this, this.itemClick, this.filterfalsecaption) + }); + this.menuItems.push(this.falseitem); + } + + dojo.forEach(this.menuItems, function(item) { + this.menu.addChild(item); + }, this); + }, + + getSearchConstraints: function () { + var res = []; + + if (this.trueitem && this.trueitem.get("checked") === true){ + res.push(this.filterattr + " = true() "); + } + if (this.falseitem && this.falseitem.get("checked") === true){ + res.push(this.filterattr + " = false()"); + } + + if (res.length == 0) {//filter all out + res.push(this.filterattr + " = true() or " + this.filterattr + " = false()"); + } + return res; + } + }); +}); diff --git a/src/TreeView/widget/Filters/EnumFilter.js b/src/TreeView/widget/Filters/EnumFilter.js new file mode 100644 index 0000000..742f11c --- /dev/null +++ b/src/TreeView/widget/Filters/EnumFilter.js @@ -0,0 +1,55 @@ +define([ + "dojo/_base/declare", + "dojo/_base/lang", + "dijit/CheckedMenuItem", + "TreeView/widget/Commons", + "TreeView/widget/Filters/ExclusiveFilter" +], function(declare, lang, CheckedMenuItem, Commons, ExclusiveFilter) { + "use strict"; + + return declare("TreeView.widget.Filters.EnumFilter", ExclusiveFilter, { + + constructor: function (args, filterManager) { + + this.enumStateMap = {}; + + this.anyitem = new CheckedMenuItem({ + label: this.anylabel, + checked: true, + onClick: lang.hitch(this, this.itemClick, this.anylabel) + }); + this.menuItems.push(this.anyitem); + + this.menuItems = this.menuItems.concat( dojo.map( + Commons.getEnumMap(filterManager.widget.entity, this.filterattr), + function (enumItem) { + + var mi = new CheckedMenuItem({ + label: enumItem.caption, + checked: false, + onClick: lang.hitch(this, this.itemClick, enumItem.caption) + }); + + this.enumStateMap[enumItem.key] = mi; + return mi; + }, this)); + + dojo.forEach(this.menuItems, function(item) { + this.menu.addChild(item); + }, this); + + }, + + getSearchConstraints: function () { + var res = []; + + for (var key in this.enumStateMap){ + if (this.enumStateMap[key].get("checked") === true){ + res.push(this.filterattr + " = '" + key + "'"); + } + } + + return res; + } + }); +}); diff --git a/src/TreeView/widget/Filters/ExclusiveFilter.js b/src/TreeView/widget/Filters/ExclusiveFilter.js new file mode 100644 index 0000000..89bb3ab --- /dev/null +++ b/src/TreeView/widget/Filters/ExclusiveFilter.js @@ -0,0 +1,54 @@ +define([ + "dojo/_base/declare", + "dojo/_base/lang", + "dijit/form/DropDownButton", + "dijit/Menu", + "dijit/CheckedMenuItem", + "TreeView/widget/Commons", + "TreeView/widget/Filters/AbstractFilter" +], function(declare, lang, DropDownButton, Menu, CheckedMenuItem, Commons, AbstractFilter) { + "use strict"; + + return declare("TreeView.widget.Filters.ExclusiveFilter", AbstractFilter, { + + itemClick: function (label, e) { + this.dropdown.set("label", label); + + for (var i = 0; i < this.menuItems.length; i++) { + var item = this.menuItems[i]; + + item.set("checked", (item.label == label)); + } + + this.filterManager.applyFilters(); + }, + + constructor: function (args, filterManager) { + this.filterManager = filterManager; + dojo.mixin(this, args); + + this.menu = new Menu({ + style: "display: none;" + }); + this.menuItems = []; + + this.dropdown = new DropDownButton({ + label : this.filterattr, + dropDown : this.menu + }); + + if (this.filteranycaption) { + this.anylabel = this.filteranycaption; + } else { + this.anylabel = this.filterattr + ": -"; + } + + this.dropdown.set("label", this.anylabel); + + dojo.addClass(this.dropdown.dropDown.domNode, "gv_filter_dropdown_menu"); + dojo.addClass(this.dropdown.domNode, "gv_filter_dropdown"); + + dojo.place(this.dropdown.domNode, this.filterManager.domNode); + } + }); +}); diff --git a/src/TreeView/widget/Filters/Filter.js b/src/TreeView/widget/Filters/Filter.js new file mode 100644 index 0000000..cdd73b8 --- /dev/null +++ b/src/TreeView/widget/Filters/Filter.js @@ -0,0 +1,101 @@ +define([ + "dojo/_base/declare", + "dojo/_base/lang", + "dijit/CheckedMenuItem", + "TreeView/widget/Commons", + "TreeView/widget/Filters/AbstractFilter" +], function(declare, lang, CheckedMenuItem, Commons, AbstractFilter) { + "use strict"; + + return declare("TreeView.widget.Filters.Filter", AbstractFilter, { + + constructor: function (args, filterManager) { + + this.isEnum = Commons.getAttributeType(filterManager.widget.entity, this.filterattr) == "Enum"; + + //setup enum menu items + if (this.isEnum) { + this.enumStateMap = {}; + + this.enumItems = dojo.map( + Commons.getEnumMap(filterManager.widget.entity, this.filterattr), + function (enumItem) { + + var mi = new CheckedMenuItem({ + label: enumItem.caption, + checked: true, + onClick: lang.hitch(this, this.itemClick) + }); + + this.enumStateMap[enumItem.key] = mi; + return mi; + }, this); + + } + + //setup boolean menu items + else { + if (this.filtertruecaption){ + this.trueitem = new CheckedMenuItem({ + label: this.filtertruecaption, + checked: "all" == this.filterbooleandefault || true == this.filterbooleandefault, + onClick: lang.hitch(this, this.itemClick) + }); + } + if (this.filterfalsecaption){ + this.falseitem = new CheckedMenuItem({ + label: this.filterfalsecaption, + checked: "all" == this.filterbooleandefault || false == this.filterbooleandefault, + onClick: lang.hitch(this, this.itemClick) + }); + } + } + + }, + + getMenuItems: function () { + if (this.isEnum){ + return this.enumItems; + } + + var res = []; + if (this.trueitem){ + res.push(this.trueitem); + } + if (this.falseitem){ + res.push(this.falseitem); + } + return res; + }, + + getSearchConstraints: function () { + var res = []; + + //enum? + if (this.isEnum) { + for (var key in this.enumStateMap){ + if (this.enumStateMap[key].get("checked") === true){ + res.push(this.filterattr + " = '" + key + "'"); + } + } + } else { // Boolean? + if (this.trueitem && this.trueitem.get("checked") === true){ + res.push(this.filterattr + " = true() "); + } + if (this.falseitem && this.falseitem.get("checked") === true){ + res.push(this.filterattr + " = false()"); + } + + //only one value is defined to filter? Then the other is always true + if (this.falseitem ^ this.trueitem){ + res.push(this.filterattr + " = " + (this.falseitem ? "true()" : "false()")); + } + } + + if (res.length == 0) {//filter all out + res.push(this.isEnum ? this.filterattr + " = NULL" : this.filterattr + " = true() and " + this.filterattr + " = false()"); + } + return res; + } + }); +}); diff --git a/src/TreeView/widget/Filters/FilterManager.js b/src/TreeView/widget/Filters/FilterManager.js new file mode 100644 index 0000000..52b45dc --- /dev/null +++ b/src/TreeView/widget/Filters/FilterManager.js @@ -0,0 +1,59 @@ +define([ + "dojo/_base/declare", + "TreeView/widget/Commons", + "TreeView/widget/Filters/MultiFilter", + "TreeView/widget/Filters/BoolFilter", + "TreeView/widget/Filters/EnumFilter" +], function(declare, Commons, MultiFilter, BoolFilter, EnumFilter) { + "use strict"; + + return declare("TreeView.widget.Filters.FilterManager", null, { + widget: null, + domNode : null, + + constructor : function(widget, data) { + this.widget = widget; + + if (!this.widget.filterexclusive) { + this.filter = new MultiFilter(this, data); + this.filters = this.filter.filters; + } else { + this.domNode = mxui.dom.create( + "div", { "class": "gv_filter_container" } + ); + + this.filters = dojo.map(data, function (d) { + if (Commons.getAttributeType(this.widget.entity, d.filterattr) == "Enum") { + return new EnumFilter(d, this); + } else { + return new BoolFilter(d, this); + } + }, this); + + } + }, + + getSearchConstraints : function() { + // var filters = (!this.filtersexclusive) ? this.filter.getFilters() : []; + + + var combined = []; + for (var i = 0; i < this.filters.length; i++) { + var constraints = this.filters[i].getSearchConstraints(); + if (constraints.length > 0) + combined = combined.concat( constraints.join(" or ") ); + } + var cs = combined.join(" ][ "); + return cs.length > 0 ? "[" + cs + "]" : ""; + }, + + free : function() { + this.filter.free(); + }, + + applyFilters : function() { + this.widget.curpage = 0; + this.widget.fetchAll(); + } + }); +}); diff --git a/src/TreeView/widget/Filters/MultiFilter.js b/src/TreeView/widget/Filters/MultiFilter.js new file mode 100644 index 0000000..c462f28 --- /dev/null +++ b/src/TreeView/widget/Filters/MultiFilter.js @@ -0,0 +1,61 @@ +define([ + "dojo/_base/declare", + "dojo/_base/lang", + "dijit/form/DropDownButton", + "dijit/Menu", + "dijit/MenuSeparator", + "TreeView/widget/Filters/Filter" +], function(declare, lang, DropDownButton, Menu, MenuSeparator, Filter) { + "use strict"; + + return declare("TreeView.widget.Filters.MultiFilter", null, { + filters : null, + + constructor: function (filterManager, data) { + this.filterManager = filterManager; + + this.filters = dojo.map(data, function (d) { + return new Filter(d, filterManager); + }, this); + + this.menu = new Menu({ + style: "display: none;" + }); + + this.dropdown = new DropDownButton({ + label : "Filter", + dropDown : this.menu + }); + + dojo.addClass(this.dropdown.dropDown.domNode, "gv_filter_dropdown_menu"); + + this.filterManager.domNode = this.dropdown.domNode; + dojo.addClass(this.filterManager.domNode, "gv_filter_dropdown"); + + for (var i = 0; i < this.filters.length; i++) { + var filter = this.filters[i]; + + if (i > 0) { + this.menu.addChild(new MenuSeparator()); + } + + dojo.forEach(filter.getMenuItems(), function(item) { + this.menu.addChild(item); + }, this); + } + + }, + + getFilters: function () { + return this.filters; + }, + + free: function () { + dojo.forEach(this.filters, function(filter) { + filter.free(); + }); + + this.dropdown.destroy(); + } + }); +}); diff --git a/src/TreeView/widget/GridView.js b/src/TreeView/widget/GridView.js index df3f107..e45cd71 100644 --- a/src/TreeView/widget/GridView.js +++ b/src/TreeView/widget/GridView.js @@ -1,1347 +1,1305 @@ -dojo.provide("TreeView.widget.GridView"); -dojo.require("TreeView.widget.Commons"); -dojo.require("dojo.NodeList-traverse"); +require([ + "dojo/_base/declare", + "mxui/widget/_WidgetBase", + "dojo/_base/lang", + "dojo/dom-attr", + "TreeView/widget/Commons", + "TreeView/widget/GridView/ColHead", + "TreeView/widget/GridView/Record", + "TreeView/widget/Commons/Action", + "TreeView/widget/Commons/ColRenderer", + "TreeView/widget/Commons/Condition", + "TreeView/widget/Filters/Filter", + "TreeView/widget/Filters/FilterManager", + "TreeView/widget/Commons/RelatedDataset", + "TreeView/widget/Commons/SearchControl", + "dojo/NodeList-traverse" +], function (declare, _WidgetBase, lang, attr, Commons, ColHead, Record, Action, ColRenderer, Condition, Filter, FilterManager, RelatedDataset, SearchControl) { + "use strict"; + + return declare("TreeView.widget.GridView", _WidgetBase, { + _multiSelection: null, + _inMultiSelectMode: false, //is true as long only checkboxes are clicked + _hoveredRow: null, + _started: false, + _suspended: false, + _iscallingdatasource: false, + contextGUID: null, + + currentSortColumn: -1, + sortInverted: false, + count: 0, + curpage: 0, + firstUpdate: true, + + //data model properties + tabindex: -1, + entity: "", + datasourcemf: "", + datasourceoffsetattr: "", + datasourcelimitattr: "", + datasourcecountattr: "", + datasourcesearchattr: "", + constraint: "", + + //display properties + colheadname: "", + colheadcaption: "", + colheadwidth: "", + colheadsortattr: "", + colheadsortdir: "", + + //rendering properties + columnindex: "", + columnname: "", + columndataset: "", + columnrendermode: "", + columnattr: "", + columnimage: "", + columnaction: "", + columnclazz: "", + columnstyle: "", + columndateformat: "", + columnissearchattr: "", + columntruecaption: "", + columnfalsecaption: "", + columneditable: "", + columneditdataset: "", + columneditautocommit: "", + columnonchangemf: "", + columncondition: "", + columnprefix: "", + columnpostfix: "", + + //action properties + actname: "", + actshowbutton: "", + actclassname: "", + actautohide: "", + actbuttoncaption: "", + actbuttonimage: "", + actmf: "", + actmultimf: "", + actisdefault: "", + actonselect: "", + actconfirmtext: "", + actnoselectionmf: "", + actdataset: "", + actappliestomultiselection: "", + actprogressmsg: "", + + //filters + filterattr: "", + filtertruecaption: "", + filterfalsecaption: "", + filterbooleandefault: "", + filteranycaption: "", + filterexclusive: false, + + //advanced settings + allowmultiselect: false, + allowsingleselect: true, + selectfirstrow: false, + defaultsortcolumn: 0, + pagesize: 20, + refreshoncontext: false, + refreshonclass: true, + searchenabled: true, + searchplaceholder: "", + searchlabeldataset: "", + realtimesearch: false, + searchmaxquerysizeenabled: false, + searchmaxquerysize: 10, + emptymessage: "", + selectionref: "", + selectionrefset: "", + colheaderenabled: true, + enableschema: true, + showtotals: true, + itemcountmessage: "", + showasdiv: false, + listenchannel: "", + singleclickdefaultaction: false, + + //related datasets + relname: "", + rellabel: "", + relentity: "", + relcontextassoc: "", + relitemassocref: "", + relitemassocrefset: "", + relnameattr: "", + relnewitemcaption: "", + relconstraint: "", + + //conditions + condname: "", + condattr: "", + condvalues: "", + condclass: "", + + /* context applied */ + _contextSubscription: null, + + constructor: function () { + logger.debug("TreeView.widget.GridView.constructor"); + + this.records = []; + this._multiSelection = []; + + this.columns = []; + this.colheads = []; + this.conditions = {}; + this.actions = []; + + this.actionsByName = {}; + }, + + postCreate: function () { + logger.debug("TreeView.widget.GridView.postCreate"); + + Commons.fixObjProps(this, ["blaat0", "blaat2", "blaat3", "blaat4", "blaat7", "blaat8"]); + + this.verifyDatasourceSettings(); + + this._setupDatasets(); + this._setupConditions(); + + this._setupLayout(); + this._setupColumns(); + + this._setupActions(); + + this._setupRendering(); + this._setupEvents(); + + if (this.refreshonclass) { + this.subscribe({ + "entity": this.entity, + callback: lang.hitch(this, function () { + this.fetchAll(); + }) + }); + } + if (this.searchenabled) { + this.searchControl = new SearchControl({ + searchplaceholder: this.searchplaceholder, + labelentity: this.labelentity, + labelcontextassoc: this.labelcontextassoc, + labelitemassoc: this.labelitemassoc, + labelnameattr: this.labelnameattr, + dataset: !this.searchlabeldataset ? null : this.dataset[this.searchlabeldataset], + realtime: this.realtimesearch + }, this); + + dojo.place(this.searchControl.domNode, this.searchbarNode); + } + + this._setupFilters(); + + //triggers data retrieval as well! + if (this.defaultsortcolumn < 0 || this.defaultsortcolumn >= this.colheads.length || !this.colheads[this.defaultsortcolumn].getSortAttr()) { + if (!this.datasourcemf) { + this.configError("Invalid default sort column. The column does not exists or no sort attribute has been defined"); + } + } + //listening formloader + if (this.listenchannel) { + this.connect(this, "onSelect", function (item) { + dojo.publish(this.uniqueid + "/" + this.listenchannel + "/context", [!item ? null : item.data()]); + }); + } + this.setCurrentSortColumn(this.defaultsortcolumn); + + setTimeout(lang.hitch(this, this.grabStartupFocus), 200); //grab focus, but with a small timeout, because wm content manager will grab focus at end of startup chain + }, + + /** + called by mxclient whenever context is replaced + */ + update: function (obj, cb) { + logger.debug("TreeView.widget.GridView.update"); + + //use the new context + this.contextObject = obj; + this.contextGUID = (this.contextObject && this.contextObject !== undefined) ? this.contextObject.getGuid() : null; + + this.listenToContext(); + + //reload + this.resetAndFetchAll(lang.hitch(this, this.updateSelectionFromContext)); + + cb && cb(); + }, + + resize: function () { + // stub function + }, + + suspended: function () { + this._suspended = true; + }, + + resumed: function () { + this._suspended = false; + this.resetAndFetchAll(lang.hitch(this, this.updateSelectionFromContext)); + }, + + isSuspended: function () { + return this._suspended; + }, + + uninitialize: function () { + logger.debug("TreeView.widget.GridView.uninitialize"); + + if (this.searchControl) { + this.searchControl.free(); + } + if (this.filterManager) { + this.filterManager.free(); + } + + dojo.forEach(this.columns, function (column) { + column.free(); + }); + dojo.forEach(this.actions, function (action) { + action.free(); + }); + dojo.forEach(this.records, function (record) { + record.free(); + }); + }, + + _setupEvents: function () { + logger.debug("TreeView.widget.GridView._setupEvents"); + + var lc = Commons.liveConnect; + + lc(this, this.gridNode, "onclick", { + "gv_multiselect_checkbox": this.multiSelectClick, + "gv_label_close": this.labelClick, + "gv_label_name": this.labelClick, + "gv_column_wrapper": this.columnClick, + "gv_cell": function (node, e) { + this.grabFocus(); + this.setSelection(this.getRowForNode(node)); + if (this.singleclickdefaultaction) { + this.invokeDefaultAction(node, e); + } + }, + "gv_th": function (node, e) { + this.setCurrentSortColumn(this.getIndex(node) - 1); + } + }); + + if (!this.singleclickdefaultaction) { + lc(this, this.gridNode, "ondblclick", { + "gv_cell": function (target, e) { + this.invokeDefaultAction(target, e); + } + }); + } + + var currentcolhover = 0; + lc(this, this.gridNode, "onmouseover", { + "gv_row": this.onRowMouseOver, + "gv_th": function (target, e) { + dojo.removeClass(target.parentNode.childNodes[currentcolhover], "gv_sort_hover"); + currentcolhover = this.getIndex(target); + dojo.addClass(target, "gv_sort_hover"); + } + }); + + lc(this, this.gridNode, "onmouseout", { + "gv_th": function (target) { + dojo.removeClass(target, "gv_sort_hover"); + } + }); + + this.connect(this.gridNode, "onmouseout", function (e) { + var target = e.target; + //TODO: this can be done more efficient by using onmouseleave and now isDescendant. + //Not yet supported by webkit though + if (this._hoveredRow && !dojo.isDescendant(this.gridNode, target)) { + dojo.removeClass(this._hoveredRow, "gv_row_hover"); + this._hoveredRow = null; + } + }); + + this.connect(this.gridNode, "onkeypress", this.keypress); + this.connect(this.pagingNode, "onclick", this.pagingClick); + }, + + _setupLayout: function () { + logger.debug("TreeView.widget.GridView._setupLayout"); + + dojo.addClass(this.domNode, "gv_grid"); + if (this.showasdiv) { + dojo.addClass(this.domNode, "gv_floating_grid"); + } + + this.headerNode = mxui.dom.create("div", { "class": "gv_header" }); + + this.gridNode = mxui.dom.create(this.showasdiv ? "div" : "table", { "class": "gv_table" }); + + this.headerRow = mxui.dom.create(this.showasdiv ? "div" : "tr", { "class": "gv_headrow" }, mxui.dom.create(this.showasdiv ? "div" : "th", { "class": "gv_multiselect_column_head gv_th gv_th_0" })); + var header = mxui.dom.create(this.showasdiv ? "div" : "thead", { "class": "gv_gridhead" }, this.headerRow); + + dojo.addClass(this.domNode, this.colheaderenabled ? "gv_columnheaders" : "gv_nocolumnheaders"); + dojo.addClass(this.domNode, this.allowmultiselect ? "gv_multiselect_enabled" : "gv_multiselect_disabled"); + + dojo.place(header, this.gridNode, "first"); + + this.footerNode = mxui.dom.create("div", { "class": "gv_footer" }); + this.pagingNode = mxui.dom.create("div", { "class": "gv_paging" }); + dojo.place(this.pagingNode, this.footerNode); + + this.searchbarNode = mxui.dom.create("div", { "class": "gv_searchnode" }); + dojo.place(this.searchbarNode, this.headerNode); + + dojo.place(this.headerNode, this.domNode); + dojo.place(this.gridNode, this.domNode); + dojo.place(this.footerNode, this.domNode); + + attr.set(this.gridNode, { + tabindex: this.tabindex, + focusindex: 0 + }); + + if (mxui.wm && mxui.wm.focus && mxui.wm.focus.addBox) { + mxui.wm.focus.addBox(this.gridNode); + } + + this.grabFocus(); + }, + + _setupColumns: function () { + logger.debug("TreeView.widget.GridView._setupColumns"); + + var data = []; + this.splitPropsTo("colheadname,colheadcaption,colheadwidth,colheadsortattr,colheadsortdir", data); + for (var i = 0, d = null; d = data[i]; i++) { // EvdP: what kind of weird loop is this? d = data[i] almost seems an error on first sight. Why not just use data.length to check? + d.colindex = i; + var colhead = new ColHead(d, this); + this.colheads.push(colhead); + } + + dojo.forEach(this.colheads, function (colhead) { + colhead.setup(this.headerNode); + }, this); + }, + + _setupDatasets: function () { + logger.debug("TreeView.widget.GridView._setupDatasets"); + + this.dataset = {}; + var data = []; + this.splitPropsTo("relname,rellabel,relnewitemcaption,relentity,relcontextassoc,relitemassocref,relitemassocrefset,relnameattr,relconstraint", data); + dojo.forEach(data, function (item) { + if (this.dataset[item.relname]) + this.configError("Related dataset \"" + item.relname + "\" is defined twice!"); + var r = new RelatedDataset(item, this); + this.dataset[item.relname] = r; + this.addToSchema((item.relitemassocref || item.relitemassocrefset) + "/" + item.relnameattr); + }, this); + }, + + _setupActions: function () { + logger.debug("TreeView.widget.GridView._setupActions"); + + var data = []; + this.splitPropsTo("actname,actprogressmsg,actshowbutton,actclassname,actautohide,actbuttoncaption,actconfirmtext,actbuttonimage,actmf,actmultimf,actappliestomultiselection,actisdefault,actonselect,actnoselectionmf,actdataset", data); + for (var i = 0, d = null; d = data[i]; i++) { // EvdP: what kind of weird loop is this? d = data[i] almost seems an error on first sight. Why not just use data.length to check? + if (d.actmultimf && !!!d.actmf) { + this.configError(d.actname + ": Actions that define a multi selection microflow need to define a single selection microflow as well. "); + } + + var action = new Action(d, this); + + this.actions.push(action); + this.actionsByName[action.actname] = action; + } + + dojo.forEach(this.actions, function (action) { + action.setup(this.headerNode); + action.updateToSelection(); + }, this); + }, + + _setupConditions: function () { + logger.debug("TreeView.widget.GridView._setupConditions"); + + var data = []; + this.splitPropsTo("condname,condattr,condvalues,condclass", data); + for (var i = 0, d = null; d = data[i]; i++) { // EvdP: what kind of weird loop is this? d = data[i] almost seems an error on first sight. Why not just use data.length to check? + var cond = new Condition(d, this); + if (this.conditions[d.condname]) { + this.configError("Condition name \"" + d.condname + "\" is not unique!"); + } + + this.conditions[d.condname] = cond; + this.addToSchema(d.condattr); + } + }, + + _setupFilters: function () { + logger.debug("TreeView.widget.GridView._setupFilters"); + + var data = []; + this.splitPropsTo("filterattr,filtertruecaption,filterfalsecaption,filterbooleandefault,filteranycaption", data); + + var fm = this.filterManager = new FilterManager(this, data); + + dojo.place(fm.domNode, this.actions.length > 0 || !this.searchenabled ? this.headerNode : this.searchbarNode, "last"); + }, + + _setupRendering: function () { + logger.debug("TreeView.widget.GridView._setupRendering"); + + var data = []; + this.splitPropsTo("columnindex,columnname,columnrendermode,columnattr,columnimage,columneditautocommit,columnonchangemf,columnaction,columnclazz,columnstyle,columnprefix,columnpostfix,columndateformat,columnissearchattr,columntruecaption,columnfalsecaption,columneditdataset,columneditable,columncondition", data); + for (var i = 0, d = null; d = data[i]; i++) { // EvdP: what kind of weird loop is this? d = data[i] almost seems an error on first sight. Why not just use data.length to check? + d.columnentity = this.entity; + this.columns.push(new ColRenderer(d, this, i)); + if (d.columnindex * 1 >= this.colheads.length) { + this.configError(this.id + " column index out of bounds: " + d.columnname); + } + + if (d.columnaction && !(d.columnaction in this.actionsByName)) { + this.configError(this.id + " refers to unknown action " + d.columnaction); + } + + if (d.columnrendermode === "thumbnail" || d.columnrendermode === "systemimage") { + //Add fileID and changedDate to schema + if (!d.columnattr) { + this.addToSchema("FileID"); + } + this.addToSchema(d.columnattr.replace(/FileID/, "") + "changedDate"); + } + + this.addToSchema(d.columnattr); + } + }, + + getContextGUID: function () { + return this.contextGUID; + }, + + getContextObject: function () { + return this.contextObject; + }, + + listenToContext: function () { + logger.debug("TreeView.widget.GridView.listenToContext"); + + //if reload on context change is enabled, reload as soon as the context object is altered + if (this.refreshoncontext) { + if (this._contextSubscription) { + mx.data.unsubscribe(this._contextSubscription); + } + + if (this.contextGUID) { + this._contextSubscription = mx.data.subscribe({ + guid: this.contextGUID, + callback: lang.hitch(this, function () { + if (!this._iscallingdatasource) { + this.resetAndFetchAll(lang.hitch(this, this.updateSelectionFromContext)); + } + }) + }); + } + } + }, + + saveAndFireSelection: function (item) { + logger.debug("TreeView.widget.GridView.saveAndFireSelection"); + + this.updatePaging(); //update selected items label + + if (this.selectionref || this.selectionrefset) { + mx.data.commit({ + mxobj: this.contextObject, + callback: lang.hitch(this, this.onSelect, item), + error: this.showError + }, this); + } else { + this.onSelect(item); + } + }, + + hasSelection: function () { + logger.debug("TreeView.widget.GridView.hasSelection"); + return this._multiSelection.length > 0; + }, + + hasMultiSelection: function () { + return this._multiSelection.length > 1; + }, + + getSelection: function () { + return this._multiSelection; + }, + + getLastSelection: function () { + return (this.hasSelection() ? this._multiSelection[this._multiSelection.length - 1] : null); + }, + + withSelection: function (scope, cb) { + dojo.forEach(this._multiSelection, cb, scope); + }, + + addToSelection: function (item, noevents) { + logger.debug("TreeView.widget.GridView.addToSelection"); + + item.checkbox.checked = true; + + if (this.allowsingleselect) { + dojo.addClass(item.domNode, "gv_selected"); + dojo.window.scrollIntoView(item.domNode); + } + + var index = dojo.indexOf(this._multiSelection, item); + + if (index === -1) { + for(var i = 0; i < this._multiSelection; i++) { + if (this._multiSelection[i].guid === item.guid) { + index = i; + } + } + } + + if (index === -1) { + this._multiSelection.push(item); + } + + if (noevents !== true) { + if (this.selectionref) + Commons.store(this.contextObject, this.selectionref, item.guid); + + if (this.selectionrefset) + Commons.store(this.contextObject, this.selectionrefset, item.guid, "add"); + + this.saveAndFireSelection(item); + } + }, + + removeFromSelection: function (item, noevents) { + logger.debug("TreeView.widget.GridView.removeFromSelection"); + + dojo.removeClass(item.domNode, "gv_selected"); + item.checkbox.checked = false; + + var idx = -1; + + for (var i = 0; i < this._multiSelection.length; i++) { + if (this._multiSelection[i].guid === item.guid) { + idx = i; + break; + } + } + + if (idx > -1) { + this._multiSelection.splice(idx, 1); + } + + var lastitem = this.getLastSelection(); + + if (noevents !== true) { + if (this.selectionref) { + Commons.store(this.contextObject, this.selectionref, lastitem ? lastitem.guid : null); + } + + if (this.selectionrefset) { + Commons.store(this.contextObject, this.selectionrefset, item.guid, "rem"); + } + + this.saveAndFireSelection(lastitem); + } + }, + + setSelection: function (item) { + logger.debug("TreeView.widget.GridView.setSelection"); + + //the same selection? + if ((!item && !this.hasSelection) || (item && this._multiSelection.length === 1 && this._multiSelection[0] === item)) { + return; + } + + this._inMultiSelectMode = false; + + while (this.hasSelection()) { + this.removeFromSelection(this.getLastSelection(), true); + } + + if (item) { + this.addToSelection(item, true); + } + + if (this.selectionref) { + Commons.store(this.contextObject, this.selectionref, item && item.guid); + } + + if (this.selectionrefset) { + Commons.store(this.contextObject, this.selectionrefset, item && item.guid); + } + + if (item) { + this.saveAndFireSelection(item); + } + }, + + multiSelectClick: function (node, e) { + logger.debug("TreeView.widget.GridView.multiSelectClick"); + + var record = this.getRowForNode(node); + + if (node.checked) { + this.addToSelection(record); + } else { + this.removeFromSelection(record); + } + + this._inMultiSelectMode = this._multiSelection.length > 0; + + return false; //no propagation + }, + + getRecordByGuid: function (guid) { + logger.debug("TreeView.widget.GridView.getRecordByGuid"); + + for (var i = 0; i < this.records.length; i++) { + if (this.records[i].guid == guid) { + return this.records[i]; + } + } + return null; + }, + + /** tries to read the selection data from the context object, and apply it*/ + updateSelectionFromContext: function () { + logger.debug("TreeView.widget.GridView.updateSelectionFromContext"); + + var guids = []; + + if (this.selectionref) { + this.contextObject.fetch(this.selectionref, function (value) { + if (value) { + guids.push(value); + } + }); + } + if (this.selectionrefset) { + this.contextObject.fetch(this.selectionrefset, function (value) { + if (value) { + guids.push(value); + } + }); + } + for (var i = 0; i < guids.length; i++) { + var record = this.getRecordByGuid(guids[i]); + if (record) { + this.addToSelection(record, true); + } + } + + //select first selection on first update + if (this.firstUpdate) { + this.setDefaultSelection(); + this.firstUpdate = false; + } + }, + + /** tries to reapply the current selection, otherwise, selects the first record */ + reapplySelection: function () { + logger.debug("TreeView.widget.GridView.reapplySelection"); + + var selected = false; + var guids = []; + //remember and empty current selection + //TODO: change triggering can be done more efficient + + //In multi selection, re-select selection if visible, leave the rest as is. + if (this._inMultiSelectMode) { + guids = dojo.map(this._multiSelection, function (item) { return item.guid; }); + + dojo.forEach(this.records, function (item) { + var idx = dojo.indexOf(guids, item.guid); + if (idx > -1) { + item.checkbox.checked = true; + dojo.addClass(item.domNode, "gv_selected"); + //replace item in multi selection array with the fresh one, to avoid memory leaks and outdated objects, or any other strange bugs + this._multiSelection[idx] = item; + } + }, this); + + } else { //else, deselect all non visible items + for (var i = this._multiSelection.length - 1; i >= 0; i--) { + guids.push(this._multiSelection[i].guid); + this.removeFromSelection(this._multiSelection[i], true); + } + + dojo.forEach(this.records, function (record) { + if (dojo.indexOf(guids, record.guid) > -1) { + this.addToSelection(record); + selected = true; + } + }, this); + + if (!selected) { + this.setSelection(null); + } + } + }, + + onSelect: function (selection) { + //stub method to connect events to + }, + + onDefaultAction: function (selection) { + //stub, this method is invoked when a row is doubleclicked/ return is pressed + }, + + resetAndFetchAll: function (cb) { + logger.debug("TreeView.widget.GridView.resetAndFetchAll"); + + if (!this.isSuspended()) { + this.curpage = 0; + this.fetchAll(cb); + } + }, + + fetchAll: function (cb) { + logger.debug("TreeView.widget.GridView.fetchAll"); + + if (!this.contextGUID || this.isSuspended()) { + cb && cb(); + } else if (this.datasourcemf != "") { + this.fetchFromMicroflowDatasource(cb); + } else { + this.fetchFromDatabaseDatasource(cb); + } + }, + + fetchFromMicroflowDatasource: function (cb) { + logger.debug("TreeView.widget.GridView.fetchFromMicroflowDatasource"); + + var contextObject = this.contextObject; + var filter = ""; + + if (this.searchenabled) { + filter = this.searchControl.searchInput.getValue(); + } + + contextObject.set(this.datasourceoffsetattr, this.curpage * this.pagesize); + contextObject.set(this.datasourcelimitattr, this.pagesize); + contextObject.set(this.datasourcesearchattr, filter && filter != this.searchplaceholder ? filter : ""); + + var self = this; + + mx.data.commit({ + mxobj: contextObject, + callback: function () { + self._iscallingdatasource = true; + mx.data.action({ + params: { + actionname: self.datasourcemf, + applyto: "selection", + guids: ["" + contextObject.getGuid()] + }, + callback: function (objlist, xhr) { + var count = contextObject.get(self.datasourcecountattr); + self.processData(cb, objlist, count); + + //The refresh instruction that are responded to the datasource mf are processed later than the callback (this callback) with the results itself. So schedule async and hope for the best. (otherwise looping will occur) + setTimeout(function () { + self._iscallingdatasource = false; + }, 1); + }, + error: function (e) { + self._iscallingdatasource = false; + self.showError(e); + } + }); + }, + error: function (e) { + self.showError(e); + } + }, this); + }, + + fetchFromDatabaseDatasource: function (cb) { + logger.debug("TreeView.widget.GridView.fetchFromDatabaseDatasource"); + + var xpath = this.buildXpath(); + + var args = { + xpath: xpath, + filter: this.enableschema ? this._schema : {}, + callback: lang.hitch(this, this.processData, cb), + count: true, + error: this.showError + }; + + //sorting + var sortCol = this.colheads[this.currentSortColumn]; + var sortdir = sortCol.getSortDir(); + + if (this.sortInverted) { + sortdir = sortdir === "asc" ? "desc" : "asc"; + } + + if (!sortCol.getSortAttr()) { + this.configError("No sortable column : " + this.currentSortColumn + " (" + sortCol.data.colheadcaption + ")"); + } + + args.filter.sort = [[sortCol.getSortAttr(), sortdir]]; + args.filter.offset = this.curpage * this.pagesize; + args.filter.amount = this.pagesize; + + //perform the get + mx.data.get(args); + }, + + buildXpath: function () { + logger.debug("TreeView.widget.GridView.buildXpath"); + var xpath = "//" + this.entity + (this.constraint ? this.constraint.replace(/\[\%CurrentObject\%\]/gi, this.contextGUID) : ""); + + if (this.searchControl) { + if (!this.searchAttrs) { + this.searchAttrs = dojo.map(dojo.filter(this.columns, function (column) { + return column.columnissearchattr && column.columnattr; + }), function (column) { + return column.columnattr; + }); + } + + if (this.searchmaxquerysizeenabled) { + xpath += this.searchControl.getSearchConstraints(this.searchAttrs, this.searchmaxquerysize); + } else { + xpath += this.searchControl.getSearchConstraints(this.searchAttrs); + } + } + + xpath += this.filterManager.getSearchConstraints(); + return xpath; + }, + + processData: function (cb, data, count) { + logger.debug("TreeView.widget.GridView.processData"); + + this.count = (dojo.isObject(count) ? count.count : count) * 1; //Mx 3 returns primitive, Mx 4 an aggregate object + + // TODO: count is an object in MX7, this.count will be NaN. + if (isNaN(this.count)) { + this.count = parseInt(count); + } + + this.updatePaging(); + + dojo.forEach(this.records, function (record) { + record.free(); + }); + + this.records = []; + + // TODO check scoping + var handleElem = function (data) { + var r = new Record(data, this); + this.records.push(r); + r.setup(this.gridNode); + }.bind(this); + + dojo.forEach(data, handleElem, this); + + // if the current page is now empty, we should go back one page + if (count <= this.curpage * this.pagesize && this.curpage > 0) { + this.curpage -= 1; + this.fetchAll(cb); + } else { + cb && cb.call(this); //MWE: cb can influence selection, call callback before reapply? + } + + this.reapplySelection(); + }, + + updatePaging: function () { + logger.debug("TreeView.widget.GridView.updatePaging"); + + dojo.empty(this.pagingNode); + + var lastpage = Math.ceil(this.count / this.pagesize) - 1; + var PAGERSIZE = 3; + + // Do we need to reset the current page? (count < limit) + if (this.count < this.pagesize) { + this.curpage = 0; + } + + //show paging at all? + if (this.count > this.pagesize || this.curpage > 0) { + //show prev btn? + if (this.curpage > 0) { + dojo.place(mxui.dom.create("a", { "class": "gv_btn_prev" }, "<"), this.pagingNode); + } + + //page 1 + dojo.place(mxui.dom.create("a", { "class": "gv_btn_page " + (0 === this.curpage ? "gv_btn_page_active" : "") }, "1"), this.pagingNode); + + //paging skipper? + if (this.curpage > PAGERSIZE) { + dojo.place(mxui.dom.create("a", { "class": "gv_btn_paging_spacer" }, ".."), this.pagingNode); + } + + for (var i = Math.max(this.curpage - PAGERSIZE + 1, 1); i < Math.min(this.curpage + PAGERSIZE, lastpage); i++) { + dojo.place(mxui.dom.create("a", { "class": "gv_btn_page " + (i === this.curpage ? "gv_btn_page_active" : "") }, "" + (i + 1)), this.pagingNode); + } + + //paging skipper? + if (this.curpage < lastpage - PAGERSIZE) { + dojo.place(mxui.dom.create("a", { "class": "gv_btn_paging_spacer" }, ".."), this.pagingNode); + } + + //last page + dojo.place(mxui.dom.create("a", { "class": "gv_btn_page " + (lastpage === this.curpage ? "gv_btn_page_active" : "") }, "" + (lastpage + 1)), this.pagingNode); + + //show next btn? + if (this.curpage < lastpage) { + dojo.place(mxui.dom.create("a", { "class": "gv_btn_next" }, ">"), this.pagingNode); + } + + } + + if (this.count === 0) { + dojo.place(mxui.dom.create("span", { "class": "gv_empty_message" }, this.emptymessage), this.pagingNode); + } else if (this.showtotals) { + dojo.place(mxui.dom.create("span", { "class": "gv_paging_totals" }, ( + this.itemcountmessage || (this._multiSelection.length > 1 ? + "{1} of {0} item(s) selected." : + "{0} item(s) in total")).replace("{0}", this.count).replace("{1}", this._multiSelection.length) + ), this.pagingNode); + } + }, + + pagingClick: function (e) { + logger.debug("TreeView.widget.GridView.pagingClick"); + + if (dojo.hasClass(e.target, "gv_btn_prev")) { + this.prevPage(); + } else if (dojo.hasClass(e.target, "gv_btn_next")) { + this.nextPage(); + } else if (dojo.hasClass(e.target, "gv_btn_page")) { + this.curpage = (1 * e.target.innerHTML) - 1; + } else { + return; + } + + this.fetchAll(); + }, + + prevPage: function () { + logger.debug("TreeView.widget.GridView.prevPage"); + + if (this.curpage > 0) { + this.curpage -= 1; + this.fetchAll(function () { + //select last item when going to previous page + if (!this._inMultiSelectMode) { + if (this.gridNode.childNodes.length > 1) { + this.setSelection(this.getRowForNode(this.gridNode.childNodes[this.gridNode.childNodes.length - 1])); + } else { + this.setSelection(null); + } + } + }); + } + }, + + nextPage: function () { + logger.debug("TreeView.widget.GridView.nextPage"); + + if (this.curpage < Math.ceil(this.count / this.pagesize) - 1) { + this.curpage += 1; + this.fetchAll(function () { + if (!this._inMultiSelectMode) { + this.selectFirstItem(); + } + }); + } + }, + + selectFirstItem: function () { + logger.debug("TreeView.widget.GridView.selectFirstItem"); + + if (this.gridNode.childNodes.length > 1) { + this.setSelection(this.getRowForNode(this.gridNode.childNodes[1])); + } else { + this.setSelection(null); + } + }, + + setDefaultSelection: function () { + logger.debug("TreeView.widget.GridView.setDefaultSelection"); + + if (this.selectfirstrow) { + this.selectFirstItem(); + } + }, + + grabStartupFocus: function () { + logger.debug("TreeView.widget.GridView.grabStartupFocus"); + if (mxui.wm && mxui.wm.focus && mxui.wm.focus.put && this.searchenabled) { + mxui.wm.focus.put(this.searchControl.searchInput.textbox); + } else { + this.grabFocus(); + } + }, + + grabFocus: function () { + logger.debug("TreeView.widget.GridView.grabFocus"); + + if (mxui.wm && mxui.wm.focus && mxui.wm.focus.get && mxui.wm.focus.get() !== this.gridNode) { + mxui.wm.focus.put(this.gridNode); + } + }, + + getIndex: function (node) { + logger.debug("TreeView.widget.GridView.getIndex"); + + return typeof node.cellIndex === "number" ? node.cellIndex : dojo.query(node.parentNode).children().indexOf(node); + }, + + keypress: function (e) { + logger.debug("TreeView.widget.GridView.keypress"); + + var record = this.getLastSelection(), //this.getRowForNode(e.target); + handled = false; + if (record) { + handled = true; + switch (e.keyCode) { + case dojo.keys.SPACE: + //TODO: this and arrow up & down needs distinction between selection and focus to work properly! + if (this.allowmultiselect) { + if (record.checkbox.checked) { + if (this.hasMultiSelection()) { //do not remove last from selection + this.removeFromSelection(record); + } + } else { + this.addToSelection(record); + } + } + break; + case dojo.keys.ENTER: + this.invokeDefaultAction(); + break; + case dojo.keys.DOWN_ARROW: + var next = this.getRowForNode(record.domNode.nextElementSibling); + if (next) { + if (!this._inMultiSelectMode) { + this.setSelection(next); + } + } + else { + this.nextPage(); + } + break; + case dojo.keys.UP_ARROW: + var prev = this.getRowForNode(record.domNode.previousElementSibling); + if (prev) { + if (!this._inMultiSelectMode) { + this.setSelection(prev); + } + } else { + this.prevPage(); + } + break; + case dojo.keys.PAGE_UP: + case dojo.keys.LEFT_ARROW: + this.prevPage(); + break; + case dojo.keys.PAGE_DOWN: + case dojo.keys.RIGHT_ARROW: + this.nextPage(); + break; + } + } + if (handled) { + dojo.stopEvent(e); + } + }, + + invokeDefaultAction: function () { + logger.debug("TreeView.widget.GridView.invokeDefaultAction"); + + for (var i = 0, a = null; a = this.actions[i++];) { + if (a.actisdefault && a.appliesToSelection()) { + a.invokeOnSelection(); + } + } + }, + + onRowMouseOver: function (target, e) { + if (target !== this._hoveredRow) { + this._hoveredRow && dojo.removeClass(this._hoveredRow, "gv_row_hover"); + dojo.addClass(target, "gv_row_hover"); + this._hoveredRow = target; + } + + e.preventDefault(); + dojo.stopEvent(e); + return false; //stop further events + }, + + getRowForNode: function (node) { + logger.debug("TreeView.widget.GridView.getRowForNode"); + + if (!node) { + return null; + } + if (dojo.hasClass(node, "gv_row")) { // EvdP: yes, functionality now depends on a classname, but this can only be avoided by using parentNode, which is errorprone for changes. + return mxui.dom.data(node, "data"); + } + return this.getRowForNode(node.parentNode); + }, + + labelClick: function (node) { + logger.debug("TreeView.widget.GridView.labelClick"); + + var isClose = dojo.hasClass(node, "gv_label_close"); + node = node.parentNode; + + //assuming dataset: { owner: record, guid : guid, dataset: this.columneditdataset, colindex: this.colindex } + var data = mxui.dom.data(node, "data"); + var record = data.owner; + var guid = data.guid; + var dataset = this.dataset[data.dataset]; + var rnd = this.columns[data.colindex]; + if (!(record && guid && dataset && rnd)) { + this.showError("Unable to handle labelclick!"); + } + + if (isClose) { + rnd.applyChange(record, guid, true); + } + + //clicked on a searchable label? + else if (dataset === this.dataset[this.searchlabeldataset]) { + this.searchControl.setSearchFilter("", dataset.existingLabelsById[guid]); + } else { + //TODO: onclick label action + } + }, + + columnClick: function (node) { + logger.debug("TreeView.widget.GridView.columnClick"); + + var col = mxui.dom.data(node, "colindex"), + record = this.getRowForNode(node); + + this.columns[col].invokeAction(record); + }, + + setCurrentSortColumn: function (index) { + logger.debug("TreeView.widget.GridView.setCurrentSortColumn"); + + if (this.colheads[index].getSortAttr()) { + //MWE: note that colhead index is one less the the TH index in the domTree + var colnode = this.headerRow.childNodes[index + 1]; + + if (index === this.currentSortColumn) { + this.sortInverted = !this.sortInverted; + dojo.removeClass(colnode, "gv_sort_up gv_sort_down"); + + } else { + if (this.currentSortColumn > -1) { + dojo.removeClass(this.headerRow.childNodes[this.currentSortColumn + 1], "gv_sortcolumn gv_sort_up gv_sort_down"); + } + + this.currentSortColumn = index; + this.sortInverted = false; + } + + dojo.addClass(colnode, "gv_sortcolumn " + ("asc" === this.colheads[index].getSortDir() ^ this.sortInverted ? "gv_sort_up" : "gv_sort_down")); + this.resetAndFetchAll(); + } + }, + + verifyDatasourceSettings: function () { + logger.debug("TreeView.widget.GridView.verifyDatasourceSettings"); + + if (this.datasourcemf) { + if (!(this.datasourceoffsetattr && this.datasourcelimitattr && this.datasourcecountattr)) { + this.configError("Offset, Limit and Count attributes need to be set if a data source microflow is used."); + } + + if (this.searchenabled && !this.datasourcesearchattr) { + this.configError("Search attribute s need to be set if a data source microflow is used and search is enabled."); + } + + if (this.defaultsortcolumn !== 0) { + this.configError("Sorting is not supported if a data source microflow is used."); + } + + if (this.relname) { + this.configError("Datasets are not allowed if a datasource microflow is used"); + } + + if (this.filterattr) { + this.configError("Filters are not allowed if a datasource microflow is used"); + } + + if (this.entityConstraint) { + this.configError("An entity constraint and datasource microflow cannot be used at the same time"); + } + } + }, + + _schema: null, + addToSchema: function (attr) { + logger.debug("TreeView.widget.GridView.addToSchema"); + + if (!attr) { return; } + + if (!this._schema) { + this._schema = { references: {}, attributes: [] }; + } + + if (attr.indexOf("/") > -1) { + var parts = attr.split("/"); + + if (!(parts[0] in this._schema.references)) { + this._schema.references[parts[0]] = { attributes: [] }; + } + + if (parts.length > 2) { + this._schema.references[parts[0]].attributes.push(parts[2]); + } + } else { + this._schema.attributes.push(attr); + } + }, + showError: function (e) { + logger.debug("TreeView.widget.GridView.showError"); -dojo.declare("TreeView.widget.Colhead", null, { - domNode : null, //correspinding render nodes - grid : null, + Commons.error(e, this); + }, + mf: function (mf, data, callback) { + logger.debug("TreeView.widget.GridView.mf"); - constructor : function(data, grid) { - this.grid = grid; - this.data = data; + Commons.mf(mf, data, callback, this); + }, - }, + configError: function (msg) { + logger.debug("TreeView.widget.GridView.configError"); - setup : function(rownode) { - this.domNode = mxui.dom[this.grid.showasdiv ? "div" : "th"]({ - 'class': 'gv_th gv_th_' - + (1 + this.data.colindex) - + (this.data.colheadname ? ' gv_th_' + this.data.colheadname.replace(/\W+/g,"") : '') - + (this.data.colheadsortattr ? ' gv_th_sortable' : ' gv_th_unsortable') - }); - if (this.data.colheadwidth) - dojo.style(this.domNode, 'width', this.getWidth()); + Commons.configError(this, msg); + }, - //sort caption? - if (this.data.colheadcaption) - dojo.place(mendix.dom.span({'class' : 'gv_sort_caption'}, this.data.colheadcaption), this.domNode); + splitPropsTo: function (props, target) { + logger.debug("TreeView.widget.GridView.splitPropsTo"); - //show sort arrow? - if (this.getSortAttr()) - dojo.place(mendix.dom.span({'class' : 'gv_sort_arrow'}), this.domNode); - - - dojo.place(this.domNode, this.grid.headerRow); - }, - - getWidth : function() { - return this.data.colheadwidth; - }, - - getSortAttr : function() { - return this.data.colheadsortattr; - }, - - getSortDir : function() { - return this.data.colheadsortdir; - }, - - free : function() { - } + Commons.splitPropsTo(this, props, target); + } + }); }); - -dojo.declare("TreeView.widget.Record", null, { - domNode : null, //correspinding render nodes - grid : null, - guid : null, - - _data : null, - _subscription : null, - _colNodes : null, //array with columnNodes - _subs : null, - checkbox : null, //checkbox node - - constructor : function(data, grid) { - this.guid = data.getGUID(); - this._data = data; - this._colNodes = []; - this._subs = []; - - this.grid = grid; - - this._subscription = mx.processor.subscribe({ - guid : this.guid, - callback : dojo.hitch(this, function(thing) { - //Do not update while suspended; all data will be fetch upon resume. - if (this.grid.isSuspended()) - return; - - if (this.grid.datasourcemf) { - //microflow data? retrieve by id - mx.processor.get({ - guid: this.guid, - callback : dojo.hitch(this, function(data) { - this.update(data); - }), - error : grid.showError - }); - } - else { - //xpath datasource? retrieve by xpath, the object might no longer be in the grid constraint - mx.processor.get({ - xpath : grid.buildXpath() + "[id = '"+this.guid+"']", - filter : grid.enableschema ? grid._schema : {}, - callback : dojo.hitch(this, function (data) { - if (data.length > 0) - this.update(data[0]); - }), - error: grid.showError - }); - } - }) - }); - - }, - - data : function() { - return this._data; - }, - - update : function(data, firstTime) { - this._data = data; - - var curCol = 0; - for(var i = 0, col = null; col = this.grid.columns[i]; i++) { - var node = this._colNodes[curCol]; - col.render(this, node, firstTime === true); - curCol += 1; - } - - for (var key in this.grid.conditions) { - var condition = this.grid.conditions[key]; - var clz = condition.getClass(); - if (clz) { //checking if class is cheaper than checking the condition itself - if (condition.appliesTo(this)) - dojo.addClass(this.domNode, clz); - else - dojo.removeClass(this.domNode, clz); - } - } - }, - - setup : function(tablenode) { - this.domNode = mendix.dom[this.grid.showasdiv ? "div" : "tr"]({ 'class' : 'gv_row gv_row_' + tablenode.childElementCount }); - - if (this.grid.showasdiv && this.grid.colheads.length > 0 && this.grid.colheads[0].getWidth()) - dojo.style(this.domNode, 'width', this.grid.colheads[0].getWidth()); - - mxui.dom.data(this.domNode, "data", this); - - this.checkbox = mendix.dom.input( { - 'type' : 'checkbox', - 'class' : 'gv_multiselect_checkbox', - 'style' : this.grid.allowmultiselect === true ? '' : 'display:none' - }); - - dojo.place(mendix.dom[this.grid.showasdiv ? "div" : "td"]({ - 'class' : 'gv_cell gv_cell_0 gv_cell_multiselect' - }, this.checkbox), this.domNode); - - //create td's - for (var i = 0; i < this.grid.colheads.length; i++) { - var cell = mendix.dom[this.grid.showasdiv ? "div" : "td"]({ - 'class' : 'gv_cell gv_cell_' + this.grid.colheads[i].data.colheadname + ' gv_cell_' + i - }); - var colwrapper = mendix.dom.div({ 'class' : 'gv_cell_wrapper'}); - - dojo.place(colwrapper, cell); - dojo.place(cell, this.domNode); - } - - //create renderers - for(i = 0, col= null; col = this.grid.columns[i]; i++) { - if (1 * col.columnindex >= this.grid.colheads.length) - this.configError("Column index out of bounds: " + col.columnindex); - - var span = mxui.dom.span({'class' : 'gv_column gv_column_' + i}); - this._colNodes.push(span); - - //wrapper node - var cw = mxui.dom.span({ 'class' : 'gv_column_wrapper' }, span); - dojo.place(cw, this.domNode.childNodes[1 + 1 * col.columnindex].children[0]); - - col.setupNode(span); - } - - this.update(this._data, true); - dojo.place(this.domNode, tablenode); - }, - - addSubscription : function(subscription) { - this._subs.push(subscription); - }, - - free : function() { - if (this._destroyed) - return; - this._destroyed = true; - - dojo.forEach(this.subs, function(sub) { - dojo.disconnect(sub); - }); - - dojo.destroy(this.domNode); - - if (this._subscription) - mx.processor.unsubscribe(this._subscription); - - } - -}); - -mxui.widget.declare("TreeView.widget.GridView", { - - _multiSelection : null, - _inMultiSelectMode : false, //is true as long only checkboxes are clicked - _hoveredRow : null, - _started : false, - _suspended : false, - _iscallingdatasource : false, - contextGUID : null, - - currentSortColumn : -1, - sortInverted : false, - count : 0, - curpage : 0, - - saveAndFireSelection : function(item) { - - this.updatePaging(); //update selected items label - - if (this.selectionref || this.selectionrefset) { - mx.processor.save({ - mxobj : this.contextObject, - callback : dojo.hitch(this, this.onSelect, item), - error : this.showError - }, this); - - mx.processor.objectUpdateNotification(this.contextObject); - } - - this.onSelect(item); - }, - - hasSelection : function() { - return this._multiSelection.length > 0; - }, - - hasMultiSelection : function() { - return this._multiSelection.length > 1; - }, - - getSelection : function() { - return this._multiSelection; - }, - - getLastSelection : function() { - return (this.hasSelection() - ? this._multiSelection[this._multiSelection.length -1] - : null); - }, - - withSelection : function(scope, cb) { - dojo.forEach(this._multiSelection, cb, scope); - }, - - - addToSelection : function(item, noevents) { - item.checkbox.checked = true; - - if (this.allowsingleselect) { - dojo.addClass(item.domNode, 'gv_selected'); - dojo.window.scrollIntoView(item.domNode); - } - - var idx = dojo.indexOf(this._multiSelection, item); - if (idx === -1) - this._multiSelection.push(item); - - if(noevents !== true) { - if (this.selectionref) - TreeView.widget.Commons.store(this.contextObject, this.selectionref, item.guid); - - if (this.selectionrefset) - TreeView.widget.Commons.store(this.contextObject, this.selectionrefset, item.guid, "add"); - - this.saveAndFireSelection(item); - } - }, - - removeFromSelection : function(item, noevents) { - dojo.removeClass(item.domNode, 'gv_selected'); - item.checkbox.checked = false; - - var idx = -1; - - for(var i = 0; i < this._multiSelection.length; i++) - if (this._multiSelection[i].guid === item.guid) { - idx = i; - break; - } - - if (idx > -1) - this._multiSelection.splice(idx, 1); - - var lastitem = this.getLastSelection(); - - if(noevents !== true) { - if (this.selectionref) - TreeView.widget.Commons.store(this.contextObject, this.selectionref, lastitem ? lastitem.guid : null); - - if (this.selectionrefset) - TreeView.widget.Commons.store(this.contextObject, this.selectionrefset, item.guid, "rem"); - - this.saveAndFireSelection(lastitem); - } - }, - - setSelection : function(item) { - //the same selection? - if (!item && !this.hasSelection || - item && this._multiSelection.length === 1 && this._multiSelection[0] === item) - return; - - this._inMultiSelectMode = false; - - while(this.hasSelection()) - this.removeFromSelection(this.getLastSelection(), true); - - if (item) - this.addToSelection(item, true); - - if (this.selectionref) - TreeView.widget.Commons.store(this.contextObject, this.selectionref, item && item.guid); - - if (this.selectionrefset) - TreeView.widget.Commons.store(this.contextObject, this.selectionrefset, item && item.guid); - - this.saveAndFireSelection(item); - }, - - multiSelectClick : function(node, e) { - var record = this.getRowForNode(node); - /*var ms = this._multiSelection; - - //clicking the current selection and selection length = 1?, reenable selection - if (ms.length == 1 && ms[0] == record) { - node.checked = true; - } - - else {*/ - if (node.checked) - this.addToSelection(record); - - else - this.removeFromSelection(record); - //} - - this._inMultiSelectMode = this._multiSelection.length > 0; - - return false; //no propagation - }, - - getRecordByGuid : function(guid) { - for(var i = 0; i < this.records.length; i++) - if (this.records[i].guid == guid) - return this.records[i]; - return null; - }, - - /** tries to read the selection data from the context object, and apply it*/ - updateSelectionFromContext : function() { - var guids = []; - - if (this.selectionref) { - var guid = TreeView.widget.Commons.getObjectAttr(this.contextObject, this.selectionref); - if (guid) - guids.push(guid); - } - if (this.selectionrefset) { - guids = guids.concat(TreeView.widget.Commons.getObjectAttr(this.contextObject, this.selectionrefset)); - } - for(var i = 0; i < guids.length; i++) { - var record = this.getRecordByGuid(guids[i]); - if (record) - this.addToSelection(record, true); - } - }, - - /** tries to reapply the current selection, otherwise, selects the first record */ - reapplySelection : function() { - var selected = false; - var guids = []; - //remember and empty current selection - //TODO: change triggering can be done more efficient - - //In multi selection, re-select selection if visible, leave the rest as is. - if (this._inMultiSelectMode) { - - guids = dojo.map(this._multiSelection, function(item) { return item.guid;}); - - dojo.forEach(this.records, function(item) { - var idx = dojo.indexOf(guids, item.guid); - if (idx > -1) { - item.checkbox.checked = true; - dojo.addClass(item.domNode, 'gv_selected'); - //replace item in multi selection array with the fresh one, to avoid memory leaks and outdated objects, or any other strange bugs - this._multiSelection[idx] = item; - } - }, this); - } - - //else, deselect all non visible items - else{ - - for (var i = this._multiSelection.length - 1; i >= 0; i--) { - guids.push(this._multiSelection[i].guid); - this.removeFromSelection(this._multiSelection[i], true); - } - - dojo.forEach(this.records, function(record) { - if (dojo.indexOf(guids, record.guid) > -1) { - this.addToSelection(record); - selected = true; - } - }, this); - - if (!selected) - this.setSelection(null); - } - }, - - onSelect : function(selection) { - //stub method to connect events to - }, - - onDefaultAction : function(selection) { - //stub, this method is invoked when a row is doubleclicked/ return is pressed - }, - - /* context applied */ - _contextSubscription : null, - - getContextGUID : function() { - return this.contextGUID; - }, - - getContextObject : function() { - return this.contextObject; - }, - - /** - called by mxclient whenever context is replaced - */ - update : function(data, cb) { - TreeView.widget.Commons.normalizeContext(data, dojo.hitch(this, function(object, guid) { - //use the new context - this.contextObject = object; - this.contextGUID = guid; - this.listenToContext(); - - //reload - this.resetAndFetchAll(dojo.hitch(this, this.updateSelectionFromContext)); - })); - - cb && cb(); - }, - - suspended : function() { - this._suspended = true; - }, - - resumed : function() { - this._suspended = false; - this.resetAndFetchAll(dojo.hitch(this, this.updateSelectionFromContext)); - }, - - isSuspended : function() { - return this._suspended; - }, - - listenToContext : function() { - //if reload on context change is enabled, reload as soon as the context object is altered - if (this.refreshoncontext) { - if (this._contextSubscription) - mx.processor.unsubscribe(this._contextSubscription); - - if (this.contextGUID) { - this._contextSubscription = mx.processor.subscribe({ - guid: this.contextGUID, - callback : dojo.hitch(this, function() { - if (!this._iscallingdatasource) - this.resetAndFetchAll(dojo.hitch(this, this.updateSelectionFromContext)); - }) - }); - } - } - }, - - resetAndFetchAll : function(cb) { - if (!this.isSuspended()) { - this.curpage = 0; - this.fetchAll(cb); - } - }, - - fetchAll : function(cb) { - if (!this.contextGUID || this.isSuspended()) { - cb && cb(); - } - - else if (this.datasourcemf != '') { - this.fetchFromMicroflowDatasource(cb); - } else { - this.fetchFromDatabaseDatasource(cb); - } - }, - - fetchFromMicroflowDatasource : function(cb) { - var contextObject = this.contextObject; - var filter = ''; - if (this.searchenabled) - filter = this.searchControl.searchInput.getValue(); - - contextObject.set(this.datasourceoffsetattr, this.curpage * this.pagesize); - contextObject.set(this.datasourcelimitattr, this.pagesize); - contextObject.set(this.datasourcesearchattr, filter && filter != this.searchplaceholder ? filter : ''); - - var self = this; - - mx.processor.save({ - mxobj : contextObject, - callback : function(){ - self._iscallingdatasource = true; - mx.data.action({ - params : { - actionname : self.datasourcemf, - applyto : "selection", - guids : ["" + contextObject.getGUID()] - }, - callback : function (objlist, xhr) { - var count = contextObject.get(self.datasourcecountattr); - self.processData(cb, objlist, count); - - //The refresh instruction that are responded to the datasource mf are processed later than the callback (this callback) with the results itself. So schedule async and hope for the best. (otherwise looping will occur) - setTimeout(function() { - self._iscallingdatasource = false; - }, 1); - }, - error : function(e) { - self._iscallingdatasource = false; - self.showError(e); - } - }); - }, - error : function (e) { - self.showError(e); - } - }, this); - }, - - fetchFromDatabaseDatasource : function(cb) { - var xpath = this.buildXpath(); - - var args = { - xpath : xpath, - filter : this.enableschema ? this._schema : {}, - callback : dojo.hitch(this, this.processData, cb), - count : true, - error : this.showError - }; - - //sorting - var sortCol = this.colheads[this.currentSortColumn]; - var sortdir = sortCol.getSortDir(); - if (this.sortInverted) - sortdir = sortdir === "asc" ? "desc" : "asc"; - - if (!sortCol.getSortAttr()) - this.configError("No sortable column : " + this.currentSortColumn + " ("+ sortCol.data.colheadcaption + ")"); - - args.filter.sort = [[ sortCol.getSortAttr(), sortdir]]; - args.filter.offset = this.curpage * this.pagesize; - args.filter.limit = this.pagesize; - - //perform the get - mx.processor.get(args); - }, - - buildXpath : function () { - var xpath = '//' + this.entity + this.constraint.replace(/\[\%CurrentObject\%\]/gi, this.contextGUID); - - if (this.searchControl) { - if (!this.searchAttrs) - this.searchAttrs = dojo.map(dojo.filter(this.columns, function(column) { - return column.columnissearchattr && column.columnattr; - }), function(column) { - return column.columnattr; - }); - - xpath += this.searchControl.getSearchConstraints(this.searchAttrs); - } - - xpath += this.filterManager.getSearchConstraints(); - return xpath; - }, - - processData : function(cb, data, count) { - this.count = (dojo.isObject(count) ? count.count : count)*1; //Mx 3 returns primitive, Mx 4 an aggregate object - this.updatePaging(); - - dojo.forEach(this.records, function(record){ - record.free(); - }); - - this.records = []; - - function handleElem(data) { - var r = new TreeView.widget.Record(data, this); - this.records.push(r); - r.setup(this.gridNode); - } - - dojo.forEach(data, handleElem, this); - - // if the current page is now empty, we should go back one page - if (count <= this.curpage * this.pagesize && this.curpage > 0) { - this.curpage -= 1; - this.fetchAll(cb); - } - else { - cb && cb.call(this); //MWE: cb can influence selection, call callback before reapply? - } - - this.reapplySelection(); - }, - - updatePaging : function() { - dojo.empty(this.pagingNode); - - var lastpage = Math.ceil(this.count / this.pagesize) - 1; - var PAGERSIZE = 3; - - // Do we need to reset the current page? (count < limit) - if (this.count < this.pagesize) { - this.curpage = 0; - } - - //show paging at all? - if (this.count > this.pagesize || this.curpage > 0) { - //show prev btn? - if (this.curpage > 0) - dojo.place(mendix.dom.a({ 'class' : 'gv_btn_prev'}, "<"), this.pagingNode); - - //page 1 - dojo.place(mendix.dom.a({ 'class' : 'gv_btn_page ' + (0 === this.curpage ? 'gv_btn_page_active' : '')}, "1"), this.pagingNode); - - //paging skipper? - if (this.curpage > PAGERSIZE) - dojo.place(mendix.dom.a({ 'class' : 'gv_btn_paging_spacer'}, ".."), this.pagingNode); - - for(var i = Math.max(this.curpage - PAGERSIZE + 1, 1); i < Math.min(this.curpage + PAGERSIZE , lastpage); i++) - dojo.place(mendix.dom.a({ 'class' : 'gv_btn_page ' + (i === this.curpage ? 'gv_btn_page_active' : '')}, "" + (i + 1)), this.pagingNode); - - //paging skipper? - if (this.curpage < lastpage - PAGERSIZE) - dojo.place(mendix.dom.a({ 'class' : 'gv_btn_paging_spacer'}, ".."), this.pagingNode); - - //last page - dojo.place(mendix.dom.a({ 'class' : 'gv_btn_page ' + (lastpage === this.curpage ? 'gv_btn_page_active' : '')}, "" + (lastpage + 1)), this.pagingNode); - - //show next btn? - if (this.curpage < lastpage) - dojo.place(mendix.dom.a({ 'class' : 'gv_btn_next'}, ">"), this.pagingNode); - - } - - if (this.count === 0) { - dojo.place(mendix.dom.span({'class' : 'gv_empty_message'}, this.emptymessage), this.pagingNode); - } - else if (this.showtotals) - dojo.place(mendix.dom.span({'class' : 'gv_paging_totals'}, - (this.itemcountmessage || (this._multiSelection.length > 1 ? "{1} of {0} item(s) selected." : "{0} item(s) in total")).replace("{0}", this.count).replace("{1}", this._multiSelection.length) - ), this.pagingNode); - }, - - pagingClick : function(e) { - if (dojo.hasClass(e.target, "gv_btn_prev")) - this.prevPage(); - - else if (dojo.hasClass(e.target, "gv_btn_next")) - this.nextPage(); - - else if (dojo.hasClass(e.target, "gv_btn_page")) - this.curpage = (1 * e.target.innerHTML) - 1; - - else - return; - - this.fetchAll(); - }, - - prevPage : function() { - if (this.curpage > 0) { - this.curpage -= 1; - this.fetchAll(function() { - //select last item when going to previous page - if (!this._inMultiSelectMode) { - if (this.gridNode.childNodes.length > 1) - this.setSelection(this.getRowForNode(this.gridNode.childNodes[this.gridNode.childNodes.length - 1])); - else - this.setSelection(null); - } - }); - } - }, - - nextPage : function() { - if (this.curpage < Math.ceil(this.count / this.pagesize) -1) { - this.curpage += 1; - this.fetchAll(function() { - if (!this._inMultiSelectMode) { - this.selectFirstItem(); - } - }); - } - }, - - selectFirstItem : function() { - if (this.gridNode.childNodes.length > 1) - this.setSelection(this.getRowForNode(this.gridNode.childNodes[1])); - else - this.setSelection(null); - }, - - /* - - - UI Events - - - */ - - grabStartupFocus : function() { - if (this.searchenabled) - mxui.wm.focus.put(this.searchControl.searchInput.textbox); - else - this.grabFocus(); - }, - - grabFocus : function() { - if (mxui.wm.focus.get() !== this.gridNode) - mxui.wm.focus.put(this.gridNode); - }, - - setupEvents : function() { - var lc = TreeView.widget.Commons.liveConnect; - - lc(this, this.gridNode, "onclick", { - "gv_multiselect_checkbox" : this.multiSelectClick, - "gv_label_close" : this.labelClick, - "gv_label_name" : this.labelClick, - "gv_column_wrapper" : this.columnClick, - "gv_cell" : function(node, e) { - this.grabFocus(); - this.setSelection(this.getRowForNode(node)); - if (this.singleclickdefaultaction) - this.invokeDefaultAction(node, e); - }, - "gv_th" : function(node, e) { - this.setCurrentSortColumn(this._getIndex(node) - 1); - } - }); - - if (!this.singleclickdefaultaction) { - lc(this, this.gridNode, "ondblclick", { - "gv_cell" : function(target, e) { - this.invokeDefaultAction(target, e); - } - }); - } - - var currentcolhover = 0; - lc(this, this.gridNode, "onmouseover", { - "gv_row" : this.onRowMouseOver, - "gv_th" : function(target, e) { - dojo.removeClass(target.parentNode.childNodes[currentcolhover], "gv_sort_hover"); - currentcolhover = this._getIndex(target); - dojo.addClass(target, "gv_sort_hover"); - } - }); - - lc(this, this.gridNode, "onmouseout", { - "gv_th" : function(target) { - dojo.removeClass(target, "gv_sort_hover"); - } - }); - - this.connect(this.gridNode, "onmouseout", function(e) { - var target = e.target; - //TODO: this can be done more efficient by using onmouseleave and now isDescendant. - //Not yet supported by webkit though - if (this._hoveredRow && !dojo.isDescendant(this.gridNode, target)) { - dojo.removeClass(this._hoveredRow, 'gv_row_hover'); - this._hoveredRow = null; - } - }); - - this.connect(this.gridNode, "onkeypress", this.keypress); - this.connect(this.pagingNode, "onclick", this.pagingClick); - }, - - _getIndex : function(node) { - return typeof node.cellIndex === "number" ? node.cellIndex : dojo.query(node.parentNode).children().indexOf(node); - }, - - keypress : function(e) { - var record = this.getLastSelection();//this.getRowForNode(e.target); - if (record) { - var handled = true; - switch (e.keyCode) { - case dojo.keys.SPACE: - //TODO: this and arrow up & down needs distinction between selection and focus to work properly! - if (this.allowmultiselect) - if (record.checkbox.checked) { - if (this.hasMultiSelection()) //do not remove last from selection - this.removeFromSelection(record); - } - else - this.addToSelection(record); - - case dojo.keys.ENTER: - this.invokeDefaultAction(); - break; - case dojo.keys.DOWN_ARROW : - var next = this.getRowForNode(record.domNode.nextElementSibling); - if (next) { - if (!this._inMultiSelectMode) - this.setSelection(next); - } - else - this.nextPage(); - break; - case dojo.keys.UP_ARROW : - var prev = this.getRowForNode(record.domNode.previousElementSibling); - if (prev) { - if (!this._inMultiSelectMode) - this.setSelection(prev); - } - else - this.prevPage(); - break; - case dojo.keys.PAGE_UP: - case dojo.keys.LEFT_ARROW: - this.prevPage(); - break; - case dojo.keys.PAGE_DOWN: - case dojo.keys.RIGHT_ARROW: - this.nextPage(); - break; - } - } - if (handled) - dojo.stopEvent(e); - }, - - invokeDefaultAction : function() { - for(var i = 0, a = null; a = this.actions[i++];) - if (a.actisdefault && a.appliesToSelection()) - a.invokeOnSelection(); - }, - - onRowMouseOver : function(target, e) { - if (target !== this._hoveredRow) { - this._hoveredRow && dojo.removeClass(this._hoveredRow, 'gv_row_hover'); - dojo.addClass(target, 'gv_row_hover'); - this._hoveredRow = target; - } - - e.preventDefault(); - dojo.stopEvent(e); - return false; //stop further events - }, - - getRowForNode : function (node) { - if (!node) - return null; - if (dojo.hasClass(node, "gv_row")) // EvdP: yes, functionality now depends on a classname, but this can only be avoided by using parentNode, which is errorprone for changes. - return mxui.dom.data(node, "data"); - return this.getRowForNode(node.parentNode); - }, - - labelClick : function(node) { - var isClose = dojo.hasClass(node, 'gv_label_close'); - node = node.parentNode; - - //assuming dataset: { owner: record, guid : guid, dataset: this.columneditdataset, colindex: this.colindex } - var data = mendix.dom.data(node, "data"); - var record = data.owner; - var guid = data.guid; - var dataset = this.dataset[data.dataset]; - var rnd = this.columns[data.colindex]; - if (!(record && guid && dataset && rnd)) - this.showError("Unable to handle labelclick!"); - - if (isClose) - rnd.applyChange(record, guid, true); - - //clicked on a searchable label? - else if (dataset === this.dataset[this.searchlabeldataset]) - this.searchControl.setSearchFilter('', dataset.existingLabelsById[guid]); - else { - //TODO: onclick label action - } - }, - - columnClick : function(node) { - var col = mxui.dom.data(node, 'colindex'); - var record = this.getRowForNode(node); - - this.columns[col].invokeAction(record); - }, - - setCurrentSortColumn : function(index) { - if (this.colheads[index].getSortAttr()) { - //MWE: note that colhead index is one less the the TH index in the domTree - var colnode = this.headerRow.childNodes[index + 1]; - - if (index === this.currentSortColumn) { - this.sortInverted = !this.sortInverted; - dojo.removeClass(colnode, 'gv_sort_up gv_sort_down'); - - } - else { - if (this.currentSortColumn > -1) - dojo.removeClass(this.headerRow.childNodes[this.currentSortColumn +1], 'gv_sortcolumn gv_sort_up gv_sort_down'); - - this.currentSortColumn = index; - this.sortInverted = false; - } - - dojo.addClass(colnode, 'gv_sortcolumn ' + ("asc" === this.colheads[index].getSortDir() ^ this.sortInverted ? 'gv_sort_up' : 'gv_sort_down')); - this.resetAndFetchAll(); - } - //else - // this.configError("Invalid (default) sort column. The column does not exists or no sort attribute has been defined"); - }, - - - /* - - - - HouseHolding - - - - */ - - inputargs : { - //data model properties - tabindex : -1, - entity : '', - datasourcemf : '', - datasourceoffsetattr : '', - datasourcelimitattr : '', - datasourcecountattr : '', - datasourcesearchattr : '', - constraint : '', - - //display properties - colheadname : '', - colheadcaption : '', - colheadwidth : '', - colheadsortattr : '', - colheadsortdir : '', - - //rendering properties - columnindex : '', - columnname : '', - columndataset : '', - columnrendermode: '', - columnattr : '', - columnimage: '', - columnaction: '', - columnclazz: '', - columnstyle: '', - columndateformat: '', - columnissearchattr : '', - columntruecaption : '', - columnfalsecaption : '', - columneditable : '', - columneditdataset : '', - columneditautocommit : '', - columnonchangemf : '', - columncondition : '', - columnprefix : '', - columnpostfix: '', - - //action properties - actname : '', - actshowbutton : '', - actclassname : '', - actautohide : '', - actbuttoncaption : '', - actbuttonimage : '', - actmf : '', - actmultimf : '', - actisdefault : '', - actonselect : '', - actconfirmtext : '', -// actonselect : '', - actnoselectionmf : '', - actdataset : '', - actappliestomultiselection : '', - actprogressmsg : '', -// actshortcut : '', - - //filters - filterattr : '', - filtertruecaption : '', - filterfalsecaption : '', - filterbooleandefault : '', - - //advanced settings - allowmultiselect : false, - allowsingleselect : true, - selectfirstrow : false, - defaultsortcolumn : 0, - pagesize : 20, - refreshoncontext : false, - refreshonclass : true, - searchenabled : true, - searchplaceholder : '', - searchlabeldataset: '', - realtimesearch : false, - emptymessage : '', - selectionref : '', - selectionrefset : '', - colheaderenabled : true, - enableschema : true, - showtotals : true, - itemcountmessage : '', - showasdiv : false, - listenchannel : '', - singleclickdefaultaction : false, - - //related datasets - relname : '', - rellabel : '', - relentity : '', - relcontextassoc : '', - relitemassocref : '', - relitemassocrefset : '', - relnameattr : '', - relnewitemcaption : '', - relconstraint : '', - - //conditions - condname : '', - condattr : '', - condvalues : '', - condclass : '' - - }, - - startup : function() { - if (this._started) //MWE: RVH said this can happen - return; - - TreeView.widget.Commons.fixObjProps(this, ["blaat0", "blaat2", "blaat3", "blaat4", "blaat7", "blaat8"]) - - this._started = true; - this.records = []; - this._multiSelection = []; - - this.columns = []; - this.colheads = []; - this.conditions = {}; - this.actions = []; - - this.actionsByName = {}; - - - this.verifyDatasourceSettings(); - - this.setupDatasets(); - this.setupConditions(); - - this.setupLayout(); - this.setupColumns(); - - this.setupActions(); - - this.setupRendering(); - this.setupEvents(); - - - if (this.refreshonclass) - this.subscribe({ - 'entity' : this.entity, - callback : dojo.hitch(this, function() { - this.fetchAll(); - }) - }); - - if (this.searchenabled) { - this.searchControl = new TreeView.widget.SearchControl({ - searchplaceholder : this.searchplaceholder, - labelentity : this.labelentity, - labelcontextassoc : this.labelcontextassoc, - labelitemassoc : this.labelitemassoc, - labelnameattr : this.labelnameattr, - dataset : !this.searchlabeldataset ? null : this.dataset[this.searchlabeldataset], - realtime : this.realtimesearch - }, this); - - dojo.place(this.searchControl.domNode, this.searchbarNode); - } - - this.setupFilters(); - - //triggers data retrieval as well! - if (this.defaultsortcolumn < 0 || this.defaultsortcolumn >= this.colheads.length || !this.colheads[this.defaultsortcolumn].getSortAttr()) - if (!this.datasourcemf) - this.configError("Invalid default sort column. The column does not exists or no sort attribute has been defined"); - - //listening formloader - if (this.listenchannel) - this.connect(this, 'onSelect', function(item) { - dojo.publish(this.getContent() + "/"+this.listenchannel+"/context", [!item ? null : item.data()]); - }); - - this.setCurrentSortColumn(this.defaultsortcolumn); - if (this.selectfirstrow) - this.selectFirstItem(); - - this.actLoaded(); - - //this.grabStartupFocus(); - - setTimeout(dojo.hitch(this, this.grabStartupFocus), 200); //grab focus, but with a small timeout, because wm content manager will grab focus at end of startup chain - }, - - verifyDatasourceSettings : function() { - if (this.datasourcemf) { - if (!(this.datasourceoffsetattr && this.datasourcelimitattr && this.datasourcecountattr)) - this.configError("Offset, Limit and Count attributes need to be set if a data source microflow is used."); - - if (this.searchenabled && !this.datasourcesearchattr) - this.configError("Search attribute s need to be set if a data source microflow is used and search is enabled."); - - if (this.defaultsortcolumn != 0) - this.configError("Sorting is not supported if a data source microflow is used."); - - if (this.relname) - this.configError("Datasets are not allowed if a datasource microflow is used"); - - if (this.filterattr) - this.configError("Filters are not allowed if a datasource microflow is used"); - - if (this.entityConstraint) - this.configError("An entity constraint and datasource microflow cannot be used at the same time"); - } - }, - - setupLayout : function() { - dojo.addClass(this.domNode, 'gv_grid'); - if (this.showasdiv) - dojo.addClass(this.domNode, 'gv_floating_grid'); - - this.headerNode = mendix.dom.div({'class' : 'gv_header'}); - - this.gridNode = mendix.dom[this.showasdiv ? "div" : "table"]({'class': 'gv_table'}); - - this.headerRow = mendix.dom[this.showasdiv ? "div" : "tr"]({'class':'gv_headrow'}, mendix.dom[this.showasdiv ? "div" : "th"]({ 'class' : 'gv_multiselect_column_head gv_th gv_th_0'})); - var header = mendix.dom[this.showasdiv ? "div" : "thead"]({'class':'gv_gridhead'}, this.headerRow); - - dojo.addClass(this.domNode, this.colheaderenabled ? 'gv_columnheaders' : 'gv_nocolumnheaders'); - dojo.addClass(this.domNode, this.allowmultiselect ? 'gv_multiselect_enabled' : 'gv_multiselect_disabled'); - - dojo.place(header, this.gridNode, 'first'); - - this.footerNode = mendix.dom.div({'class' : 'gv_footer'}); - this.pagingNode = mendix.dom.div({'class' : 'gv_paging'}); - dojo.place(this.pagingNode, this.footerNode); - - this.searchbarNode = mendix.dom.div({'class' : 'gv_searchnode'}); - dojo.place(this.searchbarNode, this.headerNode); - - dojo.place(this.headerNode, this.domNode); - dojo.place(this.gridNode, this.domNode); - dojo.place(this.footerNode, this.domNode); - - dojo.attr(this.gridNode, { - tabindex : this.tabindex, - focusindex : 0 - }); - - mxui.wm.focus.addBox(this.gridNode); - this.grabFocus(); - }, - - - - setupColumns : function() { - var data = []; - this.splitPropsTo('colheadname,colheadcaption,colheadwidth,colheadsortattr,colheadsortdir', data); - for(var i = 0, d = null; d = data[i]; i++) { // EvdP: what kind of weird loop is this? d = data[i] almost seems an error on first sight. Why not just use data.length to check? - d.colindex = i; - var colhead = new TreeView.widget.Colhead(d, this); - this.colheads.push(colhead); - } - - dojo.forEach(this.colheads, function(colhead) { - colhead.setup(this.headerNode); - }, this); - }, - - setupDatasets : function() { - this.dataset = {}; - var data = []; - this.splitPropsTo('relname,rellabel,relnewitemcaption,relentity,relcontextassoc,relitemassocref,relitemassocrefset,relnameattr,relconstraint', data); - dojo.forEach(data, function(item) { - if (this.dataset[item.relname]) - this.configError('Related dataset "' + item.relname + '" is defined twice!'); - var r = new TreeView.widget.RelatedDataset(item, this); - this.dataset[item.relname] = r; - this.addToSchema((item.relitemassocref || item.relitemassocrefset) + "/" + item.relnameattr); - }, this); - }, - - setupActions : function() { - var data = []; - this.splitPropsTo('actname,actprogressmsg,actshowbutton,actclassname,actautohide,actbuttoncaption,actconfirmtext,actbuttonimage,actmf,actmultimf,actappliestomultiselection,actisdefault,actonselect,actnoselectionmf,actdataset', data); - for(var i = 0, d = null; d = data[i]; i++) { // EvdP: what kind of weird loop is this? d = data[i] almost seems an error on first sight. Why not just use data.length to check? - - if (d.actmultimf && !!!d.actmf) - this.configError(d.actname + ": Actions that define a multi selection microflow need to define a single selection microflow as well. "); - - var action = new TreeView.widget.Action(d, this); - - this.actions.push(action); - this.actionsByName[action.actname] = action; - } - - dojo.forEach(this.actions, function(action) { - action.setup(this.headerNode); - action.updateToSelection(); - }, this); - }, - - setupConditions : function() { - var data = []; - this.splitPropsTo('condname,condattr,condvalues,condclass', data); - for(var i = 0, d = null; d = data[i]; i++) { // EvdP: what kind of weird loop is this? d = data[i] almost seems an error on first sight. Why not just use data.length to check? - var cond = new TreeView.widget.Condition(d, this); - if (this.conditions[d.condname]) - this.configError("Condition name '" + d.condname + "' is not unique!"); - - this.conditions[d.condname] = cond; - this.addToSchema(d.condattr); - } - }, - - setupFilters: function() { - var data = []; - this.splitPropsTo('filterattr,filtertruecaption,filterfalsecaption,filterbooleandefault', data); - - var fm = this.filterManager = new TreeView.widget.FilterManager(this); - - this.filters = dojo.map(data, function(d) { - return new TreeView.widget.Filter(d, fm); - }, this); - - if (this.filters.length > 0) - dojo.place(fm.domNode, this.actions.length > 0 || !this.searchenabled ? this.headerNode : this.searchbarNode, 'last'); - }, - - setupRendering : function() { - var data = []; - this.splitPropsTo('columnindex,columnname,columnrendermode,columnattr,columnimage,columneditautocommit,columnonchangemf,columnaction,columnclazz,columnstyle,columnprefix,columnpostfix,columndateformat,columnissearchattr,columntruecaption,columnfalsecaption,columneditdataset,columneditable,columncondition', data); - for(var i = 0, d = null; d = data[i]; i++) { // EvdP: what kind of weird loop is this? d = data[i] almost seems an error on first sight. Why not just use data.length to check? - d.columnentity = this.entity; - this.columns.push(new TreeView.widget.Colrenderer(d, this, i)); - if (d.columnindex * 1 >= this.colheads.length) - this.configError(this.id + " column index out of bounds: " + d.columnname); - - if (d.columnaction && !(d.columnaction in this.actionsByName)) - this.configError(this.id + " refers to unknown action " + d.columnaction); - - if (d.columnrendermode === "thumbnail" || d.columnrendermode === "systemimage") { - //Add fileID and changedDate to schema - if (!d.columnattr) - this.addToSchema("FileID"); - - this.addToSchema(d.columnattr.replace(/FileID/, "") + "changedDate"); - } - - this.addToSchema(d.columnattr); - } - }, - - _schema : null, - addToSchema : function( attr) { - if (!attr) - return; - - if (!this._schema) - this._schema = { references : {}, attributes : [] }; - - if (attr.indexOf("/") > -1) { - - var parts = attr.split("/"); - - if (!(parts[0] in this._schema.references)) - this._schema.references[parts[0]] = { attributes : []}; - - if (parts.length > 2) - this._schema.references[parts[0]].attributes.push(parts[2]); - } - else { - this._schema.attributes.push(attr); - } - }, - - showError : function(e) { - TreeView.widget.Commons.error(e, this); - }, - - mf : function(mf, data, callback) { - TreeView.widget.Commons.mf(mf, data, callback, this); - }, - - configError : function(msg) { - TreeView.widget.Commons.configError(this, msg); - }, - - splitPropsTo : function(props, target) { - TreeView.widget.Commons.splitPropsTo(this, props, target); - }, - - uninitialize : function() { - if (this.searchControl) - this.searchControl.free(); - if (this.filterManager) - this.filterManager.free(); - - dojo.forEach(this.columns, function(column) { - column.free(); - }); - dojo.forEach(this.actions, function(action) { - action.free(); - }); - } -}); \ No newline at end of file diff --git a/src/TreeView/widget/GridView/ColHead.js b/src/TreeView/widget/GridView/ColHead.js new file mode 100644 index 0000000..f3a3099 --- /dev/null +++ b/src/TreeView/widget/GridView/ColHead.js @@ -0,0 +1,56 @@ +define([ + "dojo/_base/declare", + "dojo/dom-style" +], function(declare, domStyle) { + "use strict"; + + return declare("TreeView.widget.GridView.ColHead", null, { + domNode : null, //correspinding render nodes + grid : null, + + constructor : function(data, grid) { + logger.debug("TreeView.widget.GridView.ColHead.constructor"); + this.grid = grid; + this.data = data; + + }, + + setup : function(rownode) { + logger.debug("TreeView.widget.GridView.ColHead.setup"); + this.domNode = mxui.dom.create(this.grid.showasdiv ? "div" : "th", { + "class": "gv_th gv_th_" + + (1 + this.data.colindex) + + (this.data.colheadname ? " gv_th_" + this.data.colheadname.replace(/\W+/g,"") : "") + + (this.data.colheadsortattr ? " gv_th_sortable" : " gv_th_unsortable") + }); + if (this.data.colheadwidth){ + domStyle.set(this.domNode, "width", this.getWidth()); + } + //sort caption? + if (this.data.colheadcaption){ + dojo.place(mxui.dom.create("span", {"class" : "gv_sort_caption"}, this.data.colheadcaption), this.domNode); + } + //show sort arrow? + if (this.getSortAttr()){ + dojo.place(mxui.dom.create("span", {"class" : "gv_sort_arrow"}), this.domNode); + } + + dojo.place(this.domNode, this.grid.headerRow); + }, + + getWidth : function() { + return this.data.colheadwidth; + }, + + getSortAttr : function() { + return this.data.colheadsortattr; + }, + + getSortDir : function() { + return this.data.colheadsortdir; + }, + + free : function() { + } + }); +}); diff --git a/src/TreeView/widget/GridView/Record.js b/src/TreeView/widget/GridView/Record.js new file mode 100644 index 0000000..9feb956 --- /dev/null +++ b/src/TreeView/widget/GridView/Record.js @@ -0,0 +1,163 @@ +define([ + "dojo/_base/declare", + "dojo/_base/lang", + "dojo/dom-style" +], function(declare, lang, domStyle) { + "use strict"; + + return declare("TreeView.widget.GridView.Record", null, { + domNode: null, //correspinding render nodes + grid: null, + guid: null, + + _data: null, + _subscription: null, + _colNodes: null, //array with columnNodes + _subs: null, + checkbox: null, //checkbox node + + constructor: function (data, grid) { + logger.debug("TreeView.widget.GridView.Record.constructor"); + this.guid = data.getGuid(); + this._data = data; + this._colNodes = []; + this._subs = []; + + this.grid = grid; + + this._subscription = mx.data.subscribe({ + guid: this.guid, + callback: lang.hitch(this, function (thing) { + //Do not update while suspended; all data will be fetch upon resume. + if (this.grid.isSuspended()) + return; + + if (this.grid.datasourcemf) { + //microflow data? retrieve by id + mx.data.get({ + guid: this.guid, + callback: lang.hitch(this, function (data) { + this.update(data); + }), + error: grid.showError + }); + } + else { + //xpath datasource? retrieve by xpath, the object might no longer be in the grid constraint + mx.data.get({ + xpath: grid.buildXpath() + "[id = \"" + this.guid + "\"]", + filter: grid.enableschema ? grid._schema : {}, + callback: lang.hitch(this, function (data) { + if (data.length > 0) + this.update(data[0]); + }), + error: grid.showError + }); + } + }) + }); + + }, + + data: function () { + return this._data; + }, + + update: function (data, firstTime) { + logger.debug("TreeView.widget.GridView.Record.update"); + this._data = data; + + var curCol = 0; + for (var i = 0, col = null; col = this.grid.columns[i]; i++) { + var node = this._colNodes[curCol]; + col.render(this, node, firstTime === true); + curCol += 1; + } + + for (var key in this.grid.conditions) { + var condition = this.grid.conditions[key]; + var clz = condition.getClass(); + if (clz) { //checking if class is cheaper than checking the condition itself + condition.appliesToAsync(this, lang.hitch(this, function (applies) { + if (applies) + dojo.addClass(this.domNode, clz); + else + dojo.removeClass(this.domNode, clz); + })); + } + } + }, + + setup: function (tablenode) { + this.domNode = mxui.dom.create(this.grid.showasdiv ? "div" : "tr", {"class": "gv_row gv_row_" + tablenode.childElementCount}); + + if (this.grid.showasdiv && this.grid.colheads.length > 0 && this.grid.colheads[0].getWidth()) + domStyle.set(this.domNode, "width", this.grid.colheads[0].getWidth()); + + mxui.dom.data(this.domNode, "data", this); + + this.checkbox = mxui.dom.create("input", { + "type": "checkbox", + "class": "gv_multiselect_checkbox", + "style": this.grid.allowmultiselect === true ? "" : "display:none" + }); + + dojo.place(mxui.dom.create(this.grid.showasdiv ? "div" : "td", { + "class": "gv_cell gv_cell_0 gv_cell_multiselect" + }, this.checkbox), this.domNode); + + //create td"s + for (var i = 0; i < this.grid.colheads.length; i++) { + var cell = mxui.dom.create(this.grid.showasdiv ? "div" : "td", { + "class": "gv_cell gv_cell_" + this.grid.colheads[i].data.colheadname + " gv_cell_" + i + }); + var colwrapper = mxui.dom.create("div", {"class": "gv_cell_wrapper"}); + + dojo.place(colwrapper, cell); + dojo.place(cell, this.domNode); + } + + //create renderers + for (i = 0; i < this.grid.columns.length; i++) { + var col = this.grid.columns[i]; + + if (1 * col.columnindex >= this.grid.colheads.length) + this.configError("Column index out of bounds: " + col.columnindex); + + var span = mxui.dom.create("span", {"class": "gv_column gv_column_" + i}); + this._colNodes.push(span); + + //wrapper node + var cw = mxui.dom.create("span", {"class": "gv_column_wrapper"}, span); + dojo.place(cw, this.domNode.childNodes[1 + 1 * col.columnindex].children[0]); + + col.setupNode(span); + } + + this.update(this._data, true); + dojo.place(this.domNode, tablenode); + }, + + addSubscription: function (subscription) { + logger.debug("TreeView.widget.GridView.Record.addSubscription"); + this._subs.push(subscription); + }, + + free: function () { + logger.debug("TreeView.widget.GridView.Record.free"); + if (this._destroyed) + return; + this._destroyed = true; + + dojo.forEach(this._subs, function (sub) { + dojo.disconnect(sub); + }); + + dojo.destroy(this.domNode); + + if (this._subscription) + mx.data.unsubscribe(this._subscription); + + } + }); +}); diff --git a/src/TreeView/widget/TreeView.js b/src/TreeView/widget/TreeView.js index 125e641..5572efe 100644 --- a/src/TreeView/widget/TreeView.js +++ b/src/TreeView/widget/TreeView.js @@ -1,2175 +1,1493 @@ -dojo.require("TreeView.widget.Commons"); -/** - * Edge stores a single assocation from the database, e.g. guid -> assocname -> guid. - * Parent / child is stored according to the rendering, not according to the owner. - */ -dojo.declare("TreeView.widget.Edge", null, { - parent : null, //Graphnode - name : '', //assocname - child : null, //Graphnode - owner : null, //Graphnode, either parent or child - type : null, //type in tree - tree : null, - index : -1, - - constructor : function(type, parent, child, owner) { - this.type = type; - this.name = type.assoc; - this.parent= parent; - this.child = child; - this.owner = owner; - this.tree = this.parent.tree; - - this.child._refs += 1; //increase the number of references - - //add the node for every known parent - parent.forNodes(function(parentRenderNode) { - if (parentRenderNode.children[type.index].collapsed == false) //already expanded parent, add this edge.. - new TreeView.widget.RenderNode(child, parentRenderNode, type); - }); - }, - - updateIndex : function(newindex) { - if (this.index != newindex) { - var edge = this; - - this.parent.forNodes(function (parentNode) { - edge.child.forNodes(function(childNode) { - parentNode.children[edge.type.index].move(childNode, newindex); - }); - }); - - this.index = newindex; - } - }, - - free : function(){ - if (this._destroyed) - return; - this._destroyed = true; - - //1. remove the corresponding child render nodes for every know parent, but only where the parent is the parent of this edge! - var edge = this; - - //remove every rendering of this parent -> assoc -> child - this.parent.forNodes(function (parentNode) { - edge.child.forNodes(function (childNode) { - parentNode.children[edge.type.index].remove(childNode); - }); - }); - - //2. reduce the number of refs for child, remove if necessary - this.child.free(); - - //3. Remove edges from the tree cache structure - this.tree.freeEdge(this); - } -}); +require([ + "dojo/_base/declare", + "mxui/widget/_WidgetBase", + "dojo/_base/lang", + "dojo/dom-attr", + "dojo/dom-style", + "TreeView/widget/Commons", + "TreeView/widget/Commons/ColRenderer", + "TreeView/widget/TreeView/Edge", + "TreeView/widget/TreeView/GraphNode", + "TreeView/widget/Commons/Action", + "dojo/NodeList-traverse" +], function (declare, _WidgetBase, lang, attr, domStyle, Commons, ColRenderer, Edge, GraphNode, Action) { + "use strict"; + + + return declare("TreeView.widget.TreeView", [_WidgetBase], { + root: null, //render node + dict: null, //guid -> GraphNode + types: null, //type definitions : entityName -> config + useDnd: false, + + edgesByParent: null, //parentguid.associndex.childguid + edgesByOwner: null, //ownerguid.associndex.[idx] + _selection: null, + dnd: null, //dnd state + _hoveredRow: null, + _hoveredCol: null, + selectionrefs: null, + _parent: null, + + inputargs: { + //data model properties + tabindex: -1, + entity: "", + parentassocsingle: "", + parentassocmulti: "", + assoctype: "", + assoccaption: "", + showassocname: "", + constraint: "", + allowdnd: "", + allowdndcopy: "", + dropmf: "", + assocclazz: "", + assocstyle: "", + + //display properties + columnname: "", + columnentity: "", + columnrendermode: "", + columnattr: "", + columnimage: "", + columnaction: "", + columnclazz: "", + columnstyle: "", + columndateformat: "", + columnprefix: "", + columnpostfix: "", + columntruecaption: "", + columnfalsecaption: "", + + //action properties + actname: "", + actentity: "", + actshowbutton: "", + actautohide: "", + actbuttoncaption: "", + actbuttonimage: "", + actconfirmtext: "", + actmf: "", + actisdefault: "", + actonselect: "", + actnoselectionmf: "", + actshortcut: "", + actprogressmsg: "", + + //Selection references + selectionref: "", + + //advanced settings + xentity: "", + xburstattr: "", + xlisteningform: "", + sortattr: "", + sortdir: "", + entityclazz: "", + entitystyle: "", + entitychannel: "", + allowmultiselect: false, + selectionrefset: "", + + //general properties + expandall: 1, + prefetch: true, + hiderootnode: true, + expandmode: "arrow", //arrow | row | accordion + multiselect: false + }, + + constructor: function () { + logger.debug("TreeView.widget.TreeView.startup"); + + this.dict = {}; + this.types = []; + this.columns = []; + this.actions = []; + this.actionsByName = {}; + this.dnd = {}; + + this.edgesByParent = {}; + this.edgesByOwner = {}; + + this.xsettings = []; + }, + + postCreate: function () { + Commons.fixObjProps(this, ["blaat4", "blaat", "blaat2", "blaat3", "blaat5"]); + + this.splitPropsTo("xentity,xburstattr,sortattr,sortdir,entityclazz,entitystyle,entitychannel", this.xsettings); + for (var i = 0; i < this.xsettings.length; i++) { + var x = this.xsettings[i]; + x.entitystyle = x.entitystyle.split(/\||\n/).join(";"); + x.filter = { references: {}, attributes: [] }; + + this.addToSchema(x.xentity, x.xburstattr); + this.addToSchema(x.xentity, x.sortattr); + + if (x.entitychannel) { + var onSelectHandler = + this.connect(this, "onSelect", lang.hitch(this, function (channel, entity, selection) { + if (selection != null && selection.isA(entity)) { + // KVL: This used to use this.getContent() as the first part, but that no longer exists. + // Not sure what the alternative is, so we might want to look into that... + dojo.publish("/" + channel + "/context", [selection.data()]); + } else { + // KVL: This used to use this.getContent() as the first part, but that no longer exists. + // Not sure what the alternative is, so we might want to look into that... + dojo.publish("/" + channel + "/context", [null]); + } + }, x.entitychannel, x.xentity)); + } + } + + if (this.selectionref && this.selectionref != "") { + this.selectionrefs = this.selectionref.split(";"); + } + + + this._setupTypes(); + + this._setupLayout(); + this._setupActions(); + + this._setupColumns(); + this._setupEvents(); + }, + + /* context applied */ + update: function (data, cb) { + logger.debug("TreeView.widget.TreeView.update"); + + var guid = (data && data !== undefined) ? data.getGuid() : null; + if (this.root != null && this.root.guid == guid) { //already the root, just refresh + this.processData([data]); + } else { + if (!this.getXsettings(data.getEntity())) { + this.configError("The context of this widget is a '" + data.getEntity() + "', but this type is not further defined in the entities property of the treeview"); + } + + if (this.root) { + this.root.free(); + } + + this.dict[guid] = new GraphNode(this, data, true); + this.root.forNodes(function (node) { + node.draw(); + + //unfold roots by default (note, should be one) + node.setCollapsed(false); + }); + + this.selectFirstItem(); + } + + cb && cb(); + }, + + + uninitialize: function () { + logger.debug("TreeView.widget.TreeView.uninitialize"); + + if (this.root) { + this.root.free(); + } + + if (this.searchControl) { + this.searchControl.free(); + } + + dojo.forEach(this.columns, function (column) { + column.free(); + }); + + dojo.forEach(this.actions, function (action) { + action.free(); + }); + }, + + /* MWE: not sure if these suspended are supposed here, or where deliberately deleted before merging with main branch.. + if it gives issues, the methods suspended / resumed should be deleted probably*/ + suspended: function () { + + }, -/** - * Graphnode refers to an single instance of an object. Edges are not stored here, but globally in the tree. - * A single graphnode might have multiple render nodes, for every time it is viewed in the tree. - */ -dojo.declare("TreeView.widget.GraphNode", null, { - nodes : null, //correspinding render nodes - tree : null, - guid : null, - type : '', - children : null, //stores the state of child edges per type - - _data : null, - _refs : 0, //number of parents referring to this node, if zero, free - _burst : null, - _subscription : null, - - constructor : function(tree, data, asRoot) { - this.guid = data.getGUID(); - tree.dict[this.guid] = this; - this.type = data.getClass(); - this._data = data; - - this.tree = tree; - this.xsettings = this.tree.getXsettings(this.type); - this.nodes = []; - - //store childtypes - this.children = []; - - var ar = this.getChildTypes(); - for(var i = 0; i < ar.length; i++) { - this.children[ar[i].index] = { - knowsChildren : false, - type : ar[i], - //householding for retrieving children - _afterChildrenCb : null, - _retrieving : false - } - } - - this.update(data); - - this._subscription = mx.processor.subscribe({ - guid : this.guid, - callback : dojo.hitch(this, function(thing) { - if (dojo.isObject(thing)) - this.updateWithRefs(thing); - else - mx.processor.get({ - guid : thing, - error: this.tree.showError, - callback : dojo.hitch(this, this.updateWithRefs) - }); - }) - }); - - if (asRoot == true) { - this.tree.root = this; - this._refs += 1; //additional ref for roots, to not accidentaly free them. - //place the node - var n = new TreeView.widget.RenderNode(this, null, null); //root has no assoctype associated... - } - }, - - /** convenient helper method */ - isA : function(type) { - return this._data.isA(type); - }, - - getSortIndex : function() { - if (this.xsettings) - return parseInt(this._data.get(this.xsettings.sortattr), 10); - return -1; - }, - - updateWithRefs : function(data) { - this.update(data); - this.updateEdges(); - }, - - update : function(data) { - this._data = data; - - this.checkBurst(); - - //redraw content data - this.forNodes(function(node) { - node.draw(); - }); - }, - - updateEdges : function() { - var data = this._data; - //1. mark edges stored in here invalid - var owningEdges = this.tree.getEdgesOwnedBy(this); - - dojo.forEach(owningEdges, function(edgeSet) { - if (edgeSet) - dojo.forEach(edgeSet, function(edge) { - edge._valid = false; - }); - }); - - //2. update edges owned by this object - for(var i = 0; i < this.tree.types.length; i++) { - var type = this.tree.types[i]; - if (data.isA(type.ownerentity)) { - - var rawdata = data.get(type.assoc); - var ids = this.tree.toMap(rawdata); - var unknown = []; - - for (var id in ids) { - var other = this.tree.dict[id]; - - var isChild = type.assoctype == "fromchild"; - if (other == null) { - //2a. handle unknown 'other side' - if (isChild) //skip parents not available in the tree - continue; - else { - unknown.push(id); - } - } - - if (other != null) { - - //Pre 2b: special case, if the type has a constraint and a cache burst, - //we are not sure that the currently updated object passes the constraint, so rely on the cacheburst to show this item in that parent - //if we are not loading exactly that relation, do not automatically create that edge - // - //see _retrieveChildrenCore for more about the _currentLoadingRel flag. - var nocreate = false; - if (type != this.tree._currentLoadingRel && isChild && type.constraint.length) { - var xsettings = this.tree.getXsettings(type.parententity) - if (xsettings != null && xsettings.xburstattr) - nocreate = true; - } - - //2b. ..find or create edge, mark valid - var edge = this.tree.findOrCreateEdge(type, isChild ? other : this, isChild ? this : other, this, nocreate); - if (edge) - edge._valid = true; - } - } - - //3. fetch the associations from this to child which are not known yet - this._fetchUnknownChildrenHelper(type, unknown); - } - } - - //4. remove invalid edges - dojo.forEach(owningEdges, function(arr) { - if (arr) - for(var i = arr.length -1 ; i >= 0; i--) //MWE: count down, items might be free-ed on the fly - if (!arr[i]._valid) - arr[i].free(); - }); - }, - - /** - * check whethers a burst attribute, as defined in the childtypes, has changed. If so, invalidate children - * TODO: move childeges to children - */ - checkBurst : function() { - var x = this.xsettings; - if (x != null && x.xburstattr) { - var newburst = this._data.get(x.xburstattr); - if (newburst != this._burst) { - this._burst = newburst; - dojo.forEach(this.children, function(edgeSet) { - if (edgeSet) - edgeSet.knowsChildren = false; - }); - - this.forNodes(function(node) { - if (node.collapsed == false || node == this.tree.getSelection()) { //re-draw / fetch expanded nodes - - //re-expand all assocs in the node - dojo.forEach(node.children, function(re) { - if (re && re.collapsed == false) { - re.collapsed = true; - re.setCollapsed(false, function() { - node.tree.processRecordSelectionSuggestion(); - }); - } - }); - } - }); - } - } - }, - - forNodes : function(func) { - var l = this.nodes.length; - for (var i = 0; i < l; i++) - func.call(this, this.nodes[i]); - }, - - /** - * returns all types which can be a child of this node (independent of owner) - * @return {[type]} - */ - getChildTypes : function() { - var res = []; - for(var i = 0; i < this.tree.types.length; i++) { - var type = this.tree.types[i]; - - if (this.isA(type.parententity)) - res.push(type); - } - return res; - }, - - //if a ref(set) was changed, and it refers to unknown ids, fetch them. - _fetchUnknownChildrenHelper : function(type, guids) { - if (guids.length == 0) //no guids - return; - if ( !this.children[type.index].knowsChildren) //never expanded, we need to fetch anyway on the next expand, and the edges will be created by the retrieve. Skip for now - return; - - var args = { - xpath : xpath, - filter : type.filter, - callback : dojo.hitch(this, function(data) { - this.tree.processData(data); //data should always be one (or zero if not in constraint) - - for(var i = 0; i < data.length; i++) { //create the assocations - var e = this.tree.findOrCreateEdge(type, this, this.tree.dict[data[i].getGUID()], this); - e._valid = true; - } - }), - error: this.tree.showError - } - - //Question: should the constraint be applied? - //- Yes: that is more consistent. - //- No: allows for unsaved objects to be shown in the tree if added to the parent - //Current implementation: yes if a constraint is used, otherwise, guids is used - - if (type.constraint) { - var xpath = "//" + type.entity + "[id='" + guids.join("' or id='").substring(5) + "']" + type.constraint; - args.xpath = xpath.replace(/\[\%CurrentObject\%\]/gi, this.tree.root.guid); - } - else { - args.guids = guids; - } - - mx.processor.get(args); - }, - - ensureChildren : function(type, callback) { - var c = this.children[type.index]; - if (c.knowsChildren) - callback && callback(); - - else if (c._retrieving) - callback && c._afterChildrenCb.push(callback); - - else { - c._retrieving = true; - c._afterChildrenCb = callback ? [callback] : []; //event chain - - this._retrieveChildrenCore(c, dojo.hitch(this, function() { - c._retrieving = false; - c.knowsChildren = true; - - var f; - while(f = c._afterChildrenCb.shift()) - f(); - })); - } - }, - - _retrieveChildrenCore : function(c, callback) { - var type = c.type; - - //self references leaving from the parent need a recursive constraint - var reverse = type.recursive && type.assoctype == 'fromparent' ? '[reversed()]' : ''; - var xpath = "//" + type.entity + "[" + type.assoc + reverse + " = '" + this.guid + "']" + type.constraint; - xpath = xpath.replace(/\[\%CurrentObject\%\]/gi, this.tree.root.guid); - - var kwargs = { - xpath : xpath, - filter : this.tree.getXsettings(type.entity).filter, - callback : dojo.hitch(this, function(rel, data) { - - //1. mark edges from here in here invalid - var edges = this.tree.getChildEdges(this)[rel.index]; - for(var childguid in edges) - edges[childguid]._valid = false; - - try { - //if a cacheburst is used, child to parent edgets are not created automatically to avoid byposing constraints. - //however, here, we now that the constraint is allowed to be bypassed since we are loading exactly that relation - //store this in flag. - this.tree._currentLoadingRel = rel; - this.tree.processData(data); - } - finally { - delete this._currentLoadingRel; - } - - //3. create the edge if from parent, update index, update valid state - for(var i = 0; i < data.length; i++) { - var guid = data[i].getGUID(); - var child = this.tree.dict[guid]; - - var edge = - type.assoctype == 'fromparent' - //3a. if this object is the owner of the association, the edges are not created automatically by process data, create them now - ? this.tree.findOrCreateEdge(type, this, child, this) - //3b. otherise, still retrieve the edge to update the index if necessary - : this.tree.findOrCreateEdge(type, this, child, child); - - edge.updateIndex(i); - - //we now for sure it is valid now - edge._valid = true; - } - - //4. remove invalid children after receiving all data (no need to refetch them) - for(var childguid2 in edges) - if (!edges[childguid2]._valid) - edges[childguid2].free(); - - callback(); //wait with callback until all requests are completed - }, type), - error: this.tree.showError - }; - - var xsettings = this.tree.getXsettings(type.entity); - - if (!kwargs.filter) - kwargs.filter = {}; - - if (xsettings != null) - kwargs.filter.sort = [[ xsettings.sortattr, xsettings.sortdir ]]; - - //perform the get - mx.processor.get(kwargs); - }, - - - free : function() { - this._refs -= 1; - - if (this._refs <= 0) { - if (this._destroyed) - return; - this._destroyed = true; - - if (this._subscription) - mx.processor.unsubscribe(this._subscription); - - delete this.tree.dict[this.guid]; - - this.forNodes(function(node) { - node.free(); - }) - - //Question: which edges should be freed here? - //Answer: only the owning ones. Non owning ones should be freed as a result of refreshes of their objects - var owningEdges = this.tree.getEdgesOwnedBy(this); - dojo.forEach(owningEdges, function(ar) { - if (ar) - for(var i = ar.length -1; i >= 0; i--) - ar[i].free(); - }); - - delete this.tree.edgesByParent[this.guid]; - delete this.tree.edgesByOwner[this.guid]; - } - } -}); + resize: function () { + // stub function + }, + + resumed: function () { + logger.debug("TreeView.widget.TreeView.resumed"); + + //reapply selection to continue formloader + var sel = this._selection; + this._selection = null; + this.setSelection(this._selection); + }, + + _setupLayout: function () { + logger.debug("TreeView.widget.TreeView._setupLayout"); + + dojo.addClass(this.domNode, "gg_tree"); + this.headerNode = mxui.dom.create("div", { "class": "gg_header" }); + + this.treeNode = mxui.dom.create("ul", { "class": "gg_children gg_root_wrapper" }); + if (this.hiderootnode) { + dojo.addClass(this.treeNode, "gg_hiddenroot"); + } -/** - * Rendering of a group of children - */ -dojo.declare("TreeView.widget.RenderEdge", null, { - parent : null, //rendernode - type : null, //assoc type definition - domNode : null, - tree : null, - children : null, //array with children - childNode : null, //ul with the children - collapsed : null, - isEdge : true, - visible : false, - - constructor : function(parentRenderNode, type) { - this.parent = parentRenderNode; - this.type = type; - this.tree = this.parent.tree; - this.children = []; - - var childNode = this.childNode = mxui.dom.ul({'class' : 'gg_assoc_children gg_assoc_' + type.assoc.replace(".","_")}); - var wrapperNode = this.domNode = mxui.dom.li({'class' : 'gg_assoc_wrapper ' + type.assocclazz}); - - this.visible = type.showassocname; - if (this.visible) { - var fold = this.foldNode = mxui.dom.span({}); - var caption = mxui.dom.span({'class' : 'gg_assoc_title gg_assoc_' + type.assoc.replace(".","_")}, type.assoccaption); - var div = new mxui.dom.div({'class' : 'gg_row', 'style' : type.assocstyle }, fold, caption); - dojo.place(div, wrapperNode); - - dojo.addClass(childNode, 'gg_assoc_wrapped'); - dojo.addClass(wrapperNode, 'gg_node'); //only identify as node if a wrappernode is available - - mendix.dom.data(wrapperNode, 'ggdata', this); - } - - this.setCollapsed(true); - - dojo.place(childNode, wrapperNode); - dojo.place(wrapperNode, this.parent.childNode); - - if (this.tree.expandall > this.parent.depth) - this.setCollapsed(false); - }, - - isA : function(type) { - return false; //assoc node is never a type - }, - - getChildCount : function() { - return this.children.length; - }, - - add : function(renderNode) { - var guid = renderNode.graphNode.guid; - this.children.push(renderNode); - this.childNode.appendChild(renderNode.domNode); - - this.updateFoldVisibility(); - }, - - remove : function(renderNode) { - var baseidx = dojo.indexOf(this.children, renderNode); - if (baseidx > -1) { - this.children.splice(baseidx, 1); - if (renderNode.domNode) - dojo.destroy(renderNode.domNode); - } - - this.updateFoldVisibility(); - }, - - move : function(renderNode, newindex) { - var baseidx = dojo.indexOf(this.children, renderNode); - if (baseidx != -1 && baseidx != newindex) { - this.children.splice(baseidx, 1); - this.children.splice(newindex, 0, renderNode); - this.tree.moveNode(renderNode.domNode, newindex); - } - }, - - placeChildren : function() { - var edges = this.tree.getChildEdges(this.parent.graphNode)[this.type.index]; - for(var childguid in edges) { - var found = false; - for(var i = 0; i < this.children.length; i++) - if (this.children[i].graphNode.guid == childguid) { - found = true; - break; - } - - if (!found) - new TreeView.widget.RenderNode(this.tree.dict[childguid], this.parent, this.type); - } - }, - - setCollapsed : function(collapsed, cb) { - if (this.collapsed !== collapsed) { - this.collapsed = collapsed; - - //collapse - if (collapsed) { - dojo.style(this.childNode, 'display', 'none'); - if (this.foldNode) //if wrapper node not visible, there is no foldnode.. - dojo.attr(this.foldNode, 'class', 'gg_assocfold gg_fold gg_folded'); - cb && cb(); - } - - //expand - else { - dojo.style(this.childNode, 'display', 'block'); - if (this.foldNode) //if wrapper node not visible, there is no foldnode.. - dojo.attr(this.foldNode, 'class', 'gg_assocfold gg_fold gg_loading'); - - this.parent.graphNode.ensureChildren(this.type, dojo.hitch(this, function() { - if (!this.collapsed) { //user might have clicked collapse again - dojo.style(this.childNode, 'display', 'block'); - if (this.foldNode) //if wrapper node not visible, there is no foldnode.. - dojo.attr(this.foldNode, 'class', 'gg_assocfold gg_fold gg_unfolded'); - } - - - //place the children (might not be done automatically if we were expanding an already loaded item) - this.placeChildren(); - - this.updateFoldVisibility(); - - cb && cb(); - })); - } - - } - else - cb && cb(); - }, - - updateFoldVisibility : function() { - if (this.foldNode) { - if (this.children.length == 0) - dojo.style(this.foldNode, 'visibility', 'hidden'); - else - dojo.style(this.foldNode, 'visibility', ''); - } - this.parent.updateFoldVisibility(); - }, - - getVisibleParent : function() { - return this.parent; - }, - - free : function() { - if (this._destroyed) - return; - this._destroyed = true; - - for(var i = this.children.length - 1; i >= 0; i--) - this.children[i].free(); - - if (this.domNode) - dojo.destroy(this.domNode); - } -}); + dojo.place(this.headerNode, this.domNode); + dojo.place(this.treeNode, this.domNode); + attr.set(this.treeNode, { + tabindex: this.tabindex, + focusindex: 0 + }); -//A Node in the rendering -dojo.declare("TreeView.widget.RenderNode", null, { - graphNode : null, //correspoding graphnode - children : null, //list of renderEdges - domNode : null, - rowNode : null, - dataNode : null, //rendered node - childNode : null, //node to place children in - parent : null, //rendernode - type : null, //the assoc for which this item was rendered - tree : null, - depth : 0, - collapsed : null, - index : -1, - _colNodes : null, //array with columnNodes - canHazChildren : true, - isEdge : false, - hasVisibleEdge : false, //true if there is an always visible association edge. - - constructor : function(graphNode, parentRenderNode, type) { - this.graphNode = graphNode; - this.parent = parentRenderNode; - this.isRoot = this.parent == null; - this.children = []; - this.type = type; - this.tree = graphNode.tree; - - this.canHazChildren = this.graphNode.getChildTypes().length > 0; - - this.foldNode = mendix.dom.span({'class': 'gg_nodefold gg_fold ' + (this.canHazChildren ? 'gg_folded' : 'gg_nofold')}); - this.dataNode = mendix.dom.span({'class': 'gg_data', 'style' : this.graphNode.xsettings.entitystyle }); - this.childNode = mendix.dom.ul({'class': 'gg_children'}); - this.rowNode = mendix.dom.div({'class' : 'gg_row' }, this.foldNode, this.dataNode); - this.domNode = mendix.dom.li({'class':'gg_node ' + this.graphNode.xsettings.entityclazz }, this.rowNode, this.childNode); - - mxui.dom.data(this.domNode, "ggdata", this); - - if (this.graphNode.getChildTypes().length > 0) - dojo.addClass(this.domNode, 'gg_canhazchildren'); - - this.setupColumns(); - - this.draw(true); - - this.setCollapsed(true); - - - //setup child edges - dojo.forEach(this.graphNode.getChildTypes(), function(type) { - this.children[type.index] = new TreeView.widget.RenderEdge(this, type); - this.hasVisibleEdge |= type.showassocname; - }, this); - - //root item - if (this.parent == null) { - dojo.place(this.domNode, this.tree.treeNode); - } - //place in parent - else { - this.depth = this.parent.depth + 1; - this.getEdge().add(this); - } - - graphNode.nodes.push(this); - - if (this.tree.expandall > this.depth) - this.setCollapsed(false); - else if (this.tree.prefetch == true) { - dojo.forEach(this.graphNode.getChildTypes(), function(type) { - if (!graphNode.children[type.index].knowsChildren) - graphNode.ensureChildren(type, dojo.hitch(this, function() { - this.children[type.index].placeChildren(); - this.children[type.index].updateFoldVisibility(); - })); - }, this); - } - - }, - - data : function() { - return this.graphNode._data; - }, - - getVisibleParent : function() { - if (this.parent == null) - return null; - - var e = this.getEdge(); - if (e.visible) - return e; - return this.parent; - }, - - getEdge : function() { - return this.parent.children[this.type.index]; - }, - - /** convenient helper method */ - isA : function(type) { - return this.graphNode.isA(type); - }, - - getChildCount : function() { - var res = 0; - dojo.forEach(this.children, function(edge) { - if (edge) - res += edge.children.length; - }); - return res; - }, - - updateFoldVisibility : function() { - if (this.foldNode) { - if (!this.hasVisibleEdge && this.getChildCount() == 0) - dojo.style(this.foldNode, 'visibility', 'hidden'); - else - dojo.style(this.foldNode, 'visibility', ''); - } - }, - - findMaxIndex : function() { - var max = -100000; - dojo.forEach(this.children, function(edge) { - if (edge) - for(var j = 0, c = null; c = edge.children[j++];) - max = Math.max(max, c.graphNode.getSortIndex()); - }); - return max; - }, - - findMinIndex : function() { - var min = 100000; - dojo.forEach(this.children, function(edge) { - if (edge) - for(var j = 0, c = null; c = edge.children[j++];) - min = Math.min(min, c.graphNode.getSortIndex()); - }); - return min; - }, - - setCollapsed : function(newvalue, cb) { - if (newvalue == this.collapsed) { - cb && cb(); - return; - } - - this.collapsed = newvalue; - if (this.collapsed) { - dojo.style(this.childNode, 'display', 'none'); //TODO: anim - dojo.attr(this.foldNode, 'class', 'gg_nodefold gg_fold ' + (this.canHazChildren ? 'gg_folded' : 'gg_nofold')); - cb && cb(); - } - else { - dojo.attr(this.foldNode, 'class', 'gg_nodefold gg_fold gg_loading'); - - var allChildrenCallback = dojo.hitch(this, function() { - if (!this.collapsed) { //user might have clicked collapse again - dojo.style(this.childNode, 'display', 'block'); //TODO: anim - dojo.attr(this.foldNode, 'class', 'gg_nodefold gg_fold ' + (this.canHazChildren ? 'gg_unfolded' : 'gg_nofold')); - } - - this.updateFoldVisibility(); - - cb && cb(); - }); - - var left = 0; - var self = this; - - dojo.forEach(this.children, function(re) { - if (re && !re.visible) { //collapse if no wrapper node available - left += 1; - re.setCollapsed(false, dojo.hitch(self, function() { - left-=1; - if (left == 0) { - allChildrenCallback(); - left = -1; //make sure callback is not fired twice if setCollapsed is executed synchronously... - } - })); - } - }); - if (left == 0) - allChildrenCallback(); - } - }, - - setupColumns : function() { - this._colNodes = []; - - for(var i = 0, col= null; col = this.tree.columns[i]; i++) { - if (col.appliesTo(this)) { - var span = mxui.dom.span({'class' : 'gg_column gg_column_' + i}); - this._colNodes.push(span); - this.dataNode.appendChild(mxui.dom.span({ 'class' : 'gg_column_wrapper' }, span)); //wrapper column for hovers and such - - col.setupNode(span); - } - } - }, - - draw : function(firstTime) { - var curCol = 0; - for(var i = 0, col = null; col = this.tree.columns[i]; i++) - if (col.appliesTo(this)) { - col.render(this, this._colNodes[curCol],firstTime); - curCol += 1; - } - }, - - free : function() { - if (this._destroyed) - return; - this._destroyed = true; - - if (this.tree.getSelection() == this) - this.tree.setSelection(this.parent ? this.parent : null); - - dojo.forEach(this.children, function(edge) { - if (edge) - edge.free(); - }); - - if (this.parent) - this.getEdge().remove(this); //this will destroy the domNode as well - else if (this.domNode) - dojo.destroy(this.domNode); - } -}); + if (mxui.wm && mxui.wm.focus && mxui.wm.focus.addBox) { + mxui.wm.focus.addBox(this.treeNode); + } + + this.grabFocus(); + }, + + _setupActions: function () { + logger.debug("TreeView.widget.TreeView._setupActions"); + + var data = []; + this.splitPropsTo("actname,actprogressmsg,actentity,actshowbutton,actautohide,actbuttoncaption,actconfirmtext,actbuttonimage,actmf,actisdefault,actonselect,actnoselectionmf", data); + for (var i = 0, d = null; d = data[i]; i++) { + var action = new Action(d, this); + this.actions.push(action); + this.actionsByName[action.actname] = action; + } + + dojo.forEach(this.actions, function (action) { + action.setup(this.headerNode); + action.updateToSelection(); + }, this); + }, + + _setupTypes: function () { + logger.debug("TreeView.widget.TreeView._setupTypes"); + + this.splitPropsTo("entity,parentassocsingle,parentassocmulti,constraint,sortattr,sortdir,assoctype,assoccaption,showassocname,allowdnd,allowdndcopy,dropmf,assocclazz,assocstyle", this.types); + var i = 0; + dojo.forEach(this.types, function (type) { + //more householding + type.isRefset = type.parentassocsingle === "" || !type.parentassocsingle; + type.assoc = type.isRefset ? type.parentassocmulti : type.parentassocsingle; + + type.index = i++; + if (!type.assoc) + this.configError("The relation '" + type.assoccaption + "' did not define a reference"); -mxui.widget.declare("TreeView.widget.TreeView", { - root : null, //render node - dict : null, //guid -> GraphNode - types : null, //type definitions : entityName -> config - useDnd : false, - - edgesByParent : null, //parentguid.associndex.childguid - edgesByOwner : null, //ownerguid.associndex.[idx] - _selection : null, - dnd : null, //dnd state - _hoveredRow : null, - _hoveredCol : null, - selectionrefs : null, - _parent : null, - - getContextGUID : function() { - return this.root._data.getGUID(); - }, - - getContextObject : function() { - return this.root._data; - }, - - setSelection : function(renderNode) { - if (renderNode != this._selection) { - if (!this._parent && renderNode) - this._parent = renderNode.parent; - - if (this._selection && this._parent != this._selection){ - this.testreferences(this._selection); - } - - //remove old styling - if (this._selection && this._selection.domNode) - dojo.removeClass(this._selection.domNode, 'gg_selected'); - - //set new selection and styling - this._selection = renderNode; - if (renderNode) { - dojo.addClass(this._selection.domNode, 'gg_selected'); - dojo.window.scrollIntoView(renderNode.domNode); - } - - this.saveAndFireSelection(renderNode); - - //update actions - dojo.forEach(this.actions, function(action) { - action.updateToSelection(); - }); - - //fire the on select event - this.onSelect(renderNode); - } - }, - - - testreferences : function(node) { - if (this.selectionrefs) { - for(i = 0; i < this.selectionrefs.length; i++) { - if(this.selectionrefs[i].indexOf(node.graphNode.type) > -1 && this.selectionrefs[i].indexOf('/') == -1) { - TreeView.widget.Commons.store(this.getContextObject(), this.selectionrefs[i], node && node.graphNode.guid); - } else if (this.selectionrefs[i].indexOf('/') > -1) { - var patharr = this.selectionrefs[i].split('/'); - var refentity = patharr[patharr.length - 1]; - if (refentity.indexOf(node.graphNode.type) > -1) { - TreeView.widget.Commons.store(this.getContextObject(), this.selectionrefs[i], node && node.graphNode.guid); - } - } - } - } - }, - - saveAndFireSelection : function(item) { - mx.processor.save({ - mxobj : this.getContextObject(), - callback : dojo.hitch(this, this.onSelect, item), - error : this.showError - }, this); - - mx.processor.objectUpdateNotification(this.getContextObject()); - - this.onSelect(item); - }, - - getSelection : function(allowEdge) { - if (this._selection && this._selection.isEdge && !allowEdge) - return this._selection.parent; - return this._selection; - }, - - hasSelection : function() { - return this.getSelection(false) != null; - }, - - hasMultiSelection : function() { - return false; - }, - - withSelection : function(scope,cb) { - if (this.hasSelection()) - cb.call(scope, this.getSelection(false)); - }, - - onSelect : function(selection) { - //stub method to connect events to - }, - - onDefaultAction : function(selection) { - //stub, this method is invoked when a row is doubleclicked/ return is pressed - }, - - findOrCreateEdge : function(type, parent, child, owner, nocreate) { - if (!child) - throw this.id + " assertion failed: no child to find or create edge provided. Has it been free-ed somehow?"; - - if (!(parent.guid in this.edgesByParent)) - this.edgesByParent[parent.guid] = []; - if (!this.edgesByParent[parent.guid][type.index]) - this.edgesByParent[parent.guid][type.index] = {}; - - var place = this.edgesByParent[parent.guid][type.index] - - if (child.guid in place) - return place[child.guid]; - else if (nocreate === true) - return null; - else { - var edge = new TreeView.widget.Edge(type, parent, child, owner); - - //update edgesByParent - this.edgesByParent[parent.guid][type.index][child.guid] = edge; - - //update edgesByOwner - if (!(owner.guid in this.edgesByOwner)) - this.edgesByOwner[owner.guid] = []; - if (!this.edgesByOwner[owner.guid][type.index]) - this.edgesByOwner[owner.guid][type.index] = []; - - this.edgesByOwner[owner.guid][type.index].push(edge); - - return edge; - } - }, - - freeEdge : function(edge) { - //update edgesByParent - if (edge.parent.guid in this.edgesByParent) { - delete this.edgesByParent[edge.parent.guid][edge.type.index][edge.child.guid]; - } - - //update edgesByOwner - if (edge.owner.guid in this.edgesByOwner) { - var idx = dojo.indexOf(this.edgesByOwner[edge.owner.guid][edge.type.index], edge); - this.edgesByOwner[edge.owner.guid][edge.type.index].splice(idx, 1); - } - }, - - getXsettings : function (entity) { - var meta = mx.metadata.getMetaEntity(entity); - for(var i = 0, x = null; x = this.xsettings[i]; i++) { - if (meta.isA(x.xentity)) - return x; - } - return this.configError("TreeView config error: No Entity settings found for type: " + entity); - }, - - getEdgesOwnedBy : function(owner) { - if (!(owner.guid in this.edgesByOwner)) //might not be available yet - this.edgesByOwner[owner.guid] = []; - - return this.edgesByOwner[owner.guid]; - }, - - getChildEdges : function(parent) { - if (!(parent.guid in this.edgesByParent)) - this.edgesByParent[parent.guid] = []; - - return this.edgesByParent[parent.guid]; - }, - - - /* context applied */ - update : function(data, cb) { - var guid = data.getGUID(); - if (this.root != null && this.root.guid == guid) //already the root, just refresh - this.processData([data]); - else { - if (!this.getXsettings(data.getClass())) - this.configError("The context of this widget is a '" + data.getClass() + ", but this type is not further defined in the entities property of the treeview"); - - if (this.root) - this.root.free(); - - this.dict[guid] = new TreeView.widget.GraphNode(this, data, true); - this.root.forNodes(function(node) { - node.draw(); - - //unfold roots by default (note, should be one) - node.setCollapsed(false); - }); - - this.selectFirstItem(); - } - - cb && cb(); - }, - - selectFirstItem : function() { - if (!this.hiderootnode) { - if (this.root && this.root.nodes.length > 0) - this.setSelection(this.root.nodes[0]); - } - //select first child - else { - var edges = this.root.getChildTypes(); - if (edges.length > 0) { - var edge = edges[0]; - this.root.ensureChildren(edge, dojo.hitch(this, function() { - var firstRefChildren = this.root.nodes[0].children[edge.index].children; - if (firstRefChildren.length > 0) - this.setSelection(firstRefChildren[0]); - })); - } - } - }, - - processData : function(data) { - for(var i = 0; i < data.length; i++) { - var mxobj = data[i]; - var guid = mxobj.getGUID(); - if (this.dict[guid]) - this.dict[guid].update(mxobj); - else { - var g = new TreeView.widget.GraphNode(this, mxobj, false); - if (this._recordSelectionSuggestion) { - if (!this._selectionSuggestions) - this._selectionSuggestions = [g]; - else - this._selectionSuggestions.push(g); - } - } - //TODO: pass index along to the appropriate edges... - } - }, - - processRecordSelectionSuggestion : function() { - var max = 0, - cur = null; - if (this._selectionSuggestions) { - //find the newest item from the suggestions - for(var i = 0, g = null; g = this._selectionSuggestions[i++];) { - var b = g._data.get("changedDate"); - if (b > max) { - max = b; - cur = g; - } - } - - //expand it - if (cur) { - var self = this; - cur.forNodes(function(node) { - if (node.parent == self.getSelection()) { - //expand parent first - node.parent.setCollapsed(false, function() { - //expand wrapper if needed - node.getEdge().setCollapsed(false); - self.setSelection(node); - }); - } - }) - } - } - delete this._selectionSuggestions; - delete this._recordSelectionSuggestion; - }, - - /* - - - UI Events - - - */ - - grabFocus : function() { - if (mxui.wm.focus.get() != this.treeNode) - mxui.wm.focus.put(this.treeNode); - }, - - setupEvents : function() { - var lc = TreeView.widget.Commons.liveConnect; - - lc(this, this.treeNode, "onclick", { - "gg_assocfold" : this.assocFoldClick, - "gg_nodefold" : this.foldNodeClick, - "gg_column_wrapper" : this.columnClick, - "gg_node" : function(node, e) { - this.grabFocus(); - - this.setSelection (mxui.dom.data(node, "ggdata")); - - //expand if a row is clicked somewhere (but avoid folding twice if foldnode is clicked) - if (this.expandmode == 'row' && !dojo.hasClass(e.target, 'gg_nodefold')) - this.foldNodeClick(node); - } - }); - - lc(this, this.treeNode, "ondblclick", { - "gg_node" : function(target, e) { - if (!(dojo.hasClass(e.target, 'gg_fold'))) //do not handle doubleclicks on fold nodes //TODO:nor clickables? - this.invokeDefaultAction(target, e); - } - }); - - lc(this, this.treeNode, "onmouseover", { - "gg_column_wrapper" : function(target, e) { - if (!this.dnd.isdragging && target != this._hoveredCol) { - this.hoveredCol && dojo.removeClass(this.hoveredCol, 'gg_col_hover'); - dojo.addClass(target, 'gg_col_hover'); - this.hoveredCol = target; - } - return true; - }, - "gg_fold" : function(target, e) { - dojo.addClass(target.parentNode, 'gg_fold_hover'); - return true; - }, - "gg_node" : this.onRowMouseOver, - "gg_children" : function(_, e) { - //if (this.dnd.isdragging) - dojo.stopEvent(e); - return false; //avoid bubbling if dragging between items; - } - }); - - lc(this, this.treeNode, "onmouseout", { - "gg_fold" : function(target, e) { - dojo.removeClass(target.parentNode, 'gg_fold_hover'); - return true; - } - }); - - lc(this, this.treeNode, "onmousedown", { - "gg_node" : this.onRowMouseDown - }); - - lc(this, this.treeNode, "onmouseup", { - "gg_node" : this.onRowMouseUp - }); - - this.connect(this.treeNode, "onkeypress", this.keypress); - - if (this.expandmode == 'accordion') - dojo.connect(this, 'onSelect', this.updateAccordionForSelection); - }, - - _getRenderNodeForNode : function(node) { - while(node != null) { - if (dojo.hasClass(node, 'gg_node')) { - if (node == this.dnd.tmpnode) - return this.dnd.target; - return mxui.dom.data(node, "ggdata"); - } - node = node.parentNode; - } - - return null; - }, - - keypress : function(e) { - var sel = this.getSelection(true); - if (sel) { - var handled = true; - switch (e.keyCode) { - case dojo.keys.ENTER: - this.invokeDefaultAction(); - break; - case dojo.keys.DOWN_ARROW : - if (e.ctrlKey == true) { - //TODO: swap + delete type.parentassocmulti; + delete type.parentassocsingle; + + type.parententity = type.assoc.split("/")[1]; + type.ownerentity = type.entity; //owner is always the entity + + if (type.allowdnd) { + if (!type.dropmf) + this.configError("The relation '" + type.assoccaption + "' allows drag and drop, but has no drop Microflow"); + //drag and drop needs the data to be able to update the object. This however is a costly data overhead... + this.addToSchema(type.ownerentity, type.assoc); + } + + type.assoc = type.assoc.split("/")[0]; + + //swap entity types if assoctype is "from parent" + if (type.assoctype == "fromparent") { + var e = type.entity; + type.entity = type.parententity; + type.parententity = e; + } + + type.recursive = mx.meta.getEntity(type.entity).isA(type.parententity); + + type.assocstyle = type.assocstyle ? type.assocstyle.split(/\||\n/).join(";") : ""; + + this.useDnd |= type.allowdnd; + }, this); + }, + + _setupColumns: function () { + logger.debug("TreeView.widget.TreeView._setupColumns"); + + var data = []; + this.splitPropsTo("columnname,columnentity,columnrendermode,columnattr,columnimage,columnaction,columnclazz,columnstyle,columndateformat,columnprefix,columnpostfix,columntruecaption,columnfalsecaption", data); + for (var i = 0, d = null; d = data[i]; i++) { + this.columns.push(new ColRenderer(d, this, i)); + if (d.columnaction && !(d.columnaction in this.actionsByName)) + this.configError(this.id + " refers to unknown action " + d.columnaction); + + if (d.columnrendermode == "thumbnail" || d.columnrendermode == "systemimage") { + //Add fileID and changedDate to schema + if (d.columnattr == "") { + this.addToSchema(d.columnentity, "FileID"); } - else { - //next one is a child - var fc = this._getRenderNodeForNode(this.findNextNode(sel.domNode, 'gg_node', this.treeNode)); - if (fc != null) - this.setSelection(fc); + this.addToSchema(d.columnentity, d.columnattr.replace(/FileID/, "") + "changedDate"); + } + + this.addToSchema(d.columnentity, d.columnattr); + } + }, + + getContextGUID: function () { + logger.debug("TreeView.widget.TreeView.getContextGUID"); + + return this.root._data.getGuid(); + }, + + getContextObject: function () { + logger.debug("TreeView.widget.TreeView.getContextObject"); + + return this.root._data; + }, + + setSelection: function (renderNode) { + logger.debug("TreeView.widget.TreeView.setSelection"); + + if (renderNode != this._selection) { + if (!this._parent && renderNode) { + this._parent = renderNode.parent; + } + + if (this._selection && this._parent != this._selection) { + this.testreferences(this._selection); + } + + //remove old styling + if (this._selection && this._selection.domNode) + dojo.removeClass(this._selection.domNode, "gg_selected"); + + //set new selection and styling + this._selection = renderNode; + if (renderNode) { + dojo.addClass(this._selection.domNode, "gg_selected"); + dojo.window.scrollIntoView(renderNode.domNode); + } + + this.saveAndFireSelection(renderNode); + + //update actions + dojo.forEach(this.actions, function (action) { + action.updateToSelection(); + }); + } + }, + + + testreferences: function (node) { + logger.debug("TreeView.widget.TreeView.testreferences"); + + if (this.selectionrefs) { + for (var i = 0; i < this.selectionrefs.length; i++) { + if (this.selectionrefs[i].indexOf(node.graphNode.type) > -1 && this.selectionrefs[i].indexOf("/") == -1) { + Commons.store(this.getContextObject(), this.selectionrefs[i], node && node.graphNode.guid); + } else if (this.selectionrefs[i].indexOf("/") > -1) { + var patharr = this.selectionrefs[i].split("/"); + var refentity = patharr[patharr.length - 1]; + if (refentity.indexOf(node.graphNode.type) > -1) { + Commons.store(this.getContextObject(), this.selectionrefs[i], node && node.graphNode.guid); + } + } + } + } + }, + + saveAndFireSelection: function (item) { + logger.debug("TreeView.widget.TreeView.saveAndFireSelection"); + + mx.data.commit({ + mxobj: this.getContextObject(), + callback: this.onSelect, + error: this.showError + }, this); + + this.onSelect(item); + }, + + getSelection: function (allowEdge) { + logger.debug("TreeView.widget.TreeView.getSelection"); + + if (this._selection && this._selection.isEdge && !allowEdge) { + return this._selection.parent; + } + return this._selection; + }, + + hasSelection: function () { + logger.debug("TreeView.widget.TreeView.hasSelection"); + + return this.getSelection(false) != null; + }, + + hasMultiSelection: function () { + logger.debug("TreeView.widget.TreeView.hasMultiSelection"); + + return false; + }, + + withSelection: function (scope, cb) { + logger.debug("TreeView.widget.TreeView.withSelection"); + + if (this.hasSelection()) { + cb.call(scope, this.getSelection(false)); + } + }, + + onSelect: function (selection) { + //stub method to connect events to + }, + + onDefaultAction: function (selection) { + //stub, this method is invoked when a row is doubleclicked/ return is pressed + }, + + findOrCreateEdge: function (type, parent, child, owner, nocreate) { + logger.debug("TreeView.widget.TreeView.findOrCreateEdge"); + + if (!child) { + throw this.id + " assertion failed: no child to find or create edge provided. Has it been free-ed somehow?"; + } + + if (!(parent.guid in this.edgesByParent)) { + this.edgesByParent[parent.guid] = []; + } + if (!this.edgesByParent[parent.guid][type.index]) { + this.edgesByParent[parent.guid][type.index] = {}; + } + + var place = this.edgesByParent[parent.guid][type.index]; + + if (child.guid in place) { + return place[child.guid]; + } else if (nocreate === true) { + return null; + } else { + var edge = new Edge(type, parent, child, owner); + + //update edgesByParent + this.edgesByParent[parent.guid][type.index][child.guid] = edge; + + //update edgesByOwner + if (!(owner.guid in this.edgesByOwner)) { + this.edgesByOwner[owner.guid] = []; + } + if (!this.edgesByOwner[owner.guid][type.index]) { + this.edgesByOwner[owner.guid][type.index] = []; + } + + this.edgesByOwner[owner.guid][type.index].push(edge); + + return edge; + } + }, + + freeEdge: function (edge) { + logger.debug("TreeView.widget.TreeView.freeEdge"); + + //update edgesByParent + if (edge.parent.guid in this.edgesByParent) { + delete this.edgesByParent[edge.parent.guid][edge.type.index][edge.child.guid]; + } + + //update edgesByOwner + if (edge.owner.guid in this.edgesByOwner) { + var idx = dojo.indexOf(this.edgesByOwner[edge.owner.guid][edge.type.index], edge); + this.edgesByOwner[edge.owner.guid][edge.type.index].splice(idx, 1); + } + }, + + getXsettings: function (entity) { + logger.debug("TreeView.widget.TreeView.getXsettings"); + + var meta = mx.meta.getEntity(entity); + for (var i = 0, x = null; x = this.xsettings[i]; i++) { + if (meta.isA(x.xentity)) { + return x; + } + } + return this.configError("TreeView config error: No Entity settings found for type: " + entity); + }, + + getEdgesOwnedBy: function (owner) { + logger.debug("TreeView.widget.TreeView.getEdgesOwnedBy"); + + if (!(owner.guid in this.edgesByOwner)) { //might not be available yet + this.edgesByOwner[owner.guid] = []; + } + + return this.edgesByOwner[owner.guid]; + }, + + getChildEdges: function (parent) { + logger.debug("TreeView.widget.TreeView.getChildEdges"); + + if (!(parent.guid in this.edgesByParent)) + this.edgesByParent[parent.guid] = []; + + return this.edgesByParent[parent.guid]; + }, + + selectFirstItem: function () { + logger.debug("TreeView.widget.TreeView.selectFirstItem"); + + if (!this.hiderootnode) { + if (this.root && this.root.nodes.length > 0) { + this.setSelection(this.root.nodes[0]); + } + } else { //select first child + var edges = this.root.getChildTypes(); + if (edges.length > 0) { + var edge = edges[0]; + this.root.ensureChildren(edge, lang.hitch(this, function () { + var firstRefChildren = this.root.nodes[0].children[edge.index].children; + if (firstRefChildren.length > 0) { + this.setSelection(firstRefChildren[0]); + } + })); + } + } + }, + + processData: function (data) { + logger.debug("TreeView.widget.TreeView.processData"); + + for (var i = 0; i < data.length; i++) { + var mxobj = data[i]; + var guid = mxobj.getGuid(); + if (this.dict[guid]) { + this.dict[guid].update(mxobj); + } else { + var g = new GraphNode(this, mxobj, false); + if (this._recordSelectionSuggestion) { + if (!this._selectionSuggestions) { + this._selectionSuggestions = [g]; + } else { + this._selectionSuggestions.push(g); + } } - break; - case dojo.keys.UP_ARROW : - if (e.ctrlKey == true) { - //TODO: swap + } + //TODO: pass index along to the appropriate edges... + } + }, + + processRecordSelectionSuggestion: function () { + logger.debug("TreeView.widget.TreeView.processRecordSelectionSuggestion"); + + var max = 0, + cur = null; + if (this._selectionSuggestions) { + //find the newest item from the suggestions + for (var i = 0, g = null; g = this._selectionSuggestions[i++];) { + var b = g._data.get("changedDate"); + if (b > max) { + max = b; + cur = g; } - else { - var prev = this._getRenderNodeForNode(this.findPreviousNode(sel.domNode, 'gg_node', this.treeNode)); - if (prev) - this.setSelection(prev); + } + + //expand it + if (cur) { + var self = this; + cur.forNodes(function (node) { + if (node.parent == self.getSelection()) { + //expand parent first + node.parent.setCollapsed(false, function () { + //expand wrapper if needed + node.getEdge().setCollapsed(false); + self.setSelection(node); + }); + } + }); + } + } + delete this._selectionSuggestions; + delete this._recordSelectionSuggestion; + }, + + /* + UI Events + */ + + grabFocus: function () { + logger.debug("TreeView.widget.TreeView.grabFocus"); + + if (mxui.wm && mxui.wm.focus && mxui.wm.focus.get && mxui.wm.focus.get() != this.treeNode) { + mxui.wm.focus.put(this.treeNode); + } + }, + + _setupEvents: function () { + logger.debug("TreeView.widget.TreeView._setupEvents"); + + var lc = Commons.liveConnect; + + lc(this, this.treeNode, "onclick", { + "gg_assocfold": this.assocFoldClick, + "gg_nodefold": this.foldNodeClick, + "gg_column_wrapper": this.columnClick, + "gg_node": function (node, e) { + this.grabFocus(); + + this.setSelection(mxui.dom.data(node, "ggdata")); + + //expand if a row is clicked somewhere (but avoid folding twice if foldnode is clicked) + if (this.expandmode == "row" && !dojo.hasClass(e.target, "gg_nodefold")) { + this.foldNodeClick(node); + } + } + }); + + lc(this, this.treeNode, "ondblclick", { + "gg_node": function (target, e) { + if (!(dojo.hasClass(e.target, "gg_fold"))) //do not handle doubleclicks on fold nodes //TODO:nor clickables? + this.invokeDefaultAction(target, e); + } + }); + lc(this, this.treeNode, "mouseleave", { + "gg_assoc_wrapper": function (target, e) { + dojo.removeClass(target, "gg_row_hover"); + this._hoveredRow = null; + return true; + }, + "gg_column_wrapper": function (target, e) { + dojo.removeClass(target, "gg_col_hover"); + this.hoveredCol = null; + return true; + }, + "gg_node": function (target, e) { + dojo.removeClass(target, "gg_row_hover"); + this._hoveredRow = null; + return true; + } + }); + lc(this, this.treeNode, "onmouseover", { + "gg_column_wrapper": function (target, e) { + if (!this.dnd.isdragging && target != this._hoveredCol) { + if (this.hoveredCol) { + dojo.removeClass(this.hoveredCol, "gg_col_hover"); + } + dojo.addClass(target, "gg_col_hover"); + this.hoveredCol = target; } - break; - case dojo.keys.LEFT_ARROW: - if (e.ctrlKey == false) { - if (!sel.collapsed) - sel.setCollapsed(true); - else if (sel.getVisibleParent()) - this.setSelection(sel.getVisibleParent()); - } - else - this.itemIndent(this.getSelection(), true); //TODO: - break; - case dojo.keys.RIGHT_ARROW: - if (e.ctrlKey == false) { - if (sel.collapsed) - sel.setCollapsed(false); - else { - var fs = this._getRenderNodeForNode(this.findNextNode(sel.domNode, 'gg_node', this.treeNode)); - if (fs) - this.setSelection(fs); - } - } - else - this.itemIndent(this.getSelection(), false); //TODO: - break; - default : - if (e.charCode == dojo.keys.SPACE) { - sel.setCollapsed(!sel.collapsed); - } - else { - handled = false; - } - break; - } - } - if (handled) + return true; + }, + "gg_fold": function (target, e) { + dojo.addClass(target.parentNode, "gg_fold_hover"); + return true; + }, + "gg_node": this.onRowMouseOver, + "gg_children": function (_, e) { + //if (this.dnd.isdragging) + dojo.stopEvent(e); + return false; //avoid bubbling if dragging between items; + } + }); + + lc(this, this.treeNode, "onmouseout", { + "gg_fold": function (target, e) { + dojo.removeClass(target.parentNode, "gg_fold_hover"); + return true; + } + }); + + lc(this, this.treeNode, "onmousedown", { + "gg_node": this.onRowMouseDown + }); + + lc(this, this.treeNode, "onmouseup", { + "gg_node": this.onRowMouseUp + }); + + this.connect(this.treeNode, "onkeypress", this.keypress); + + if (this.expandmode == "accordion") { + dojo.connect(this, "onSelect", this.updateAccordionForSelection); + } + }, + + _getRenderNodeForNode: function (node) { + logger.debug("TreeView.widget.TreeView._getRenderNodeForNode"); + + while (node != null) { + if (dojo.hasClass(node, "gg_node")) { + if (node == this.dnd.tmpnode) { + return this.dnd.target; + } + return mxui.dom.data(node, "ggdata"); + } + node = node.parentNode; + } + + return null; + }, + + keypress: function (e) { + logger.debug("TreeView.widget.TreeView.keypress"); + + var sel = this.getSelection(true), + handled = false; + if (sel) { + handled = true; + switch (e.keyCode) { + case dojo.keys.ENTER: + this.invokeDefaultAction(); + break; + case dojo.keys.DOWN_ARROW: + if (e.ctrlKey == true) { + //TODO: swap + } else { + //next one is a child + var fc = this._getRenderNodeForNode(this.findNextNode(sel.domNode, "gg_node", this.treeNode)); + + if (fc != null) { + this.setSelection(fc); + } + } + break; + case dojo.keys.UP_ARROW: + if (e.ctrlKey == true) { + //TODO: swap + } else { + var prev = this._getRenderNodeForNode(this.findPreviousNode(sel.domNode, "gg_node", this.treeNode)); + if (prev) { + this.setSelection(prev); + } + } + break; + case dojo.keys.LEFT_ARROW: + if (e.ctrlKey == false) { + if (!sel.collapsed) { + sel.setCollapsed(true); + } else if (sel.getVisibleParent()) { + this.setSelection(sel.getVisibleParent()); + } + } else { + this.itemIndent(this.getSelection(), true); //TODO: + } + break; + case dojo.keys.RIGHT_ARROW: + if (e.ctrlKey == false) { + if (sel.collapsed) { + sel.setCollapsed(false); + } else { + var fs = this._getRenderNodeForNode(this.findNextNode(sel.domNode, "gg_node", this.treeNode)); + if (fs) + this.setSelection(fs); + } + } else { + this.itemIndent(this.getSelection(), false); //TODO + } + break; + default: + if (e.charCode == dojo.keys.SPACE) { + sel.setCollapsed(!sel.collapsed); + } else { + handled = false; + } + break; + } + } + if (handled) { + dojo.stopEvent(e); + } + }, + + invokeDefaultAction: function () { + logger.debug("TreeView.widget.TreeView.invokeDefaultAction"); + + for (var i = 0, a = null; a = this.actions[i++];) { + if (a.actisdefault && a.appliesToSelection()) { + a.invokeOnSelection(); + } + } + }, + + onRowMouseOver: function (target, e) { + if (!this.dnd.isdragging && target != this._hoveredRow) { + this._hoveredRow && dojo.removeClass(this._hoveredRow, "gg_row_hover"); + dojo.addClass(target, "gg_row_hover"); + this._hoveredRow = target; + } + + if (!this.dnd.isdragging && this.useDnd && this.dnd.mousedown) { + if (this.dnd.startNode == target + && (Math.abs(e.pageX - this.dnd.startX) > 1 //NaN > 5 === false, so thats ok + || Math.abs(e.pageY - this.dnd.startY) > 1 + )) { + this.startDrag(target, e); + } + } else if (this.dnd.isdragging) { + this.onDrag(target, e); + } + e.preventDefault(); dojo.stopEvent(e); - }, - - invokeDefaultAction : function() { - for(var i = 0, a = null; a = this.actions[i++];) - if (a.actisdefault && a.appliesToSelection()) - a.invokeOnSelection(); - }, - - onRowMouseOver : function(target, e) { -// console.log("over"); - if (!this.dnd.isdragging && target != this._hoveredRow) { - this._hoveredRow && dojo.removeClass(this._hoveredRow, 'gg_row_hover'); - dojo.addClass(target, 'gg_row_hover'); - this._hoveredRow = target; - } - - if (!this.dnd.isdragging && this.useDnd && this.dnd.mousedown) { - if (this.dnd.startNode == target - && (Math.abs(e.pageX - this.dnd.startX) > 1 //NaN > 5 === false, so thats ok - || Math.abs(e.pageY - this.dnd.startY) > 1 - )) { - this.startDrag(target, e); - } - } - - else if (this.dnd.isdragging) { - this.onDrag(target, e); - } - e.preventDefault(); - dojo.stopEvent(e); - return false; //stop further events - }, - - onRowMouseDown : function(target, e) { -// console.log("on down"); - if (!this.dnd.isdragging) { - this.dnd.mousedown = true; - this.dnd.startNode = target; - this.dnd.startX = e.pageX; - this.dnd.startY = e.pageY; - } - e.preventDefault(); - return false; - }, - - onRowMouseUp : function(target, e) { -// console.log("on up"); - this.dnd.mousedown = false; - if (this.dnd.isdragging) { - this.onEndDrag(target, e); - } - return false; - }, - - resetDndClasses : function(node) { - if (node) - dojo.removeClass(node, 'gg_drag_over gg_drag_accept gg_drag_deny'); - }, - - startDrag : function(target, e) { - //tmp node for drag an drop operations - this.dnd.tmpnode = mxui.dom.li({'class' : 'gg_node gg_anchor'}, mxui.dom.div({'class' : 'gg_anchor_inner'})); - - var current = this.dnd.current = this._getRenderNodeForNode(target); - this.setSelection(current); - - //if this item does not support DnD, go on as usual - if (current.isEdge || current.isRoot || !current.type.allowdnd) - return false; - - var avatar = this.dnd.avatar = mxui.dom.div({'class' : 'gg_avatar'}, dojo.clone(current.rowNode)); //TODO: make beter avatar - - //hooray, we can start thedrag -// console.log("start drag"); - - this.dnd.isdragging = true; - this.dnd.beginBox = dojo.position(target); - - dojo.addClass(current.domNode, 'gg_dragging'); - - dojo.addClass(avatar, 'gg_avatar'); - dojo.place(avatar, dojo.body(), 'last'); - - //update position - dojo.style(avatar, { - 'position' : 'absolute', - 'zIndex' : 10000 - }); - - this.dnd.bodyconnect = dojo.connect(dojo.body(), "onmouseup", dojo.hitch(this, function() { - this.onEndDrag(); - })) - - this.dnd.bodyconnect2 = dojo.connect(dojo.body(), "onmouseover", dojo.hitch(this, function(e) { - //console.log("mouse out"); - dojo.style(this.dnd.avatar, { - 'top' : (e.pageY + 32) + 'px', - 'left' : (e.pageX + 32) + 'px' - }); - - if (this.dnd.target) - this.resetDndClasses(this.dnd.target.domNode); - - this.dnd.accept = false; - this.dnd.target = null; - - dojo.addClass(this.dnd.avatar, 'gg_drag_outside'); - })); - - return true; - }, - - onDrag : function(targetNode, e) { - //Hide selection, especially needed in IE. - if(document.selection && document.selection.empty) { - document.selection.empty(); - } else if(window.getSelection) { - var sel = window.getSelection(); - sel.removeAllRanges(); - } - - var prevtarget = this.dnd.target; - var prevbefore = this.dnd.dropbefore; - - if (prevtarget) - this.resetDndClasses(prevtarget.domNode); - - var current = this.dnd.current; - var avatar = this.dnd.avatar; - var mbox = dojo.marginBox(targetNode.children[0]); //take the first child, as the node itself might include the descendants - var target = this.dnd.target = this._getRenderNodeForNode(targetNode); - - if (!target) - return; - - var candropBefore = this.canDropBefore(current, target); - var pos; - var tmpnode = this.dnd.tmpnode; - var copy = this.dnd.copy = e && e.ctrlKey; - - var oy = e.offsetY || e.layerY; //for FF compatiblity - - //drag over tmpnode - if (targetNode == tmpnode) { - pos = this.dnd.pos = this.dnd.tmppos; - } - //drag over a real node - else { - pos = this.dnd.pos = 'last'; - if (candropBefore) { - if (oy > mbox.h / 2) { - if (target.collapsed == true || target.childNode.children.length == 0) { - this.dnd.tmppos = 'after'; - dojo.place(tmpnode, target.domNode, 'after'); - } - else { - //as first child - this.dnd.tmppos = 'first'; - dojo.place(tmpnode, target.childNode, 'first'); - } - } - else { - this.dnd.tmppos = 'before'; - dojo.place(tmpnode, target.domNode, 'before'); - } - } - } - - var accept = this.dnd.accept = this.dragOver(current, target, pos, copy); - -// console.log("DnD over: pos: " + pos + " candropBefore: " + candropBefore + " copy: " + copy + " accept: " + accept + " tmppos: " + this.dnd.tmppos + " rowh: " + mbox.h + " y: " + oy); - this.resetDndClasses(target.domNode); - this.resetDndClasses(tmpnode); - dojo.removeClass(avatar, 'gg_drag_outside'); - - //update classes according to state - if (copy) - dojo.addClass(avatar, 'gg_drag_copy'); - else - dojo.removeClass(avatar, 'gg_drag_copy'); - - if (pos == 'last') { - dojo.addClass(target.domNode, 'gg_drag_over'); - dojo.addClass(target.domNode, accept? 'gg_drag_accept' : 'gg_drag_deny'); - } - else { - dojo.addClass(tmpnode, 'gg_drag_over gg_drag_accept'); - } - - var acceptTmp = candropBefore && (pos != 'last' || this.dragOver(current, target, this.dnd.tmppos, copy)); //is the tmppos allowed? - dojo.style(tmpnode, 'display', acceptTmp ? 'list-item' : 'none'); - - dojo.style(avatar, { - 'top' : (e.pageY + 32) + 'px', - 'left' : (e.pageX + 32) + 'px' - }); - - //stop / start the expand timer - //auto expand stuff - if (prevtarget != target || pos != 'last') { - if (this.dnd.expandtimer) { - clearTimeout(this.dnd.expandtimer); - this.dnd.expandtimer = null; - } - } - else if (pos == 'last') - if (!this.dnd.expandtimer) { - this.dnd.expandtimer = setTimeout(dojo.hitch(this, function(toexpand) { - if (toexpand == this.dnd.target && this.dnd.pos == 'last') { //still the same ? - toexpand.setCollapsed(false); - //if (this.dnd.tmpnode) - // this.dnd.tmpnode.parentNode = null; //hide current tmpnode, mwe: disabled for now, parentNode = null breaks in IE - } - }, target), 500); - } - }, - - onEndDrag : function(target, e) { -// console.log("stop drag"); - - //MWE: e is null in the case of the body mouseup - //if (e) - // this.onDrag(target, e); //make sure everything is up to date - var result = this.dnd.accept && this.performDrop(this.dnd.current, this.dnd.target, this.dnd.pos, this.dnd.copy); - - //update event state - this.dnd.isdragging = false; - this.dnd.mousedown = false; - dojo.disconnect(this.dnd.bodyconnect); - dojo.disconnect(this.dnd.bodyconnect2); - - //hide temporary nodes node - dojo.style(this.dnd.tmpnode, 'display', 'none'); - dojo.destroy(this.dnd.tmpnode); - delete this.dnd.tmpnode; - - if (result) { - this.resetNodesAfterDnD(); - } - else { //revert, move the node back if cancelled - dojo.animateProperty({ - node : this.dnd.avatar, - properties : { - left : this.dnd.beginBox.x, - top : this.dnd.beginBox.y - }, - duration : 500, - onEnd : dojo.hitch(this, this.resetNodesAfterDnD) - }).play(); - } - }, - - resetNodesAfterDnD : function() { - if (this.dnd.target) - this.resetDndClasses(this.dnd.target.domNode); - this.resetDndClasses(this.dnd.current.domNode); - dojo.removeClass(this.dnd.current.domNode, 'gg_dragging'); - - dojo.destroy(this.dnd.avatar); - delete this.dnd.avatar; - }, - - _findDropAssocs : function(item, target, pos, copy) { - var assocs = []; - - if (pos == 'before' || pos == 'after' || target.isEdge) { - //the drop assocation is the association of the current target item, thats easy :) - if (!target.isRoot && target.type.allowdnd - && (!copy || target.type.allowdndcopy) - && item.isA(target.type.entity)) - assocs.push(target.type); - } - else { - //return find assocition from target -> child with dnd - for(var i = 0, a = null; a = this.types[i++];) { - if (a.allowdnd - && (!copy || a.allowdndcopy) - && item.isA(a.entity) - && target.isA(a.parententity) - ) { - assocs.push(a); - } - } - } - return assocs; - }, - - //indicate whether it is possible to drop before another item - canDropBefore : function(item, target) { - if (!target || target.isEdge) //we cannot drop before assoc nodes - return false; - - var e = item.graphNode.type; - //use a cache - if (!this.dnd.allowDropBefore) - this.dnd.allowDropBefore = {}; - if (!(e in this.dnd.allowDropBefore)) { - var x = item.graphNode.xsettings; - this.dnd.allowDropBefore[e] = x && mx.metadata.getMetaEntity(e).getAttributeType(x.sortattr) == "Float"; - } - return this.dnd.allowDropBefore[e]; - }, - - /** - * triggered when dragging item over target. - * @param {[type]} item [description] - * @param {[type]} target [description] - * @param {[type]} before [description] - * @return {[type]} Whether the item can be dropped here. Used for styling - */ - dragOver : function(item, target, pos, copy) { - //Avoid drop in self - // - var validSelfdrop = (target == item && (pos == 'before' || pos == 'after')); - - if (!validSelfdrop) { - var cur = target; - while(cur != null) { - if (cur == item) - return false; - cur = cur.parent; - } - } - return this._findDropAssocs(item, target, pos, copy).length > 0; - }, - - /** - * Drops an item on a target. Return true if the drop was accepted / succesful - * The return value is used for styling purposes only (if false, a revert animation is playe). - * The real data should be updated as a result of callbacks - * @param {[type]} item [description] - * @param {[type]} target [description] - * @param {[type]} pos [description] - * @return {[type]} - */ - performDrop : function(item, target, pos, copy) { - if (!target) - return false; - - var assocs = this._findDropAssocs(item, target, pos, copy); - var actions = []; //microflows to invoke to persist the changes - - var i = item.graphNode._data; //item - var o = item.parent.graphNode._data; //old parent - - //determine drop target - var t = null; - if (target.isEdge) - t = target.parent.graphNode._data; - else if (pos == 'before' || pos == 'after') - t = target.parent.graphNode._data - else - t = target.graphNode._data; //new parent - - //0) always remove existing parent if not copy, regardless which association it was.. - if (!copy) { - var assoc1 = item.type; - if (assoc1.assoctype == "fromparent") { - if (assoc1.isRefset) - o.removeReferences(assoc1.assoc, [i.getGUID()]); - else - o.set(assoc1.assoc, ''); - - actions.push([assoc1.dropmf, o]); //otherwise o might never know that a child was removed - } - else { //from child - if (assoc1.isRefset) - i.removeReferences(assoc1.assoc, [o.getGUID()]); - else - i.set(assoc1.assoc, ''); - - //actions.push([assoc.dropmf, i]); //MWE: I guess this is not needed, since a drop mf is most likely already fired for i, and this might confuse the logic. - } - } - - //1) update new parent - for(var k = 0, assoc = null; assoc = assocs[k++];) { - //assoc stored in parent - if (assoc.assoctype == "fromparent") { - actions.push([assoc.dropmf, t]); - - //reference set (was already removed from orig parent in step 0) - if (assoc.isRefset) { - t.addReferences(assoc.assoc, [i.getGUID()]); - } - - //normal reference - else - t.set(assoc.assoc, i.getGUID()); - } - - //assoc stored in child - else { - actions.push([assoc.dropmf, i]); - - if (assoc.isRefset) { - i.addReferences(assoc.assoc, [t.getGUID()]); - } - else - i.set(assoc.assoc, t.getGUID()); //copy is not supported, dont bother - } - } - - //2) update position. Note that this position applies to all assocs! which is a bit weird... - var x = item.graphNode.xsettings; - if (x && mx.metadata.getMetaEntity(item.graphNode.type).getAttributeType(x.sortattr) == "Float") { - if (pos == 'before' || pos == 'after') { - //find the other related element for drop in between - var othernode = pos == 'before' ? target.domNode.previousElementSibling : target.domNode.nextElementSibling; - if (this.dnd.tmpnode != null && othernode == this.dnd.tmpnode) - othernode = pos == 'before' ? othernode.previousElementSibling : othernode.nextElementSibling; - var other = this._getRenderNodeForNode(othernode); - - var nidx; - if (other == null || other.isEdge) //either first or last - nidx = target.graphNode.getSortIndex() + (pos == 'before' ? -1024 : 32354); - - else //put between - nidx = (target.graphNode.getSortIndex() + other.graphNode.getSortIndex()) / 2; - } - else { - if (pos == 'last') - nidx = (target.isEdge ? target.parent : target).findMaxIndex() + 14056; - else - nidx = (target.isEdge ? target.parent : target).findMinIndex() - 4313; - } - - if (isNaN(nidx)) - nidx = 0; //probably div by zero - - item.graphNode._data.set(x.sortattr, nidx); - } - - //3) loop items in actions, store the changes and invoke the microflows - var left = actions.length; - var gn = item.graphNode; - //TODO: check whether ar doesn't have double items? - for (var l = 0, ar = null; ar = actions[l++];) { - TreeView.widget.Commons.mf(ar[0],ar[1], function() { - - //when all mf's have fired, find the best node to select - left -= 1; - if (left == 0 ) { - //find the best node to animate - var bestnode = null; - gn.forNodes(function(node) { - if (bestnode == null || node.parent == target || node.parent == target.parent) - bestnode = node; - }); - - if (bestnode) - this.setSelection(bestnode); - } - }, this); - } - - return true; - }, - - foldNodeClick : function(node) { - var renderNode = this._getRenderNodeForNode(node); - renderNode.setCollapsed(!renderNode.collapsed); - }, - - updateAccordionForSelection : function(record) { - if (!record) - return; - record.setCollapsed(false); - - var c = record; - var p = record.getVisibleParent(); - while (p != null) { - dojo.forEach(p.children, function(assoc) { - if (assoc) dojo.forEach(assoc.children, function(child) { - if (child != c) - child.setCollapsed(true); - }); - }); - c = p; - p = p.getVisibleParent(); - } - }, - - assocFoldClick : function(node) { - var renderEdge = mxui.dom.data(node.parentNode.parentNode, "ggdata"); - renderEdge.setCollapsed(!renderEdge.collapsed); - }, - - columnClick : function(node) { - var col = mxui.dom.data(node, 'colindex'); - var renderNode = this._getRenderNodeForNode(node); - - this.columns[col].invokeAction(renderNode); - }, - - /* - - - - HouseHolding - - - - */ - - inputargs : { - //data model properties - tabindex : -1, - entity : '', - parentassocsingle : '', - parentassocmulti : '', - assoctype : '', - assoccaption : '', - showassocname : '', - constraint : '', - allowdnd : '', - allowdndcopy : '', - dropmf : '', - assocclazz : '', - assocstyle : '', - - //display properties - columnname : '', - columnentity : '', - columnrendermode : '', - columnattr : '', - columnimage : '', - columnaction : '', - columnclazz : '', - columnstyle : '', - columndateformat : '', - columnprefix : '', - columnpostfix : '', - columntruecaption : '', - columnfalsecaption : '', - - //action properties - actname : '', - actentity : '', - actshowbutton : '', - actautohide : '', - actbuttoncaption : '', - actbuttonimage : '', - actconfirmtext : '', - actmf : '', - actisdefault : '', - actonselect : '', - actnoselectionmf : '', - actshortcut : '', - actprogressmsg : '', - - //Selection references - selectionref: '', - - //advanced settings - xentity : '', - xburstattr: '', - xlisteningform: '', - sortattr : '', - sortdir : '', - entityclazz : '', - entitystyle : '', - entitychannel : '', - allowmultiselect: false, - selectionrefset: '', - - //general properties - expandall : 1, - prefetch : true, - hiderootnode : true, - expandmode : 'arrow', //arrow | row | accordion - multiselect : false - }, - - _started : false, - - mixins : [ mxui.mixin._Scriptable ], - - startup : function() { - if (this._started) //MWE: RVH said this can happen - return; - - TreeView.widget.Commons.fixObjProps(this, ["blaat4", "blaat", "blaat2", "blaat3", "blaat5"]) - - this._started = true; - this.dict = {}; - this.types = []; - this.columns = []; - this.actions = []; - this.actionsByName = {}; - this.dnd = {}; - - this.edgesByParent = {}; - this.edgesByOwner = {}; - - - this.xsettings = []; - this.splitPropsTo('xentity,xburstattr,xlisteningform,sortattr,sortdir,entityclazz,entitystyle,entitychannel', this.xsettings); - for(var i = 0; i < this.xsettings.length; i++) { - var x = this.xsettings[i]; - x.entitystyle = x.entitystyle.split(/\||\n/).join(";"); - x.filter = { references : {}, attributes : [] }; - - this.addToSchema(x.xentity, x.xburstattr); - this.addToSchema(x.xentity, x.sortattr); - - if (x.entitychannel) { - this.connect(this, 'onSelect', dojo.hitch(this, function(channel,entity,selection) { - if(selection != null && selection.isA(entity)) - dojo.publish(this.getContent() + "/"+ channel +"/context", [selection.data()]); - else - dojo.publish(this.getContent() + "/"+ channel +"/context", [null]); - }, x.entitychannel, x.xentity)); - } - } - - if (this.selectionref != '') - this.selectionrefs = this.selectionref.split(';'); - - this.offerInterface("close"); - - this.setupTypes(); - - this.setupLayout(); - this.setupActions(); - - this.setupColumns(); - this.setupEvents(); - - this.actLoaded(); - }, - - setupLayout : function() { - dojo.addClass(this.domNode, 'gg_tree'); - this.headerNode = mendix.dom.div({'class' : 'gg_header'}); - - this.treeNode = mendix.dom.ul({'class': 'gg_children gg_root_wrapper'}); - if (this.hiderootnode) - dojo.addClass(this.treeNode, 'gg_hiddenroot'); - - dojo.place(this.headerNode, this.domNode); - dojo.place(this.treeNode, this.domNode); - dojo.attr(this.treeNode, { - tabindex : this.tabindex, - focusindex : 0 - }); - mxui.wm.focus.addBox(this.treeNode); - this.grabFocus(); - }, - - setupActions : function() { - var data = []; - this.splitPropsTo('actname,actprogressmsg,actentity,actshowbutton,actautohide,actbuttoncaption,actconfirmtext,actbuttonimage,actmf,actisdefault,actonselect,actnoselectionmf,actshortcut', data); - for(var i = 0, d = null; d = data[i]; i++) { - var action = new TreeView.widget.Action(d, this); - this.actions.push(action); - this.actionsByName[action.actname] = action; - } - - dojo.forEach(this.actions, function(action) { - action.setup(this.headerNode); - action.updateToSelection(); - }, this); - }, - - setupTypes : function() { - this.splitPropsTo('entity,parentassocsingle,parentassocmulti,constraint,sortattr,sortdir,assoctype,assoccaption,showassocname,allowdnd,allowdndcopy,dropmf,assocclazz,assocstyle', this.types); - var i = 0; - dojo.forEach(this.types, function(type) { - //more householding - type.isRefset = type.parentassocsingle == ''; - type.assoc = type.isRefset ? type.parentassocmulti : type.parentassocsingle; - - type.index = i++; - - if (!type.assoc) - this.configError("The relation '" + type.assoccaption + "' did not define a reference"); - - delete type.parentassocmulti; - delete type.parentassocsingle; - - type.parententity = type.assoc.split("/")[1]; - type.ownerentity = type.entity; //owner is always the entity - - if (type.allowdnd) { - if (!type.dropmf) - this.configError("The relation '" + type.assoccaption + "' allows drag and drop, but has no drop Microflow"); - //drag and drop needs the data to be able to update the object. This however is a costly data overhead... - this.addToSchema(type.ownerentity, type.assoc); - } - - type.assoc = type.assoc.split("/")[0]; - - //swap entity types if assoctype is 'from parent' - if (type.assoctype == 'fromparent') { - var e = type.entity; - type.entity = type.parententity; - type.parententity = e; - } - - type.recursive = mx.metadata.getMetaEntity(type.entity).isA(type.parententity); - - type.assocstyle = type.assocstyle.split(/\||\n/).join(";"); - - this.useDnd |= type.allowdnd; - }, this); - }, - - setupColumns : function() { - var data = []; - this.splitPropsTo('columnname,columnentity,columnrendermode,columnattr,columnimage,columnaction,columnclazz,columnstyle,columndateformat,columnprefix,columnpostfix,columntruecaption,columnfalsecaption', data); - for(var i = 0, d = null; d = data[i]; i++) { - this.columns.push(new TreeView.widget.Colrenderer(d, this, i)); - if (d.columnaction && !(d.columnaction in this.actionsByName)) - this.configError(this.id + " refers to unknown action " + d.columnaction); - - if (d.columnrendermode == "thumbnail" || d.columnrendermode == "systemimage" ) { - //Add fileID and changedDate to schema - if (d.columnattr == "") - this.addToSchema(d.columnentity, "FileID"); - - this.addToSchema(d.columnentity, d.columnattr.replace(/FileID/, "") + "changedDate"); - } - - this.addToSchema(d.columnentity, d.columnattr); - } - }, - - addToSchema : function(entity, attr) { - if (!attr) - return; - - var t = this.getXsettings(entity); - if (attr.indexOf("/") > -1) { - if (!t) - this.configError("Attribute " + attr + " refers to unconfigured type " + entity); - - var parts = attr.split("/"); - - if (!(parts[0] in t.filter.references)) - t.filter.references[parts[0]] = { attributes : []}; - - if (parts.length > 2) - t.filter.references[parts[0]].attributes.push(parts[2]); - } - else { - t.filter.attributes.push(attr); - } - }, - - /** - converts Mx reference data to a map with guids. The data can either be an array of guids, empty string or an object - */ - toMap : function(thing) { - var res = {}; - if (thing == "") - return res; - if (dojo.isArray(thing)) { - dojo.forEach(thing, function(guid) { - res[guid] = true; - }); - return res; - } - if (dojo.isObject(thing)) { - if (thing.guid) - res[thing.guid] = true; - else - return thing; - } - //a guid - res[thing] = true; - return res; - }, - - /** - * moves the domNode within the parent to the designated position. Precondition: domNode.parentNode - * @param {[type]} domNode [description] - * @param {[type]} targetIndex [description] - */ - moveNode : function(domNode, targetIndex) { - var parent = domNode.parentNode; - - if (parent == null) - throw "tree.moveNode: domNode has no parent"; - - dojo.place(domNode, parent, targetIndex); - }, - - /** - * find treewise the previous node preceding the startnode - * @param {[type]} clazz [description] - * @param {[type]} limitNode [description] - * @return {[type]} - */ - findPreviousNode : function(node, clazz, limitNode) { - if (node.previousElementSibling == null) { - //find a matching parent - var cur = node.parentNode; - while (cur != null && cur != limitNode) { - if (dojo.hasClass(cur, clazz)) - return cur; - cur = cur.parentNode; - } - } - else { - //find a previously matching sibling - var findLast = function(cur) { - for(var i = cur.children.length - 1; i >= 0; i--) { - if (dojo.style(cur.children[i], 'display') == 'none') - continue; - var l = findLast(cur.children[i]); - if (l != null) - return l; - } - - if (dojo.hasClass(cur, clazz)) - return cur; + return false; //stop further events + }, + + onRowMouseDown: function (target, e) { + if (!this.dnd.isdragging) { + this.dnd.mousedown = true; + this.dnd.startNode = target; + this.dnd.startX = e.pageX; + this.dnd.startY = e.pageY; + } + e.preventDefault(); + return false; + }, + + onRowMouseUp: function (target, e) { + this.dnd.mousedown = false; + if (this.dnd.isdragging) { + this.onEndDrag(target, e); + } + return false; + }, + + resetDndClasses: function (node) { + logger.debug("TreeView.widget.TreeView.resetDndClasses"); + + if (node) { + dojo.removeClass(node, "gg_drag_over gg_drag_accept gg_drag_deny"); + } + }, + + startDrag: function (target, e) { + logger.debug("TreeView.widget.TreeView.startDrag"); + + //tmp node for drag an drop operations + this.dnd.tmpnode = mxui.dom.create("li", { "class": "gg_node gg_anchor" }, mxui.dom.create("div", { "class": "gg_anchor_inner" })); + + var current = this.dnd.current = this._getRenderNodeForNode(target); + this.setSelection(current); + + //if this item does not support DnD, go on as usual + if (current.isEdge || current.isRoot || !current.type.allowdnd) { + return false; + } + + var avatar = this.dnd.avatar = mxui.dom.create("div", { "class": "gg_avatar" }, dojo.clone(current.rowNode)); //TODO: make beter avatar + + //hooray, we can start thedrag + // console.log("start drag"); + + this.dnd.isdragging = true; + this.dnd.beginBox = dojo.position(target); + + dojo.addClass(current.domNode, "gg_dragging"); + + dojo.addClass(avatar, "gg_avatar"); + dojo.place(avatar, dojo.body(), "last"); + + //update position + domStyle.set(avatar, { + "position": "absolute", + "zIndex": 10000 + }); + + this.dnd.bodyconnect = dojo.connect(dojo.body(), "onmouseup", lang.hitch(this, function () { + this.onEndDrag(); + })); + + this.dnd.bodyconnect2 = dojo.connect(dojo.body(), "onmouseover", lang.hitch(this, function (e) { + //console.log("mouse out"); + domStyle.set(this.dnd.avatar, { + "top": (e.pageY + 32) + "px", + "left": (e.pageX + 32) + "px" + }); + + if (this.dnd.target) + this.resetDndClasses(this.dnd.target.domNode); + + this.dnd.accept = false; + this.dnd.target = null; + + dojo.addClass(this.dnd.avatar, "gg_drag_outside"); + })); + + return true; + }, + + onDrag: function (targetNode, e) { + logger.debug("TreeView.widget.TreeView.onDrag"); + + //Hide selection, especially needed in IE. + if (document.selection && document.selection.empty) { + document.selection.empty(); + } else if (window.getSelection) { + var sel = window.getSelection(); + sel.removeAllRanges(); + } + + var prevtarget = this.dnd.target; + var prevbefore = this.dnd.dropbefore; + + if (prevtarget) { + this.resetDndClasses(prevtarget.domNode); + } + + var current = this.dnd.current; + var avatar = this.dnd.avatar; + var mbox = dojo.marginBox(targetNode.children[0]); //take the first child, as the node itself might include the descendants + var target = this.dnd.target = this._getRenderNodeForNode(targetNode); + + if (!target) { + return; + } + + var candropBefore = this.canDropBefore(current, target); + var pos; + var tmpnode = this.dnd.tmpnode; + var copy = this.dnd.copy = e && e.ctrlKey; + + var oy = e.offsetY || e.layerY; //for FF compatiblity + + //drag over tmpnode + if (targetNode == tmpnode) { + pos = this.dnd.pos = this.dnd.tmppos; + } else { //drag over a real node + pos = this.dnd.pos = "last"; + if (candropBefore) { + if (oy > mbox.h / 2) { + if (target.collapsed == true || target.childNode.children.length == 0) { + this.dnd.tmppos = "after"; + dojo.place(tmpnode, target.domNode, "after"); + } else { //as first child + this.dnd.tmppos = "first"; + dojo.place(tmpnode, target.childNode, "first"); + } + } else { + this.dnd.tmppos = "before"; + dojo.place(tmpnode, target.domNode, "before"); + } + } + } + + var accept = this.dnd.accept = this.dragOver(current, target, pos, copy); + + this.resetDndClasses(target.domNode); + this.resetDndClasses(tmpnode); + dojo.removeClass(avatar, "gg_drag_outside"); + + //update classes according to state + if (copy) { + dojo.addClass(avatar, "gg_drag_copy"); + } else { + dojo.removeClass(avatar, "gg_drag_copy"); + } + + if (pos == "last") { + dojo.addClass(target.domNode, "gg_drag_over"); + dojo.addClass(target.domNode, accept ? "gg_drag_accept" : "gg_drag_deny"); + } else { + dojo.addClass(tmpnode, "gg_drag_over gg_drag_accept"); + } + + var acceptTmp = candropBefore && (pos != "last" || this.dragOver(current, target, this.dnd.tmppos, copy)); //is the tmppos allowed? + domStyle.set(tmpnode, "display", acceptTmp ? "list-item" : "none"); + + domStyle.set(avatar, { + "top": (e.pageY + 32) + "px", + "left": (e.pageX + 32) + "px" + }); + + //stop / start the expand timer + //auto expand stuff + if (prevtarget != target || pos != "last") { + if (this.dnd.expandtimer) { + clearTimeout(this.dnd.expandtimer); + this.dnd.expandtimer = null; + } + } else if (pos == "last") + if (!this.dnd.expandtimer) { + this.dnd.expandtimer = setTimeout(lang.hitch(this, function (toexpand) { + if (toexpand == this.dnd.target && this.dnd.pos == "last") { //still the same ? + toexpand.setCollapsed(false); + //if (this.dnd.tmpnode) + // this.dnd.tmpnode.parentNode = null; //hide current tmpnode, mwe: disabled for now, parentNode = null breaks in IE + } + }, target), 500); + } + }, + + onEndDrag: function (target, e) { + logger.debug("TreeView.widget.TreeView.onEndDrag"); + + var result = this.dnd.accept && this.performDrop(this.dnd.current, this.dnd.target, this.dnd.pos, this.dnd.copy); + + //update event state + this.dnd.isdragging = false; + this.dnd.mousedown = false; + dojo.disconnect(this.dnd.bodyconnect); + dojo.disconnect(this.dnd.bodyconnect2); + + //hide temporary nodes node + domStyle.set(this.dnd.tmpnode, "display", "none"); + dojo.destroy(this.dnd.tmpnode); + delete this.dnd.tmpnode; + + if (result) { + this.resetNodesAfterDnD(); + } else { //revert, move the node back if cancelled + dojo.animateProperty({ + node: this.dnd.avatar, + properties: { + left: this.dnd.beginBox.x, + top: this.dnd.beginBox.y + }, + duration: 500, + onEnd: lang.hitch(this, this.resetNodesAfterDnD) + }).play(); + } + }, + + resetNodesAfterDnD: function () { + logger.debug("TreeView.widget.TreeView.resetNodesAfterDnD"); + + if (this.dnd.target) { + this.resetDndClasses(this.dnd.target.domNode); + } + this.resetDndClasses(this.dnd.current.domNode); + dojo.removeClass(this.dnd.current.domNode, "gg_dragging"); + + dojo.destroy(this.dnd.avatar); + delete this.dnd.avatar; + }, + + _findDropAssocs: function (item, target, pos, copy) { + logger.debug("TreeView.widget.TreeView._findDropAssocs"); + + var assocs = []; + + if (pos == "before" || pos == "after" || target.isEdge) { + //the drop assocation is the association of the current target item, thats easy :) + if (!target.isRoot && target.type.allowdnd + && (!copy || target.type.allowdndcopy) + && item.isA(target.type.entity)) + assocs.push(target.type); + } else { + //return find assocition from target -> child with dnd + for (var i = 0, a = null; a = this.types[i++];) { + if (a.allowdnd + && (!copy || a.allowdndcopy) + && item.isA(a.entity) + && target.isA(a.parententity) + ) { + assocs.push(a); + } + } + } + return assocs; + }, + + //indicate whether it is possible to drop before another item + canDropBefore: function (item, target) { + logger.debug("TreeView.widget.TreeView.canDropBefore"); + + if (!target || target.isEdge) //we cannot drop before assoc nodes + return false; + + var e = item.graphNode.type; + //use a cache + if (!this.dnd.allowDropBefore) { + this.dnd.allowDropBefore = {}; + } + if (!(e in this.dnd.allowDropBefore)) { + var x = item.graphNode.xsettings; + this.dnd.allowDropBefore[e] = x && mx.meta.getEntity(e).getAttributeType(x.sortattr) == "Decimal"; + } + return this.dnd.allowDropBefore[e]; + }, + + /** + * triggered when dragging item over target. + * @param {[type]} item [description] + * @param {[type]} target [description] + * @param {[type]} before [description] + * @return {[type]} Whether the item can be dropped here. Used for styling + */ + dragOver: function (item, target, pos, copy) { + logger.debug("TreeView.widget.TreeView.dragOver"); + + //Avoid drop in self + var validSelfdrop = (target == item && (pos == "before" || pos == "after")); + + if (!validSelfdrop) { + var cur = target; + while (cur != null) { + if (cur == item) { + return false; + } + cur = cur.parent; + } + } + return this._findDropAssocs(item, target, pos, copy).length > 0; + }, + + /** + * Drops an item on a target. Return true if the drop was accepted / succesful + * The return value is used for styling purposes only (if false, a revert animation is playe). + * The real data should be updated as a result of callbacks + * @param {[type]} item [description] + * @param {[type]} target [description] + * @param {[type]} pos [description] + * @return {[type]} + */ + performDrop: function (item, target, pos, copy) { + logger.debug("TreeView.widget.TreeView.performDrop"); + + if (!target) { + return false; + } + + var assocs = this._findDropAssocs(item, target, pos, copy); + var actions = []; //microflows to invoke to persist the changes + + var i = item.graphNode._data; //item + var o = item.parent.graphNode._data; //old parent + + //determine drop target + var t = null; + if (target.isEdge) { + t = target.parent.graphNode._data; + } else if (pos == "before" || pos == "after") { + t = target.parent.graphNode._data; + } else { + t = target.graphNode._data; //new parent + } + + //0) always remove existing parent if not copy, regardless which association it was.. + if (!copy) { + var assoc1 = item.type; + if (assoc1.assoctype == "fromparent") { + if (assoc1.isRefset) { + o.removeReferences(assoc1.assoc, [i.getGuid()]); + } + else { + o.set(assoc1.assoc, ""); + } + actions.push([assoc1.dropmf, o]); //otherwise o might never know that a child was removed + } + else { //from child + if (assoc1.isRefset) { + i.removeReferences(assoc1.assoc, [o.getGuid()]); + } else { + i.set(assoc1.assoc, ""); + } + + //actions.push([assoc.dropmf, i]); //MWE: I guess this is not needed, since a drop mf is most likely already fired for i, and this might confuse the logic. + } + } + + //1) update new parent + for (var k = 0, assoc = null; assoc = assocs[k++];) { + //assoc stored in parent + if (assoc.assoctype == "fromparent") { + actions.push([assoc.dropmf, t]); + + //reference set (was already removed from orig parent in step 0) + if (assoc.isRefset) { + t.addReferences(assoc.assoc, [i.getGuid()]); + } else { //normal reference + t.set(assoc.assoc, i.getGuid()); + } + } else { //assoc stored in child + actions.push([assoc.dropmf, i]); + + if (assoc.isRefset) { + i.addReferences(assoc.assoc, [t.getGuid()]); + } else { + i.set(assoc.assoc, t.getGuid()); //copy is not supported, dont bother + } + } + } + + //2) update position. Note that this position applies to all assocs! which is a bit weird... + var x = item.graphNode.xsettings; + if (x && mx.meta.getEntity(item.graphNode.type).getAttributeType(x.sortattr) == "Decimal") { + var nidx; + if (pos == "before" || pos == "after") { + //find the other related element for drop in between + var othernode = pos == "before" ? target.domNode.previousElementSibling : target.domNode.nextElementSibling; + if (this.dnd.tmpnode != null && othernode == this.dnd.tmpnode) { + othernode = pos == "before" ? othernode.previousElementSibling : othernode.nextElementSibling; + } + var other = this._getRenderNodeForNode(othernode); + + if (other == null || other.isEdge) { //either first or last + nidx = target.graphNode.getSortIndex() + (pos == "before" ? -1024 : 32354); + } else { //put between + nidx = (target.graphNode.getSortIndex() + other.graphNode.getSortIndex()) / 2; + } + } else { + if (pos == "last") { + nidx = (target.isEdge ? target.parent : target).findMaxIndex() + 14056; + } else { + nidx = (target.isEdge ? target.parent : target).findMinIndex() - 4313; + } + } + + if (isNaN(nidx)) { + nidx = 0; //probably div by zero + } + + item.graphNode._data.set(x.sortattr, nidx); + } + + //3) loop items in actions, store the changes and invoke the microflows + var left = actions.length; + var gn = item.graphNode; + //TODO: check whether ar doesn"t have double items? + for (var l = 0, ar = null; ar = actions[l++];) { + Commons.mf(ar[0], ar[1], function () { + + //when all mf"s have fired, find the best node to select + left -= 1; + if (left == 0) { + //find the best node to animate + var bestnode = null; + gn.forNodes(function (node) { + if (bestnode == null || node.parent == target || node.parent == target.parent) { + bestnode = node; + } + }); + + if (bestnode) { + this.setSelection(bestnode); + } + } + }, this); + } + + return true; + }, + + foldNodeClick: function (node) { + logger.debug("TreeView.widget.TreeView.foldNodeClick"); + + var renderNode = this._getRenderNodeForNode(node); + renderNode.setCollapsed(!renderNode.collapsed); + }, + + updateAccordionForSelection: function (record) { + logger.debug("TreeView.widget.TreeView.updateAccordionForSelection"); + + if (!record) { + return; + } + record.setCollapsed(false); + + var c = record; + var p = record.getVisibleParent(); + while (p != null) { + dojo.forEach(p.children, function (assoc) { + if (assoc) dojo.forEach(assoc.children, function (child) { + if (child != c) { + child.setCollapsed(true); + } + }); + }); + c = p; + p = p.getVisibleParent(); + } + }, + + assocFoldClick: function (node) { + logger.debug("TreeView.widget.TreeView.assocFoldClick"); + + var renderEdge = mxui.dom.data(node.parentNode.parentNode, "ggdata"); + renderEdge.setCollapsed(!renderEdge.collapsed); + }, + + columnClick: function (node) { + logger.debug("TreeView.widget.TreeView.columnClick"); + + var col = mxui.dom.data(node, "colindex"); + var renderNode = this._getRenderNodeForNode(node); + + this.columns[col].invokeAction(renderNode); + }, + + addToSchema: function (entity, attr) { + logger.debug("TreeView.widget.TreeView.addToSchema"); + + if (!attr) { + return; + } + + var t = this.getXsettings(entity); + if (attr.indexOf("/") > -1) { + if (!t) { + this.configError("Attribute " + attr + " refers to unconfigured type " + entity); + } + + var parts = attr.split("/"); + + if (!(parts[0] in t.filter.references)) { + t.filter.references[parts[0]] = { attributes: [] }; + } + + if (parts.length > 2) { + t.filter.references[parts[0]].attributes.push(parts[2]); + } + + } else { + t.filter.attributes.push(attr); + } + }, + + /** + converts Mx reference data to a map with guids. The data can either be an array of guids, empty string or an object + */ + toMap: function (thing) { + logger.debug("TreeView.widget.TreeView.toMap"); + + var res = {}; + if (thing == "") { + return res; + } + if (dojo.isArray(thing)) { + dojo.forEach(thing, function (guid) { + res[guid] = true; + }); + return res; + } + if (dojo.isObject(thing)) { + if (thing.guid) + res[thing.guid] = true; + else + return thing; + } + //a guid + res[thing] = true; + return res; + }, + + /** + * moves the domNode within the parent to the designated position. Precondition: domNode.parentNode + * @param {[type]} domNode [description] + * @param {[type]} targetIndex [description] + */ + moveNode: function (domNode, targetIndex) { + logger.debug("TreeView.widget.TreeView.moveNode"); + + var parent = domNode.parentNode; + + if (parent == null) { + throw "tree.moveNode: domNode has no parent"; + } + + dojo.place(domNode, parent, targetIndex); + }, + + /** + * find treewise the previous node preceding the startnode + * @param {[type]} clazz [description] + * @param {[type]} limitNode [description] + * @return {[type]} + */ + findPreviousNode: function (node, clazz, limitNode) { + logger.debug("TreeView.widget.TreeView.findPreviousNode"); + + if (node.previousElementSibling == null) { + //find a matching parent + var cur = node.parentNode; + while (cur != null && cur != limitNode) { + if (dojo.hasClass(cur, clazz)) { + return cur; + } + cur = cur.parentNode; + } + } else { + //find a previously matching sibling + var findLast = function (cur) { + for (var i = cur.children.length - 1; i >= 0; i--) { + if (domStyle.set(cur.children[i], "display") == "none") { + continue; + } + var l = findLast(cur.children[i]); + if (l != null) { + return l; + } + } + + if (dojo.hasClass(cur, clazz)) { + return cur; + } + return null; + }; + + var lc = findLast(node.previousElementSibling); + if (lc) { + return lc; + } + return this.findPreviousNode(node.previousElementSibling, clazz, limitNode); + } + return null; + }, + + findNextNode: function (node, clazz, limitNode) { + logger.debug("TreeView.widget.TreeView.findNextNode"); + + var findChild = function (cur) { + for (var i = 0; i < cur.children.length; i++) { + if (domStyle.set(cur.children[i], "display") != "none") { + if (dojo.hasClass(cur.children[i], clazz)) { + return cur.children[i]; + } + var fc = findChild(cur.children[i]); + if (fc) { + return fc; + } + } + } return null; - } - - var lc = findLast(node.previousElementSibling); - if (lc) - return lc; - return this.findPreviousNode(node.previousElementSibling, clazz, limitNode); - } - return null; - }, - - findNextNode : function(node, clazz, limitNode) { - var findChild = function(cur) { - for(var i = 0; i < cur.children.length; i++) { - if (dojo.style(cur.children[i], 'display') != 'none') { - if (dojo.hasClass(cur.children[i], clazz)) - return cur.children[i]; - var fc = findChild(cur.children[i]); - if (fc) - return fc; - } - } - return null; - } - - var fc = findChild(node); - if (fc) - return fc; - - var cur = node; - while (cur != limitNode && cur != null) { - var n = cur.nextElementSibling; - if (n != null && dojo.hasClass(n, clazz) && dojo.style(n, 'display') != 'none') - return n; - cur = cur.parentNode; - } - - return null; - }, - - /* MWE: not sure if these suspended are supposed here, or where deliberately deleted before merging with main branch.. - if it gives issues, the methods suspended / resumed should be deleted probably*/ - suspended : function() { - - }, - - resumed : function() { - //reapply selection to continue formloader - var sel = this._selection; - this._selection = null; - this.setSelection(this._selection); - }, - - configError : function(msg) { - TreeView.widget.Commons.configError(this, msg); - }, - - showError : function(e) { - TreeView.widget.Commons.error(e, this); - }, - - splitPropsTo : function(props, target) { - TreeView.widget.Commons.splitPropsTo(this, props, target); - }, - - close : function(){ - this.disposeContent(); - }, - - uninitialize : function() { - this.root && this.root.free(); - - if (this.searchControl) - this.searchControl.free(); - - dojo.forEach(this.columns, function(column) { - column.free(); - }); - - dojo.forEach(this.actions, function(action) { - action.free(); - }); - } -}); + }; + var fc = findChild(node); + if (fc) { + return fc; + } + + var cur = node; + while (cur != limitNode && cur != null) { + var n = cur.nextElementSibling; + if (n != null && dojo.hasClass(n, clazz) && domStyle.set(n, "display") != "none") { + return n; + } + cur = cur.parentNode; + } + + return null; + }, + + configError: function (msg) { + logger.debug("TreeView.widget.TreeView.configError"); + + Commons.configError(this, msg); + }, + + showError: function (e) { + logger.debug("TreeView.widget.TreeView.showError"); + + Commons.error(e, this); + }, + + splitPropsTo: function (props, target) { + logger.debug("TreeView.widget.TreeView.splitPropsTo"); + + Commons.splitPropsTo(this, props, target); + }, + + close: function () { + this.disposeContent(); + } + }); +}); diff --git a/src/TreeView/widget/TreeView/Edge.js b/src/TreeView/widget/TreeView/Edge.js new file mode 100644 index 0000000..520b048 --- /dev/null +++ b/src/TreeView/widget/TreeView/Edge.js @@ -0,0 +1,78 @@ +/** + * Edge stores a single assocation from the database, e.g. guid -> assocname -> guid. + * Parent / child is stored according to the rendering, not according to the owner. + */ +define([ + "dojo/_base/declare", + "TreeView/widget/TreeView/RenderNode" +], function(declare, RenderNode) { + "use strict" + + return declare("TreeView.widget.TreeView.Edge", null, { + parent: null, //Graphnode + name: "", //assocname + child: null, //Graphnode + owner: null, //Graphnode, either parent or child + type: null, //type in tree + tree: null, + index: -1, + + constructor: function (type, parent, child, owner) { + logger.debug("TreeView.widget.Edge.constructor"); + this.type = type; + this.name = type.assoc; + this.parent = parent; + this.child = child; + this.owner = owner; + this.tree = this.parent.tree; + + this.child._refs += 1; //increase the number of references + + //add the node for every known parent + parent.forNodes(function (parentRenderNode) { + if (parentRenderNode.children[type.index].collapsed == false){ //already expanded parent, add this edge.. + new RenderNode(child, parentRenderNode, type); + } + }); + }, + + updateIndex: function (newindex) { + logger.debug("TreeView.widget.Edge.updateIndex"); + if (this.index != newindex) { + var edge = this; + + this.parent.forNodes(function (parentNode) { + edge.child.forNodes(function (childNode) { + parentNode.children[edge.type.index].move(childNode, newindex); + }); + }); + + this.index = newindex; + } + }, + + free: function () { + logger.debug("TreeView.widget.Edge.free"); + if (this._destroyed){ + return; + } + this._destroyed = true; + + //1. remove the corresponding child render nodes for every know parent, but only where the parent is the parent of this edge! + var edge = this; + + //remove every rendering of this parent -> assoc -> child + this.parent.forNodes(function (parentNode) { + edge.child.forNodes(function (childNode) { + parentNode.children[edge.type.index].remove(childNode); + }); + }); + + //2. reduce the number of refs for child, remove if necessary + this.child.free(); + + //3. Remove edges from the tree cache structure + this.tree.freeEdge(this); + } + }); +}); diff --git a/src/TreeView/widget/TreeView/GraphNode.js b/src/TreeView/widget/TreeView/GraphNode.js new file mode 100644 index 0000000..6c8bf9e --- /dev/null +++ b/src/TreeView/widget/TreeView/GraphNode.js @@ -0,0 +1,406 @@ +/** + * Graphnode refers to an single instance of an object. Edges are not stored here, but globally in the tree. + * A single graphnode might have multiple render nodes, for every time it is viewed in the tree. + */ +define([ + "dojo/_base/declare", + "dojo/_base/lang", + "TreeView/widget/TreeView/RenderNode" +], function (declare, lang, RenderNode) { + "use strict"; + + return declare("TreeView.widget.TreeView.GraphNode", null, { + nodes: null, //correspinding render nodes + tree: null, + guid: null, + type: "", + children: null, //stores the state of child edges per type + + _data: null, + _refs: 0, //number of parents referring to this node, if zero, free + _burst: null, + _subscription: null, + + constructor: function (tree, data, asRoot) { + logger.debug("TreeView.widget.GraphNode.constructor"); + this.guid = data.getGuid(); + tree.dict[this.guid] = this; + this.type = data.getEntity(); + this._data = data; + + this.tree = tree; + this.xsettings = this.tree.getXsettings(this.type); + this.nodes = []; + + //store childtypes + this.children = []; + + var ar = this.getChildTypes(); + for (var i = 0; i < ar.length; i++) { + this.children[ar[i].index] = { + knowsChildren: false, + type: ar[i], + //householding for retrieving children + _afterChildrenCb: null, + _retrieving: false + }; + } + + this.update(data); + + this._subscription = mx.data.subscribe({ + guid: this.guid, + callback: lang.hitch(this, function (thing) { + if (dojo.isObject(thing)) { + this.updateWithRefs(thing); + } else { + mx.data.get({ + guid: thing, + error: this.tree.showError, + callback: lang.hitch(this, this.updateWithRefs) + }); + } + }) + }); + + if (asRoot == true) { + this.tree.root = this; + this._refs += 1; //additional ref for roots, to not accidentaly free them. + //place the node + var n = new RenderNode(this, null, null); //root has no assoctype associated... + } + }, + + /** convenient helper method */ + isA: function (type) { + //logger.debug("TreeView.widget.GraphNode.isA"); + return this._data.isA(type); + }, + + getSortIndex: function () { + logger.debug("TreeView.widget.GraphNode.getSortIndex"); + if (this.xsettings) + return parseInt(this._data.get(this.xsettings.sortattr), 10); + return -1; + }, + + updateWithRefs: function (data) { + logger.debug("TreeView.widget.GraphNode.updateWithRefs"); + this.update(data); + this.updateEdges(); + }, + + update: function (data) { + logger.debug("TreeView.widget.GraphNode.update"); + this._data = data; + + this.checkBurst(); + + //redraw content data + this.forNodes(function (node) { + node.draw(); + }); + }, + + updateEdges: function () { + logger.debug("TreeView.widget.GraphNode.updateEdges"); + var data = this._data; + //1. mark edges stored in here invalid + var owningEdges = this.tree.getEdgesOwnedBy(this); + + dojo.forEach(owningEdges, function (edgeSet) { + if (edgeSet) + dojo.forEach(edgeSet, function (edge) { + edge._valid = false; + }); + }); + + //2. update edges owned by this object + for (var i = 0; i < this.tree.types.length; i++) { + var type = this.tree.types[i]; + if (data.isA(type.ownerentity)) { + + var rawdata = data.get(type.assoc); + var ids = this.tree.toMap(rawdata); + var unknown = []; + + for (var id in ids) { + var other = this.tree.dict[id]; + + var isChild = type.assoctype == "fromchild"; + if (other == null) { + //2a. handle unknown "other side" + if (isChild) //skip parents not available in the tree + continue; + else { + unknown.push(id); + } + } + + if (other != null) { + + //Pre 2b: special case, if the type has a constraint and a cache burst, + //we are not sure that the currently updated object passes the constraint, so rely on the cacheburst to show this item in that parent + //if we are not loading exactly that relation, do not automatically create that edge + // + //see _retrieveChildrenCore for more about the _currentLoadingRel flag. + var nocreate = false; + if (type != this.tree._currentLoadingRel && isChild && type.constraint.length) { + var xsettings = this.tree.getXsettings(type.parententity) + if (xsettings != null && xsettings.xburstattr) { + nocreate = true; + } + } + + //2b. ..find or create edge, mark valid + var edge = this.tree.findOrCreateEdge(type, isChild ? other : this, isChild ? this : other, this, nocreate); + if (edge) + edge._valid = true; + } + } + + //3. fetch the associations from this to child which are not known yet + this._fetchUnknownChildrenHelper(type, unknown); + } + } + + //4. remove invalid edges + dojo.forEach(owningEdges, function (arr) { + if (arr) + for (var i = arr.length - 1; i >= 0; i--) //MWE: count down, items might be free-ed on the fly + if (!arr[i]._valid) + arr[i].free(); + }); + }, + + /** + * check whethers a burst attribute, as defined in the childtypes, has changed. If so, invalidate children + * TODO: move childeges to children + */ + checkBurst: function () { + logger.debug("TreeView.widget.GraphNode.checkBurst"); + var x = this.xsettings; + if (x != null && x.xburstattr) { + var newburst = this._data.get(x.xburstattr); + if (newburst != this._burst) { + this._burst = newburst; + dojo.forEach(this.children, function (edgeSet) { + if (edgeSet) { + edgeSet.knowsChildren = false; + } + }); + + this.forNodes(function (node) { + if (node.collapsed == false || node == this.tree.getSelection()) { //re-draw / fetch expanded nodes + + //re-expand all assocs in the node + dojo.forEach(node.children, function (re) { + if (re && re.collapsed == false) { + re.collapsed = true; + re.setCollapsed(false, function () { + node.tree.processRecordSelectionSuggestion(); + }); + } + }); + } + }); + } + } + }, + + forNodes: function (func) { + logger.debug("TreeView.widget.GraphNode.forNodes"); + var l = this.nodes.length; + for (var i = 0; i < l; i++) { + func.call(this, this.nodes[i]); + } + }, + + /** + * returns all types which can be a child of this node (independent of owner) + * @return {[type]} + */ + getChildTypes: function () { + logger.debug("TreeView.widget.GraphNode.getChildTypes"); + var res = []; + for (var i = 0; i < this.tree.types.length; i++) { + var type = this.tree.types[i]; + + if (this.isA(type.parententity)) + res.push(type); + } + return res; + }, + + //if a ref(set) was changed, and it refers to unknown ids, fetch them. + _fetchUnknownChildrenHelper: function (type, guids) { + logger.debug("TreeView.widget.GraphNode._fetchUnknownChildrenHelper"); + var xpath; + if (guids.length == 0) { //no guids + return; + } + if (!this.children[type.index].knowsChildren) {//never expanded, we need to fetch anyway on the next expand, and the edges will be created by the retrieve. Skip for now + return; + } + + var args = { + xpath: xpath, + filter: type.filter, + callback: lang.hitch(this, function (data) { + this.tree.processData(data); //data should always be one (or zero if not in constraint) + + for (var i = 0; i < data.length; i++) { //create the assocations + var e = this.tree.findOrCreateEdge(type, this, this.tree.dict[data[i].getGuid()], this); + e._valid = true; + } + }), + error: this.tree.showError + }; + + //Question: should the constraint be applied? + //- Yes: that is more consistent. + //- No: allows for unsaved objects to be shown in the tree if added to the parent + //Current implementation: yes if a constraint is used, otherwise, guids is used + + if (type.constraint) { + xpath = "//" + type.entity + "[id=\"" + guids.join("\" or id=\"").substring(5) + "\"]" + type.constraint; + args.xpath = xpath.replace(/\[\%CurrentObject\%\]/gi, this.tree.root.guid); + } else { + args.guids = guids; + } + + mx.data.get(args); + }, + + ensureChildren: function (type, callback) { + logger.debug("TreeView.widget.GraphNode.ensureChildren"); + var c = this.children[type.index]; + if (c.knowsChildren) { + callback && callback(); + } else if (c._retrieving) { + if (callback) { + c._afterChildrenCb.push(callback); + } + } else { + c._retrieving = true; + c._afterChildrenCb = callback ? [callback] : []; //event chain + + this._retrieveChildrenCore(c, lang.hitch(this, function () { + c._retrieving = false; + c.knowsChildren = true; + + var f; + while (f = c._afterChildrenCb.shift()) { + f(); + } + })); + } + }, + + _retrieveChildrenCore: function (c, callback) { + logger.debug("TreeView.widget.GraphNode._retrieveChildrenCore"); + var type = c.type; + + //self references leaving from the parent need a recursive constraint + var reverse = type.recursive && type.assoctype == "fromparent" ? "[reversed()]" : ""; + var xpath = "//" + type.entity + "[" + type.assoc + reverse + " = \"" + this.guid + "\"]" + (type.constraint ? type.constraint : ""); + xpath = xpath.replace(/\[\%CurrentObject\%\]/gi, this.tree.root.guid); + + var kwargs = { + xpath: xpath, + filter: this.tree.getXsettings(type.entity).filter, + callback: lang.hitch(this, function (rel, data) { + + //1. mark edges from here in here invalid + var edges = this.tree.getChildEdges(this)[rel.index]; + for (var childguid in edges) { + edges[childguid]._valid = false; + } + + try { + //if a cacheburst is used, child to parent edgets are not created automatically to avoid byposing constraints. + //however, here, we now that the constraint is allowed to be bypassed since we are loading exactly that relation + //store this in flag. + this.tree._currentLoadingRel = rel; + this.tree.processData(data); + } + finally { + delete this._currentLoadingRel; + } + + //3. create the edge if from parent, update index, update valid state + for (var i = 0; i < data.length; i++) { + var guid = data[i].getGuid(); + var child = this.tree.dict[guid]; + + var edge = + type.assoctype == "fromparent" + //3a. if this object is the owner of the association, the edges are not created automatically by process data, create them now + ? this.tree.findOrCreateEdge(type, this, child, this) + //3b. otherise, still retrieve the edge to update the index if necessary + : this.tree.findOrCreateEdge(type, this, child, child); + + edge.updateIndex(i); + + //we now for sure it is valid now + edge._valid = true; + } + + //4. remove invalid children after receiving all data (no need to refetch them) + for (var childguid2 in edges) + if (!edges[childguid2]._valid) + edges[childguid2].free(); + + callback(); //wait with callback until all requests are completed + }, type), + error: this.tree.showError + }; + + var xsettings = this.tree.getXsettings(type.entity); + + if (!kwargs.filter) { + kwargs.filter = {}; + } + + if (xsettings != null) { + kwargs.filter.sort = [[xsettings.sortattr, xsettings.sortdir]]; + } + + //perform the get + mx.data.get(kwargs); + }, + + + free: function () { + logger.debug("TreeView.widget.GraphNode.free"); + this._refs -= 1; + + if (this._refs <= 0) { + if (this._destroyed) + return; + this._destroyed = true; + + if (this._subscription) + mx.data.unsubscribe(this._subscription); + + delete this.tree.dict[this.guid]; + + this.forNodes(function (node) { + node.free(); + }); + + //Question: which edges should be freed here? + //Answer: only the owning ones. Non owning ones should be freed as a result of refreshes of their objects + var owningEdges = this.tree.getEdgesOwnedBy(this); + dojo.forEach(owningEdges, function (ar) { + if (ar) + for (var i = ar.length - 1; i >= 0; i--) + ar[i].free(); + }); + + delete this.tree.edgesByParent[this.guid]; + delete this.tree.edgesByOwner[this.guid]; + } + } + }); +}); diff --git a/src/TreeView/widget/TreeView/RenderEdge.js b/src/TreeView/widget/TreeView/RenderEdge.js new file mode 100644 index 0000000..dbd02db --- /dev/null +++ b/src/TreeView/widget/TreeView/RenderEdge.js @@ -0,0 +1,188 @@ +/** + * Rendering of a group of children + */ +define([ + "dojo/_base/declare", + "dojo/_base/lang", + "dojo/dom-attr", + "dojo/dom-style", + "TreeView/widget/TreeView/RenderNode" +], function (declare, lang, attr, domStyle, RenderNode) { + "use strict"; + + return declare("TreeView.widget.TreeView.RenderEdge", null, { + parent: null, //rendernode + type: null, //assoc type definition + domNode: null, + tree: null, + children: null, //array with children + childNode: null, //ul with the children + collapsed: null, + isEdge: true, + visible: false, + + constructor: function (parentRenderNode, type) { + logger.debug("TreeView.widget.TreeView.RenderEdge.constructor"); + this.parent = parentRenderNode; + this.type = type; + this.tree = this.parent.tree; + this.children = []; + + var childNode = this.childNode = mxui.dom.create("ul", { "class": "gg_assoc_children gg_assoc_" + type.assoc.replace(".", "_") }); + var wrapperNode = this.domNode = mxui.dom.create("li", { "class": "gg_assoc_wrapper " + type.assocclazz }); + + this.visible = type.showassocname; + if (this.visible) { + var fold = this.foldNode = mxui.dom.create("span", {}); + var caption = mxui.dom.create("span", { "class": "gg_assoc_title gg_assoc_" + type.assoc.replace(".", "_") }, type.assoccaption); + var div = new mxui.dom.create("div", { "class": "gg_row", "style": type.assocstyle }, fold, caption); + dojo.place(div, wrapperNode); + + dojo.addClass(childNode, "gg_assoc_wrapped"); + dojo.addClass(wrapperNode, "gg_node"); //only identify as node if a wrappernode is available + + mxui.dom.data(wrapperNode, "ggdata", this); + } + + this.setCollapsed(true); + + dojo.place(childNode, wrapperNode); + dojo.place(wrapperNode, this.parent.childNode); + + if (this.tree.expandall > this.parent.depth) + this.setCollapsed(false); + }, + + isA: function (type) { + //logger.debug("TreeView.widget.TreeView.RenderEdge.isA"); + return false; //assoc node is never a type + }, + + getChildCount: function () { + logger.debug("TreeView.widget.TreeView.RenderEdge.getChildCount"); + return this.children.length; + }, + + add: function (renderNode) { + logger.debug("TreeView.widget.TreeView.RenderEdge.add"); + var guid = renderNode.graphNode.guid; + this.children.push(renderNode); + this.childNode.appendChild(renderNode.domNode); + + this.updateFoldVisibility(); + }, + + remove: function (renderNode) { + logger.debug("TreeView.widget.TreeView.RenderEdge.remove"); + var baseidx = dojo.indexOf(this.children, renderNode); + if (baseidx > -1) { + this.children.splice(baseidx, 1); + if (renderNode.domNode) + dojo.destroy(renderNode.domNode); + } + + this.updateFoldVisibility(); + }, + + move: function (renderNode, newindex) { + logger.debug("TreeView.widget.TreeView.RenderEdge.move"); + var baseidx = dojo.indexOf(this.children, renderNode); + if (baseidx != -1 && baseidx != newindex) { + this.children.splice(baseidx, 1); + this.children.splice(newindex, 0, renderNode); + this.tree.moveNode(renderNode.domNode, newindex); + } + }, + + placeChildren: function () { + logger.debug("TreeView.widget.TreeView.RenderEdge.placeChildren"); + var edges = this.tree.getChildEdges(this.parent.graphNode)[this.type.index]; + for (var childguid in edges) { + var found = false; + for (var i = 0; i < this.children.length; i++) + if (this.children[i].graphNode.guid == childguid) { + found = true; + break; + } + + if (!found) { + new RenderNode.constructor(this.tree.dict[childguid], this.parent, this.type); + } + } + }, + + setCollapsed: function (collapsed, cb) { + logger.debug("TreeView.widget.TreeView.RenderEdge.setCollapsed"); + if (this.collapsed !== collapsed) { + this.collapsed = collapsed; + + //collapse + if (collapsed) { + domStyle.set(this.childNode, "display", "none"); + if (this.foldNode) //if wrapper node not visible, there is no foldnode.. + attr.set(this.foldNode, "class", "gg_assocfold gg_fold gg_folded"); + cb && cb(); + } + + //expand + else { + domStyle.set(this.childNode, "display", "block"); + if (this.foldNode) //if wrapper node not visible, there is no foldnode.. + attr.set(this.foldNode, "class", "gg_assocfold gg_fold gg_loading"); + + this.parent.graphNode.ensureChildren(this.type, lang.hitch(this, function () { + if (!this.collapsed) { //user might have clicked collapse again + domStyle.set(this.childNode, "display", "block"); + if (this.foldNode) //if wrapper node not visible, there is no foldnode.. + attr.set(this.foldNode, "class", "gg_assocfold gg_fold gg_unfolded"); + } + + + //place the children (might not be done automatically if we were expanding an already loaded item) + this.placeChildren(); + + this.updateFoldVisibility(); + + cb && cb(); + })); + } + + } + else + cb && cb(); + }, + + updateFoldVisibility: function () { + logger.debug("TreeView.widget.TreeView.RenderEdge.updateFoldVisibility"); + if (this.foldNode) { + if (this.children.length == 0) { + domStyle.set(this.foldNode, "visibility", "hidden"); + } else { + domStyle.set(this.foldNode, "visibility", ""); + } + } + this.parent.updateFoldVisibility(); + }, + + getVisibleParent: function () { + logger.debug("TreeView.widget.TreeView.RenderEdge.getVisibleParent"); + return this.parent; + }, + + free: function () { + logger.debug("TreeView.widget.TreeView.RenderEdge.free"); + if (this._destroyed) { + return; + } + this._destroyed = true; + + for (var i = this.children.length - 1; i >= 0; i--) { + this.children[i].free(); + } + + if (this.domNode) { + dojo.destroy(this.domNode); + } + } + }); +}); diff --git a/src/TreeView/widget/TreeView/RenderNode.js b/src/TreeView/widget/TreeView/RenderNode.js new file mode 100644 index 0000000..9e06a7a --- /dev/null +++ b/src/TreeView/widget/TreeView/RenderNode.js @@ -0,0 +1,270 @@ +//A Node in the rendering +define([ + "dojo/_base/declare", + "dojo/_base/lang", + "dojo/dom-attr", + "dojo/dom-style", + "TreeView/widget/TreeView/RenderEdge" +], function (declare, lang, attr, domStyle, RenderEdge) { + "use strict"; + + return declare("TreeView.widget.TreeView.RenderNode", null, { + graphNode: null, //correspoding graphnode + children: null, //list of renderEdges + domNode: null, + rowNode: null, + dataNode: null, //rendered node + childNode: null, //node to place children in + parent: null, //rendernode + type: null, //the assoc for which this item was rendered + tree: null, + depth: 0, + collapsed: null, + index: -1, + _colNodes: null, //array with columnNodes + canHazChildren: true, + isEdge: false, + hasVisibleEdge: false, //true if there is an always visible association edge. + + constructor: function (graphNode, parentRenderNode, type) { + logger.debug("TreeView.widget.TreeView.RenderNode.constructor"); + this.graphNode = graphNode; + this.parent = parentRenderNode; + this.isRoot = this.parent === null; + this.children = []; + this.type = type; + this.tree = graphNode.tree; + + this.canHazChildren = this.graphNode.getChildTypes().length > 0; + + this.foldNode = mxui.dom.create("span", { "class": "gg_nodefold gg_fold " + (this.canHazChildren ? "gg_folded" : "gg_nofold") }); + this.dataNode = mxui.dom.create("span", { + "class": "gg_data", + "style": this.graphNode.xsettings.entitystyle + }); + this.childNode = mxui.dom.create("ul", { "class": "gg_children" }); + this.rowNode = mxui.dom.create("div", { "class": "gg_row" }, this.foldNode, this.dataNode); + this.domNode = mxui.dom.create("li", { "class": "gg_node " + this.graphNode.xsettings.entityclazz }, this.rowNode, this.childNode); + + mxui.dom.data(this.domNode, "ggdata", this); + + if (this.graphNode.getChildTypes().length > 0) + dojo.addClass(this.domNode, "gg_canhazchildren"); + + this._setupColumns(); + + this.draw(true); + + this.setCollapsed(true); + + // set depth based on having a parent, root is 0, should be done before creating Rendering Edges. + if (this.parent != null) { + this.depth = this.parent.depth + 1; + } + + //setup child edges + dojo.forEach(this.graphNode.getChildTypes(), function (type) { + this.children[type.index] = new RenderEdge(this, type); + this.hasVisibleEdge |= type.showassocname; + }, this); + + //root item + if (this.parent == null) { + dojo.place(this.domNode, this.tree.treeNode); + } + //place in parent + else { + this.getEdge().add(this); + } + + graphNode.nodes.push(this); + + if (this.tree.expandall > this.depth) { + this.setCollapsed(false); + } else if (this.tree.prefetch == true) { + dojo.forEach(this.graphNode.getChildTypes(), function (type) { + if (!graphNode.children[type.index].knowsChildren) { + graphNode.ensureChildren(type, lang.hitch(this, function () { + this.children[type.index].placeChildren(); + this.children[type.index].updateFoldVisibility(); + })); + } + }, this); + } + + }, + + data: function () { + logger.debug("TreeView.widget.TreeView.RenderNode.data"); + return this.graphNode._data; + }, + + getVisibleParent: function () { + logger.debug("TreeView.widget.TreeView.RenderNode.getVisibleParent"); + if (this.parent == null) { + return null; + } + + var e = this.getEdge(); + if (e.visible) { + return e; + } + return this.parent; + }, + + getEdge: function () { + logger.debug("TreeView.widget.TreeView.RenderNode.getEdge"); + return this.parent.children[this.type.index]; + }, + + /** convenient helper method */ + isA: function (type) { + //logger.debug("TreeView.widget.TreeView.RenderNode.isA"); + return this.graphNode.isA(type); + }, + + getChildCount: function () { + logger.debug("TreeView.widget.TreeView.RenderNode.getChildCount"); + var res = 0; + dojo.forEach(this.children, function (edge) { + if (edge) { + res += edge.children.length; + } + }); + return res; + }, + + updateFoldVisibility: function () { + logger.debug("TreeView.widget.TreeView.RenderNode.updateFoldVisibility"); + if (this.foldNode) { + if (!this.hasVisibleEdge && this.getChildCount() == 0) { + domStyle.set(this.foldNode, "visibility", "hidden"); + } else { + domStyle.set(this.foldNode, "visibility", ""); + } + } + }, + + findMaxIndex: function () { + logger.debug("TreeView.widget.TreeView.RenderNode.findMaxIndex"); + var max = -100000; + dojo.forEach(this.children, function (edge) { + if (edge) { + for (var j = 0, c = null; c = edge.children[j++];) { + max = Math.max(max, c.graphNode.getSortIndex()); + } + } + }); + return max; + }, + + findMinIndex: function () { + logger.debug("TreeView.widget.TreeView.RenderNode.findMinIndex"); + var min = 100000; + dojo.forEach(this.children, function (edge) { + if (edge) { + for (var j = 0, c = null; c = edge.children[j++];) { + min = Math.min(min, c.graphNode.getSortIndex()); + } + } + }); + return min; + }, + + setCollapsed: function (newvalue, cb) { + logger.debug("TreeView.widget.TreeView.RenderNode.setCollapsed"); + if (newvalue == this.collapsed) { + cb && cb(); + return; + } + + this.collapsed = newvalue; + if (this.collapsed) { + domStyle.set(this.childNode, "display", "none"); //TODO: anim + attr.set(this.foldNode, "class", "gg_nodefold gg_fold " + (this.canHazChildren ? "gg_folded" : "gg_nofold")); + cb && cb(); + } else { + attr.set(this.foldNode, "class", "gg_nodefold gg_fold gg_loading"); + + var allChildrenCallback = lang.hitch(this, function () { + if (!this.collapsed) { //user might have clicked collapse again + domStyle.set(this.childNode, "display", "block"); //TODO: anim + attr.set(this.foldNode, "class", "gg_nodefold gg_fold " + (this.canHazChildren ? "gg_unfolded" : "gg_nofold")); + } + + this.updateFoldVisibility(); + + cb && cb(); + }); + + var left = 0; + var self = this; + + dojo.forEach(this.children, function (re) { + if (re && !re.visible) { //collapse if no wrapper node available + left += 1; + re.setCollapsed(false, lang.hitch(self, function () { + left -= 1; + if (left == 0) { + allChildrenCallback(); + left = -1; //make sure callback is not fired twice if setCollapsed is executed synchronously... + } + })); + } + }); + if (left == 0) { + allChildrenCallback(); + } + } + }, + + _setupColumns: function () { + logger.debug("TreeView.widget.TreeView.RenderNode.setupColumns"); + this._colNodes = []; + + for (var i = 0, col = null; col = this.tree.columns[i]; i++) { + if (col.appliesTo(this)) { + var span = mxui.dom.create("span", { "class": "gg_column gg_column_" + i }); + this._colNodes.push(span); + this.dataNode.appendChild(mxui.dom.create("span", { "class": "gg_column_wrapper" }, span)); //wrapper column for hovers and such + + col.setupNode(span); + } + } + }, + + draw: function (firstTime) { + logger.debug("TreeView.widget.TreeView.RenderNode.draw"); + var curCol = 0; + for (var i = 0, col = null; col = this.tree.columns[i]; i++) { + if (col.appliesTo(this)) { + col.render(this, this._colNodes[curCol], firstTime); + curCol += 1; + } + } + }, + + free: function () { + logger.debug("TreeView.widget.TreeView.RenderNode.free"); + if (this._destroyed) + return; + this._destroyed = true; + + if (this.tree.getSelection() == this) { + this.tree.setSelection(this.parent ? this.parent : null); + } + + dojo.forEach(this.children, function (edge) { + if (edge) { + edge.free(); + } + }); + + if (this.parent) { + this.getEdge().remove(this); //this will destroy the domNode as well + } else if (this.domNode) { + dojo.destroy(this.domNode); + } + } + }); +}); diff --git a/src/TreeView/widget/ui/TreeView.css b/src/TreeView/widget/ui/TreeView.css index 91ccdf1..af6d7cb 100644 --- a/src/TreeView/widget/ui/TreeView.css +++ b/src/TreeView/widget/ui/TreeView.css @@ -27,7 +27,7 @@ } .gg_column_wrapper { - display: inline-block; + display: inline-block; float : left; } @@ -38,8 +38,12 @@ .gg_assoc_wrapper > .gg_row { } +.gg_assoc_wrapper li{ + list-style-type: none; +} + .gg_nochildren { - + } .gg_row { @@ -105,7 +109,7 @@ } .gg_row_hover > .gg_row{ - background-color : #e2e2e2; + background-color : #e2e2e2; } .gg_clickable { @@ -113,7 +117,7 @@ } /* note the '>', we don't want to highlight all the descendants! */ -.gg_selected > .gg_row, +.gg_selected > .gg_row, .gg_selected > .gg_row_hover { background-color : #faffbd; outline: 1px dotted #333; @@ -122,7 +126,7 @@ /* Drag and drop styling */ .gg_dragging > .gg_row { - background-color : #e2e2e2; + background-color : #e2e2e2; opacity : 0.5; color : #444; } @@ -149,7 +153,7 @@ } .gg_drag_accept { - border-top-color : #BFDF8B; + border-top-color : #BFDF8B; } .gg_drag_deny { @@ -172,7 +176,7 @@ } .gg_drag_deny > .gg_anchor_inner { - border-color: #FC807E; + border-color: #FC807E; } .gg_drag_outside { @@ -226,4 +230,21 @@ padding-left: 8px; margin-left: 8px; border-left: 1px solid #ccc; -} \ No newline at end of file +} + +.gg_children { + outline: none; +} + +/* remove list styles */ +.gg_assoc_wrapper { + list-style: none; +} + +.gg_children.gg_root_wrapper.gg_hiddenroot { + list-style: none; +} +/* reset bootrap ul li styling*/ +.gg_assoc_children { + padding-left: 0; +} diff --git a/src/package.xml b/src/package.xml index fd0c076..9fd6d9a 100644 --- a/src/package.xml +++ b/src/package.xml @@ -1,12 +1,12 @@ - - - - - - - - - - - - + + + + + + + + + + + + \ No newline at end of file diff --git a/test/widgets/TreeView.mpk b/test/widgets/TreeView.mpk deleted file mode 100644 index 81391a7..0000000 Binary files a/test/widgets/TreeView.mpk and /dev/null differ diff --git a/test/TreeviewDemo.mpr b/test_5_21_4/TreeviewDemo.mpr similarity index 51% rename from test/TreeviewDemo.mpr rename to test_5_21_4/TreeviewDemo.mpr index b8d83f9..ba901f3 100644 Binary files a/test/TreeviewDemo.mpr and b/test_5_21_4/TreeviewDemo.mpr differ diff --git a/test/widgets/FormLoader.mpk b/test_5_21_4/widgets/FormLoader.mpk similarity index 100% rename from test/widgets/FormLoader.mpk rename to test_5_21_4/widgets/FormLoader.mpk diff --git a/test_5_21_4/widgets/MendixAppCloudNavigation.mpk b/test_5_21_4/widgets/MendixAppCloudNavigation.mpk new file mode 100644 index 0000000..5bf816d Binary files /dev/null and b/test_5_21_4/widgets/MendixAppCloudNavigation.mpk differ diff --git a/test_5_21_4/widgets/SprintrFeedbackWidget.mpk b/test_5_21_4/widgets/SprintrFeedbackWidget.mpk new file mode 100644 index 0000000..96dd6cc Binary files /dev/null and b/test_5_21_4/widgets/SprintrFeedbackWidget.mpk differ diff --git a/test_5_21_4/widgets/TreeView.mpk b/test_5_21_4/widgets/TreeView.mpk new file mode 100644 index 0000000..980a594 Binary files /dev/null and b/test_5_21_4/widgets/TreeView.mpk differ diff --git a/test_6_10_8/GridViewPlayground.mpr b/test_6_10_8/GridViewPlayground.mpr new file mode 100644 index 0000000..1aa4b08 Binary files /dev/null and b/test_6_10_8/GridViewPlayground.mpr differ diff --git a/test_6_10_8/widgets/MendixAppCloudNavigation.mpk b/test_6_10_8/widgets/MendixAppCloudNavigation.mpk new file mode 100644 index 0000000..5bf816d Binary files /dev/null and b/test_6_10_8/widgets/MendixAppCloudNavigation.mpk differ diff --git a/test_6_10_8/widgets/SprintrFeedbackWidget.mpk b/test_6_10_8/widgets/SprintrFeedbackWidget.mpk new file mode 100644 index 0000000..96dd6cc Binary files /dev/null and b/test_6_10_8/widgets/SprintrFeedbackWidget.mpk differ diff --git a/test_6_10_8/widgets/TreeView.mpk b/test_6_10_8/widgets/TreeView.mpk new file mode 100644 index 0000000..7320c71 Binary files /dev/null and b/test_6_10_8/widgets/TreeView.mpk differ