Skip to content

Commit

Permalink
Merge pull request #3352 from AtlasOfLivingAustralia/feature/issue2880
Browse files Browse the repository at this point in the history
Feature/issue2880
  • Loading branch information
temi authored Oct 29, 2024
2 parents 1adcb15 + 2aa07d4 commit 34f8e7b
Show file tree
Hide file tree
Showing 64 changed files with 69,864 additions and 1,466 deletions.
199 changes: 199 additions & 0 deletions grails-app/assets/components/javascript/associated-orgs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
/*
* Copyright (C) 2022 Atlas of Living Australia
* All Rights Reserved.
*
* The contents of this file are subject to the Mozilla Public
* License Version 1.1 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*/
/**
* This component renders a list of organisations and their relationship to an entity
*/
ko.components.register('associated-orgs', {

/**
* @param params an object with the following keys:
* externalIds: an observable array of objects, each object will have two observables, idType and externalId.
* externalIdTypes: an array of label/value pairs that define the selectable options for the idType
* validationNamespace: a string to store the validation function in the global namespace for use by jquery validation engine
* validate: a jquery-validation-engine style function that will validate the external ids. (Note this function
* should return a string containing the error if the validation fails).
*/
viewModel: function (params) {
var self = this;
self.organisationSearchUrl = params.organisationSearchUrl;
self.organisationViewUrl = params.organisationViewUrl;
self.displayName = params.displayName;
self.relationshipTypes = params.relationshipTypes;

var $modal = $('#add-or-edit-organisation');
$modal.find('form').validationEngine();

function AssociatedOrg(associatedOrg) {

associatedOrg = associatedOrg || {};
this.name = ko.observable(associatedOrg.name);
this.organisationName = ko.observable(associatedOrg.organisationName);
this.description = ko.observable(associatedOrg.description);
this.organisationId = ko.observable(associatedOrg.organisationId);
this.fromDate = ko.observable(associatedOrg.fromDate).extend({simpleDate:false});
this.toDate = ko.observable(associatedOrg.toDate).extend({simpleDate:false});

this.toJSON = function() {
return ko.mapping.toJS(this);
}
}

self.associatedOrgs = ko.observableArray(_.map(params.associatedOrgs(), function(org) {
return new AssociatedOrg(org);
}));
// Overwrites the associatedOrgs observable with the mapped values so they are editable and changes in
// this component are reflected in the parent component.
params.associatedOrgs(self.associatedOrgs());

self.validationNamespace = params.validationNamespace;

self.organisationSearchUrl = params.organisationSearchUrl;
self.allowedNames = ko.observableArray([]);

self.removeAssociatedOrg = function (org) {
self.associatedOrgs.remove(org);
}

// Maintains the state of which organisation is being edited or added
self.selectedOrganisation = null;

self.addAssociatedOrg = function () {
self.selectedOrganisation = new AssociatedOrg();
openEditModal();
}

self.editAssociatedOrg = function (organisation) {
self.selectedOrganisation = organisation;
openEditModal();
}

function openEditModal() {

var orgId = self.selectedOrganisation.organisationId();
if (orgId) {
findMatchingOrganisation(orgId, function(matchingOrg) {
if (matchingOrg && matchingOrg._source) {
self.allowedNames(self.allowedNamesForOrganisation(matchingOrg._source));
copy(self.selectedOrganisation, self.editableOrganisation);
$('#searchOrganisation').val(matchingOrg._source.name);
$modal.modal('show');
}
else {
bootbox.alert("Unable to edit organisation")
}
});
}
else {
self.clearSelectedOrganisation();
$modal.modal('show');
}

}

function findMatchingOrganisation(organisationId, callback) {
$.get(self.organisationSearchUrl+'?searchTerm='+organisationId).done(function(results) {
if (results && results.hits && results.hits.hits) {
var matchingOrg = _.find(results.hits.hits, function (hit) {
return hit._id == organisationId;
});

callback(matchingOrg);
}
});
}

self.okPressed = function () {
var valid = $modal.find('form').validationEngine('validate');
if (!valid) {
return;
}
if (!_.contains(self.associatedOrgs(), self.selectedOrganisation)) {
self.associatedOrgs.push(self.selectedOrganisation);
}
copy(self.editableOrganisation, self.selectedOrganisation);
self.close();
}

self.close = function() {
$modal.modal('hide');
}

function copy(source, destination) {
destination.organisationId(source.organisationId());
destination.name(source.name());
destination.description(source.description());
destination.fromDate(source.fromDate());
destination.toDate(source.toDate());
}

self.allowedNamesForOrganisation = function(organisation) {
var allowedNames = [];
allowedNames.push(organisation.name);
if (organisation.entityName) {
allowedNames.push(organisation.entityName);
}
if (organisation.businessNames) {
allowedNames = allowedNames.concat(organisation.businessNames);
}
if (organisation.contractNames) {
allowedNames = allowedNames.concat(organisation.contractNames);
}
return allowedNames;
}

/**
* This method is designed to be used by the jquery validation engine so a passed validation will
* return undefined / null, and a failed validation will return an error message.
* @returns {*} A message to display if validation failed.
*/
self.associatedOrgValidation = function() {
if (params.validate) {
return params.validate();
}
}

self.organisationNames = ko.observableArray();

self.selectOrganisation = function(item) {

if (item && item.source) {
self.editableOrganisation.organisationId(item.source.organisationId);
self.editableOrganisation.organisationName(item.source.name);
if (!self.editableOrganisation.name()) {
self.editableOrganisation.name(item.source.name);
}
self.allowedNames(self.allowedNamesForOrganisation(item.source));
}
else {
self.editableOrganisation.organisationId(null);
}

}

self.clearSelectedOrganisation = function() {
$('#searchOrganisation').val('');
self.allowedNames([]);
self.editableOrganisation.organisationId(null);
self.editableOrganisation.name('');
self.editableOrganisation.description('');
self.editableOrganisation.fromDate('');
self.editableOrganisation.toDate('');
}

self.editableOrganisation = new AssociatedOrg();

},
template: componentService.getTemplate('associated-orgs')
});
101 changes: 101 additions & 0 deletions grails-app/assets/components/template/associated-orgs.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@

