Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Item stubs #2216

Open
wants to merge 48 commits into
base: develop
Choose a base branch
from
Open
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
38f48a6
Begin implementing resource stub
jimsafley Jul 9, 2024
e9c5850
Continue dev
jimsafley Jul 9, 2024
e7db625
Continue dev
jimsafley Jul 10, 2024
e40a196
Continue dev
jimsafley Jul 11, 2024
6950b81
Continue dev
jimsafley Jul 11, 2024
0d8995e
Continue dev
jimsafley Jul 12, 2024
057d345
Continue dev
jimsafley Jul 13, 2024
99f0eda
Continue dev
jimsafley Jul 15, 2024
664ac71
Continue dev
jimsafley Jul 15, 2024
0da03d1
Continue dev
jimsafley Jul 16, 2024
f4b4f28
Continue dev
jimsafley Jul 16, 2024
3e73aad
Begin converting to template-based item stub form
jimsafley Jul 18, 2024
7aa38a8
Continue dev
jimsafley Jul 19, 2024
d71c1c2
Continue dev
jimsafley Jul 20, 2024
6423db5
Begin implementing resource stub
jimsafley Jul 9, 2024
e827180
Continue dev
jimsafley Jul 9, 2024
3d9655c
Continue dev
jimsafley Jul 10, 2024
1273963
Continue dev
jimsafley Jul 11, 2024
6ae3386
Continue dev
jimsafley Jul 11, 2024
15f10f5
Continue dev
jimsafley Jul 12, 2024
79109ab
Continue dev
jimsafley Jul 13, 2024
4167a55
Continue dev
jimsafley Jul 15, 2024
45c742e
Continue dev
jimsafley Jul 15, 2024
b7d415a
Continue dev
jimsafley Jul 16, 2024
feeb9d8
Continue dev
jimsafley Jul 16, 2024
43b7280
Begin converting to template-based item stub form
jimsafley Jul 18, 2024
1b61b17
Continue dev
jimsafley Jul 19, 2024
e2ef051
Continue dev
jimsafley Jul 20, 2024
3882a8b
Merge branch 'item-stub' of github.com:omeka/omeka-s into item-stub
jimsafley Jul 20, 2024
4e7a5e1
Continue dev
jimsafley Jul 20, 2024
e459987
Continue dev
jimsafley Jul 21, 2024
8d6eb27
Continue dev
jimsafley Jul 22, 2024
316c1d2
Continue dev
jimsafley Jul 23, 2024
bfc19a5
Continue dev
jimsafley Jul 23, 2024
8f731e1
Continue dev
jimsafley Jul 23, 2024
121ef5d
Continue dev
jimsafley Jul 24, 2024
76dba50
Continue dev
jimsafley Jul 24, 2024
74d38ba
Continue dev
jimsafley Jul 24, 2024
33356f8
Continue dev
jimsafley Jul 24, 2024
3a26c0b
Continue dev
jimsafley Jul 24, 2024
ebf7f0f
Style required tag for value annotation values.
kimisgold Jul 24, 2024
fbd903b
Style sidebar tabs.
kimisgold Jul 24, 2024
de1c6be
Add heading to item stub form for consistency.
kimisgold Jul 24, 2024
febc3c2
Ensure value annotation value inputs use full sidebar width.
kimisgold Jul 24, 2024
1123698
Continue dev
jimsafley Jul 24, 2024
6c4e7fe
Adjust sidebar tabs for new div container markup.
kimisgold Jul 25, 2024
1e88223
Continue dev
jimsafley Jul 25, 2024
599060a
Continue dev
jimsafley Jul 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion application/asset/css/style.css

Large diffs are not rendered by default.

