Jump to content
a-ok

Adding a custom Page Reference field to ProcessPageEditLink

Recommended Posts

Is it possible to hook into ProcessPageEditLink in order to add a custom Page Reference field (for a Repeater on the same page!) for the user to link? This is specifically for annotations or footnotes so my thinking is the link would return a `data-annotation-id` of the repeater ID and row or similar.

What are your thoughts?

Share this post


Link to post
Share on other sites

I had written something like the following before, adding to `wire/modules/Process/ProcessPageEditLink/ProcessPageEditLink.module`, but I'd prefer to use a hook and avoid the custom JS if possible?

public function ___executeAnnotations() {
	if(!$this->page->id) throw new WireException("A page id must be specified"); 	
	$annotations = $this->getAnnotations();	
	return wireEncodeJSON($annotations);
}

protected function getAnnotations() {
	$annotations = array();
	if($this->page->id) foreach($this->page->speech_archive_detail_annotations as $annotation) {
		$annotation_clean = '#annotation--' . $this->wire('sanitizer')->pageName($annotation->id);
		$annotations[$annotation_clean] = $annotation->speech_archive_detail_annotations_title; 
	}
	asort($annotations); 
	return $annotations;
}

protected function getAnnotationsField() {
	$field = $this->modules->get("InputfieldSelect"); 
	$field->label = $this->_("Select Annotation");
	$field->attr('id+name', 'link_page_annotation'); 
	$annotations = $this->getAnnotations();
	$field->addOption('');
	$field->addOptions($annotations); 
	$field->collapsed = Inputfield::collapsedYes; 
	if($this->page->id) $field->notes = $this->_('Showing annotations on page:') .  ' **' . $this->page->url . '**';
	$field->description = 
		$this->_('Select the annotation from this page that you want to link to.');
	$field->icon = 'comment-o';
	return $field;

}

 

Share this post


Link to post
Share on other sites

I think I've kinda got it, based on this post below, however I need to get the repeater elements on the page I'm editing and I can't seem to get the page?

This is very rough and not tidied up at all, yet.

$wire->addHookAfter('ProcessPageEditLink::execute', function(HookEvent $event) {

	$page = $event->object;

	$form = $this->modules->get("InputfieldForm");
    $form->attr('id', 'ProcessPageEditLinkForm');

    $field = wire('modules')->get('InputfieldSelect');
    $field->label = $this->_("Select File");
    $field->attr('id+name', 'link_page_file');
    $field->icon = 'file-pdf-o';

	var_dump($field->hasPage) ;

	$annotations = array();
	foreach($page->textAnnotations as $annotation) {
		$annotation_clean = '#annotation--' . wire('sanitizer')->pageName($annotation->id);
		$annotations[$annotation_clean] = $annotation->text;
	}
	asort($annotations);
	var_dump($page);

	$options = $annotations;

    $field->addOption(''); // Allow unselect
    $field->addOptions($options);
    $form->add($field);

    // We need something like this to get the nice JS generated preview
    $markup = '<span id="link_page_url_input"></span>';
    $form->set('appendMarkup', $markup);
    $event->return =  $form->render() . "<p class='detail ui-priority-secondary'><code id='link_markup'></code></p>";

});

 

Share this post


Link to post
Share on other sites

 

Think I've got it.
 

Only thing would be the ability to add a data attribute to the link AND append to the other inputfields rather than replace them?

$wire->addHookAfter('ProcessPageEditLink::execute', function(HookEvent $event) {

	$page = wire('pages')->get(wire('input')->get('id'));

	$form = $this->modules->get('InputfieldForm');
    $form->attr('id', 'ProcessPageEditLinkForm');

    $field = wire('modules')->get('InputfieldSelect');
    $field->label = $this->_('Select annotation');
    $field->attr('id+name', 'link_page_file');
    $field->icon = 'link';

	$annotations = array();
	foreach($page->textAnnotations as $annotation) {
		$annotationClean = '#annotation--' . wire('sanitizer')->pageName($annotation->id);
		$annotations[$annotationClean] = $annotation->text;
	}
	asort($annotations);

    $field->addOption(''); // Allow unselect
    $field->addOptions($annotations);
    $form->add($field);

    // JS generated preview below selected link in popup
    $markup = '<span id="link_page_url_input"></span>';
    $form->set('appendMarkup', $markup);
    $event->return =  $form->render() . "<p class='detail ui-priority-secondary'><code id='link_markup'></code></p>";

});

 

Share this post


Link to post
Share on other sites

I also think I need to add the `$field` to the `$form` that already exists, rather than creating a new one.

