Commit 411b079c authored by Rafael Ostertag's avatar Rafael Ostertag
Browse files

Work in progress commit:

BSTabs.js, PageState.js, Form.js: done.
Alert.js: first draw, layout not of alerts not proper yet.
Added jasmine tests.
parent 88f7e3a6
......@@ -9,6 +9,8 @@
/.npmpackages
/.phpdocinstall
/js
/css
/fonts
/qfq.flowchart.dia.autosave
/extension/qfq.ini
/support
......
......@@ -40,6 +40,39 @@ module.exports = function (grunt) {
}
]
},
bootstrap_dev: {
files: [
{
cwd: 'bower_components/bootstrap/dist/css/',
src: [
'bootstrap.min.css',
'bootstrap-theme.min.css'
],
dest: 'css/',
filter: 'isFile',
expand: true,
flatten: true
},
{
cwd: 'bower_components/bootstrap/dist/js/',
src: [
'bootstrap.min.js'
],
dest: 'js/',
expand: true,
flatten: true
},
{
cwd: 'bower_components/bootstrap/dist/fonts/',
expand: true,
src: [
'*'
],
dest: 'fonts/',
flatten: true
}
]
},
jquery: {
files: [
{
......@@ -96,6 +129,37 @@ module.exports = function (grunt) {
dest: typo3_css
}
]
},
jqwidgets_devel: {
files: [
{
cwd: 'bower_components/jqwidgets/jqwidgets/',
src: [
'jqx-all.js'
],
expand: true,
dest: 'js/',
flatten: true
},
{
cwd: 'bower_components/jqwidgets/jqwidgets/styles/',
src: [
'jqx.base.css',
'jqx.darkblue.css'
],
expand: true,
dest: 'css/',
flatten: true
},
{
cwd: 'bower_components/jqwidgets/jqwidgets/styles/',
src: [
'images/**'
],
expand: true,
dest: 'css/'
}
]
}
},
uglify: {
......@@ -114,14 +178,18 @@ module.exports = function (grunt) {
},
concat: {
debug: {
src: [ 'javascript/src/*.js'],
src: [
'javascript/src/Helper/*.js',
'javascript/src/*.js'
],
dest: 'js/<%= pkg.name %>.debug.js'
}
},
watch: {
scripts: {
files: [
'javascript/src/*.js'
'javascript/src/*.js',
'javascript/src/Helper/*.js'
],
tasks: [ 'default' ],
options: {
......
/**
* @author Rafael Ostertag <rafael.ostertag@math.uzh.ch>
*/
/* global $ */
if (!QfqNS) {
var QfqNS = {};
}
(function (n) {
'use strict';
n.Alert = function (message) {
this.message = message;
this.$alertDiv = null;
this.shown = false;
// this.timeout < 1 means forever
this.timeout = 0;
this.fadeInDuration = 400;
this.fadeOutDuration = 400;
this.timerId = null;
};
/**
*
* @private
*/
n.Alert.prototype.makeAlertContainerSingleton = function () {
var alertContainer = $("#qfqAlertContainer");
if (alertContainer.length === 0) {
// No container so far, create one
alertContainer = $("<div>").attr("id", "qfqAlertContainer");
$("body").append(alertContainer);
}
return alertContainer;
};
/**
*
* @returns {number|jQuery}
* @private
*/
n.Alert.prototype.countAlertsInAlertContainer = function () {
return $("#qfqAlertContainer > div").length;
};
/**
* @private
*/
n.Alert.prototype.removeAlertContainer = function () {
$("#qfqAlertContainer").remove();
};
n.Alert.prototype.show = function () {
if (this.shown) {
// We only allow showing once
return;
}
var $alertContainer = this.makeAlertContainerSingleton();
this.$alertDiv = $("<div>")
.addClass("alert")
.addClass("alert-success")
.attr("role", "alert")
.append(this.message)
.click(this.removeAlert.bind(this));
$alertContainer.append(this.$alertDiv);
this.$alertDiv.fadeIn(this.fadeInDuration, this.afterFadeIn.bind(this));
this.shown = true;
};
n.Alert.prototype.afterFadeIn = function () {
if (this.timeout > 0) {
this.timerId = window.setTimeout(this.removeAlert.bind(this), this.timeout);
}
};
/**
*
* @param event
*
* @private
*/
n.Alert.prototype.removeAlert = function (event) {
if (!event || event.type !== "click") {
// No user click, so it must be a timer event
if (this.timerId) {
window.clearTimeout(this.timerId);
this.timerId = null;
} else {
QfqNS.Log.error("Alert.remove(): Identified timer event, but had no timer id");
}
}
var that = this;
this.$alertDiv.fadeOut(this.fadeOutDuration, function () {
that.$alertDiv.remove();
that.$alertDiv = null;
that.shown = false;
if (that.countAlertsInAlertContainer() === 0) {
that.removeAlertContainer();
}
});
};
n.Alert.prototype.isShown = function () {
return this.shown;
};
})(QfqNS);
\ No newline at end of file
......@@ -23,7 +23,7 @@ if (!QfqNS) {
this._tabActiveSelector = '#' + this.tabId + ' .active a[data-toggle="tab"]';
this.tabs = {};
this.currentTab = this.getActiveTabFromDOM();
this.userTabShowHandlers = [];
this.userTabShowHandlers = new QfqNS.Helper.FunctionList();
// Fill this.tabs
......@@ -99,9 +99,7 @@ if (!QfqNS) {
var that = this;
QfqNS.Log.debug("BSTabs.tabShowHandler(): invoke user handler(s)");
this.userTabShowHandlers.forEach(function (handler) {
handler(that);
});
this.userTabShowHandlers.call(that);
QfqNS.Log.debug('Exit: BSTabs.tabShowHandler()');
};
......@@ -165,11 +163,7 @@ if (!QfqNS) {
* @param {function} handler handler function. `this` will be passed as first and only argument to the handler.
*/
n.BSTabs.prototype.addTabShowHandler = function (handler) {
if (typeof handler !== "function") {
throw new Error("Tab show handler must be function");
}
this.userTabShowHandlers.push(handler);
this.userTabShowHandlers.addFunction(handler);
};
n.BSTabs.prototype.getTabName = function (tabId) {
......@@ -185,4 +179,24 @@ if (!QfqNS) {
return this.currentTab;
};
n.BSTabs.prototype.getContainingTabIdForFormControl = function (formControlName) {
var $formControl = $("[name=" + formControlName + "]");
if ($formControl.length === 0) {
QfqNS.Log.debug("BSTabs.getContainingTabForFormControl(): unable to find form control with name '" + formControlName + "'");
return null;
}
var iterator = $formControl[0];
while (iterator !== null) {
if (iterator.hasAttribute('role') &&
iterator.getAttribute('role') === 'tabpanel') {
return iterator.id || null;
}
iterator = iterator.parentElement;
}
return null;
};
})(QfqNS);
\ No newline at end of file
/**
* @author Rafael Ostertag <rafael.ostertag@math.uzh.ch>
*/
/* global $ */
if (!QfqNS) {
var QfqNS = {};
}
(function (n) {
'use strict';
n.Form = function (formId) {
this.formId = formId;
this.formChanged = false;
this.userFormChangeHandlers = new QfqNS.Helper.FunctionList();
this.userResetHandlers = new QfqNS.Helper.FunctionList();
this.userSubmitSuccessHandlers = new QfqNS.Helper.FunctionList();
this.userSubmitFailureHandlers = new QfqNS.Helper.FunctionList();
if (!document.forms[this.formId]) {
throw new Error("Form '" + formId + "' does not exist.");
}
this.$form = $(document.forms[this.formId]);
this.$form.on("change", this.changeHandler.bind(this));
};
/**
*
* @param event
*
* @private
*/
n.Form.prototype.changeHandler = function (event) {
this.formChanged = true;
this.userFormChangeHandlers.call(this);
};
n.Form.prototype.addChangeHandler = function (callback) {
this.userFormChangeHandlers.addFunction(callback);
};
n.Form.prototype.addResetHandler = function (callback) {
this.userResetHandlers.addFunction(callback);
};
n.Form.prototype.addSubmitSuccessHandler = function (callback) {
this.userSubmitSuccessHandlers.addFunction(callback);
};
n.Form.prototype.addSubmitFailureHandler = function (callback) {
this.userSubmitFailureHandlers.addFunction(callback);
};
n.Form.prototype.getFormChanged = function () {
return this.formChanged;
};
n.Form.prototype.resetFormChanged = function () {
this.formChanged = false;
this.userResetHandlers.call(this);
};
n.Form.prototype.submitTo = function (to) {
$.post(to, this.$form.serialize())
.done(this.ajaxSuccessHandler.bind(this))
.fail(this.ajaxFailureHandler.bind(this));
};
/**
*
* @param data
* @param textStatus
* @param jqXHR
*
* @private
*/
n.Form.prototype.ajaxSuccessHandler = function (data, textStatus, jqXHR) {
this.userSubmitSuccessHandlers.call(this, data, textStatus);
};
/**
*
*
* @private
*/
n.Form.prototype.ajaxFailureHandler = function (jqXHR, textStatus, errorThrown) {
this.userSubmitFailureHandlers.call(this, textStatus, jqXHR, errorThrown);
};
})(QfqNS);
/**
* @author Rafael Ostertag <rafael.ostertag@math.uzh.ch>
*/
/* global $ */
if (!QfqNS) {
var QfqNS = {};
}
if (!QfqNS.Helper) {
QfqNS.Helper = {};
}
(function (n) {
'use strict';
n.FunctionList = function () {
this.functions = [];
};
n.FunctionList.prototype.addFunction = function (func, thisObj) {
if (typeof func !== "function") {
throw new TypeError("Expected function");
}
if (thisObj) {
this.functions.push([func, thisObj]);
} else {
this.functions.push(func);
}
};
n.FunctionList.prototype.call = function () {
var _arguments = arguments;
this.functions.forEach(function (func) {
if (typeof func === "function") {
func.apply(undefined, _arguments);
} else {
// func is an array [ Function, thisObj ]
func[0].apply(func[1], _arguments);
}
});
};
})(QfqNS.Helper);
\ No newline at end of file
......@@ -14,7 +14,7 @@ if (!QfqNS) {
this.pageState = location.hash.slice(1);
this.data = null;
this.inPoppingHandler = false;
this.userPopStateHandlers = [];
this.userPopStateHandlers = new QfqNS.Helper.FunctionList();
window.addEventListener("popstate", this.popStateHandler.bind(this));
};
......@@ -33,9 +33,8 @@ if (!QfqNS) {
this.data = window.history.state;
QfqNS.Log.debug("PageState.popStateHandler(): invoke user pop state handler(s)");
this.userPopStateHandlers.forEach(function (handler) {
handler(this);
}, this);
this.userPopStateHandlers.call(this);
this.inPoppingHandler = false;
QfqNS.Log.debug("Exit: PageState.popStateHandler()");
......@@ -74,10 +73,7 @@ if (!QfqNS) {
};
n.PageState.prototype.addStateActivationHandler = function (handler) {
if (typeof handler !== "function") {
throw new Error("PageState.addStateActivationHandler(): argument must be function.");
}
this.userPopStateHandlers.push(handler);
this.userPopStateHandlers.addFunction(handler);
};
......
/**
* @author Rafael Ostertag <rafael.ostertag@math.uzh.ch>
*/
/* global $ */
if (!QfqNS) {
var QfqNS = {};
}
(function (n) {
'use strict';
n.QfqForm = function (formId) {
this.formId = formId;
this.form = new n.Form(this.formId);
this.getSaveButton().addClass("disabled");
this.form.addChangeHandler(this.changeHandler.bind(this));
this.form.addSubmitSuccessHandler(this.submitSuccessDispatcher.bind(this));
this.saveHandler = null;
this.closeHandler = null;
this.deleteHandler = null;
this.newHandler = null;
};
/**
*
* @param form {QfqNS.QfqForm}
*
* @private
*/
n.QfqForm.prototype.changeHandler = function (form) {
this.getSaveButton().removeClass("disabled");
this.getSaveButton().addClass("alert-warning");
this.getSaveButton().removeAttr("disabled");
};
/**
*
* @param form {QfqNS.QfqForm}
*
* @private
*/
n.QfqForm.prototype.resetHandler = function (form) {
this.getSaveButton().removeClass("alert-warning");
this.getSaveButton().addClass("disabled");
this.getSaveButton().attr("disabled", "disabled");
};
/**
*
* @returns {jQuery|HTMLElement}
*
* @private
*/
n.QfqForm.prototype.getSaveButton = function () {
return $("#save-button");
};
/**
*
* @returns {jQuery|HTMLElement}
*
* @private
*/
n.QfqForm.prototype.getCloseButton = function () {
return $("#close-button");
};
/**
*
* @returns {jQuery|HTMLElement}
*
* @private
*/
n.QfqForm.prototype.getDeleteButton = function () {
return $("#delete-button");
};
/**
*
* @returns {jQuery|HTMLElement}
*
* @private
*/
n.QfqForm.prototype.getNewButton = function () {
return $("#new-button");
};
/**
* @private
*/
n.QfqForm.prototype.submitSuccessDispatcher = function (form, data, textStatus) {
if (!data.success) {
throw new Error("No 'success' property in 'data'");
}
switch (data.status) {
case "error":
this.handleLogicSubmitError(form, data);
break;
case "success":
this.handleSubmitSuccess(form, data);
break;
default:
throw new Error("Status '" + data.status + "' unknown.");
}
};
n.QfqForm.prototype.handleLogicSubmitError = function (form, data) {
if (!data.message) {
throw Error("Status is 'error' but required 'message' attribute is missing.");
}
var alert = new QfqNS.Alert(data.message);
alert.show();
};
})(QfqNS);
\ No newline at end of file
......@@ -16,11 +16,18 @@ if (!QfqNS) {
this.settings = $.extend(
{
tabsId: "qfqTabs",
formId: "qfqForm",
pageState: new QfqNS.PageState()
}, settings
);
this.bsTabs = new QfqNS.BSTabs(this.settings.tabsId);
try {
this.qfqForm = new QfqNS.QfqForm(this.settings.formId);
} catch (e) {
QfqNS.Log.error(e.message);
this.qfqForm = null;
}
var currentState = this.settings.pageState.getPageState();
if (currentState !== "") {
......
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="../css/bootstrap.min.css">
<link rel="stylesheet" href="../css/bootstrap-theme.min.css">
<title>Alert</title>
<style>
#qfqAlertContainer {
position: absolute;
top: 100px;
left: 0;
}
</style>
</head>
<body>