Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CRM/Core/Resources/Common.php
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ protected static function coreResourceList($region) {
"js/crm.datepicker.js",
"js/crm.ajax.js",
"js/wysiwyg/crm.wysiwyg.js",
"js/crm.elements.js",
];

// Dynamic localization script
Expand Down
5 changes: 5 additions & 0 deletions CRM/Core/xml/Menu/Misc.xml
Original file line number Diff line number Diff line change
Expand Up @@ -284,4 +284,9 @@
<page_callback>CRM_Contact_Form_Task_Delete</page_callback>
<access_arguments>access CiviCRM</access_arguments>
</item>
<item>
<path>civicrm/elements</path>
<page_callback>Civi\Core\Elements::get</page_callback>
<access_arguments>*always allow*</access_arguments>
</item>
</menu>
4 changes: 2 additions & 2 deletions CRM/Custom/Form/Field.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@ public function preProcess() {
CRM_Core_Error::statusBounce("You cannot add or edit fields in a reserved custom field-set.");
}

// Add crm-options-repeat web component. FIXME: need an autoloader for web components.
\Civi::resources()->addScriptFile('civicrm', 'js/CrmOptionsRepeat.js');
// Add crm-options-repeat web component. FIXME: autoload
\Civi::resources()->addScript('CRM.loadElement("civi-options-repeat")', ['weight' => -100]);

