diff --git a/Gruntfile.js b/Gruntfile.js
index 7467d1aa82b1f66e74ffead2fb2488030d51c3ca..1a762e6482f507adb486caede8178ae4ea24c5b7 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -6,9 +6,7 @@ module.exports = function (grunt) {
var typo3_js = 'extension/Resources/Public/JavaScript/';
var typo3_fonts = 'extension/Resources/Public/fonts/';
var js_sources = [
- 'javascript/src/Helper/*.js',
- 'javascript/src/Element/*.js',
- 'javascript/src/*.js'
+ 'javascript/src/**/*.js',
];
// Project configuration.
diff --git a/javascript/src/QfqForm.js b/javascript/src/QfqForm.js
index 99e73277f596ac92516e937a8af66bebf978f463..d634aae706bb4f06af2315d1e89e7cb11f330c23 100644
--- a/javascript/src/QfqForm.js
+++ b/javascript/src/QfqForm.js
@@ -1190,4 +1190,38 @@ var QfqNS = QfqNS || {};
window.history.back();
};
+ n.QfqForm.prototype.disableSaveButton = function () {
+ this.setButtonEnabled(this.getSaveButton(), false);
+ this.getSaveButton().removeClass(this.getSaveButtonAttentionClass());
+ };
+
+ n.QfqForm.prototype.enableSaveButton = function () {
+ this.setButtonEnabled(this.getSaveButton(), true);
+ this.getSaveButton().addClass(this.getSaveButtonAttentionClass());
+ };
+
+ n.QfqForm.prototype.disableCloseButton = function () {
+ this.setButtonEnabled(this.getCloseButton(), false);
+ };
+
+ n.QfqForm.prototype.enableCloseButton = function () {
+ this.setButtonEnabled(this.getCloseButton(), true);
+ };
+
+ n.QfqForm.prototype.disableNewButton = function () {
+ this.setButtonEnabled(this.getNewButton(), false);
+ };
+
+ n.QfqForm.prototype.enableNewButton = function () {
+ this.setButtonEnabled(this.getNewButton(), true);
+ };
+
+ n.QfqForm.prototype.disableDeleteButton = function () {
+ this.setButtonEnabled(this.getDeleteButton(), false);
+ };
+
+ n.QfqForm.prototype.enableDeleteButton = function () {
+ this.setButtonEnabled(this.getDeleteButton(), true);
+ };
+
})(QfqNS);
\ No newline at end of file
diff --git a/javascript/src/QfqFormStates/Buttons/Closed.js b/javascript/src/QfqFormStates/Buttons/Closed.js
new file mode 100644
index 0000000000000000000000000000000000000000..4426fdc6844af4fdcbbd15b267a29b1daaea5438
--- /dev/null
+++ b/javascript/src/QfqFormStates/Buttons/Closed.js
@@ -0,0 +1,41 @@
+var QfqNS = QfqNS || {};
+QfqNS.QfqFormStates = QfqNS.QfqFormStates || {};
+QfqNS.QfqFormStates.Buttons = QfqNS.QfqFormStates.Buttons || {};
+
+(function (n) {
+ 'use strict';
+
+ n.Closed = function (qfqForm) {
+ this.qfqForm = qfqForm;
+ };
+
+ n.Closed.prototype.saveButtonPressed = function () {
+ QfqNS.Log.warning("saveButtonPressed is not a valid transition for Closed");
+ return this;
+ };
+
+ n.Closed.prototype.deleteButtonPressed = function () {
+ QfqNS.Log.warning("deleteButtonPressed is not a valid transition for Closed");
+ return this;
+ };
+
+ n.Closed.prototype.newButtonPressed = function () {
+ QfqNS.Log.debug("Transition from Closed to New");
+ this.qfqForm.disableSaveButton();
+ this.qfqForm.disableDeleteButton();
+ this.qfqForm.enableNewButton();
+ this.qfqForm.enableCloseButton();
+ return n.New(this.qfqForm);
+ };
+
+ n.Closed.prototype.closeButtonPressed = function () {
+ QfqNS.Log.debug("Transition from Closed to Closed");
+ return this;
+ };
+
+ n.Closed.prototype.formModified = function () {
+ QfqNS.Log.warning("formModified is not a valid transition for Closed");
+ return this;
+ };
+
+})(QfqNS.QfqFormStates.Buttons);
\ No newline at end of file
diff --git a/javascript/src/QfqFormStates/Buttons/Context.js b/javascript/src/QfqFormStates/Buttons/Context.js
new file mode 100644
index 0000000000000000000000000000000000000000..7d992f38ec9d79cb38d97cafbe354d65414e2102
--- /dev/null
+++ b/javascript/src/QfqFormStates/Buttons/Context.js
@@ -0,0 +1,41 @@
+var QfqNS = QfqNS || {};
+QfqNS.QfqFormStates = QfqNS.QfqFormStates || {};
+QfqNS.QfqFormStates.Buttons = QfqNS.QfqFormStates.Buttons || {};
+
+(function (n) {
+ 'use strict';
+
+ /**
+ * Buttons State Context.
+ *
+ * @param initialState optional initial state. If omitted, New is the initial state.
+ *
+ * @param qfqForm optional qfqForm instance. Only used when no `initialState` is provided.
+ *
+ * @constructor
+ */
+ n.Context = function (initialState, qfqForm) {
+ this.state = initialState || new n.New(qfqForm);
+ };
+
+ n.Context.prototype.saveButtonPressed = function () {
+ this.state = this.state.saveButtonPressed();
+ };
+
+ n.Context.prototype.deleteButtonPressed = function () {
+ this.state = this.state.deleteButtonPressed();
+ };
+
+ n.Context.prototype.newButtonPressed = function () {
+ this.state = this.state.newButtonPressed();
+ };
+
+ n.Context.prototype.closeButtonPressed = function () {
+ this.state = this.state.closeButtonPressed();
+ };
+
+ n.Context.prototype.formModified = function () {
+ this.state = this.state.formModified();
+ };
+
+})(QfqNS.QfqFormStates.Buttons);
\ No newline at end of file
diff --git a/javascript/src/QfqFormStates/Buttons/Deleted.js b/javascript/src/QfqFormStates/Buttons/Deleted.js
new file mode 100644
index 0000000000000000000000000000000000000000..8f39f5c2a6c530c33a3ac92d2e937d9c8d85f18c
--- /dev/null
+++ b/javascript/src/QfqFormStates/Buttons/Deleted.js
@@ -0,0 +1,37 @@
+var QfqNS = QfqNS || {};
+QfqNS.QfqFormStates = QfqNS.QfqFormStates || {};
+QfqNS.QfqFormStates.Buttons = QfqNS.QfqFormStates.Buttons || {};
+
+(function (n) {
+ 'use strict';
+
+ n.Deleted = function (qfqForm) {
+ this.qfqForm = qfqForm;
+ };
+
+ n.Deleted.prototype.saveButtonPressed = function () {
+ QfqNS.Log.warning("saveButtonPressed is not a valid transition for Deleted");
+ return this;
+ };
+
+ n.Deleted.prototype.deleteButtonPressed = function () {
+ QfqNS.Log.warning("deleteButtonPressed is not a valid transition for Deleted");
+ return this;
+ };
+
+ n.Deleted.prototype.newButtonPressed = function () {
+ QfqNS.Log.warning("newButtonPressed is not a valid transition for Deleted");
+ return n.New(this);
+ };
+
+ n.Deleted.prototype.closeButtonPressed = function () {
+ QfqNS.Log.warning("closeButtonPressed is not a valid transition for Deleted");
+ return n.Closed(this);
+ };
+
+ n.Deleted.prototype.formModified = function () {
+ QfqNS.Log.warning("formModified is not a valid transition for Deleted");
+ return this;
+ };
+
+})(QfqNS.QfqFormStates.Buttons);
\ No newline at end of file
diff --git a/javascript/src/QfqFormStates/Buttons/Modified.js b/javascript/src/QfqFormStates/Buttons/Modified.js
new file mode 100644
index 0000000000000000000000000000000000000000..f4507b82a158d8d98124e95abaad0f261e4149a9
--- /dev/null
+++ b/javascript/src/QfqFormStates/Buttons/Modified.js
@@ -0,0 +1,41 @@
+var QfqNS = QfqNS || {};
+QfqNS.QfqFormStates = QfqNS.QfqFormStates || {};
+QfqNS.QfqFormStates.Buttons = QfqNS.QfqFormStates.Buttons || {};
+
+(function (n) {
+ 'use strict';
+
+ n.Modified = function (qfqForm) {
+ this.qfqForm = qfqForm;
+ };
+
+ n.Modified.prototype.saveButtonPressed = function () {
+ QfqNS.Log.warning("saveButtonPressed: transition from Modified to Unchanged");
+ qfqForm.disableSaveButton();
+ qfqForm.enableCloseButton();
+ qfqForm.enableNewButton();
+ qfqForm.enableDeleteButton();
+ return new n.Unchanged(this.qfqForm);
+ };
+
+ n.Modified.prototype.deleteButtonPressed = function () {
+ QfqNS.Log.warning("deleteButtonPressed is not a valid transition for Modified");
+ return this;
+ };
+
+ n.Modified.prototype.newButtonPressed = function () {
+ QfqNS.Log.warning("newButtonPressed is not a valid transition for Modified");
+ return this;
+ };
+
+ n.Modified.prototype.closeButtonPressed = function () {
+ QfqNS.Log.warning("closeButtonPressed is not a valid transition for Deleted");
+ return n.Closed(this.qfqForm);
+ };
+
+ n.Modified.prototype.formModified = function () {
+ QfqNS.Log.debug("formModified: transition from Modified to Modified");
+ return this;
+ };
+
+})(QfqNS.QfqFormStates.Buttons);
\ No newline at end of file
diff --git a/javascript/src/QfqFormStates/Buttons/New.js b/javascript/src/QfqFormStates/Buttons/New.js
new file mode 100644
index 0000000000000000000000000000000000000000..9f388af3f2e3503f2b3726432d3b051ee74b71c2
--- /dev/null
+++ b/javascript/src/QfqFormStates/Buttons/New.js
@@ -0,0 +1,43 @@
+var QfqNS = QfqNS || {};
+QfqNS.QfqFormStates = QfqNS.QfqFormStates || {};
+QfqNS.QfqFormStates.Buttons = QfqNS.QfqFormStates.Buttons || {};
+
+(function (n) {
+ 'use strict';
+
+ n.New = function (qfqForm) {
+ this.qfqForm = qfqForm;
+ };
+
+ n.New.prototype.saveButtonPressed = function () {
+ QfqNS.Log.warning("saveButtonPressed is not a valid transition for New");
+ return this;
+ };
+
+ n.New.prototype.deleteButtonPressed = function () {
+ QfqNS.Log.warning("deleteButtonPressed is not a valid transition for New");
+ return this;
+ };
+
+ n.New.prototype.newButtonPressed = function () {
+ QfqNS.Log.debug("Transition from New to New");
+ return this;
+ };
+
+ n.New.prototype.closeButtonPressed = function () {
+ QfqNS.Log.warning("closeButtonPressed: transation from New to Closed");
+ this.qfqForm.disableSaveButton();
+ this.qfqForm.disableCloseButton();
+ this.qfqForm.disableDeleteButton();
+ this.qfqForm.disableNewButton();
+ return n.Closed(this.qfqForm);
+ };
+
+ n.New.prototype.formModified = function () {
+ QfqNS.Log.debug("formModified: transition from New to Changed");
+ this.qfqForm.enableSaveButton();
+
+ return new n.Modified(this.qfqForm);
+ };
+
+})(QfqNS.QfqFormStates.Buttons);
diff --git a/javascript/src/QfqFormStates/Buttons/README.md b/javascript/src/QfqFormStates/Buttons/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..f6c500ad3386af0c98d774f06e6edc020541b58c
--- /dev/null
+++ b/javascript/src/QfqFormStates/Buttons/README.md
@@ -0,0 +1,10 @@
+State interface
+===
+
+This interface must be implemented by all Button State objects.
+
+ StateInterface#saveButtonPressed()
+ StateInterface#deleteButtonPressed()
+ StateInterface#newButtonPressed()
+ StateInterface#closeButtonPressed()
+ StateInterface#formModified()
diff --git a/javascript/src/QfqFormStates/Buttons/Saved.js b/javascript/src/QfqFormStates/Buttons/Saved.js
new file mode 100644
index 0000000000000000000000000000000000000000..597bb97f08f74a6acfda1495368e45339e0551a6
--- /dev/null
+++ b/javascript/src/QfqFormStates/Buttons/Saved.js
@@ -0,0 +1,9 @@
+var QfqNS = QfqNS || {};
+QfqNS.QfqFormStates = QfqNS.QfqFormStates || {};
+QfqNS.QfqFormStates.Buttons = QfqNS.QfqFormStates.Buttons || {};
+
+(function (n) {
+ 'use strict';
+
+
+})(QfqNS.QfqFormStates.Buttons);
\ No newline at end of file
diff --git a/javascript/src/QfqFormStates/Buttons/Unchanged.js b/javascript/src/QfqFormStates/Buttons/Unchanged.js
new file mode 100644
index 0000000000000000000000000000000000000000..cca46b122b4c2e19c7c5a3a3dcf3c27b409c0e85
--- /dev/null
+++ b/javascript/src/QfqFormStates/Buttons/Unchanged.js
@@ -0,0 +1,37 @@
+var QfqNS = QfqNS || {};
+QfqNS.QfqFormStates = QfqNS.QfqFormStates || {};
+QfqNS.QfqFormStates.Buttons = QfqNS.QfqFormStates.Buttons || {};
+
+(function (n) {
+ 'use strict';
+ n.Unchanged = function (qfqForm) {
+ this.qfqForm = qfqForm;
+ };
+
+ n.Unchanged.prototype.saveButtonPressed = function () {
+ QfqNS.Log.warning("saveButtonPressed is not a valid transition for Unchanged");
+ return this;
+ };
+
+ n.Unchanged.prototype.deleteButtonPressed = function () {
+ QfqNS.Log.warning("deleteButtonPressed is not a valid transition for Unchanged");
+ return this;
+ };
+
+ n.Unchanged.prototype.newButtonPressed = function () {
+ QfqNS.Log.debug("Transition from Unchanged to New");
+ return new n.New(this.qfqForm);
+ };
+
+ n.Unchanged.prototype.closeButtonPressed = function () {
+ QfqNS.Log.debug("Transition from Unchanged to New");
+ return new n.Closed(this.qfqForm);
+ };
+
+ n.Unchanged.prototype.formModified = function () {
+ QfqNS.Log.debug("Transition from Unchanged to Modified");
+ this.qfqForm.enableSaveButton();
+ return new n.Closed(this.qfqForm);
+ };
+})(QfqNS.QfqFormStates.Buttons);
+
diff --git a/tests/jasmine/unit/SpecRunner.html b/tests/jasmine/unit/SpecRunner.html
index 71ac68cde99b607ea7e76ea2c4e9bb4c636455d2..221a0cd98488df4bd8c64702452ab304ab6d491c 100644
--- a/tests/jasmine/unit/SpecRunner.html
+++ b/tests/jasmine/unit/SpecRunner.html
@@ -37,7 +37,7 @@
-
+
diff --git a/tests/jasmine/unit/spec/ButtonStateSpec.js b/tests/jasmine/unit/spec/ButtonStateSpec.js
new file mode 100644
index 0000000000000000000000000000000000000000..bd2db7646866b8674df210708f1fd69a1d80ff6c
--- /dev/null
+++ b/tests/jasmine/unit/spec/ButtonStateSpec.js
@@ -0,0 +1,80 @@
+/* global $ */
+/* global describe */
+/* global it */
+/* global expect */
+/* global QfqNS */
+/* global beforeAll */
+/* global beforeEach */
+/* global jasmine */
+
+describe("Button Sate", function () {
+ 'use strict';
+ var stateSpy;
+
+ beforeEach(function () {
+ stateSpy = {
+ saveButtonPressed: function () {
+ return "saveButtonPressed";
+ },
+ deleteButtonPressed: function () {
+ return "deleteButtonPressed";
+ },
+ newButtonPressed: function () {
+ return "newButtonPressed";
+ },
+ closeButtonPressed: function () {
+ return "closeButtonPressed";
+ },
+ formModified: function () {
+ return "formModified";
+ }
+ };
+
+ // Hmm, ain't there a simpler approach to achieve the same?
+ spyOn(stateSpy, 'saveButtonPressed').and.callThrough();
+ spyOn(stateSpy, 'deleteButtonPressed').and.callThrough();
+ spyOn(stateSpy, 'newButtonPressed').and.callThrough();
+ spyOn(stateSpy, 'closeButtonPressed').and.callThrough();
+ spyOn(stateSpy, 'formModified').and.callThrough();
+ });
+
+ it("should call the 'saveButtonPressed' handler and set proper state", function () {
+ var buttonContext = new QfqNS.QfqFormStates.Buttons.Buttons(stateSpy);
+ buttonContext.saveButtonPressed();
+
+ expect(stateSpy.saveButtonPressed).toHaveBeenCalledTimes(1);
+ expect(buttonContext.state).toBe("saveButtonPressed");
+ });
+
+ it("should call the 'deleteButtonPressed' handler and set proper state", function () {
+ var buttonContext = new QfqNS.QfqFormStates.Buttons.Buttons(stateSpy);
+ buttonContext.deleteButtonPressed();
+
+ expect(stateSpy.deleteButtonPressed).toHaveBeenCalledTimes(1);
+ expect(buttonContext.state).toBe("deleteButtonPressed");
+ });
+
+ it("should call the 'newButtonPressed' handler and set proper state", function () {
+ var buttonContext = new QfqNS.QfqFormStates.Buttons.Buttons(stateSpy);
+ buttonContext.newButtonPressed();
+
+ expect(stateSpy.newButtonPressed).toHaveBeenCalledTimes(1);
+ expect(buttonContext.state).toBe("newButtonPressed");
+ });
+
+ it("should call the 'closeButtonPressed' handler and set proper state", function () {
+ var buttonContext = new QfqNS.QfqFormStates.Buttons.Buttons(stateSpy);
+ buttonContext.closeButtonPressed();
+
+ expect(stateSpy.closeButtonPressed).toHaveBeenCalledTimes(1);
+ expect(buttonContext.state).toBe("closeButtonPressed");
+ });
+
+ it("should call the 'formModified' handler and set proper state", function () {
+ var buttonContext = new QfqNS.QfqFormStates.Buttons.Buttons(stateSpy);
+ buttonContext.formModified();
+
+ expect(stateSpy.formModified).toHaveBeenCalledTimes(1);
+ expect(buttonContext.state).toBe("formModified");
+ });
+});
\ No newline at end of file