198 changes: 176 additions & 22 deletions application/asset/js/resource-form.js
Original file line number Diff line number Diff line change
@@ -107,27 +107,7 @@
// Handle "Set annotations" click.
vaSetButton.on('click', function(e) {
e.preventDefault();
const values = {};
vaContainer.find('.value-annotation').each(function() {
const thisValueAnnotation = $(this);
if (thisValueAnnotation.data('removed')) {
// This annotation was flagged for removal.
return;
}
const value = {};
// Map the the data-value-key attributes to the values object.
thisValueAnnotation.find(':input').each(function() {
const thisInput = $(this);
const valueKey = thisInput.data('valueKey');
if (!valueKey) return;
value[valueKey] = thisInput.val();
});
const propertyTerm = thisValueAnnotation.find('.property_term').val();
if (!values.hasOwnProperty(propertyTerm)) {
values[propertyTerm] = [];
}
values[propertyTerm].push(value);
});
const values = collectValueAnnotationValues(vaContainer);
annotatingValue.data('valueAnnotations', values);
Omeka.closeSidebar(vaSidebar);
});
@@ -167,7 +147,17 @@
e.preventDefault();
const thisVisibilityIcon = $(this);
const isPublicInput = thisVisibilityIcon.closest('.value').find('input.is_public');
isPublicInput.val(thisVisibilityIcon.hasClass('o-icon-public') ? 1 : 0);
if (thisVisibilityIcon.hasClass('o-icon-public')) {
thisVisibilityIcon
.attr('title', Omeka.jsTranslate('Make private'))
.attr('aria-label', Omeka.jsTranslate('Make private'));
isPublicInput.val('1');
} else {
thisVisibilityIcon
.attr('title', Omeka.jsTranslate('Make public'))
.attr('aria-label', Omeka.jsTranslate('Make public'));
isPublicInput.val('0');
}
});

// Select property
@@ -397,6 +387,145 @@
$('#values-json').val(JSON.stringify(collectValues()));
});

// Handle tabbed navigation in sidebar.
$(document).on('click', '.sidebar-section-nav button', function(e) {
const thisButton = $(this);
const sidebarSectionNav = thisButton.closest('.sidebar-section-nav');
// Set "active" status on nav list items.
sidebarSectionNav.find('li').removeClass('active');
thisButton.closest('li').addClass('active');
// Set "active" status on sections.
sidebarSectionNav.find('button').each(function() {
$(`#${$(this).data('id')}`).removeClass('active');
});
$(`#${thisButton.data('id')}`).addClass('active');
});

/** ITEM STUB FORM */