<div class="associatedOrgs mt-3">
Associated Organisations

<div class=" pl-0 associated-org-list" data-bind="foreach:associatedOrgs">

<div class="actions">
<button class="btn btn-sm btn-container" data-bind="click:$parent.editAssociatedOrg"><i class="fa fa-edit"></i></button>
<button class="btn btn-sm btn-container" data-bind="click:$parent.removeAssociatedOrg"><i class="fa fa-remove"></i></button>
</div>
<div>
<!-- ko if: $data.organisationId() -->
<a href="" data-bind="attr:{href:$parent.organisationViewUrl + '/' + $data.organisationId()}">
<span data-bind="text:name"></span>
</a>
<!-- /ko -->

<!-- ko if:!$data.organisationId() -->
<span data-bind="text:name"></span>
<!-- /ko -->
</div>
<div>
( <span data-bind="text:description"></span>
<!-- ko if:fromDate -->
<span> from <span data-bind="text:fromDate.formattedDate"></span></span>
<!-- /ko -->
<!-- ko if:toDate -->
<span> to <span data-bind="text:toDate.formattedDate"></span></span>
<!-- /ko -->
)
</div>

</div>
<div class="mb-3">
<button id="addAssociatedOrgButton" class="btn btn-sm" data-bind="click:addAssociatedOrg">Add Organisation</button>
<input type="text" class="invisibleValidationHolder" name="invisibleValidationHolder" data-bind="jqueryValidationEngine:{namespace:validationNamespace, validationFunction:associatedOrgValidation}">
</div>

</div>

<!-- ko using:editableOrganisation -->
<div id="add-or-edit-organisation" class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" id="title">Organisation relationship</h4>
</div>


