Commit e4eaa1fb authored by Carsten  Rose's avatar Carsten Rose
Browse files

Merge remote-tracking branch 'origin/raos_work' into crose_work

parents 8cd9955c e12b61b8
var path = require('path');
module.exports = function (grunt) {
var typo3_css = 'extension/Resources/Public/Css/';
var typo3_js = 'extension/Resources/Public/JavaScript/';
......@@ -181,14 +182,50 @@ module.exports = function (grunt) {
'javascript/src/*.js'
]
},
concat: {
concat_in_order: {
debug_standalone: {
src: js_sources,
dest: 'js/<%= pkg.name %>.debug.js'
options: {
extractRequired: function (filepath, filecontent) {
var workingdir = path.normalize(filepath).split(path.sep);
workingdir.pop();
var deps = this.getMatches(/\*\s*@depend\s(.*\.js)/g, filecontent);
deps.forEach(function (dep, i) {
var dependency = workingdir.concat([dep]);
deps[i] = path.join.apply(null, dependency);
});
return deps;
},
extractDeclared: function (filepath) {
return [filepath];
},
onlyConcatRequiredFiles: false
},
files: {
'js/<%= pkg.name %>.debug.js': js_sources
}
},
debug_extension: {
src: js_sources,
dest: typo3_js + '<%= pkg.name %>.debug.js'
options: {
extractRequired: function (filepath, filecontent) {
var workingdir = path.normalize(filepath).split(path.sep);
workingdir.pop();
var deps = this.getMatches(/\*\s*@depend\s(.*\.js)/g, filecontent);
deps.forEach(function (dep, i) {
var dependency = workingdir.concat([dep]);
deps[i] = path.join.apply(null, dependency);
});
return deps;
},
extractDeclared: function (filepath) {
return [filepath];
},
onlyConcatRequiredFiles: false
},
files: {
'extension/Resources/Public/JavaScript/<%= pkg.name %>.debug.js': js_sources
}
}
},
less: {
......@@ -234,7 +271,7 @@ module.exports = function (grunt) {
'javascript/src/Element/*.js',
'less/qfq-bs.css.less'
],
tasks: [ 'default' ],
tasks: ['default'],
options: {
spawn: true
}
......@@ -247,14 +284,15 @@ module.exports = function (grunt) {
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-concat-in-order');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-less');
grunt.loadNpmTasks('grunt-contrib-jasmine');
// Default task(s).
grunt.registerTask('default', ['jshint', 'concat', 'uglify', 'copy', 'less']);
grunt.registerTask('default', ['jshint', 'concat_in_order', 'uglify', 'copy', 'less']);
grunt.registerTask('run-jasmine', ['jshint', 'concat', 'jasmine']);
grunt.registerTask('run-jasmine', ['jshint', 'concat_in_order', 'jasmine']);
};
\ No newline at end of file
{
"source": {
"include": [
"javascript/src/"
"javascript/src/",
"javascript/src/Helper/",
"javascript/src/Element/"
],
"includePattern": ".+\\.js"
},
......
......@@ -44,8 +44,6 @@ if (!QfqNS) {
* functions of the `Cancel` or `No` button are added by calling Alert#addCancelButtonHandler(). Lastly,
* Alert#addSaveButtonHandler() adds callback functions to the `Save` button.
*
* Regardless of the
*
*
* @param message {string} message to be displayed
* @param messageType {string} type of message, can either be `"info"`, `"warning"`, or `"error"`.
......@@ -200,6 +198,7 @@ if (!QfqNS) {
var $alertContainer = this.makeAlertContainerSingleton();
this.$alertDiv = $("<div>")
.hide()
.addClass("alert")
.addClass(this.getAlertClassBasedOnMessageType())
.attr("role", "alert")
......@@ -216,7 +215,7 @@ if (!QfqNS) {
}
$alertContainer.append(this.$alertDiv);
this.$alertDiv.fadeIn(this.fadeInDuration, this.afterFadeIn.bind(this));
this.$alertDiv.slideDown(this.fadeInDuration, this.afterFadeIn.bind(this));
this.shown = true;
......@@ -233,22 +232,18 @@ if (!QfqNS) {
/**
*
* @param event
*
* @private
*/
n.Alert.prototype.removeAlert = function (event) {
if (!event || event.type !== "click") {
// No user click, so it must be a timer event
n.Alert.prototype.removeAlert = function () {
// In case we have an armed timer (or expired timer, for that matter), disarm it.
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 () {
this.$alertDiv.slideUp(this.fadeOutDuration, function () {
that.$alertDiv.remove();
that.$alertDiv = null;
that.shown = false;
......
/**
* @author Rafael Ostertag <rafael.ostertag@math.uzh.ch>
*/
/* @depend FormGroup.js */
if (!QfqNS) {
var QfqNS = {};
}
if (!QfqNS.Element) {
QfqNS.Element = {};
}
(function (n) {
'use strict';
/**
*
* @param $element
* @constructor
*/
function Checkbox($element) {
n.FormGroup.call(this, $element);
if (!this.isType("checkbox")) {
throw new Error("$element is not of type 'checkbox'");
}
}
Checkbox.prototype = Object.create(n.FormGroup.prototype);
Checkbox.prototype.constructor = Checkbox;
Checkbox.prototype.setValue = function (val) {
this.$element.prop('checked', val);
};
Checkbox.prototype.getValue = function () {
return this.$element.prop('checked');
};
n.Checkbox = Checkbox;
})(QfqNS.Element);
/**
* @author Rafael Ostertag <rafael.ostertag@math.uzh.ch>
*/
if (!QfqNS) {
var QfqNS = {};
}
if (!QfqNS.Element) {
QfqNS.Element = {};
}
(function (n) {
'use strict';
n.Element = function ($element) {
if (!$element || $element.length === 0) {
throw new Error("No element");
}
this.formGroup = new n.FormGroup($element);
};
/**
*
* @param type
* @returns {boolean}
*
* @protected
*/
n.Element.prototype.isType = function (type) {
var lowerCaseType = type.toLowerCase();
var isOfType = true;
this.formGroup.$element.each(function () {
if (this.hasAttribute('type')) {
if (this.getAttribute('type') === lowerCaseType) {
return true;
} else {
isOfType = false;
return false;
}
} else {
// <select> is not an attribute value, obviously, so check for nodename
if (this.nodeName.toLowerCase() === lowerCaseType) {
return true;
} else if (lowerCaseType === 'text') {
return true;
} else {
isOfType = false;
return false;
}
}
});
return isOfType;
};
})(QfqNS.Element);
\ No newline at end of file
......@@ -13,6 +13,17 @@ if (!QfqNS.Element) {
(function (n) {
'use strict';
/**
* Form Group represents a `<input>/<select>` element including the label and help block.
*
* It is not meant to be used directly. Use the specialized objects instead.
*
* @param $enclosedElement {jQuery} a jQuery object contained in the Form Group. It used to find the enclosing
* HTML element having the `.form-group` class assigned.
*
*
* @constructor
*/
n.FormGroup = function ($enclosedElement) {
if (!$enclosedElement || $enclosedElement.length === 0) {
throw new Error("No enclosed element");
......@@ -24,6 +35,33 @@ if (!QfqNS.Element) {
this.$helpBlock = this.$formGroup.find(".help-block");
};
n.FormGroup.prototype.isType = function (type) {
var lowerCaseType = type.toLowerCase();
var isOfType = true;
this.$element.each(function () {
if (this.hasAttribute('type')) {
if (this.getAttribute('type') === lowerCaseType) {
return true;
} else {
isOfType = false;
return false;
}
} else {
// <select> is not an attribute value, obviously, so check for nodename
if (this.nodeName.toLowerCase() === lowerCaseType) {
return true;
} else if (lowerCaseType === 'text') {
return true;
} else {
isOfType = false;
return false;
}
}
});
return isOfType;
};
/**
*
* @param $enclosedElement
......@@ -58,7 +96,55 @@ if (!QfqNS.Element) {
};
n.FormGroup.prototype.setReadOnly = function (readonly) {
this.$element.propr('readonly', readonly);
this.$element.prop('readonly', readonly);
this.handleReadOnlyEmulationIfRequired(readonly);
};
/**
* @private
* @param readonlyState
*/
n.FormGroup.prototype.handleReadOnlyEmulationIfRequired = function (readonlyState) {
if (!this.readOnlyEmulationRequired()) {
return;
}
if (readonlyState) {
// In case we're called with readonlyState===true twice in a row, make sure only one handler will be
// active at a time
this.$element.off('click', this.readOnlyHandler);
this.$element.on('click', this.readOnlyHandler);
} else {
this.$element.off('click', this.readOnlyHandler);
}
};
n.FormGroup.prototype.readOnlyEmulationRequired = function () {
// Keep this at top, since select does not feature the type attribute.
if (n.readOnlyIgnored.indexOf(this.$element[0].nodeName.toLowerCase())) {
return true;
}
if (!this.$element[0].hasAttribute('type')) {
// if there is no type attribute, browsers default to text, which is `properly implements` read only.
return false;
}
if (n.readOnlyIgnored.indexOf(this.$element[0].getAttribute('type').toLowerCase())) {
return true;
}
return false;
};
/**
* Read Only click handler.
*
* Since the readonly attribute does not work as expected on certain input types, emulate read only
*/
n.FormGroup.prototype.readOnlyHandler = function () {
return false;
};
......
/**
* @author Rafael Ostertag <rafael.ostertag@math.uzh.ch>
*/
/* global $ */
if (!QfqNS) {
var QfqNS = {};
}
if (!QfqNS.Element) {
QfqNS.Element = {};
}
(function (n) {
'use strict';
n.getElement = function (name) {
var $element = $('[name=' + name + ']');
if ($element.length === 0) {
throw Error('No element with name "' + name + '" found.');
}
if ($element[0].nodeName.toLowerCase() === "select") {
return new n.Select($element);
}
if (!$element[0].hasAttribute('type')) {
return new n.Text($element);
}
var type = $element[0].getAttribute('type').toLowerCase();
switch (type) {
case 'checkbox':
return new n.Checkbox($element);
case 'radio':
return new n.Radio($element);
case 'text':
return new n.Text($element);
default:
throw new Error("Don't know how to handle <input> of type '" + type + "'");
}
};
})(QfqNS.Element);
\ No newline at end of file
......@@ -20,23 +20,23 @@ if (!QfqNS.Element) {
* @constructor
*/
function Radio($element) {
n.Element.call(this, $element);
n.FormGroup.call(this, $element);
if (!this.isType("radio")) {
throw new Error("$element is not of type 'radio'");
}
}
Radio.prototype = Object.create(n.Element.prototype);
Radio.prototype = Object.create(n.FormGroup.prototype);
Radio.prototype.constructor = Radio;
Radio.prototype.setValue = function (val) {
this.formGroup.$element.prop('checked', false);
this.formGroup.$element.filter('[value=' + val + "]").prop('checked', true);
this.$element.prop('checked', false);
this.$element.filter('[value=' + val + "]").prop('checked', true);
};
Radio.prototype.getValue = function () {
return this.formGroup.$element.filter(':checked').val();
return this.$element.filter(':checked').val();
};
n.Radio = Radio;
......
......@@ -22,14 +22,14 @@ if (!QfqNS.Element) {
* @constructor
*/
function Select($element) {
n.Element.call(this, $element);
n.FormGroup.call(this, $element);
if (!this.isType("select")) {
throw new Error("$element is not of type 'select'");
}
}
Select.prototype = Object.create(n.Element.prototype);
Select.prototype = Object.create(n.FormGroup.prototype);
Select.prototype.constructor = Select;
/**
......@@ -39,22 +39,22 @@ if (!QfqNS.Element) {
* array of objects, `<select>` will have its `<option>` tags set correspondingly.
*/
Select.prototype.setValue = function (val) {
if (typeof(val) in ['string', 'number']) {
if (['string', 'number'].indexOf(typeof(val)) !== -1) {
this.setSelection(val);
} else if (Array.isArray(val)) {
this.formGroup.$element.empty();
this.$element.empty();
// Fill array with new <select> elements first and add it to the dom in one step, instead of appending
// each '<select>' separately.
var selectArray;
var selectArray = [];
val.forEach(function (selectObj) {
var $option = $('<option>')
.addAttribute('value', selectObj.value ? selectObj.value : selectObj.text)
.attr('value', selectObj.value ? selectObj.value : selectObj.text)
.prop('selected', selectObj.selected ? selectObj.selected : false)
.append(selectObj.text);
selectArray.append($option);
selectArray.push($option);
});
this.formGroup.$element.append(selectArray);
this.$element.append(selectArray);
} else {
throw Error('Unsupported type of argument in Select.setValue: "' + typeof(val) + '". Expected either' +
' "string" or "array"');
......@@ -72,11 +72,11 @@ if (!QfqNS.Element) {
// First, see if we find an <option> tag having an attribute 'value' matching val. If that doesn't work,
// fall back to comparing text content of <option> tags.
var $selectionByValue = this.formGroup.$element.find('option[value=' + val);
var $selectionByValue = this.$element.find('option[value=' + val + ']');
if ($selectionByValue.length > 0) {
$selectionByValue.prop('selected', true);
} else {
this.formGroup.$element.find('option').each(function () {
this.$element.find('option').each(function () {
var $element = $(this);
if ($element.text() === val) {
$element.prop('selected', true);
......@@ -91,14 +91,14 @@ if (!QfqNS.Element) {
* @private
*/
Select.prototype.clearSelection = function () {
this.formGroup.$element.find(':selected').each(function () {
this.$element.find(':selected').each(function () {
$(this).prop('selected', false);
});
};
Select.prototype.getValue = function () {
var returnValue = [];
this.formGroup.$element.find(':selected').each(
this.$element.find(':selected').each(
function () {
if (this.hasAttribute('value')) {
returnValue.push(this.getAttribute('value'));
......
......@@ -19,25 +19,25 @@ if (!QfqNS.Element) {
* @param $element
* @constructor
*/
function Input($element) {
n.Element.call(this, $element);
function Text($element) {
n.FormGroup.call(this, $element);
if (!this.isType("text")) {
throw new Error("$element is not of type 'text'");
}
}
Input.prototype = Object.create(n.Element.prototype);
Input.prototype.constructor = Input;
Text.prototype = Object.create(n.FormGroup.prototype);
Text.prototype.constructor = Text;
Input.prototype.setValue = function (val) {
this.formGroup.$element.val(val);
Text.prototype.setValue = function (val) {
this.$element.val(val);
};
Input.prototype.getValue = function () {
return this.formGroup.$element.val();
Text.prototype.getValue = function () {
return this.$element.val();
};
n.Input = Input;
n.Text = Text;
})(QfqNS.Element);
\ No newline at end of file
......@@ -45,4 +45,17 @@ if (!QfqNS.Element) {
'color'
];
/*
* See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input for input types ignoring the readonly
* attribute.
*/
n.readOnlyIgnored = [
'hidden',
'range',
'checkbox',
'radio',
'file',
'select'
];
})(QfqNS.Element);
\ No newline at end of file
......@@ -74,6 +74,10 @@ if (!QfqNS) {
.fail(this.submitFailureHandler.bind(this));
};
n.Form.prototype.serialize = function () {
return this.$form.serialize();
};
/**
*
* @param data
......
......@@ -11,10 +11,11 @@ if (!QfqNS) {
(function (n) {
'use strict';
n.QfqForm = function (formId, submitTo, deleteUrl) {
n.QfqForm = function (formId, submitTo, deleteUrl, dataRefreshUrl) {
this.formId = formId;
this.submitTo = submitTo;
this.deleteUrl = deleteUrl;
this.dataRefreshUrl = dataRefreshUrl;
this.form = new n.Form(this.formId);
this.bsTabs = null;
this.lastButtonPress = null;
......@@ -31,6 +32,8 @@ if (!QfqNS) {
this.getCloseButton().click(this.handleCloseClick.bind(this));
this.getNewButton().click(this.handleNewClick.bind(this));
this.getDeleteButton().click(this.handleDeleteClick.bind(this));
this.setupRefreshHandler();
};