Any thoughts?

Share this post


Link to post
Share on other sites

Here's a quick and dirty demo how to add your own anchors to the builtin anchor feature of ProcessPageEditLink. No form manipulation shenanigans and no JS either. 😉

wire()->addHookBefore("ProcessPageEditLink::execute", function(HookEvent $event) {
	$page = wire('pages')->get((int)wire('input')->get('id'));
	// Limit to a certain template type:
	if($page->template->name !== 'home') return;
	
	$anchors = is_array(wire('input')->get->anchors) ? wire('input')->get->anchors : [];

	// This is the spot to add your own annotation anchors:
	$myAnchors = [
		"some_anchor",
		"other_anchor",
		"third_anchor"
	];
	
	$anchors = array_merge($anchors, $myAnchors);
	
	wire('input')->get->anchors = $anchors;
});

 

  • Like 1

Share this post


Link to post
Share on other sites
21 hours ago, BitPoet said:

Here's a quick and dirty demo how to add your own anchors to the builtin anchor feature of ProcessPageEditLink. No form manipulation shenanigans and no JS either. 😉

Thanks for the help!

What’s the built in anchor feature? I’ve added your code to test with but the options don’t appear when adding a link? Do I have to enable something else?

Share this post


Link to post
Share on other sites
36 minutes ago, a-ok said:

What’s the built in anchor feature? I’ve added your code to test with but the options don’t appear when adding a link? Do I have to enable something else?

The builtin feature means that PW parses the HTML for named anchors, and if found, adds a "Select Anchor" select box at the top right. Did you remove the if($page->template...) line in my example? This was just meant for demonstration of limiting the functionality to a certain template.

Share this post


Link to post
Share on other sites
3 hours ago, BitPoet said:

The builtin feature means that PW parses the HTML for named anchors, and if found, adds a "Select Anchor" select box at the top right. Did you remove the if($page->template...) line in my example? This was just meant for demonstration of limiting the functionality to a certain template.

Thanks again but I'm slightly confused. There's no 'Select Anchor' select box? What HTML does it parse? The template check is removed, yep.

 

Screenshot 2020-03-08 at 16.31.36.jpg

Share this post


Link to post
Share on other sites

It looks at the HTML in the CKEditor field you're triggering PWLink from, but that shouldn't matter anyway. This is how it should look:

anchor.thumb.png.474ea2d5085b94470bf1de7ac36ce575.png

The anchor select only appears if there are anchors in the array. What does bd($anchors) say before it's assigned to the input var?

Share this post


Link to post
Share on other sites