<form class="modal-body">
<div class="form-group">
<label for="searchOrganisation">Search for an existing organisation</label>
<div class="input-group input-append">
<input type="text" id="searchOrganisation" name="organisation-search" autocomplete="off" class="form-control form-control-sm" placeholder="Search organisations..."
data-bind="enable:!organisationId(), elasticSearchAutocomplete:{url:$parent.organisationSearchUrl, value:'name', label:'name', result:$parent.selectOrganisation}"/>
<span class="input-group-text"><i class="fa fa-remove" data-bind="click:$parent.clearSelectedOrganisation"></i></span>
</div>
</div>
<!-- ko if:$parent.displayName -->
<div class="form-group">
<label for="name-to-use" class="required">Organisation name as it appears on the project page (e.g. name used in contract)</label>

<select class="form-control" id="name-to-use" data-bind="enable:organisationId(), value:name, options:$parent.allowedNames"
data-validation-engine="validate[required]" data-prompt-position="topLeft"></select>
</div>
<!-- /ko -->
<div class="form-group">
<label class="required" for="relationship-description">Relationship</label>
<!-- ko if:$parent.relationshipTypes -->
<select id="relationship-description" name="description" class="form-control form-control-sm" data-bind="enable:organisationId(), options:$parent.relationshipTypes, value:description"></select>
<!-- /ko -->
<!-- ko if:!$parent.relationshipTypes -->
<input type="text" id="relationship-description" name="description" class="form-control form-control-sm" data-bind="enable:organisationId(), value:description"></input>
<!-- /ko -->
</div>
<div class="form-group">
<label for="relationship-from-date">From date</label>
<div class="input-group input-append">
<input id="relationship-from-date" name="relationship-from-date" autocomplete="off" class="form-control" data-bind="enable:organisationId(), datepicker:fromDate.date" type="text">
<span class="input-group-text open-datepicker"><i class="fa fa-th"></i></span>
</div>
</div>
<div class="form-group">
<label for="relationship-to-date">To date</label>
<div class="input-group input-append">
<input id="relationship-to-date" class="form-control" autocomplete="off" data-bind="enable:organisationId(), datepicker:toDate.date" name="to-date" type="text"
data-validation-engine="validate[future[#relationship-from-date]]" data-prompt-position="topLeft">
<span class="input-group-text open-datepicker"><i class="fa fa-th"></i></span>
</div>
</div>
</form>

<div class="modal-footer">
<button type="button" class="btn btn-sm btn-success" name="save-org-changes"
data-bind="enable:organisationId(), click:$parent.okPressed">OK</button>
<button class="btn btn-sm btn-danger" data-bind="click:$parent.close">Cancel</button>
</div>
</div>
</div>
</div>
<!-- /ko -->
19 changes: 19 additions & 0 deletions grails-app/assets/javascripts/admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,27 @@ var ProjectImportViewModel = function (config) {
self.importing = ko.observable(false);
self.update = ko.observable(false);

self.success = ko.computed(function() {
var success = !self.preview();
if (success) {
for (var i = 0; i < self.progressDetail().length; i++) {
success = success && self.progressDetail()[i].success;
}
}

return success;

});

self.uploadOptions = {
url: config.importUrl,
change: function() {
self.preview(true);
self.finishedPreview(false);
self.finished(false);
self.progressDetail([]);
self.progressSummary('');
},
done: function (e, data) {

if (data.result) {
Expand Down
2 changes: 1 addition & 1 deletion grails-app/assets/javascripts/fieldcapture-application.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ function autoSaveModel(viewModel, saveUrl, options) {
preventNavigationIfDirty: false,
defaultDirtyFlag:ko.simpleDirtyFlag,
dirtyFlagRateLimitMs: 0, // If ko.dirtyFlag is used (rather than ko.simpleDirtyFlag), this is the rate limit in ms for detecting changes.
healthCheckUrl:fcConfig && fcConfig.healthCheckUrl
healthCheckUrl: fcConfig && fcConfig.healthCheckUrl
};
var config = $.extend(defaults, options);

Expand Down
1 change: 0 additions & 1 deletion grails-app/assets/javascripts/knockout-custom-bindings.js
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,6 @@ ko.bindingHandlers.elasticSearchAutocomplete = {
};
options.select = function(event, ui) {
result(ui.item);
$(this).val(""); // Clear the search field
};

$(element).autocomplete(options);
Expand Down
Loading

0 comments on commit 34f8e7b

Please sign in to comment.