OpenProject is the leading open source project management software.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
openproject/app/assets/javascripts/modal.js

534 lines
16 KiB

//-- copyright
// OpenProject is a project management system.
//
// Copyright (C) 2012-2013 the OpenProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// See doc/COPYRIGHT.rdoc for more details.
//++
var ModalHelper = (function() {
var ModalHelper = function(timeline, options) {
this.options = options;
this.timeline = timeline;
}
/** display the loading modal (spinner in a box)
* also fix z-index so it is always on top.
*/
ModalHelper.prototype.showLoadingModal = function() {
jQuery('#ajax-indicator').show().css('zIndex', 1020);
};
/** hide the loading modal */
ModalHelper.prototype.hideLoadingModal = function() {
jQuery('#ajax-indicator').hide();
};
/** submit a form in the background.
* @param form: form element
* @param url: url to submit to. can be undefined if so, url is taken from form.
* @param callback: called with results
*/
//TODO fix this inconsistency w/ optional url.
ModalHelper.prototype.submitBackground = function(form, url, callback) {
var data = form.serialize();
if (typeof url === 'function') {
callback = url;
url = undefined;
}
if (typeof url === 'undefined') {
url = form.attr('action');
}
jQuery.ajax({
type: 'POST',
url: url,
data: data,
error: function(obj, error) {
callback(obj.status, obj.responseText);
},
success: function(response) {
callback(null, response);
}
});
};
/** create a planning modal
* @param type either new, edit or show.
* @param projectId id of the project to create the modal for.
* @param elementId element id to create the modal for. not needed for new type.
* @param callback called when done
*/
ModalHelper.prototype.createPlanningModal = function(type, projectId, elementId, callback) {
var modalHelper = this;
var timeline = modalHelper.timeline;
var non_api_url = modalHelper.options.url_prefix +
modalHelper.options.project_prefix +
"/" +
projectId +
'/planning_elements/';
var api_url = modalHelper.options.url_prefix +
modalHelper.options.api_prefix +
modalHelper.options.project_prefix +
"/" +
projectId +
'/planning_elements/';
if (typeof elementId === 'function') {
callback = elementId;
elementId = undefined;
}
// in the following lines we create the url to get the data from
// also we create the url we submit the data to for the edit action.
//TODO: escape projectId and elementId.
if (type === 'new') {
non_api_url += 'new.js';
} else if (type === 'edit') {
if (typeof elementId === 'string' || typeof elementId === 'number') {
non_api_url += elementId + '/edit.js';
api_url += elementId + '.json';
} else {
throw new Error('need an element id for editing.');
}
} else if (type === 'show') {
if (typeof elementId === 'string' || typeof elementId === 'number') {
non_api_url += elementId + '.js';
} else {
throw new Error('need an element id for showing.');
}
} else {
throw new Error('invalid action. allowed: new, show, edit');
}
//create the modal by using the html the url gives us.
modalHelper.createModal(non_api_url, function(ele) {
var projects = timeline.projects;
var project;
var projectSelect;
var fields = ele.find(':input');
ele.data('changed', false);
var submitFunction = function(e) {
modalHelper.showLoadingModal();
if (type === 'new') {
api_url = modalHelper.options.url_prefix +
modalHelper.options.api_prefix +
modalHelper.options.project_prefix +
"/" +
projectSelect.val() +
'/planning_elements.json';
}
modalHelper.submitBackground(jQuery(this), api_url, function(err, res) {
var element;
modalHelper.hideLoadingModal();
// display errors correctly.
if (!err) {
currentURL = '';
timeline.reload();
if (elementId === undefined) {
try {
// internet explorer has a text attribute instead of textContent.
element = res.getElementsByTagName('id')[0];
elementId = element.textContent || element.text;
} catch (e) {
console.log(e);
}
}
if (elementId !== undefined) {
modalHelper.createPlanningModal('show', projectId, elementId);
}
} else if (err !== '500') {
ele.find('.errorExplanation').remove();
var error = jQuery('<div>').attr('class', 'errorExplanation').attr('id', 'errorExplanation');
var json = jQuery.parseJSON(res);
var i, errorSpan, errorFormEle;
var errorField;
for (errorField in json.errors) {
if (json.errors.hasOwnProperty(errorField)) {
//for (i = 0; i < json.errors.length; i += 1) {
error.append(
jQuery('<ul/>').append(
jQuery('<li/>').text(I18n.t('js.timelines.' + errorField) + ' ' + json.errors[errorField]))
);
try {
errorSpan = jQuery('<span/>').attr('class', 'errorSpan');
errorFormEle = jQuery('#planning_element_' + errorField);
errorFormEle.before(errorSpan);
errorSpan.append(errorFormEle);
} catch (e) {
// nop
}
}
}
ele.prepend(error);
ele.scrollTop(0);
}
});
if (e) {
e.preventDefault();
}
};
//if we want to create a new element, the project must be selectable.
if (type === 'new') {
ele.find('tbody').first().prepend(
jQuery('<tr><th>' + I18n.t('js.timelines.create_planning_select_project') + '</th><td><select id="projectSelect"/></td></tr>')
);
projectSelect = ele.find('#projectSelect');
for (project in projects) {
if (projects.hasOwnProperty(project)) {
if (projects[project].permissions.edit_planning_elements === true) {
projectSelect.append(jQuery('<option/>').attr('value', projects[project].identifier).text(projects[project].name));
}
}
}
projectSelect.change(function() {
var planningElementName = ele.find('#planning_element_name').val();
var planningElementDescription = ele.find('#planning_element_description').val();
var planningElementType = ele.find('#planning_element_planning_element_type_id').val();
var planningElementResponsible = ele.find('#planning_element_responsible_id').val();
var planningElementStartDate = ele.find('#planning_element_start_date').val();
var planningElementEndDate = ele.find('#planning_element_end_date').val();
//just overwrite the current planning modal.
modalHelper.createPlanningModal('new', projectSelect.val(), function(ele) {
ele.find('#planning_element_name').val(planningElementName);
ele.find('#planning_element_description').val(planningElementDescription);
ele.find('#planning_element_planning_element_type_id').val(planningElementType);
ele.find('#planning_element_responsible_id').val(planningElementResponsible);
ele.find('#planning_element_start_date').val(planningElementStartDate);
ele.find('#planning_element_end_date').val(planningElementEndDate);
});
});
// set to given project id.
projectSelect.val(projectId);
}
//create cancel and save button
if (type === 'new' || type === 'edit') {
var cancel = jQuery("<a>").addClass("icon").addClass("icon-cancel").text(I18n.t("js.timelines.cancel")).attr("href", "#").click(function (e) {
e.preventDefault();
if (ele.data('changed') !== true || confirm(I18n.t('js.timelines.really_close_dialog'))) {
if (typeof elementId === "undefined") {
if (ele.data('changed') !== true || confirm(I18n.t('js.timelines.really_close_dialog'))) {
ele.data('changed', false);
ele.dialog('close');
}
} else {
modalHelper.createPlanningModal('show', projectId, elementId);
}
}
});
var save = jQuery("<a>").addClass("icon").addClass("icon-save").text(I18n.t("js.timelines.save")).attr("href", "#").click(function (e) {
e.preventDefault();
submitFunction.call(ele.find('form'));
});
ele.find('form').prepend(
jQuery("<div>").append(
cancel
).append(
save
).addClass("contextual")
);
//remove old submit/cancel elements
ele.find('form').find(':submit').css("display", "none");
ele.find('form').find('[name=cancelButton]').remove();
//make textareas bigger
if (ele.height() > 800) {
ele.find('textarea').attr("rows", 10);
} else if (ele.height() > 600) {
ele.find('textarea').attr("rows", 8);
}
}
//overwrite the action for the edit button.
if (type === 'show') {
ele.find('.icon-edit').click(function(e) {
modalHelper.createPlanningModal('edit', projectId, elementId);
e.preventDefault();
});
ele.find('.icon-cancel').click(function(e) {
modalHelper.showLoadingModal();
modalHelper.submitBackground(jQuery(ele.find('.icon-cancel').parent()[0]),
function(err, res) {
modalHelper.hideLoadingModal();
// display errors correctly.
if (!err) {
ele.dialog('close');
timeline.reload();
}
}
);
e.preventDefault();
});
ele.find('.icon-del').click(function(e) {
var tokenName, token, action, data = {};
var url = modalHelper.options.url_prefix +
modalHelper.options.project_prefix +
"/" +
projectId +
'/planning_elements/';
tokenName = jQuery('meta[name=csrf-param]').attr('content');
token = jQuery('meta[name=csrf-token]').attr('content');
if (jQuery(this).attr('href').indexOf("destroy") == -1) {
modalHelper.showLoadingModal();
action = 'delete';
data['_method'] = 'delete';
data[tokenName] = token;
jQuery.post(url + elementId + '/move_to_trash',
data,
function() {
modalHelper.hideLoadingModal();
ele.dialog('close');
timeline.reload();
}).error(function() {
modalHelper.hideLoadingModal();
alert(I18n.t('js.timelines.error'));
});
//move to bin
} else if (confirm(I18n.t('js.timelines.really_delete_planning_element'))) {
modalHelper.showLoadingModal();
action = 'delete';
data['_method'] = 'delete';
data[tokenName] = token;
data['commit'] = 'delete';
jQuery.post(url + elementId,
data,
function() {
modalHelper.hideLoadingModal();
ele.dialog('close');
timeline.reload();
}).error(function() {
modalHelper.hideLoadingModal();
alert(I18n.t('js.timelines.error'));
});
//move to bin
}
e.preventDefault();
});
}
fields.change(function(e) {
ele.data('changed', true);
});
// calendar click must be stopped so it does not close the modal.
ele.find('.calendar-trigger').click(function() {
jQuery('.calendar').click(function(e) {
e.stopPropagation();
}).css('zIndex', 2000);
});
// if a form is submitted, we stop it and submit it in the background.
ele.find('form').submit(submitFunction);
if (typeof callback === 'function') {
callback(ele);
}
});
};
/** create a modal dialog from url html data
* @param url url to load html from.
* @param callback called when done. called with modal div.
*/
ModalHelper.prototype.createModal = function(url, callback) {
var modalHelper = this;
if (modalHelper.loadingModal) {
return;
}
modalHelper.loadingModal = true;
try {
modalHelper.showLoadingModal();
// get html for url.
jQuery.ajax({
type: 'GET',
url: url,
dataType: 'html',
error: function(obj, error) {
modalHelper.hideLoadingModal();
modalHelper.loadingModal = false;
},
success: function(data) {
try {
modalHelper.hideLoadingModal();
currentURL = url;
var ta = modalHelper.modalDiv;
// write html to div
ta.html(data);
// show dialog.
ta.dialog({
modal: true,
resizable: false,
draggable: false,
width: '900px',
height: jQuery(window).height() * 0.8,
position: {
my: 'center',
at: 'center'
}
});
// close when cancel is clicked.
ta.find('[name=cancelButton]').click(function(e) {
e.preventDefault();
if (ta.data('changed') !== true || confirm(I18n.t('js.timelines.really_close_dialog'))) {
ta.data('changed', false);
ta.dialog('close');
}
});
// hide dialog header
//TODO: we need a default close button somewhere
jQuery('#planningElementDialog').parent().prepend('<div id="ui-dialog-closer" />');
jQuery('.ui-dialog-titlebar').hide();
if (typeof callback === 'function') {
callback(ta);
}
} catch (e) {
console.log(e);
} finally {
modalHelper.loadingModal = false;
}
}
});
} catch (e) {
console.log(e);
modalHelper.loadingModal = false;
}
};
ModalHelper.prototype.setup = function() {
var body = jQuery('body');
var timeline = this.timeline;
var modalDiv;
// whatever globals there are, they need to be added to the
// prototype, so that all ModalHelper instances can share them.
if (ModalHelper.prototype.done !== true) {
// one time initialization
modalDiv = jQuery('<div/>').css('hidden', true).attr('id', 'planningElementDialog');
body.append(modalDiv);
// close when body is clicked
body.click(function(e) {
if (modalDiv.data('changed') !== true || confirm(I18n.t('js.timelines.really_close_dialog'))) {
modalDiv.data('changed', false);
modalDiv.dialog('close');
} else {
e.stopPropagation();
}
});
// do not close when element is clicked
modalDiv.click(function(e) {
e.stopPropagation();
});
ModalHelper.prototype.done = true;
} else {
modalDiv = jQuery('#planningElementDialog');
}
// every-time initialization
jQuery(timeline).on('dataLoaded', function() {
var projects = timeline.projects;
var project;
for (project in projects) {
if (projects.hasOwnProperty(project)) {
if (projects[project].permissions.edit_planning_elements === true) {
jQuery('#newPlanning').show();
break;
}
}
}
});
var loadingModalDiv = jQuery('<div/>');
body.append(loadingModalDiv);
loadingModalDiv.css('hidden', true).attr('id', 'loadingModal');
this.loadingModal = false;
this.modalDiv = modalDiv;
}
return ModalHelper;
})();