The annotations are generated from the repeater field `$page->textAnnotations` and then the idea is that the user would be able to select a repeater row (nothing more than a sanitised anchor of the title/ID field for each repeater row. So with my original code I was attempting to create a new `InputfieldSelect` field within `ProcessPageEditLink` with options for each `$page->textAnnotations` repeater row and it would output the link href as, for example, `#annotation-example-row-title` or `#annotation-1827`.

I think we're on the same page BUT `$anchors` returns `array(3) { [0]=> string(11) "some_anchor" [1]=> string(12) "other_anchor" [2]=> string(12) "third_anchor" }`

Share this post


Link to post
Share on other sites

Looking at `ProcessPageEditLink.module` I feel like the code you've presented is correct; we're pushing these options and setting them to `wire('input')->get->anchors` so it should show... but it's not. Hmm.

UPDATE

I was using `addHookAfter` instead of `addHookBefore` 🙄

Share this post


Link to post
Share on other sites

Thanks so much for the help, @BitPoet. One final thing. Is it possible to keep the value of the select option as it is but change the label? My annotations list read as `#annotation-1020`, for example, which is super useful for some JS stuff I'll be doing with it, but ideally, for the user, it would be good to have these options a bit more descriptive. Any idea?

I know you can add a label within the `InputfieldSelect::addOption()` method but obviously as I'm updating the anchors before it runs this I can't amend it.

https://github.com/processwire/processwire/blob/51629cdd5f381d3881133baf83e1bd2d9306f867/wire/modules/Process/ProcessPageEditLink/ProcessPageEditLink.module#L203 I guess having the option on this line to pass in a key/value (key would be the label) into the anchors array (otherwise label = null).

Share this post


Link to post
Share on other sites
21 hours ago, a-ok said:

One final thing. Is it possible to keep the value of the select option as it is but change the label?

Not with the built-in anchor select. If you don't mind a bit of regex ugliness:

wire()->addHookAfter("ProcessPageEditLink::execute", function(HookEvent $event) {
	$page = wire('pages')->get((int)wire('input')->get('id'));
	if(wire('input')->get('href')) {
		$currentValue = wire('sanitizer')->url(wire('input')->get('href'), array(
			'stripQuotes' => false,
			'allowIDN' => true,
		));
	} else {
		$currentValue = '';
	}
	
	$inp = wire('modules')->get('InputfieldSelect');
	$inp->attr('id+name', 'select_anchor');
	$inp->label = wire()->_('Select Repeater Anchor');
	$inp->icon = 'anchor';
	$inp->collapsed = $currentValue ? Inputfield::collapsedNo : Inputfield::collapsedYes;
	// Instead of addOptions here, fill your select however you want
	$inp->addOptions([
		"" => "",
		"#some_anchor" => "some_anchor_text",
		"#other_anchor" => "other_anchor_text",
		"#third_anchor" => "third_anchor_text"
	]);
	if($currentValue) $inp->attr('value', $currentValue);
	
	$wrap = new InputfieldWrapper();
	$wrap->append($inp);
	
	// It's a bit ugly, but to get our InputfieldSelect to render correctly, we need
	// it rendered by an InputfieldWrapper. We do that, then strip out the wrapping
	// parts afterwards (i.e. everything before the first <li> and after the last </li>).
	$html = $wrap->render();
	$html = preg_replace('~^.*?(?=<li\b)~is', '', $html);
	$html = preg_replace('~</li>.*?$~is', '', $html);
	
	$js = '<script>
		$("#select_anchor").bind("change", function(evt) { $("#link_page_url_input").val($(evt.target).val()).change(); });
	</script>
	';
	
	$html .= $js . "</li>";
	
	$event->return = preg_replace("~(?=<li[^>]+id='wrap_link_page_file')~", $html, $event->return);
});

 

  • Like 1

Share this post


Link to post
Share on other sites

Building on @BitPoet's code, here's an alternative way you could add labels to the select options:

$wire->addHookBefore('ProcessPageEditLink::execute', function(HookEvent $event) {

	$input = $event->wire('input');
	$page = $event->wire('pages')->get($input->get->id);
	// Do some check on $page to return early when not applicable

	$anchors = $input->get->anchors ?: [];
	$my_anchors = [
		'some_anchor' => 'Some anchor',
		'other_anchor' => 'Other anchor',
		'third_anchor' => 'Third anchor',
	];
	$anchors = array_merge($anchors, array_keys($my_anchors));
	$input->get->anchors = $anchors;

	$event->wire()->addHookBefore('InputfieldSelect::render', function(HookEvent $event) use ($my_anchors) {
		$inputfield = $event->object;
		if($inputfield->name !== 'link_page_anchor') return;
		$options = $inputfield->options;
		$inputfield->options = [];
		foreach($options as $option) {
			$anchor_name = ltrim($option, '#');
			if(isset($my_anchors[$anchor_name])) {;
				$inputfield->addOption($option, $my_anchors[$anchor_name]);
			} else {
				$inputfield->addOption($option);
			}
		}
	});

});

 

  • Like 3

Share this post


Link to post
Share on other sites

Thanks very much for the help everyone. Hopefully this'll help others too so much appreciated.

This is what I ended up with (went with @Robin S's solution in the end). MUCH appreciated help @BitPoet 🙂

// Add annotations as anchor options within text
$wire->addHookBefore('ProcessPageEditLink::execute', function(HookEvent $event) {

	$page = wire('pages')->get(wire('input')->get('id'));
	if ($page->template->name !== 'projectsSingle') return;

	$input = $event->wire('input');
	$anchors = $input->get->anchors ? : [];

	$annotations = array();
	foreach ($page->textAnnotations as $key => $annotation) {
		$annotationClean = 'annotation-' . wire('sanitizer')->pageName($annotation->id);
		$annotations[$annotationClean] = $annotation->text;
	}
	$anchors = array_merge($anchors, array_keys($annotations));

	$input->get->anchors = $anchors;

	//
	$event->wire()->addHookBefore('InputfieldSelect::render', function(HookEvent $event) use ($annotations) {

		$inputfield = $event->object;
		if ($inputfield->name !== 'link_page_anchor') return;

		$options = $inputfield->options;
		$inputfield->options = [];

		foreach ($options as $option) {
			$anchorName = ltrim($option, '#');
			if (isset($annotations[$anchorName])) {;
				$inputfield->addOption($option, $annotations[$anchorName]);
			} else {
				$inputfield->addOption($option);
			}
		}
	});

});

 

  • Like 1

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...