concrete 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (128) hide show
  1. data/CHANGELOG +32 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +87 -0
  4. data/concrete/basic_inline_editor.js +73 -0
  5. data/concrete/clipboard.js +72 -0
  6. data/concrete/concrete.js +58 -0
  7. data/concrete/constraint_checker.js +297 -0
  8. data/concrete/editor.js +964 -0
  9. data/concrete/element_extension.js +68 -0
  10. data/concrete/external_identifier_provider.js +112 -0
  11. data/concrete/helper.js +63 -0
  12. data/concrete/identifier_provider.js +168 -0
  13. data/concrete/inline_editor.js +55 -0
  14. data/concrete/metamodel_provider.js +171 -0
  15. data/concrete/model_interface.js +429 -0
  16. data/concrete/scroller.js +106 -0
  17. data/concrete/selector.js +302 -0
  18. data/concrete/template_provider.js +141 -0
  19. data/concrete/ui/abstract_dialog.js +80 -0
  20. data/concrete/ui/concrete_ui.js +28 -0
  21. data/concrete/ui/create_module_dialog.js +55 -0
  22. data/concrete/ui/images/close.png +0 -0
  23. data/concrete/ui/images/document-new.png +0 -0
  24. data/concrete/ui/images/document-save.png +0 -0
  25. data/concrete/ui/images/edit-find-replace.png +0 -0
  26. data/concrete/ui/images/emblem-symbolic-link.png +0 -0
  27. data/concrete/ui/images/help-browser.png +0 -0
  28. data/concrete/ui/images/minus_11px.png +0 -0
  29. data/concrete/ui/images/plus_11px.png +0 -0
  30. data/concrete/ui/images/preferences-system.png +0 -0
  31. data/concrete/ui/images/process-stop.png +0 -0
  32. data/concrete/ui/images/system-search.png +0 -0
  33. data/concrete/ui/layout_manager.js +54 -0
  34. data/concrete/ui/module_browser.js +88 -0
  35. data/concrete/ui/module_editor.js +217 -0
  36. data/concrete/ui/open_element_dialog.js +90 -0
  37. data/concrete/ui/preferences_dialog.js +75 -0
  38. data/concrete/ui/proceed_dialog.js +52 -0
  39. data/concrete/ui/search_replace_dialog.js +323 -0
  40. data/concrete/ui/style.css +296 -0
  41. data/concrete/ui/toolbar.js +74 -0
  42. data/concrete/ui/workbench.js +165 -0
  43. data/doc/concrete_developers_guide.html +1054 -0
  44. data/doc/concrete_developers_guide.txt +502 -0
  45. data/doc/concrete_users_guide.html +694 -0
  46. data/doc/concrete_users_guide.txt +223 -0
  47. data/example/formula_editor/example_data/example1.json +11 -0
  48. data/example/formula_editor/formula_editor.html +83 -0
  49. data/example/formula_editor/sqrt_horz.png +0 -0
  50. data/example/formula_editor/sqrt_vert.png +0 -0
  51. data/example/formula_editor/style.css +31 -0
  52. data/example/metamodel_editor/edit.rb +54 -0
  53. data/example/metamodel_editor/example_data/formula_metamodel.json +18 -0
  54. data/example/metamodel_editor/example_data/meta_metamodel.json +22 -0
  55. data/example/metamodel_editor/example_data/statemachine_metamodel.json +32 -0
  56. data/example/metamodel_editor/metamodel_editor.html +120 -0
  57. data/example/metamodel_editor/metamodel_editor2.html +135 -0
  58. data/example/metamodel_editor/metamodel_editor3.html +151 -0
  59. data/example/metamodel_editor/style.css +8 -0
  60. data/example/metamodel_editor/style2.css +19 -0
  61. data/example/metamodel_editor/style3.css +35 -0
  62. data/example/minimal_editor/minimal_editor.html +43 -0
  63. data/example/statemachine_editor/example_data/example1.json +11 -0
  64. data/example/statemachine_editor/state_background.png +0 -0
  65. data/example/statemachine_editor/statemachine_editor0.html +55 -0
  66. data/example/statemachine_editor/statemachine_editor1.html +62 -0
  67. data/example/statemachine_editor/statemachine_editor2.html +103 -0
  68. data/example/statemachine_editor/style0.css +8 -0
  69. data/example/statemachine_editor/style1.css +32 -0
  70. data/example/statemachine_editor/style2.css +43 -0
  71. data/example/themes/cobalt.css +176 -0
  72. data/example/themes/dialog-error.png +0 -0
  73. data/example/themes/dialog-information.png +0 -0
  74. data/example/themes/dialog-warning.png +0 -0
  75. data/example/themes/dots_12px.png +0 -0
  76. data/example/themes/fold_button_dots_when_hidden.css +18 -0
  77. data/example/themes/fold_button_plus_minus.css +21 -0
  78. data/example/themes/fold_button_plus_when_hidden.css +18 -0
  79. data/example/themes/light_blue.css +177 -0
  80. data/example/themes/minus_11px.png +0 -0
  81. data/example/themes/minus_13px.png +0 -0
  82. data/example/themes/minus_9px.png +0 -0
  83. data/example/themes/plus_11px.png +0 -0
  84. data/example/themes/plus_13px.png +0 -0
  85. data/example/themes/plus_9px.png +0 -0
  86. data/example/themes/white.css +177 -0
  87. data/lib/concrete/concrete_syntax_provider.rb +63 -0
  88. data/lib/concrete/config.rb +36 -0
  89. data/lib/concrete/file_cache_map.rb +88 -0
  90. data/lib/concrete/index_builder.rb +108 -0
  91. data/lib/concrete/metamodel/concrete_mmm.rb +45 -0
  92. data/lib/concrete/metamodel/ecore_to_concrete.rb +80 -0
  93. data/lib/concrete/server.rb +92 -0
  94. data/lib/concrete/util/logger.rb +24 -0
  95. data/lib/concrete/util/string_writer.rb +17 -0
  96. data/lib/concrete/working_set.rb +41 -0
  97. data/rakefile +33 -0
  98. data/redist/prototype.js +4320 -0
  99. data/redist/scriptaculous/builder.js +136 -0
  100. data/redist/scriptaculous/controls.js +991 -0
  101. data/redist/scriptaculous/dragdrop.js +975 -0
  102. data/redist/scriptaculous/effects.js +1130 -0
  103. data/redist/scriptaculous/scriptaculous.js +60 -0
  104. data/redist/scriptaculous/slider.js +275 -0
  105. data/redist/scriptaculous/sound.js +55 -0
  106. data/redist/scriptaculous/unittest.js +568 -0
  107. data/test/concrete_test.rb +5 -0
  108. data/test/file_cache_map_test.rb +90 -0
  109. data/test/file_cache_map_test/testdir/fileA +1 -0
  110. data/test/index_builder_test.rb +68 -0
  111. data/test/index_builder_test/ecore_index.js +85 -0
  112. data/test/index_builder_test/ecore_index_expected.js +85 -0
  113. data/test/integration/external_elements_test.html +114 -0
  114. data/test/metamodel_test.rb +40 -0
  115. data/test/metamodel_test/concrete_mmm_expected.js +19 -0
  116. data/test/metamodel_test/concrete_mmm_generated.js +19 -0
  117. data/test/metamodel_test/concrete_mmm_regenerated.js +19 -0
  118. data/test/unit/external_identifier_provider_test.html +138 -0
  119. data/test/unit/identifier_provider_test.html +269 -0
  120. data/test/unit/metamodel_provider_test.html +318 -0
  121. data/test/unit/model_interface_test.html +257 -0
  122. data/test/unit/template_provider_test.html +171 -0
  123. data/test/unit/test.css +90 -0
  124. data/test/working_set_test.rb +54 -0
  125. data/test/working_set_test/file1.txt +0 -0
  126. data/test/working_set_test/file2 +0 -0
  127. data/test/working_set_test/subdir/file3.xml +0 -0
  128. metadata +201 -0
