Skip to content

Add ResourcePickerSelect form element - #2474

Open
jimsafley wants to merge 5 commits into
developfrom
feature/resource-picker-select
Open

Add ResourcePickerSelect form element#2474
jimsafley wants to merge 5 commits into
developfrom
feature/resource-picker-select

Conversation

@jimsafley

@jimsafley jimsafley commented Apr 17, 2026

Copy link
Copy Markdown
Member

Summary

This PR adds ResourcePickerSelect, a reusable form element for selecting one or more Omeka resources through a paginated sidebar widget. It also adds picker endpoints for resource classes and properties, and replaces the corresponding old <select>-based elements throughout the admin UI.


The Element

ResourcePickerSelect renders as a list of currently selected resources alongside a "Select" button. Clicking the button opens a sidebar that fetches and paginates resources from a configurable endpoint. The user can search, filter, and click resources to add them to the widget. Selections are stored as hidden inputs; a remove button and (in multi-select mode) a clear button allow deselection.

Resources already selected in the widget are visually marked in the sidebar, and clicking them again has no effect. When a form is rendered with existing selections, those resources are pre-populated from the API without opening the sidebar.


Differences from Existing Select Elements

The existing typed select elements — ItemSetSelect, SiteSelect, ResourceClassSelect, PropertySelect, UserSelect — and the generalized ResourceSelect base class all extend Laminas' Select element. They fetch all matching resources from the API at form-render time, build a list of <option> elements, and rely on Chosen.js for client-side search.

ResourcePickerSelect differs in several ways:

  • On-demand loading. Resources are not fetched until the user opens the picker, avoiding potentially large API calls on every page render.
  • Server-side pagination. The sidebar endpoint paginates results, so the element handles any number of resources correctly.
  • Richer search UI. The sidebar can include resource-type-specific filters alongside a text search field.
  • Multiple selections. The element natively supports selecting multiple resources, with each selection stored as a separate hidden input.
  • No subclassing. The element is fully configured through options; no per-type subclass is needed.

The existing elements remain in place and are appropriate where a compact inline dropdown is preferred and the total result set is small enough to load upfront.


Architecture: Generic Element, Specialized Endpoints

The implementation is split into two concerns.

The element (ResourcePickerSelect + FormResourcePickerSelect view helper + resource-picker-select-form.phtml partial + resource-picker-select-form.js) handles what is universal across all resource types: the widget UI, sidebar integration, selection state (hidden inputs), and add/remove/clear behavior.

Each endpoint (a controller action + a sidebar view template + a resource partial) handles what is specific to a resource type: which API resource to query, what search and filter fields to expose, how individual resources are displayed, and ACL enforcement.

This split means adding support for a new resource type requires only adding an endpoint — the element itself does not change. It also keeps ACL enforcement in the controller, where it belongs, rather than in the form layer. The endpoint is entirely independent: it can apply whatever query constraints, filter UI, or access rules are appropriate for that resource type without any coupling to the element.


Implementing a New Resource Type

To support a new resource type in the picker, add three things:

1. A controller action that:

  • Reads query params from the request, including resource_partial
  • Removes resource_partial (and value_type, if used) from the query before passing it to the API
  • Calls the API with pagination (via $this->paginator())
  • Passes the result set, search state, and $resourcePartial to the view
  • Calls $view->setTerminal(true)

2. A sidebar view template (conventionally resource-picker.phtml) that:

  • Wraps content in .resource-picker-sidebar-content
  • Includes a .resource-search div with data-search-url pointing back to the same route
  • Uses $this->queryToHiddenInputs() to preserve filter and pagination state across searches, excluding resource_partial, value_type, and any params the controller strips from the query
  • Provides a text search input (name="search") and a button with class resource-picker-search-button
  • Optionally includes collapsible filter fields specific to the resource type
  • Renders each resource as an anchor with class resource-picker-select-resource, data-resource-id, and data-resource-html attributes
  • Includes $this->pagination('common/sidebar-pagination.phtml')

3. A resource partial (conventionally resource-picker-resource.phtml) that renders a single resource for display both in the sidebar list and in the widget's selections list.

You will also need to grant the resource-picker action to any roles that should access the endpoint, in AclFactory.


Adding the Element to a Form

use Omeka\Form\Element\ResourcePickerSelect;

$form->add([
    'name' => 'my_field',
    'type' => ResourcePickerSelect::class,
    'options' => [
        'label'                           => 'Select resources',
        'api_resource'                    => 'items',        // required
        'resources_endpoint_route_params' => ['controller' => 'item'], // controller required
        'query'    => ['is_public' => true],                 // optional
        'multiple' => true,                                  // optional, default false
        'value_type' => 'id',                                // optional, default 'id'
    ],
]);

To pre-populate the element with existing selections, call setValue() with the stored value or values:

$element->setValue([1, 5, 12]); // IDs when value_type is 'id'
$element->setValue('dcterms:title'); // term when value_type is 'term'

Options

Option Required Description
api_resource Yes The API resource name (e.g. 'items', 'sites') used to fetch and pre-populate existing selections at render time.
resources_endpoint_route_params Yes* Route params for the endpoint. The controller key is required; action defaults to 'resource-picker'.
resources_endpoint_route No The Laminas route name for the controller action serving the sidebar. Defaults to 'admin/default'.
resource_partial No The view partial that renders a single resource. Defaults to omeka/admin/{controller}/resource-picker-resource, derived from the controller in resources_endpoint_route_params.
value_type No Which resource identifier is submitted with the form. Defaults to 'id'. Use 'term' for vocabulary members (properties, resource classes) when storing terms rather than numeric IDs.
query No Additional query params merged into the sidebar endpoint URL. Forwarded to the API on initial load and preserved across sidebar searches via hidden inputs.
multiple No If true, allows selecting multiple resources. Defaults to false (single-select; sidebar closes immediately after selection).

Previous resource selectors loaded all resources upfront and duplicated
widget logic per resource type. The new element fetches and paginates
resources on demand via a sidebar, with resource-type-specific endpoints
handling search, filtering, and display. Implements pickers for item sets
and sites; uses them in the user settings form.
@jimsafley
jimsafley requested a review from zerocrates April 17, 2026 14:33
jimsafley added 4 commits May 5, 2026 14:28
Add value_type option to control which resource identifier is submitted
(e.g. 'id', 'term'). Add smart defaults for endpoint route, action, and
resource partial derived from controller name. Switch pre-population
lookup to searchOne() for non-id value types.
Replace PropertySelect, ResourceClassSelect, ItemSetSelect, and SiteSelect
with ResourcePickerSelect across ResourceForm, ResourceTemplateForm,
ItemStubForm, SiteSettingsForm, SettingForm, ResourceBatchUpdateForm, and
UserForm. Update applyResourceTemplate() in resource-form.js for the new
class picker. Fix missing escaping on data-resource-id in site and item-set
picker templates.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant