concrete 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,171 @@
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.MetamodelProvider = Class.create({
8
+
9
+ initialize: function(metamodelJson, opts) {
10
+ this.metamodel = metamodelJson;
11
+ this.metaclassesByName = {};
12
+ this.metaclasses = [];
13
+ this._extendedChecks = (opts == undefined || !(opts.extended_checks == false));
14
+ this._resolveMetamodel();
15
+ },
16
+
17
+ _resolveMetamodel: function() {
18
+ var rootClass = {name: "_Root", abstract: true, subTypes: [], superTypes: [], features: []};
19
+ Object.extend(rootClass, Concrete.MetamodelExtension.Class);
20
+
21
+ var datatypesByName = {};
22
+ this.metamodel.each(function(c) {
23
+ if (c._class == "Class") {
24
+ if (!c.name || c.name.length == 0) throw new Error("Class name is empty");
25
+ if (this.metaclassesByName[c.name]) throw new Error("Class name '"+c.name+"' not unique");
26
+ if (this._extendedChecks) this._extendedClassChecks(c);
27
+ Object.extend(c, Concrete.MetamodelExtension.Class);
28
+ c.subTypes = [];
29
+ if (c.abstract == undefined) c.abstract = false;
30
+ this.metaclassesByName[c.name] = c;
31
+ this.metaclasses.push(c);
32
+ }
33
+ else if (c._class == "Datatype" || c._class == "Enum") {
34
+ if (!c.name || c.name.length == 0) throw new Error("Datatype name is empty");
35
+ if (datatypesByName[c.name]) throw new Error("Datatype name '"+c.name+"' not unique");
36
+ if (this._extendedChecks) this._extendedDatatypeChecks(c);
37
+ Object.extend(c, Concrete.MetamodelExtension.Datatype);
38
+ datatypesByName[c.name] = c;
39
+ }
40
+ else {
41
+ throw new Error ("specify '_class' property, it must be one of [Datatype, Enum, Class]");
42
+ }
43
+ }, this);
44
+ // default datatype in case it is not present
45
+ if (!datatypesByName["String"]) {
46
+ var dt = {name: "String"};
47
+ Object.extend(dt, Concrete.MetamodelExtension.Datatype);
48
+ datatypesByName["String"] = dt;
49
+ }
50
+ this.metaclasses.each(function(c) {
51
+ if (c.superTypes) {
52
+ if (!(c.superTypes instanceof Array)) c.superTypes = [c.superTypes];
53
+ c.superTypes = [rootClass].concat(c.superTypes.collect(function(t) {
54
+ var target = this.metaclassesByName[t];
55
+ if (!target) throw new Error("Can not resolve supertype '"+t+"' in class '"+c.name+"'");
56
+ return target;
57
+ }, this));
58
+ }
59
+ else {
60
+ c.superTypes = [rootClass];
61
+ }
62
+ c.superTypes.each(function(t) {
63
+ t.subTypes.push(c);
64
+ });
65
+ featureNames = {};
66
+ c.features = c.features || [];
67
+ if (!(c.features instanceof Array)) c.features = [c.features];
68
+ c.features.each(function(f) {
69
+ if (!f.name || f.name.length == 0) throw new Error("Feature name is empty in class '"+c.name+"'");
70
+ if (featureNames[f.name]) throw new Error("Feature name '"+f.name+"' not unique in class '"+c.name+"'");
71
+ if (this._extendedChecks) this._extendedFeatureChecks(f, c);
72
+ Object.extend(f, Concrete.MetamodelExtension.Feature);
73
+ f.containingClass = c;
74
+ featureNames[f.name] = true;
75
+ f.kind = f.kind || "attribute";
76
+ if (f.isReference() || f.isContainment()) {
77
+ if (f.type) {
78
+ var target = this.metaclassesByName[f.type];
79
+ if (!target) throw new Error("Can not resolve type '"+f.type+"' in feature '"+f.name+"' class '"+c.name+"'");
80
+ f.type = target;
81
+ }
82
+ else {
83
+ f.type = rootClass;
84
+ }
85
+ if (f.isReference()) {
86
+ f.upperLimit = f.upperLimit || 1;
87
+ }
88
+ else {
89
+ f.upperLimit = f.upperLimit || -1;
90
+ }
91
+ f.lowerLimit = f.lowerLimit || 0;
92
+ }
93
+ else {
94
+ var typename = f.type || "String";
95
+ f.type = datatypesByName[typename];
96
+ if (!f.type) throw new Error("Can not resolve datatype '"+typename+"' in feature '"+f.name+"' class '"+c.name+"'");
97
+ f.upperLimit = f.upperLimit || 1;
98
+ f.lowerLimit = f.lowerLimit || 0;
99
+ }
100
+ }, this);
101
+ }, this);
102
+ },
103
+
104
+ _extendedDatatypeChecks: function(type) {
105
+ for (p in type) {
106
+ if (!["_class", "name", "literals"].include(p)) throw new Error("Unknown property '"+p+"' in type '"+type.name+"'");
107
+ }
108
+ if (type._class != "Enum" && type.literals != undefined) {
109
+ throw new Error("Literals can only be specified for Enums");
110
+ }
111
+ if (type.literals != undefined) {
112
+ if (!(type.literals instanceof Array) || !type.literals.all(function(l) { return Object.isString(l); })) throw new Error("Enum literals must be an Array of Strings in type '"+type.name+"'");
113
+ }
114
+ if (type._class == "Datatype") {
115
+ if (!(["String", "Integer", "Float", "Boolean"].include(type.name))) throw new Error("Plain Datatypes (excluding Enums) must be named one of [String, Integer, Float, Boolean]");
116
+ }
117
+ },
118
+
119
+ _extendedClassChecks: function(clazz) {
120
+ for (p in clazz) {
121
+ if (!["_class", "features", "name", "superTypes", "abstract"].include(p)) throw new Error("Unknown property '"+p+"' in class '"+clazz.name+"'");
122
+ }
123
+ if (clazz.abstract != undefined) {
124
+ if (clazz.abstract != true && clazz.abstract != false) throw new Error("Abstract property must be true or false in class '"+clazz.name+"'");
125
+ }
126
+ },
127
+ _extendedFeatureChecks: function(feat, clazz) {
128
+ var loc = " in class '"+clazz.name+"', feature '"+feat.name+"'";
129
+ for (p in feat) {
130
+ if (!["_class", "name", "kind", "type", "lowerLimit", "upperLimit"].include(p)) throw new Error("Unknown property '"+p+"'" + loc );
131
+ if (p == "kind") {
132
+ if (!["attribute", "reference", "containment"].include(feat.kind)) throw new Error("Feature kind must be one of 'attribute', 'reference', 'containment'" + loc);
133
+ }
134
+ }
135
+ }
136
+
137
+ });
138
+
139
+ Concrete.MetamodelExtension = {};
140
+
141
+ Concrete.MetamodelExtension.Class = {
142
+
143
+ allSubTypes: function() {
144
+ this.subTypes = this.subTypes || [];
145
+ return this.subTypes.concat(this.subTypes.collect(function(t) { return t.allSubTypes(); })).flatten().uniq();
146
+ },
147
+
148
+ allSuperTypes: function() {
149
+ this.superTypes = this.superTypes || [];
150
+ return this.superTypes.collect(function(t) { return t.allSuperTypes(); }).concat(this.superTypes).flatten().uniq();
151
+ },
152
+
153
+ allFeatures: function() {
154
+ return this.allSuperTypes().collect(function(t) { return t.features; }).concat(this.features).flatten().uniq();
155
+ }
156
+ };
157
+
158
+ Concrete.MetamodelExtension.Datatype = {
159
+ isEnum: function() { return this._class == "Enum"; },
160
+ isString: function() { return this.name == "String"; },
161
+ isInteger: function() { return this.name == "Integer"; },
162
+ isFloat: function() { return this.name == "Float"; },
163
+ isBoolean: function() { return this.name == "Boolean"; }
164
+ };
165
+
166
+ Concrete.MetamodelExtension.Feature = {
167
+ isContainment: function() { return this.kind == "containment"; },
168
+ isReference: function() { return this.kind == "reference"; },
169
+ isAttribute: function() { return this.kind == "attribute"; }
170
+ };
171
+
@@ -0,0 +1,429 @@
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
+ // The ModelInterface manages the model represented by DOM elements
8
+
9
+ Element.addMethods({
10
+ // speed optimization in case the class is known this will be faster
11
+ feature: function(e, clazz) {
12
+ if (clazz) {
13
+ var feat = e.findAncestor(clazz);
14
+ }
15
+ else {
16
+ var feat = e.findAncestor(["ct_attribute", "ct_reference", "ct_containment"]);
17
+ }
18
+ return feat;
19
+ },
20
+
21
+ mmFeature: function(e, clazz) {
22
+ return e.feature(clazz).mmFeature;
23
+ },
24
+
25
+ featureValues: function(e, feature) {
26
+ if (Object.isString(feature)) {
27
+ if (!e.featuresByName) {
28
+ e.featuresByName = {};
29
+ e.features.each(function(f) { e.featuresByName[f.mmFeature.name] = f; });
30
+ }
31
+ feature = e.featuresByName[feature];
32
+ }
33
+ else if (!feature.mmFeature) {
34
+ feature = e.features.find(function(f) {return f.mmFeature == feature; });
35
+ }
36
+ var values = feature.slot.childElements();
37
+ if (feature.mmFeature.isContainment()) {
38
+ if (values.size() > 1) {
39
+ // optimization: empty placeholder values can not appear amoung other childs
40
+ return values;
41
+ }
42
+ else if (values.size() == 1 && !values[0].hasClassName("ct_empty")) {
43
+ return [values[0]];
44
+ }
45
+ else {
46
+ return [];
47
+ }
48
+ }
49
+ else {
50
+ if (values.size() > 1) {
51
+ // optimization: empty placeholder values can not appear amoung other childs
52
+ return values.collect(function(c) {return c.value});
53
+ }
54
+ else if (values.size() == 1 && !values[0].hasClassName("ct_empty")) {
55
+ return [values[0].value];
56
+ }
57
+ else {
58
+ return [];
59
+ }
60
+ }
61
+ },
62
+
63
+ isElement: function(e) {
64
+ return e.mmClass != undefined;
65
+ }
66
+ })
67
+
68
+ Concrete.ModelInterface = Class.create({
69
+
70
+ // +modelRoot+ is the DOM element containing the model elements
71
+ // Options:
72
+ // displayValueProvider: a function which returns the display value for an attribute or reference,
73
+ // the function must take two arguments, the original value text and the value node's feature parent
74
+ // default: none
75
+ //
76
+ initialize: function(modelRoot, templateProvider, metamodelProvider, options) {
77
+ this.modelRoot = modelRoot;
78
+ this.templateProvider = templateProvider;
79
+ this.metamodelProvider = metamodelProvider;
80
+ options = options || {};
81
+ this._displayValueProvider = options.displayValueProvider;
82
+ this._modelChangeListeners = [];
83
+ },
84
+
85
+ addModelChangeListener: function(listener) {
86
+ if (!listener.elementChanged || !(listener.elementChanged instanceof Function) ||
87
+ !listener.elementAdded || !(listener.elementAdded instanceof Function) ||
88
+ !listener.elementRemoved || !(listener.elementRemoved instanceof Function))
89
+ throw new Error ("incomplete listener interface");
90
+ this._modelChangeListeners.push(listener);
91
+ },
92
+
93
+ setDisplayValueProvider: function(dvp) {
94
+ this._displayValueProvider = dvp;
95
+ },
96
+
97
+ elements: function() {
98
+ return this.modelRoot.childElements().collect(function(e) {
99
+ return this._collectElementsRecursive(e);
100
+ }, this).flatten();
101
+ },
102
+
103
+ // creates the element or elements described be +model+ before or after the element +target+
104
+ // or at the bottom of slot +target+
105
+ createElement: function(target, where, model, options) {
106
+ if (!(["before", "after", "bottom"].include(where))) throw new Error ("unknown position");
107
+ if (where == "bottom" && target != this.modelRoot && !target.hasClassName("ct_slot")) throw new Error ("not a slot");
108
+ if (where != "bottom" && !target.hasClassName("ct_element")) throw new Error ("not an element");
109
+ if (!(model instanceof Array)) model = [ model ];
110
+ if (where == "after") model = model.reverse();
111
+ model.each(function(e) {
112
+ var inst = this._instantiateTemplateRecursive(e, target, where, options);
113
+ this._notifyModelChangeListeners("added", inst);
114
+ }, this);
115
+ var parent = target.up(".ct_element");
116
+ var feature = parent && target.up(".ct_containment");
117
+ parent = parent || this.modelRoot;
118
+ this._notifyModelChangeListeners("changed", parent, feature);
119
+ this._notifyModelChangeListeners("commit");
120
+ if (parent != this.modelRoot) {
121
+ if (parent.foldButton) parent.foldButton.removeClassName("ct_fold_empty");
122
+ }
123
+ },
124
+
125
+ moveElement: function(element) {
126
+ // TODO
127
+ },
128
+
129
+ removeElement: function(elements) {
130
+ if (!(elements instanceof Array)) elements = [ elements ];
131
+ elements.each(function(element) {
132
+ if (!element.hasClassName("ct_element")) throw new Error ("not an element");
133
+ var parent = element.up(".ct_element");
134
+ var feature = parent && element.up(".ct_containment");
135
+ parent = parent || this.modelRoot;
136
+ element.remove();
137
+ this._notifyModelChangeListeners("removed", element);
138
+ this._notifyModelChangeListeners("changed", parent, feature);
139
+ if (parent != this.modelRoot) {
140
+ if (parent.foldButton && !this._hasChildElements(parent)) parent.foldButton.addClassName("ct_fold_empty");
141
+ }
142
+ }, this);
143
+ this._notifyModelChangeListeners("commit");
144
+ },
145
+
146
+ createValue: function(target, where, text) {
147
+ if (!(["before", "after", "bottom"].include(where))) throw new Error ("unknown position");
148
+ if (where == "bottom" && !target.hasClassName("ct_slot")) throw new Error ("not a slot");
149
+ if (where != "bottom" && !target.hasClassName("ct_value")) throw new Error ("not a value");
150
+ text = (text || "").toString();
151
+ var feature = target.findAncestor(["ct_attribute", "ct_reference"]);
152
+ var valueNode = Concrete.Helper.createDOMNode('span', {class: 'ct_value'}, this._displayValue(text, feature));
153
+ valueNode.value = text;
154
+ var arg = {}; arg[where] = valueNode;
155
+ target.insert(arg);
156
+ this._notifyModelChangeListeners("changed", target.up(".ct_element"), feature);
157
+ this._notifyModelChangeListeners("commit");
158
+ },
159
+
160
+ changeValue: function(value, text) {
161
+ if (!value.hasClassName("ct_value")) throw new Error("not a value");
162
+ var feature = value.findAncestor(["ct_attribute", "ct_reference"]);
163
+ text = text.toString();
164
+ value.textContent = this._displayValue(text, feature);
165
+ value.value = text;
166
+ this._notifyModelChangeListeners("changed", value.up(".ct_element"), feature);
167
+ this._notifyModelChangeListeners("commit");
168
+ },
169
+
170
+ removeValue: function(value) {
171
+ if (!value.hasClassName("ct_value")) throw new Error("not a value");
172
+ var element = value.up(".ct_element");
173
+ var feature = value.findAncestor(["ct_attribute", "ct_reference"]);
174
+ value.remove();
175
+ this._notifyModelChangeListeners("changed", element, feature);
176
+ this._notifyModelChangeListeners("commit");
177
+ },
178
+
179
+ extractModel: function(element) {
180
+ var result = {_class: element.mmClass.name}
181
+ element.features.each(function(f) {
182
+ var childs = f.slot.childElements().reject(function(v){return v.hasClassName("ct_empty"); });
183
+ if (childs.size() > 0) {
184
+ if (f.mmFeature.isContainment()) {
185
+ var converted = childs.collect(function(v){return this.extractModel(v); }, this);
186
+ }
187
+ else if (f.mmFeature.isReference()) {
188
+ var converted = childs.collect(function(v){return v.value; }, this);
189
+ }
190
+ else {
191
+ var converted = childs.collect(function(v){
192
+ if (f.mmFeature.type.isInteger()) {
193
+ return parseInt(v.value);
194
+ }
195
+ else if (f.mmFeature.type.isFloat()) {
196
+ return parseFloat(v.value);
197
+ }
198
+ else if (f.mmFeature.type.isBoolean()) {
199
+ return v.value == "true";
200
+ }
201
+ else {
202
+ return v.value;
203
+ }
204
+ });
205
+ }
206
+ if (childs.size() == 1) {
207
+ result[f.mmFeature.name] = converted.first();
208
+ }
209
+ else {
210
+ result[f.mmFeature.name] = converted;
211
+ }
212
+ }
213
+ }, this)
214
+ return result;
215
+ },
216
+
217
+ // if no +element+ is provided redrawing will start on model root
218
+ redrawDisplayValues: function(element) {
219
+ if (!this._displayValueProvider) return;
220
+ if (element == undefined) {
221
+ this.modelRoot.childElements().each(function(c) {
222
+ this.redrawDisplayValues(c);
223
+ }, this);
224
+ }
225
+ else {
226
+ element.features.each(function(f) {
227
+ var childs = f.slot.childElements().reject(function(v){return v.hasClassName("ct_empty"); });
228
+ if (childs.size() > 0) {
229
+ if (f.mmFeature.isContainment()) {
230
+ childs.each(function(c) {
231
+ this.redrawDisplayValues(c);
232
+ }, this);
233
+ }
234
+ else {
235
+ childs.each(function(c) {
236
+ c.textContent = this._displayValue(c.value, f);
237
+ }, this);
238
+ }
239
+ }
240
+ }, this);
241
+ }
242
+ },
243
+
244
+ // Private
245
+
246
+ _hasChildElements: function(element) {
247
+ return element.features.any(function(f) {
248
+ return f.mmFeature.isContainment() && f.slot.childElements().any(function(c) {
249
+ return !c.hasClassName("ct_empty");
250
+ });
251
+ });
252
+ },
253
+
254
+ _notifyModelChangeListeners: function(type, element, feature) {
255
+ if (type == "changed")
256
+ if (element == this.modelRoot)
257
+ this._modelChangeListeners.each(function(l) {l.rootChanged(this.modelRoot);}, this);
258
+ else
259
+ this._modelChangeListeners.each(function(l) {l.elementChanged(element, feature);});
260
+ else if (type == "added")
261
+ this._modelChangeListeners.each(function(l) {l.elementAdded(element);});
262
+ else if (type == "removed")
263
+ this._modelChangeListeners.each(function(l) {l.elementRemoved(element);});
264
+ else if (type == "commit")
265
+ this._modelChangeListeners.each(function(l) {l.commitChanges();});
266
+ else
267
+ throw new Error("unknown type");
268
+ },
269
+
270
+ _collectElementsRecursive: function(element) {
271
+ var result = [element];
272
+ element.features.each(function(f) {
273
+ if (f.mmFeature.isContainment()) {
274
+ result = result.concat(f.slot.childElements().collect(function(c) {
275
+ return this._collectElementsRecursive(c);
276
+ }, this).flatten());
277
+ }
278
+ }, this);
279
+ return result;
280
+ },
281
+
282
+ // inserts a instance of the template representing element into slot
283
+ // also inserts template instances for all contained elements
284
+ // this function is optimized to minimize model load time
285
+ _instantiateTemplateRecursive: function(element, target, where, options) {
286
+ options = options || {};
287
+ var clazz = this.metamodelProvider.metaclassesByName[element._class];
288
+ if (!clazz) return;
289
+ var tmpl = this.templateProvider.templateByClass(clazz);
290
+ if (!tmpl.featurePositions) this._addTemplateInfo(tmpl);
291
+
292
+ var inst = Concrete.Helper.createDOMNode(tmpl.tagName, {class: tmpl.className, style: tmpl.readAttribute("style")},"");
293
+ if (where == "bottom") {
294
+ target.appendChild(inst);
295
+ }
296
+ else if (where == "before") {
297
+ target.parentNode.insertBefore(inst, target);
298
+ }
299
+ else if (where == "after") {
300
+ if (target.next()) {
301
+ target.parentNode.insertBefore(inst, target.next());
302
+ }
303
+ else {
304
+ target.parentNode.appendChild(inst);
305
+ }
306
+ }
307
+ // set inner HTML only after the new node has been hooked into its parent
308
+ // (otherwise the browser filters nodes like "tr" which it considers invalid at this place)
309
+ inst.innerHTML = tmpl.innerHTML;
310
+ inst.mmClass = tmpl.mmClass;
311
+
312
+ inst.features = [];
313
+ var childs = inst.allChildren();
314
+ var hasChildElements = false;
315
+ for (var i=0; i<tmpl.featurePositions.length; i++) {
316
+ var mmf = tmpl.mmFeatures[i];
317
+ var f = childs[tmpl.featurePositions[i]];
318
+ inst.features.push(f);
319
+ var values = element[mmf.name];
320
+ if (!(values instanceof Array)) values = [ values ].compact();
321
+ f.mmFeature = mmf;
322
+ var slot = childs[tmpl.slotPositions[i]];
323
+ f.slot = slot;
324
+ if (values.size() > 0) {
325
+ if (mmf.isContainment()) {
326
+ if (options.collapse) f.hide();
327
+ values.each(function(v) {
328
+ this._instantiateTemplateRecursive(v, slot, "bottom", options);
329
+ hasChildElements = true;
330
+ }, this);
331
+ }
332
+ else {
333
+ values.each(function(v) {
334
+ var vale = Concrete.Helper.createDOMNode('span', {class: 'ct_value'}, this._displayValue(v.toString(), f));
335
+ vale.value = v.toString();
336
+ slot.appendChild(vale);
337
+ }, this);
338
+ }
339
+ }
340
+ else if (f.hasClassName("ct_auto_hide")) {
341
+ f.hide();
342
+ }
343
+ if (f.hasClassName("ct_always_hide")) {
344
+ f.hide();
345
+ }
346
+ }
347
+ if (tmpl.foldButtonPosition != undefined) {
348
+ inst.foldButton = childs[tmpl.foldButtonPosition];
349
+ if (options.collapse) {
350
+ inst.foldButton.addClassName("ct_fold_closed");
351
+ }
352
+ else {
353
+ inst.foldButton.addClassName("ct_fold_open");
354
+ }
355
+ if (!hasChildElements) inst.foldButton.addClassName("ct_fold_empty");
356
+ }
357
+ return inst;
358
+ },
359
+
360
+ _displayValue: function(text, feature) {
361
+ if (this._displayValueProvider) {
362
+ return this._displayValueProvider(text, feature);
363
+ }
364
+ else {
365
+ return text;
366
+ }
367
+ },
368
+
369
+ // add information used to make template instantiation more efficient
370
+ _addTemplateInfo: function(tmpl) {
371
+ var allChilds = tmpl.allChildren();
372
+ var ftmpls = tmpl.select(".ct_attribute").concat(tmpl.select(".ct_reference")).concat(tmpl.select(".ct_containment"));
373
+ tmpl.featurePositions = ftmpls.collect(function(ft) { return allChilds.indexOf(ft); });
374
+ tmpl.mmFeatures = ftmpls.collect(function(ft) { return ft.mmFeature; });
375
+ tmpl.slotPositions = ftmpls.collect(function(ft) { return allChilds.indexOf(ft.down(".ct_slot")); });
376
+ var foldButton = tmpl.down(".ct_fold_button");
377
+ tmpl.foldButtonPosition = foldButton && allChilds.indexOf(foldButton);
378
+ }
379
+
380
+ });
381
+
382
+ Concrete.ModelInterface.Helper = {
383
+
384
+ // returns the next element in depth first search order
385
+ // or false if the last element in the model has been reached
386
+ //
387
+ // as a speed optimization an optional stack can be used which keeps the
388
+ // parent containers over several invocations of this method
389
+ // in this case the stack must either be empty or it must be in the state
390
+ // established by the last call of this method (i.e. it must not be modified)
391
+ //
392
+ nextElement: function(element, stack) {
393
+ var fIndex = 0;
394
+ while (true) {
395
+ var feature = element.features[fIndex];
396
+ var values = feature && element.featureValues(feature.mmFeature.name);
397
+ if (feature && feature.mmFeature.isContainment() && values.size() > 0 && values[0].isElement()) {
398
+ // found first child in feature
399
+ if (stack) {
400
+ stack.push(element);
401
+ stack.push(feature);
402
+ }
403
+ return values[0];
404
+ }
405
+ else if (fIndex < element.features.size()-1) {
406
+ // next feature
407
+ fIndex++;
408
+ }
409
+ else if (element.next()) {
410
+ // next element
411
+ return element.next();
412
+ }
413
+ else {
414
+ var parentFeature = (stack && stack.pop()) || element.up(".ct_containment");
415
+ if (parentFeature) {
416
+ // go up to parent
417
+ var parentElement = (stack && stack.pop()) || parentFeature.up(".ct_element");
418
+ var fIndex = parentElement.features.indexOf(parentFeature) + 1;
419
+ element = parentElement;
420
+ }
421
+ else {
422
+ return false;
423
+ }
424
+ }
425
+ }
426
+ }
427
+
428
+ };
429
+