recogito/recogito-client-core

Drop-down menu for the tag and custom widget into editor

Closed this issue · 5 comments

  • We need a drop-down menu instead of autoComplete for the tag widget
  • We need custom widgets like dropdown or input text to show in the editor dialog

How could I do these needs, is there any example?

Best Regards.

See documentation on building custom editor widgets here:
https://recogito.github.io/guides/editor-widgets/

and here:
https://recogito.github.io/guides/example-react-widget/

People have been building dropdown entry widgets this way. A slightly more elaborate example is here:
#51

(But, beware: that's a bit outdated. I think some of the API has changed since.)

P.S.: if you just want a dropdown, but otherwise keep the tag widget: if you hit the arrow-down key, it will display all options. Just in case this is already sufficient for your use case.

@rsimon, thanks for your help.
I worked the last link you mentioned and it works but has some bugs. For example, when an option is selected, annotations body elements increase every time, it is not being updated it is always appended like above. How could I fix it, can you help me?

index.jsx (modified)---------------------------------------------------

import React from 'react';
import ReactDOM from 'react-dom';
import CommentWidget from './comment/CommentWidget'
import TagWidget from './tag/TagWidget';
import SelectWidget from './select/SelectWidget';
import WrappedWidget from './WrappedWidget';

window.React = React;
window.ReactDOM = ReactDOM;

const BUILTIN_WIDGETS = {
  COMMENT: CommentWidget,
  TAG: TagWidget,
  SELECT: SelectWidget
};

export const DEFAULT_WIDGETS = [
  <CommentWidget />, <TagWidget />, <SelectWidget />
]
.....
...
..

SelectWidget.jsx (added) -------------------------------------

import React, { useState } from 'react';

const getDraftSelect = existingSelect =>
  existingSelect ? existingSelect : {
    type: 'TextualBody', value: '', purpose: 'dropdown', draft: true
  };

const SelectWidget = props => { 
// All comments
const all = props.annotation ?  props.annotation.bodies.filter(body => (body.purpose === props.purpose)) : [];

// Last draft select goes into the input field
const draftSelect = getDraftSelect(all.slice().reverse().find(b => b.draft)); 
	
const lastAnnotation = all && all.length ? all[all.length - 1] : null;

const parentAll = props.annotation && props.parent? props.annotation.bodies.filter(body => (body.purpose === props.parent)) : [];

const lastParentAnnotation = parentAll && parentAll.length?parentAll[parentAll.length - 1]:null;

        **_// this method has the bug I think_**
	const onChange = (event) => {
	
		props.onAppendBody({value:event.target.value, purpose:props.purpose, creator:props.creator});
	}

	const isSelected = (option) =>{
		if(lastAnnotation){
			return lastAnnotation.value === option?'selected':'';
		}
		if(props.defaultValue){
			return props.defaultValue === option?'selected':'';;
		}
		return '';
	}

	const getOptionTags = options =>{
		if(!props.parent){
			return options.map( option => {
				return <option value={option} selected={isSelected(option)}>{option}</option>
			})
		}else if(lastParentAnnotation){
			const subcategoryOptions = options[lastParentAnnotation.value];
			return subcategoryOptions.map( option => {
				return <option value={option} selected={isSelected(option)}>{option}</option>
			})
		}
		return null;
	}
	

	return(
		<div className="r6o-widget r6o-select">
			<select onChange={onChange}>
				<option value="">{props.label}</option>
				{getOptionTags(props.options)}
			</select>
		</div>
	)

}

export default SelectWidget;

index.html

...
anno = Annotorious.init({
          image: 'hallstatt',
          locale: 'auto',
          widgets: [
            { widget: 'COMMENT' },
            { widget: 'TAG', vocabulary: [ 'Animal', 'Building', 'Waterbody'] },
			{ widget: "SELECT", options: [ "Animal", "Building", "Waterbody"], purpose:"category" }
          ]
        });
...

image

Looks good!

Yes, .onAppendBody will just add additional bodies (which is, e.g., useful for comments). In your case, you'll want to use the .onUpsertBody callback instead: this will either append a new body, or replace an existing one.

In your case:

// Find the existing 'category' body first (if it exists)
const currentCategory = props.annotation ? 
    props.annotation.bodies.find(b => b.purpose === props.purpose) : null;

const onChange = (event) => {	
  // Will append if currentCategory == null, or replace otherwise
  props.onUpsertBody(currentCategory, { value:event.target.value, purpose: props.purpose });
}

You don't need to add the creator by the way. Creator info and timestamp are inserted automatically when the annotation is stored.

That's it! It works, I appreciate your help. Here is the last code that works:

import React, { useState } from 'react';


const SelectWidget = props => { 

const all = props.annotation ?  props.annotation.bodies.filter(body => (body.purpose === props.purpose)) : [];
	
const lastAnnotation = all && all.length ? all[all.length - 1] : null;

const parentAll = props.annotation && props.parent? props.annotation.bodies.filter(body => (body.purpose === props.parent)) : [];

const lastParentAnnotation = parentAll && parentAll.length?parentAll[parentAll.length - 1]:null;

const currentCategory = props.annotation ? props.annotation.bodies.find(b => b.purpose === props.purpose) : null;

	const onChange = (event) => {
		props.onUpsertBody(currentCategory, { value:event.target.value, purpose: props.purpose });
	}

	const isSelected = (option) =>{
		if(lastAnnotation){
			return lastAnnotation.value === option?'selected':'';
		}
		if(props.defaultValue){
			return props.defaultValue === option?'selected':'';;
		}
		return '';
	}

	const getOptionTags = options =>{
		if(!props.parent){
			return options.map( option => {
				return <option value={option} selected={isSelected(option)}>{option}</option>
			})
		}else if(lastParentAnnotation){
			const subcategoryOptions = options[lastParentAnnotation.value];
			return subcategoryOptions.map( option => {
				return <option value={option} selected={isSelected(option)}>{option}</option>
			})
		}
		return null;
	}
	

	return(
		<div className="r6o-widget r6o-select">
			<select onChange={onChange} style="width: 100%; padding:4px">
				<option value="">{props.label}</option>
				{getOptionTags(props.options)}
			</select>
		</div>
	)

}

export default SelectWidget;