// Handle building the initial item stub form.
$('#select-resource').on('o:sidebar-content-loaded', function(e) {
const itemStubForm = $('#item-stub-form');
if (!itemStubForm.length) {
return; // Build the form only when needed.
}
const propertyValues = $('#item-stub-property-values');
const properties = {
'dcterms:title': itemStubForm.data('titleProperty'),
'dcterms:description': itemStubForm.data('descriptionProperty')
};
$.each(properties, function(term, property) {
const valueAnnotation = $($.parseHTML(vaTemplates['literal']));
valueAnnotation.find('.value-annotation-heading').text(property['o:label']);
valueAnnotation.find('input.property_id').val(property['o:id']);
valueAnnotation.find('input.property_term').val(term);
valueAnnotation.find('input.is_public').val('1');
propertyValues.append(valueAnnotation);
});
});
// Handle applying chosen to item stub form select elements.
$(document).on('click', '[data-id="sidebar-section-item-stub"]', function(e) {
const itemStubForm = $('#item-stub-form');
itemStubForm.find('.chosen-select').chosen({allow_single_deselect: true});
});
// Handle resource template change.
$(document).on('change', '#item-stub-resource-template', function(e) {
const itemStubForm = $('#item-stub-form');
const resourceTemplate = $('#item-stub-resource-template');
const resourceClass = $('#item-stub-resource-class');
const propertyValues = $('#item-stub-property-values');
const resourceTemplateUrl = itemStubForm.data('resourceTemplateUrl') + '/' + resourceTemplate.val();
$.get(resourceTemplateUrl, function(rtData) {
const templateResourceClass = rtData['o:resource_class'];
// Set the template-defined class.
if (templateResourceClass) {
resourceClass.val(templateResourceClass['o:id']);
resourceClass.trigger('chosen:updated');
}
// Get, hydrate, and append the property value markup.
propertyValues.empty();
$.each(rtData['o:resource_template_property'], function(index, rtProperty) {
let dataTypeName = rtProperty['o:data_type'][0];
// Set non-supported and "resource" data types to "literal".
if (dataTypeName === undefined
|| !(dataTypeName in vaTemplates)
|| ['resource', 'resource:item', 'resource:itemset', 'resource:media'].includes(dataTypeName)
) {
dataTypeName = 'literal';
}
// Hydrate the property values. Get property data (term and
// label) from an existing property select. We account for an
// alternate label set by the resource template.
const propertyId = rtProperty['o:property']['o:id'];
const property = $('#value-annotation-property-select').find(`option[data-property-id="${propertyId}"]`);
const propertyTerm = property.data('term');
const propertyLabel = rtProperty['o:alternate_label'] ?? property.text();
const valueAnnotation = $($.parseHTML(vaTemplates[dataTypeName]));
valueAnnotation.find('.value-annotation-heading').text(propertyLabel);
valueAnnotation.find('input.property_id').val(propertyId);
valueAnnotation.find('input.property_term').val(propertyTerm);
if (rtProperty['o:is_private']) {
valueAnnotation.find('input.is_public').val('0');
valueAnnotation.find('.value-annotation-visibility').removeClass('o-icon-public').addClass('o-icon-private');
} else {
valueAnnotation.find('input.is_public').val('1');
}
if (rtProperty['o:is_required']) {
valueAnnotation.addClass('required');
}
$(document).trigger('o:prepare-value-annotation', [dataTypeName, valueAnnotation]);
propertyValues.append(valueAnnotation);
});
});
});
// Handle item stub form submission.
$(document).on('click', '#item-stub-submit', function(e) {
e.preventDefault();
const itemStubForm = $('#item-stub-form');
const resourceTemplate = $('#item-stub-resource-template');
const resourceClass = $('#item-stub-resource-class');
// Build the item data.
const itemData = {};
itemData['csrf'] = itemStubForm.find('input[name="csrf"]').val();
if (resourceTemplate.val()) {
itemData['o:resource_template'] = {'o:id': resourceTemplate.val()};
}
if (resourceClass.val()) {
itemData['o:resource_class'] = {'o:id': resourceClass.val()};
}
// Validate the values according to the required flag.
const propertyValues = $('#item-stub-property-values');
let hasError = false;
propertyValues.children('.value-annotation').each(function(key, value) {
const valueAnnotation = $(this);
const valueInput = valueAnnotation.find('[data-value-key="@value"]');
if (valueAnnotation.hasClass('required') && !valueInput.val()) {
valueInput[0].setCustomValidity(Omeka.jsTranslate('Required field must be completed'));
valueInput[0].reportValidity();
hasError = true;
return false;
}
});
if (hasError) return;
// Collect values, create the item, and populate the field.
const values = collectValueAnnotationValues(propertyValues);
$.post(itemStubForm.data('submitUrl'), {...itemData, ...values})
.done(function(data) {
const selectedResource = $('.selecting-resource').find('.selected-resource');
selectedResource.prev('span.default').hide();
const a = $('<a>', {href: data['admin_url']}).text(data['display_title']);
selectedResource.find('.o-title').removeClass().addClass('o-title items').html(a);
selectedResource.find('.value').val(data['o:id']);
Omeka.closeSidebar($('#select-resource'));
})
.fail(function(data) {
alert(Omeka.jsTranslate('Something went wrong'));
Omeka.closeSidebar($('#select-resource'));
});
});

/** END ITEM STUB FORM */

initPage();
});

@@ -434,6 +563,31 @@
return values;
};

var collectValueAnnotationValues = function (vaContainer) {
const values = {};
vaContainer.find('.value-annotation').each(function() {
const thisValueAnnotation = $(this);
if (thisValueAnnotation.data('removed')) {
// This annotation was flagged for removal.
return;
}
const value = {};
// Map the the data-value-key attributes to the values object.
thisValueAnnotation.find(':input').each(function() {
const thisInput = $(this);
const valueKey = thisInput.data('valueKey');
if (!valueKey) return;
value[valueKey] = thisInput.val();
});
const propertyTerm = thisValueAnnotation.find('.property_term').val();
if (!values.hasOwnProperty(propertyTerm)) {
values[propertyTerm] = [];
}
values[propertyTerm].push(value);
});
return values;
};

/**
* Make a new value.
*/
76 changes: 75 additions & 1 deletion application/asset/sass/_screen.scss
Original file line number Diff line number Diff line change
@@ -2039,7 +2039,34 @@ fieldset.section > legend {
padding: 0 $spacing-small;
}

.sidebar .value-annotation-heading {
display: inline-block;
margin-right: $spacing-small;
}

.sidebar .value-annotation {
display: flex;
flex-wrap: wrap;
}

.sidebar .value-annotation .required-tag {
display: none;
}

.sidebar .value-annotation.required .required-tag {
display: inline-block;
padding: .5 * $spacing-small;
font-size: .75 * $base-font-size;
background-color: #FDEFEF;
color: $red;
border-radius: 2px;
margin: (.5 * $spacing-small) 0 (.5 * $spacing-small);
line-height: 1;
align-self: center;
}

.sidebar .value-annotation .value {
width: 100%;
padding-bottom: 0;
}

@@ -3189,6 +3216,54 @@ fieldset.section > legend {
}
}

.sidebar-section-nav {
margin-bottom: $spacing-medium;
position: relative;

&:before {
content: "";
position: absolute;
top: -$spacing-large;
bottom: 0;
right: -$spacing-large;
left: -$spacing-large;
background-color: #fff;
z-index: -1;
}
}

.sidebar-section-nav ul {
margin: 0;
padding: 0;
}

.sidebar-section-nav li {
display: inline-block;
margin-right: $spacing-small;
}

.sidebar-section-nav button[type="button"] {
background-color: rgba(0,0,0,.08);
color: $copy-gray;
border-radius: $spacing-large;
box-shadow: none;
font-size: .875 * $base-font-size;
padding: $spacing-small $spacing-medium;
min-height: 0;
line-height: 1;
}
.sidebar-section-nav .active button[type="button"] {
font-weight: bold;
}

.sidebar-section {
display: none;

&.active {
display: block;
}
}

.confirm-main {
position: absolute;
top: 0;
@@ -3199,7 +3274,6 @@ fieldset.section > legend {
padding: $spacing-medium $spacing-medium 0;
}


.confirm-panel {
position: absolute;
bottom: 0;
2 changes: 2 additions & 0 deletions application/config/module.config.php
Original file line number Diff line number Diff line change
@@ -430,6 +430,7 @@
'ckEditor' => View\Helper\CkEditor::class,
'sitePagePagination' => View\Helper\SitePagePagination::class,
'sectionNav' => View\Helper\SectionNav::class,
'sidebarSectionNav' => View\Helper\SidebarSectionNav::class,
'uploadLimit' => View\Helper\UploadLimit::class,
'formRecaptcha' => Form\View\Helper\FormRecaptcha::class,
'formCkeditor' => Form\View\Helper\FormCkeditor::class,
@@ -520,6 +521,7 @@
],
'factories' => [
'Omeka\Form\ResourceForm' => Service\Form\ResourceFormFactory::class,
'Omeka\Form\ItemStubForm' => Service\Form\ItemStubFormFactory::class,
'Omeka\Form\VocabularyForm' => Service\Form\VocabularyFormFactory::class,
'Omeka\Form\ResourceBatchUpdateForm' => Service\Form\ResourceBatchUpdateFormFactory::class,
'Omeka\Form\UserForm' => Service\Form\UserFormFactory::class,
26 changes: 26 additions & 0 deletions application/src/Controller/Admin/ItemController.php
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@
namespace Omeka\Controller\Admin;

use Omeka\Form\ConfirmForm;
use Omeka\Form\ItemStubForm;
use Omeka\Form\ResourceForm;
use Omeka\Form\ResourceBatchUpdateForm;
use Omeka\Media\Ingester\Manager;
@@ -103,6 +104,7 @@ public function sidebarSelectAction()
$view->setVariable('itemSetId', $this->params()->fromQuery('item_set_id'));
$view->setVariable('id', $this->params()->fromQuery('id'));
$view->setVariable('showDetails', true);
$view->setVariable('itemStubForm', $this->getForm(ItemStubForm::class));
$view->setTerminal(true);
return $view;
}
@@ -236,6 +238,30 @@ public function addAction()
return $view;
}

public function addItemStubAction()
{
$request = $this->getRequest();
$response = $this->getResponse();
if (!$request->isPost()) {
$response->setStatusCode(500);
return $response;
}
$itemData = $this->params()->fromPost();
$form = $this->getForm(ItemStubForm::class);
$form->setData($itemData);
if (!$form->isValid()) {
$response->setStatusCode(500);
return $response;
}
$item = $this->api(null, true)->create('items', $itemData)->getContent();
$itemJson = json_decode(json_encode($item), true);
$itemJson['admin_url'] = $this->url()->fromRoute('admin/id', ['action' => 'show', 'id' => $item->id()], true);
$itemJson['display_title'] = $item->displayTitle();
$response->getHeaders()->addHeaders(['Content-Type' => 'application/ld+json']);
$response->setContent(json_encode($itemJson));
return $response;
}

public function editAction()
{
$item = $this->api()->read('items', $this->params('id'))->getContent();
Loading
Loading