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,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
+