@@ -0,0 +1,32 @@
1
+ =0.1.0 (February 3rd, 2010)
2
+
3
+ * First public release
4
+
5
+ =0.2.0 (June 14th, 2010)
6
+
7
+ * Added higher level "workbench" editor support
8
+ * Added Ruby/RGen backend support
9
+ * Added model info hovers which share the popup window used for error descriptions
10
+ * Added fold button support
11
+ * Added read only mode of editor widget
12
+ * Added float datatype support
13
+ * Added support for table based element layouts
14
+ * Added metamodel editor examples showing box based and table based layouts
15
+ * Added support for feature sort function and element icons in TemplateProvider
16
+ * Added external identifier support
17
+ * Added "always hide" strategy for features
18
+ * Added selector option "cursorEdgeOnly", fixed non-edge-only mode
19
+ * Added "white" theme
20
+ * Added helper for iterating through model elements in depth first order
21
+ * Added button to open Concrete Users Guide
22
+ * The empty element/value placeholder shows the feature name
23
+ * Constraint checks run asynchronously in the background
24
+ * Several performance improvements for larger models
25
+ * Elements can be created in collapsed state
26
+ * Concrete can be distributed as a Ruby gem
27
+ * Fixed scrolling to selected element to also work in scroll containers
28
+ * Fixed error hovers to be only shown for focused editor
29
+ * Fixed Scriptaculous bugs: do not scroll page while moving in autocompletion box, do not hide autocompletion box when scrollbar is operated
30
+ * Fixed marker (cursor) positioning for editor nodes with offset
31
+ * Fixed exception in case fold control nodes are not present
32
+
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Martin Thiede
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,87 @@
1
+ = Concrete Model Editor
2
+
3
+ Concrete is a lightweight, web-based model editor which can be configured for different DSLs (Domain Specific Languages).
4
+
5
+ The basic idea is to make use of the browser's document object model (DOM) to represent models and it's rendering engine to create the graphical represenation. DSLs are defined by metamodels (abstract syntax) and DOM templates combined with CSS (concrete syntax).
6
+
7
+ Concrete is a Javascript widget based on Prototype and Scriptaculous. It exchanges model data in JSON format and can be integrated easily using Ajax.
8
+
9
+ Besides the pure editor widget Concrete now also contains additional support for building larger editors. There is Javascript code implementing a simple "workbench" with an index view and edit view and search and replace functionality. In addition, there is Ruby code implementing the server side counterpart for loading and storing data with a local file system. As an example, check out the separate "mmedit" project on github.
10
+
11
+ Note that Concrete can also be used with non-Ruby backends.
12
+
13
+ Concrete has been tested sucessfully with Firefox and Chrome.
14
+
15
+
16
+ == Screencast
17
+
18
+ See Concrete in action by watching the screencast[http://vimeo.com/9164866].
19
+
20
+
21
+ == Download
22
+
23
+ You can get the latest version from github.
24
+
25
+
26
+ == Installation
27
+
28
+ Concrete is not a standalone editor but needs to be integrated into a (web) application. For more information how to do this, see the developers guide and the examples included.
29
+
30
+ Ruby users can install Concrete as a gem:
31
+
32
+ > gem install concrete
33
+
34
+ The Ruby based functionality of Concrete requires the "rgen" modelling framework and the little "andand" gem which should be installed automatically. Otherwise it can be installed using:
35
+
36
+ > gem install andand rgen
37
+
38
+ RGen is also available on github.
39
+
40
+ The Ruby part of Concrete should work with any Ruby version starting with Ruby 1.8.6. The Ruby installers can be downloaded from:
41
+
42
+ http://ruby-lang.org
43
+
44
+
45
+ == Documentation
46
+
47
+ There is a Users Guide and a Developers Guide in the doc folder. The Users Guide explains the usage of the editor independant of any specific DSL. The Developers Guide describes how to create own DSLs and how to integrate Concrete.
48
+
49
+
50
+ == Examples
51
+
52
+ Currently, the examples folder contains:
53
+ * A metamodel editor which can be used to create and edit Concrete metamodels
54
+ * A simple formula editor which shows some more math like graphical representation
55
+ * A simple statechart editor, featuring graphical representation of states
56
+
57
+ To use the examples, just open the HTML file in your browser (currently it should be Firefox). You can start from scratch or load a given model in JSON format. To do so just paste it into the clipboard box visible on each example page, then focus the editor and paste again using Ctrl-V. To store an example model, select it in the editor using Ctrl-A, copy it to the internal clipboard using Ctrl-C and then copy the JSON from the clipboard area to where you need it.
58
+
59
+ The metamodel editor comes in 3 flavours demonstrating text based, table based and box based layouts.
60
+
61
+ In the metamodel example, there is also a little Ruby program which shows server interaction with Ajax. You can run the edit.rb with a JSON file as argument and it will fire up the browser, load the model, let you edit it and write it back to the file when you click the "Save" button. There is a folder "example_data" which contains the plain metamodels of the examples, they are basically the same as what is embedded in the respective example HTML files.
62
+
63
+ To edit the statemachine example metamodel, you would do:
64
+
65
+ example/metamodel_editor> ruby edit.rb example_data/statemachine_metamodel.json
66
+
67
+ If you want to try the respective editor with the changed metamodel, you have to put it into the HTML file. It will not load the metamodel from the "example_data" folder.
68
+
69
+ A much more sophisticated example metamodel editor is "mmedit" which is available as a separate gem on github.
70
+
71
+
72
+ == License
73
+
74
+ Concrete is licensed under the terms of the MIT license, see the included MIT-LICENSE file.
75
+
76
+
77
+ == Credits
78
+
79
+ Concrete is based on the great Prototype Javascript framework which makes Javascript feel much more like Ruby:
80
+ http://www.prototypejs.org
81
+
82
+ For auto completion and some visual effects, as well as the unit test framework, thanks go to Scriptaculous:
83
+ http://script.aculo.us
84
+
85
+ Almost all of the icons are taken from the Tango icon theme:
86
+ http://tango.freedesktop.org
87
+
@@ -0,0 +1,73 @@
1
+ // Concrete Model Editor
2
+ //
3
+ // Copyright (c) 2010 Martin Thiede
4
+ //
5
+ // Concrete is freely distributable under the terms of an MIT-style license.
6
+
7
+ Concrete.BasicInlineEditor = Class.create({
8
+
9
+ show: function(element, initialText, partial, completionOptions) {
10
+ if (this.element) {
11
+ // edit still in progress, cancel
12
+ this.hide();
13
+ }
14
+ this.element = element;
15
+ if (element.parentNode.tagName.toUpperCase() == "TBODY") {
16
+ cols = element.up("table").select("tr").max(function(r) { return r.childElements().select(function(c) {return c.tagName.toUpperCase() == "TD";}).size(); });
17
+ Element.insert(element, { after:
18
+ "<tr><td colspan='"+cols+"'>" +
19
+ "<div class='ct_inline_editor' style='display: inline'>" +
20
+ "<input type='text' size='"+initialText.length+"' value='"+initialText+"'/>" +
21
+ "<div style='display: none; position: absolute; border: 1px solid grey; background-color: white;'></div>" +
22
+ "</div>" +
23
+ "</td></tr>"
24
+ });
25
+ input = element.next().down().down().down();
26
+ }
27
+ else {
28
+ Element.insert(element, { after:
29
+ "<div class='ct_inline_editor' style='display: inline'>" +
30
+ "<input type='text' size='"+initialText.length+"' value='"+initialText+"'/>" +
31
+ "<div style='display: none; position: absolute; border: 1px solid grey; background-color: white;'></div>" +
32
+ "</div>"
33
+ });
34
+ input = element.next().down();
35
+ }
36
+ element.hide();
37
+ this.input = input
38
+ input.select();
39
+ this._interval = window.setInterval(function() {
40
+ // set size one larger than the text, otherwise the first char is shifted out left when new chars are added at the right
41
+ input.size = input.value.length + 1;
42
+ }, 50);
43
+ new Autocompleter.Local(input, input.next(), completionOptions, {partialSearch: partial, fullSearch: partial, minChars: 0, partialChars: 0, onShow:
44
+ function(element, update){
45
+ if(!update.style.position || update.style.position=='absolute') {
46
+ update.style.position = 'absolute';
47
+ Position.clone(element, update, {
48
+ setHeight: false,
49
+ setWidth: false, // in contrast to the original built-in default, do not set width
50
+ offsetTop: element.offsetHeight
51
+ });
52
+ }
53
+ Effect.Appear(update,{duration:0.15});
54
+ }
55
+ })
56
+ },
57
+
58
+ getText: function() {
59
+ return this.input.value;
60
+ },
61
+
62
+ setError: function() {
63
+ this.element.next().addClassName("ct_error");
64
+ },
65
+
66
+ hide: function() {
67
+ if (this._interval) window.clearInterval(this._interval);
68
+ this.element.show();
69
+ this.element.next().remove();
70
+ this.element = undefined;
71
+ }
72
+
73
+ })
@@ -0,0 +1,72 @@
1
+ // Concrete Model Editor
2
+ //
3
+ // Copyright (c) 2010 Martin Thiede
4
+ //
5
+ // Concrete is freely distributable under the terms of an MIT-style license.
6
+
7
+ Concrete.Clipboard = Class.create({
8
+
9
+ // if a storageElement is specified, the data is stored as textContent of this element
10
+ initialize: function(storageElement) {
11
+ this.element = storageElement;
12
+ this.kind = undefined;
13
+ },
14
+
15
+ read: function() {
16
+ var data = this._readRaw();
17
+ if (data && Object.isString(data) && data.isJSON()) {
18
+ return data.evalJSON();
19
+ }
20
+ else {
21
+ return data;
22
+ }
23
+ },
24
+
25
+ write: function(data) {
26
+ if (data instanceof Object) {
27
+ this._writeRaw(Concrete.Helper.prettyPrintJSON(Object.toJSON(data)));
28
+ }
29
+ else {
30
+ this._writeRaw(data);
31
+ }
32
+ },
33
+
34
+ containsElement: function() {
35
+ var data = this._readRaw();
36
+ return data && Object.isString(data) && data.isJSON();
37
+ },
38
+
39
+ containsValue: function() {
40
+ var data = this._readRaw();
41
+ return data && Object.isString(data) && !data.isJSON();
42
+ },
43
+
44
+ _readRaw: function() {
45
+ if (this.element) {
46
+ if (this.element.tagName == "TEXTAREA") {
47
+ return this.element.value;
48
+ }
49
+ else {
50
+ return this.element.innerHTML;
51
+ }
52
+ }
53
+ else {
54
+ return this.data;
55
+ }
56
+ },
57
+
58
+ _writeRaw: function(data) {
59
+ if (this.element) {
60
+ if (this.element.tagName == "TEXTAREA") {
61
+ this.element.value = data;
62
+ }
63
+ else {
64
+ this.element.innerHTML = data;
65
+ }
66
+ }
67
+ else {
68
+ this.data = data;
69
+ }
70
+ }
71
+
72
+ });
@@ -0,0 +1,58 @@
1
+ // Concrete Model Editor
2
+ //
3
+ // Copyright (c) 2010 Martin Thiede
4
+ //
5
+ // Permission is hereby granted, free of charge, to any person obtaining
6
+ // a copy of this software and associated documentation files (the
7
+ // "Software"), to deal in the Software without restriction, including
8
+ // without limitation the rights to use, copy, modify, merge, publish,
9
+ // distribute, sublicense, and/or sell copies of the Software, and to
10
+ // permit persons to whom the Software is furnished to do so, subject to
11
+ // the following conditions:
12
+ //
13
+ // The above copyright notice and this permission notice shall be
14
+ // included in all copies or substantial portions of the Software.
15
+ //
16
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ var Concrete = {
25
+ Version: '0.1.0',
26
+ require: function(r) {
27
+ document.write('<script type="text/javascript" src="'+r+'"><\/script>');
28
+ },
29
+ load: function() {
30
+ // TODO: check prototype and scriptaculous version
31
+ var js = /concrete\.js(\?.*)?$/;
32
+ $$('head script[src]').findAll(function(s) {
33
+ return s.src.match(js);
34
+ }).each(function(s) {
35
+ var path = s.src.replace(js, '');
36
+ [
37
+ 'editor',
38
+ 'selector',
39
+ 'scroller',
40
+ 'element_extension',
41
+ 'basic_inline_editor',
42
+ 'inline_editor',
43
+ 'template_provider',
44
+ 'model_interface',
45
+ 'metamodel_provider',
46
+ 'identifier_provider',
47
+ 'external_identifier_provider',
48
+ 'constraint_checker',
49
+ 'clipboard',
50
+ 'helper'
51
+ ].each( function(include) {
52
+ Concrete.require(path + include + '.js');
53
+ });
54
+ });
55
+ }
56
+ }
57
+
58
+ Concrete.load();
@@ -0,0 +1,297 @@
1
+ // Concrete Model Editor
2
+ //
3
+ // Copyright (c) 2010 Martin Thiede
4
+ //
5
+ // Concrete is freely distributable under the terms of an MIT-style license.
6
+
7
+ Concrete.ConstraintChecker = Class.create({
8
+
9
+ // Options:
10
+ // externalIdentifierProvider: external identifier provider, default: none
11
+ // externalModule: name of the current module within external index, default: none
12
+ // allowDuplicates: classes of which instances with same indentifier may exist, default: none
13
+ // automaticChecking: if set to false, do not run constraint checks when model changes, default: true
14
+ //
15
+ initialize: function(rootClasses, identifierProvider, options) {
16
+ this.options = options || {};
17
+ this.options.allowDuplicates = this.options.allowDuplicates || [];
18
+ this.rootClasses = rootClasses;
19
+ this.identifierProvider = identifierProvider;
20
+ this.externalIdentifierProvider = this.options.externalIdentifierProvider;
21
+ this.featureConstraints = {};
22
+ this._automaticChecking = (this.options.automaticChecking == undefined) ? true : this.options.automaticChecking;
23
+ },
24
+
25
+ // model root must be set for the checker to work
26
+ setModelRoot: function(modelRoot) {
27
+ this.modelRoot = modelRoot;
28
+ },
29
+
30
+ addConstraint: function(constraint) {
31
+ if (constraint instanceof Concrete.ConstraintChecker.FeatureValueConstraint) {
32
+ this.featureConstraints[constraint.class()] = this.featureConstraints[constraint.class()] || {}
33
+ this.featureConstraints[constraint.class()][constraint.feature()] = this.featureConstraints[constraint.class()][constraint.feature()] || [];
34
+ this.featureConstraints[constraint.class()][constraint.feature()].push(constraint);
35
+ }
36
+ },
37
+
38
+ // ModelChangeListener Interface
39
+
40
+ elementAdded: function(element) {
41
+ },
42
+
43
+ elementChanged: function(element, feature) {
44
+ },
45
+
46
+ elementRemoved: function(element) {
47
+ },
48
+
49
+ rootChanged: function(root) {
50
+ },
51
+
52
+ commitChanges: function() {
53
+ if (this._automaticChecking) {
54
+ this.updateAllProblems();
55
+ }
56
+ },
57
+
58
+ // ModelChangeListener End
59
+
60
+ isValidInstance: function(type, element) {
61
+ this._allowedTypes = this._allowedTypes || {};
62
+ this._allowedTypes[type.name] = this._allowedTypes[type.name] || type.allSubTypes().concat(type);
63
+ return this._allowedTypes[type.name].include(element.mmClass);
64
+ },
65
+
66
+ isValidValue: function(mmFeature, value) {
67
+ var opts = this.attributeOptions(mmFeature);
68
+ return (!(opts instanceof RegExp) || opts.test(value)) &&
69
+ ((opts instanceof RegExp) || opts == undefined || opts.include(value));
70
+ },
71
+
72
+ attributeOptions: function(mmFeature) {
73
+ if (mmFeature.type.isEnum()) {
74
+ // enum
75
+ return mmFeature.type.literals;
76
+ }
77
+ else if (mmFeature.type.isBoolean()) {
78
+ return ["true", "false"];
79
+ }
80
+ else if (mmFeature.type.isInteger()) {
81
+ return /^(-?[1-9]\d*|0)$/;
82
+ }
83
+ else if (mmFeature.type.isFloat()) {
84
+ return /^(-?[1-9]\d*|0)(\.\d+)?$/;
85
+ }
86
+ else {
87
+ return undefined;
88
+ }
89
+ },
90
+
91
+ elementOptions: function(slot) {
92
+ if (slot.hasClassName("ct_root")) {
93
+ return this.rootClasses.reject(function(t){return t.abstract}).collect(function(c){ return c.name});
94
+ }
95
+ else {
96
+ var type = slot.mmFeature().type;
97
+ return type.allSubTypes().concat(type).reject(function(t){return t.abstract}).collect(function(t){ return t.name});
98
+ }
99
+ },
100
+
101
+ // enable and disable automatic checking
102
+ setAutomaticChecking: function(checking) {
103
+ this._automaticChecking = checking;
104
+ },
105
+
106
+ // run all constraint checks and update the error annotations
107
+ // this method must be called explicitly if automatic checking is disabled
108
+ //
109
+ updateAllProblems: function() {
110
+ var element = this.modelRoot.childElements().first();
111
+ // in case the first element is an empty element, skip to next
112
+ if (element && !element.isElement()) element = element.next();
113
+ if (!element) return;
114
+ var stack = [];
115
+ if (this._intervalTimer) window.clearInterval(this._intervalTimer);
116
+ this._intervalTimer = window.setInterval(function() {
117
+ var i;
118
+ for (i=0; i<100; i++) {
119
+ this._updateElementProblems(element);
120
+ element = Concrete.ModelInterface.Helper.nextElement(element, stack);
121
+ if (!element) {
122
+ window.clearInterval(this._intervalTimer);
123
+ this._intervalTimer = undefined;
124
+ break;
125
+ }
126
+ }
127
+ }.bind(this), 0.01);
128
+ },
129
+
130
+ // private
131
+
132
+ _updateElementProblems: function(element) {
133
+ if (!element || !element.isElement()) return [];
134
+ this._removeErrors(element);
135
+ this._checkElement(element).each(function(p) { this._addError(element, p); }, this);
136
+ element.features.each(function(f) {
137
+ this._removeErrors(f);
138
+ this._checkFeature(element, f).each(function(p) { this._addError(f, p); }, this);
139
+ }, this);
140
+ },
141
+
142
+ _checkElement: function(element) {
143
+ var problems = [];
144
+ if (element.parentNode.hasClassName("ct_root")) {
145
+ if (!this.rootClasses.include(element.mmClass)) {
146
+ problems.push("element of class '"+element.mmClass.name+"' not allowed");
147
+ }
148
+ }
149
+ else {
150
+ if (!this.isValidInstance(element.mmFeature("ct_containment").type, element)) {
151
+ problems.push("element of class '"+element.mmClass.name+"' not allowed");
152
+ }
153
+ }
154
+ if (element.mmClass.abstract) {
155
+ problems.push("class '"+element.mmClass.name+"' is abstract");
156
+ }
157
+ if (!this.options.allowDuplicates.include(element.mmClass)) {
158
+ var ident = this.identifierProvider.getIdentifier(element);
159
+ if (this.identifierProvider.getElement(ident) instanceof Array) {
160
+ problems.push("duplicate identifier '"+ident+"'");
161
+ }
162
+ else if (this.externalIdentifierProvider) {
163
+ var ei = this.externalIdentifierProvider.getElementInfo(ident, {ignoreModule: this.options.externalModule});
164
+ if (ei) {
165
+ var loc = Object.isString(ei.module) ? ei.module : "external module";
166
+ problems.push("duplicate identifier '"+ident+"', also defined in "+loc);
167
+ }
168
+ }
169
+ }
170
+ return problems;
171
+ },
172
+
173
+ _checkFeature: function(element, feature) {
174
+ var problems = [];
175
+ var mmf = feature.mmFeature;
176
+ var children = feature.slot.childElements().select(function(c) { return !c.hasClassName("ct_empty"); });
177
+ var featureConstraints = this._featureConstraints(element, feature);
178
+ if (mmf.upperLimit > -1 && children.size() > mmf.upperLimit) {
179
+ if (mmf.upperLimit == 1) {
180
+ if (mmf.isContainment()) {
181
+ problems.push("only one element may be specified as '"+mmf.name+"'");
182
+ }
183
+ else {
184
+ problems.push("only one value may be specified as '"+mmf.name+"'");
185
+ }
186
+ }
187
+ problems.push("above upper limit of '"+mmf.upperLimit+"'");
188
+ }
189
+ if (mmf.lowerLimit > 0 && children.size() < mmf.lowerLimit) {
190
+ if (mmf.lowerLimit == 1) {
191
+ problems.push("'"+mmf.name+"' must be specified");
192
+ }
193
+ else {
194
+ problems.push("below lower limit of '"+mmf.upperLimit+"'");
195
+ }
196
+ }
197
+ if (mmf.isContainment()) {
198
+ // correct element type is checked for each element
199
+ children.each(function(c) {
200
+ this._checkFeatureConstraints(featureConstraints, element, c, problems);
201
+ }, this);
202
+ }
203
+ else if (mmf.isReference()) {
204
+ children.each(function(c) {
205
+ var targets = this.identifierProvider.getElement(c.value);
206
+ if (!(targets instanceof Array)) targets = [targets].compact();
207
+ if (this.externalIdentifierProvider) {
208
+ var ei = this.externalIdentifierProvider.getElementInfo(c.value, {ignoreModule: this.options.externalModule});
209
+ if (ei) {
210
+ // here we add a type instead of an element
211
+ targets = targets.concat(ei.type);
212
+ }
213
+ }
214
+ if (targets.size() == 0) {
215
+ problems.push("can not resolve reference");
216
+ }
217
+ else if (targets.size() > 1) {
218
+ problems.push("multiple targets for reference");
219
+ }
220
+ else {
221
+ var type = targets[0].mmClass ? targets[0].mmClass : targets[0];
222
+ if (!(mmf.type.allSubTypes().concat(mmf.type).include(type))) {
223
+ problems.push("reference to class '"+type.name+"' not allowed");
224
+ }
225
+ else if (targets[0].mmClass) {
226
+ // if target is an element, i.e. is local
227
+ this._checkFeatureConstraints(featureConstraints, element, targets[0], problems);
228
+ }
229
+ }
230
+ }, this);
231
+ }
232
+ else {
233
+ children.each(function(c) {
234
+ if (!this.isValidValue(mmf, c.value)) {
235
+ problems.push("value not allowed");
236
+ }
237
+ else {
238
+ this._checkFeatureConstraints(featureConstraints, element, c.value, problems);
239
+ }
240
+ }, this);
241
+ }
242
+ return problems.uniq();
243
+ },
244
+
245
+ _featureConstraints: function(element, feature) {
246
+ var byFeature = this.featureConstraints[element.mmClass.name]
247
+ return (byFeature && byFeature[feature.mmFeature.name]) || [];
248
+ },
249
+
250
+ _checkFeatureConstraints: function(constraints, element, value, problems) {
251
+ constraints.each(function(c) {
252
+ if (!c.check(element, value)) {
253
+ problems.push(c.message(element));
254
+ }
255
+ });
256
+ },
257
+
258
+ _addError: function(node, text) {
259
+ if (!node._errorDescriptions) {
260
+ node._errorDescriptions = [];
261
+ node.addClassName("ct_error");
262
+ }
263
+ var desc = Concrete.Helper.createDOMNode("div", {class: "ct_error_description", style: "display: none"}, text);
264
+ node.appendChild(desc);
265
+ node._errorDescriptions.push(desc);
266
+ },
267
+
268
+ _removeErrors: function(node) {
269
+ if (node._errorDescriptions) {
270
+ node._errorDescriptions.each(function(d) {
271
+ d.remove();
272
+ });
273
+ node.removeClassName("ct_error");
274
+ node._errorDescriptions = undefined;
275
+ }
276
+ }
277
+
278
+ });
279
+
280
+ Concrete.ConstraintChecker.FeatureValueConstraint = Class.create({
281
+ initialize: function(options) {
282
+ this.options = options;
283
+ },
284
+ class: function() {
285
+ return this.options.class;
286
+ },
287
+ feature: function() {
288
+ return this.options.feature;
289
+ },
290
+ check: function(element, value) {
291
+ return this.options.checker(element, value);
292
+ },
293
+ message: function(element) {
294
+ var msg = this.options.message;
295
+ return Object.isFunction(msg) ? msg(element) : msg;
296
+ }
297
+ });