if ($this->_gid) {
$url = CRM_Utils_System::url('civicrm/admin/custom/group/field',
Expand Down
56 changes: 56 additions & 0 deletions Civi/Core/Elements.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

namespace Civi\Core;

use GuzzleHttp\Psr7\Response;
use GuzzleHttp\Psr7\ServerRequest;

/**
* @service civi.elements
*/
class Elements {

public static function get(ServerRequest $request) {
$query = $request->getQueryParams();
$element = explode('/', $query['q'])[2];
$locale = $query['locale'] ?? NULL;

// TODO cache control?
$headers = [];

// load css/js alongside the template?
// inserting <script> tags into the DOM doesn't work so well
// $css = \file_get_contents(\Civi::paths()->getPath("[civicrm.root]/elements/{$element}.css"));
// $js = \file_get_contents(\Civi::paths()->getPath("[civicrm.root]/elements/{$element}.js"));
// $html = self::renderTemplate($element, $locale);

// $body = <<<HTML
// <template id="#{$element}">$html</template>
// <script>{$js}</script>
// <style>{$css}</style>
// HTML;

$body = self::renderTemplate($element, $locale);

return new Response(200, $headers, $body);

}

protected static function renderTemplate(string $element, ?string $locale = NULL): string {
if ($locale) {
try {
\CRM_Core_I18n::singleton()->setLocale($locale);
}
catch (\Throwable $e) {
// just use default
}
}
try {
return \CRM_Core_Smarty::singleton()->fetch("elements/{$element}.tpl");
}
catch (\Throwable $e) {
return '';
}
}

}
64 changes: 6 additions & 58 deletions js/CrmOptionsRepeat.js → elements/civi-options-repeat.js
Original file line number Diff line number Diff line change
@@ -1,62 +1,6 @@
class CrmOptionsRepeat extends HTMLElement {

static template = `
<table>
<thead>
<tr>
<th></th>
<th>{ts}Default{/ts}</th>
<th>
{ts}Label{/ts}
<a class="crm-hover-button crm-options-repeat-sort" title="{ts escape='html'}Sort by label{/ts}">
<i class="crm-i fa-sort-alpha-down" aria-hidden="true" role="img"></i>
<span class="sr-only">{ts}Sort by label{/ts}</span>
</a>
</th>

<th>
{ts}Value{/ts}
<a class="crm-hover-button crm-options-repeat-sort" title="{ts escape='html'}Sort by value{/ts}">
<i class="crm-i fa-sort-numeric-down" aria-hidden="true" role="img"></i>
<span class="sr-only">{ts}Sort by value{/ts}</span>
</a>
</th>
<th>{ts}Enabled{/ts}</th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>
<a class="crm-draggable">
<i class="crm-i fa-arrows-up-down" role="img" aria-hidden="true"></i>
<span class="sr-only">{ts}Change order{/ts}</span>
</a>
</td>
<td><input type="radio" name="is_default" class="crm-form-radio"></td>
<td><input type="text" name="label" class="crm-form-text required" required></td>
<td><input type="text" name="value" class="crm-form-text required" required value="1"></td>
<td><input type="checkbox" name="is_active" class="crm-form-checkbox" checked></td>
<td>
<a class="crm-hover-button crm-options-repeat-remove" title="{ts escape='html'}Delete{/ts}">
<i class="crm-i fa-trash" role="img" aria-hidden="true"></i>
<span class="sr-only">{ts}Delete{/ts}</span>
</a>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="6">
<button type="button" class="crm-options-repeat-add">
<i class="crm-i fa-plus" role="img" aria-hidden="true"></i>
{ts}Add Option{/ts}
</button>
</td>
</tr>
</tfoot>
</table>
`;
static tag = 'civi-options-repeat';

constructor() {
super();
Expand All @@ -73,12 +17,16 @@ class CrmOptionsRepeat extends HTMLElement {
}
}

renderTemplate() {
const template = document.querySelector('template#' + this.constructor.tag);
this.append(document.importNode(template.content, true));
}

init() {
// TODO: use this element's value attribute
this.hiddenInput = this.previousElementSibling;

this.innerHTML = CrmOptionsRepeat.template;
this.renderTemplate();
this.table = this.querySelector('table tbody');


Expand Down
18 changes: 18 additions & 0 deletions js/crm.elements.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
CRM = CRM || {};

// TODO: merge into elements mixin autoloader
CRM.loadElement = (tagName) => {

// load template first, so the JS doesn't have to wait for it
// TODO: skip if no template
fetch(CRM.url(`civicrm/elements/${tagName}`))
.then((response) => response.text())
.then((content) => {
template = document.createElement('template');
template.id = tagName;
template.innerHTML= content;
document.body.append(template);
})
// now load the custom element definition
.then(() => import(`${CRM.resourceUrls.civicrm}/elements/${tagName}.js`));
}
55 changes: 55 additions & 0 deletions templates/elements/civi-options-repeat.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<table>
<thead>
<tr>
<th></th>
<th>{ts}Default{/ts}</th>
<th>
{ts}Label{/ts}
<a class="crm-hover-button crm-options-repeat-sort" title="{ts escape='html'}Sort by label{/ts}">
<i class="crm-i fa-sort-alpha-down" aria-hidden="true" role="img"></i>
<span class="sr-only">{ts}Sort by label{/ts}</span>
</a>
</th>

<th>
{ts}Value{/ts}
<a class="crm-hover-button crm-options-repeat-sort" title="{ts escape='html'}Sort by value{/ts}">
<i class="crm-i fa-sort-numeric-down" aria-hidden="true" role="img"></i>
<span class="sr-only">{ts}Sort by value{/ts}</span>
</a>
</th>
<th>{ts}Enabled{/ts}</th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>
<a class="crm-draggable">
<i class="crm-i fa-arrows-up-down" role="img" aria-hidden="true"></i>
<span class="sr-only">{ts}Change order{/ts}</span>
</a>
</td>
<td><input type="radio" name="is_default" class="crm-form-radio"></td>
<td><input type="text" name="label" class="crm-form-text required" required></td>
<td><input type="text" name="value" class="crm-form-text required" required value="1"></td>
<td><input type="checkbox" name="is_active" class="crm-form-checkbox" checked></td>
<td>
<a class="crm-hover-button crm-options-repeat-remove" title="{ts escape='html'}Delete{/ts}">
<i class="crm-i fa-trash" role="img" aria-hidden="true"></i>
<span class="sr-only">{ts}Delete{/ts}</span>
</a>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="6">
<button type="button" class="crm-options-repeat-add">
<i class="crm-i fa-plus" role="img" aria-hidden="true"></i>
{ts}Add Option{/ts}
</button>
</td>
</tr>
</tfoot>
</table>