diff --git a/css/shs.form.css b/css/shs.form.css index 3ec9ed1fb..39f4d0b74 100644 --- a/css/shs.form.css +++ b/css/shs.form.css @@ -16,3 +16,7 @@ .shs-addnew-container { margin-top: .5rem; } + +.shs-field-container .shs-select:empty { + display: none !important; +} \ No newline at end of file diff --git a/js/models/WidgetModel.js b/js/models/WidgetModel.js index d5c3895cb..32847c978 100644 --- a/js/models/WidgetModel.js +++ b/js/models/WidgetModel.js @@ -31,6 +31,11 @@ */ defaultValue: '_none', + /** + * The new item that was created. + */ + createValue: null, + /** * Position of widget in app. * diff --git a/js/views/AppView.js b/js/views/AppView.js index 8693214dd..dfed10a5b 100644 --- a/js/views/AppView.js +++ b/js/views/AppView.js @@ -65,9 +65,6 @@ parents: parents })); }); -// $.each(app.getConfig('parents'), function (index, item) { -// // Add WidgetModel for each parent. -// }); app.collection.trigger('initialize:shs'); @@ -127,6 +124,19 @@ // Use value of parent widget (which is the id of the model ;)). value = widgetModel.get('id'); } + else if (widgetModel.get('createValue')) { + // Add the created item to the original select item. + var options = $("option", app.$el).map(function () { + return $(this).val(); + }).get(); + if ($.inArray(value, options) === -1) { + var item = widgetModel.get('createValue'); + app.$el.append($("").val(item.tid).text(item.name)); + // We can now reset our widget model to the new tid. + widgetModel.set('createValue', null); + widgetModel.set('defaultValue', item.tid); + } + } } // Set the updated value. app.$el.val(value).trigger({ diff --git a/js/views/ContainerView.js b/js/views/ContainerView.js index 2ec82181c..8e6a25c38 100644 --- a/js/views/ContainerView.js +++ b/js/views/ContainerView.js @@ -42,7 +42,7 @@ } this.collection = new Drupal.shs.WidgetCollection({ - url: Drupal.url(this.app.getConfig('baseUrl') + '/' + this.app.getConfig('fieldName') + '/' + this.app.getConfig('bundle')) + url: Drupal.url.toAbsolute(this.app.getConfig('baseUrl') + '/' + this.app.getConfig('fieldName') + '/' + this.app.getConfig('bundle')) }); this.collection.reset(); @@ -119,13 +119,21 @@ container.collection.remove(models); var anyValue = container.app.getSetting('anyValue'); - if (value !== anyValue) { + var createValue = container.app.getSetting('createValue'); + + if (value === createValue && widgetModel.get('createValue')) { + var item = widgetModel.get('createValue'); + value = item.tid; + } + + if ((value !== anyValue) && (value !== createValue)) { // Add new model with current selection. container.collection.add(new Drupal.shs.classes[container.app.getConfig('fieldName')].models.widget({ id: value, level: widgetModel.get('level') + 1 })); } + if (value === anyValue && widgetModel.get('level') > 0) { // Use value of parent widget (which is the id of the model ;)). value = widgetModel.get('id'); diff --git a/js/views/WidgetView.js b/js/views/WidgetView.js index f80528ec9..a88f99364 100644 --- a/js/views/WidgetView.js +++ b/js/views/WidgetView.js @@ -175,6 +175,76 @@ $container.append(widget.$el.fadeIn(widget.container.app.getConfig('display.animationSpeed'))); } + var createValue2 = widget.model.get('createValue'); + if (widget.$el.val() === createValue2 || widget.$el.val() === "_none") { + var create_item_input_id = 'shs-widget-create-new-item-' + widget.container.app.getConfig('fieldName') + '-delta-' + widget.container.model.get('delta'); + $container.append($('') + .on('keyup', function (e) { + if (e.keyCode === 13) { + // Do something + $('#' + create_item_input_id + '_button').trigger('click'); + e.preventDefault(); + return false; + } + }) + .attr('type', 'text') + .attr('id', create_item_input_id) + .attr('name', 'create_new_item') + .attr('class', 'text-full form-text') + .attr('placeholder', Drupal.t("New term")) + ).append($('') + .attr('id', create_item_input_id + '_button') + .attr('class', 'button') + .html(Drupal.t('Add New')) + .on('click', function (e) { + var value = $('#' + create_item_input_id).val(); + + // Get the langcode value if present. + var $language = $('select[name="langcode[0][value]"]'); + var langcode = 'und'; + if ($language) { + langcode = $language.val(); + } + + $.ajax({ + url: Drupal.url.toAbsolute(widget.container.app.getConfig('createUrl')), + type: 'POST', + dataType: 'json', + async: false, + contentType: 'application/json', + Accept: 'application/json', + data: JSON.stringify({ + arguments: { + value: value, + bundle: widget.container.app.getConfig('bundle'), + entity_id: widget.model.get('id'), + langcode: langcode + } + }) + }).then(function (data) { + + if (data !== 'null') { + // Force a reload. + widget.model.set('dataLoaded', false); + + // Update create value of attached model. + widget.model.set('createValue', data.defaultValue); + $('.shs-container').append('Select newly added ' + data.vocabulary_label + ' From Drop down List'); + // Fire events to hide message. + setTimeout(function() { + $('.append_message').remove(); + }, 10000); + + widget.container.collection.trigger('update:selection', widget.model, widget.model.get('defaultValue'), widget); + } + }); + + e.preventDefault(); + return false; + }) + ); + } + widget.model.set('dataLoaded', true); // Return self for chaining. return widget; diff --git a/shs.routing.yml b/shs.routing.yml index cf93fd91e..dae481cde 100644 --- a/shs.routing.yml +++ b/shs.routing.yml @@ -6,3 +6,10 @@ shs.term_data: entity_id: null requirements: _permission: 'access content' +shs.create_term: + path: '/shs-create-term' + defaults: + _controller: '\Drupal\shs\Controller\ShsController::createTerm' + requirements: + _csrf_token: 'TRUE' + _custom_access: '\Drupal\shs\Controller\ShsController::createTermAccess' diff --git a/src/Controller/ShsController.php b/src/Controller/ShsController.php index 8ad52ba60..9eadff32b 100644 --- a/src/Controller/ShsController.php +++ b/src/Controller/ShsController.php @@ -2,12 +2,15 @@ namespace Drupal\shs\Controller; +use Drupal\Core\Access\AccessResult; use Drupal\Core\Controller\ControllerBase; use Drupal\Core\Entity\TranslatableInterface; use Drupal\Core\Language\LanguageInterface; +use Drupal\Core\Session\AccountInterface; use Drupal\shs\Cache\ShsCacheableJsonResponse; use Drupal\shs\Cache\ShsTermCacheDependency; -use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\Request; /** * Controller for getting taxonomy terms. @@ -15,28 +18,7 @@ class ShsController extends ControllerBase { /** - * The dependency injection container. - * - * @var \Symfony\Component\DependencyInjection\ContainerInterface - */ - protected $container; - - /** - * Construct a new ShsController object. - */ - public function __construct(ContainerInterface $container) { - $this->container = $container; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container) { - return new static($container); - } - - /** - * Load term data. + * Gets the term data. * * @param string $identifier * Name of field to load the data for. @@ -46,7 +28,7 @@ public static function create(ContainerInterface $container) { * @param int $entity_id * Id of parent term to load all children for. Defaults to 0. * - * @return CacheableJsonResponse + * @return \Drupal\shs\Cache\ShsCacheableJsonResponse * Cacheable Json response. */ public function getTermData($identifier, $bundle, $entity_id = 0) { @@ -66,8 +48,8 @@ public function getTermData($identifier, $bundle, $entity_id = 0) { $translation_enabled = FALSE; if ($this->moduleHandler()->moduleExists('content_translation')) { - /** @var \Drupal\content_translation\ContentTranslationManagerInterface $translation_manager */ - $translation_manager = $this->container->get('content_translation.manager'); + /** @var \Drupal\content_translation\ContentTranslationManager $translation_manager */ + $translation_manager = \Drupal::service('content_translation.manager'); // If translation is enabled for the vocabulary, we need to load the full // term objects to get the translation for the current language. $translation_enabled = $translation_manager->isEnabled('taxonomy_term', $bundle); @@ -111,4 +93,96 @@ public function getTermData($identifier, $bundle, $entity_id = 0) { return $response; } + /** + * Create term data. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * + * @return JsonResponse + * Json response. + */ + public function createTerm(Request $request) { + + $result = NULL; + + // Obtain the data from the json. + $data = json_decode($request->getContent()); + $value = $data->arguments->value; + $bundle = $data->arguments->bundle; + $entity_id = $data->arguments->entity_id; + $langcode = \Drupal::languageManager()->getCurrentLanguage()->getId(); + + if (!$langcode) { + $langcode = \Drupal::languageManager()->getCurrentLanguage()->getId(); + } + + $storage = $this->entityTypeManager()->getStorage('taxonomy_term'); + + // Try to find the term. + $found = NULL; + $terms = $storage->loadTree($bundle, $entity_id, 1, TRUE); + foreach ($terms as $term) { + if ($term->hasTranslation($langcode)) { + $term = $term->getTranslation($langcode); + } + else { + $langcode = $term->default_langcode; + } + + if (strcasecmp($term->getName(), $value) === 0) { + $found = $term; + break; + } + } + + if (!$found) { + $term = $storage->create([ + 'vid' => $bundle, + 'langcode' => $langcode, + 'name' => $value, + 'parent' => [$entity_id], + ]); + $term->save(); + } + else { + $term = $found; + } + + $vid = $term->bundle(); + $vocabulary = \Drupal::entityTypeManager()->getStorage('taxonomy_vocabulary')->load($vid); + + $result = (object) [ + 'tid' => $term->id(), + 'name' => $term->getName(), + 'vocabulary_label' => $vocabulary->label(), + 'description__value' => $term->getDescription(), + 'langcode' => $langcode, + 'hasChildren' => shs_term_has_children($term->id()), + ]; + + $response = new JsonResponse(); + $response->setData($result); + + return $response; + } + + /** + * Checks access for a specific request. + * + * @param \Drupal\Core\Session\AccountInterface $account + * Run access checks for this account. + * + * @return bool + */ + public function createTermAccess(AccountInterface $account) { + $request = \Drupal::request(); + $data = json_decode($request->getContent()); + $bundle = $data->arguments->bundle; + + return AccessResult::allowedIfHasPermissions($account, [ + "edit terms in {$bundle}", + 'administer taxonomy', + ], 'OR'); + } + } diff --git a/src/Plugin/Field/FieldWidget/OptionsShsWidget.php b/src/Plugin/Field/FieldWidget/OptionsShsWidget.php index 26a9b44f8..d43dbea2b 100644 --- a/src/Plugin/Field/FieldWidget/OptionsShsWidget.php +++ b/src/Plugin/Field/FieldWidget/OptionsShsWidget.php @@ -4,11 +4,14 @@ use Drupal\Component\Utility\Html; use Drupal\Component\Utility\NestedArray; +use Drupal\Core\Entity\FieldableEntityInterface; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Field\Plugin\Field\FieldWidget\OptionsSelectWidget; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; +use Drupal\Core\Render\BubbleableMetadata; +use Drupal\Core\Url; use Drupal\shs\StringTranslationTrait; use Drupal\shs\WidgetDefaults; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -98,7 +101,6 @@ public function settingsForm(array $form, FormStateInterface $form_state) { '#title' => $this->t('Allow creating new items'), '#default_value' => $this->getSetting('create_new_items'), '#description' => $this->t('Allow users to create new items of the source bundle.'), - '#disabled' => TRUE, ]; $element['create_new_levels'] = [ '#type' => 'checkbox', @@ -110,7 +112,6 @@ public function settingsForm(array $form, FormStateInterface $form_state) { ':input[name="fields[' . $field_name . '][settings_edit_form][settings][create_new_items]"]' => ['checked' => TRUE], ], ], - '#disabled' => TRUE, ]; $element['force_deepest'] = [ '#type' => 'checkbox', @@ -208,11 +209,23 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen $parents = $this->widgetDefaults->getParentDefaults($default_value, $settings_additional['anyValue'], $this->fieldDefinition->getItemDefinition()->getSetting('target_type'), $cardinality); } + // Generate a token for our ajax url. + // We need to run this through a render function because Drupal.url generates + // a placeholder token. + $urlBubbleable = Url::fromRoute('shs.create_term')->toString(TRUE); + $urlRender = array( + '#markup' => $urlBubbleable->getGeneratedUrl(), + ); + BubbleableMetadata::createFromRenderArray($urlRender) + ->merge($urlBubbleable)->applyTo($urlRender); + $url = (string) \Drupal::service('renderer')->renderPlain($urlRender); + $settings_shs = [ 'settings' => $this->getSettings() + $settings_additional, 'bundle' => $bundle, 'bundleLabel' => $vocabulary->label(), 'baseUrl' => 'shs-term-data', + 'createUrl' => $url, 'cardinality' => $cardinality, 'parents' => $parents, 'defaultValue' => $default_value, @@ -389,4 +402,35 @@ protected function settingToString($key) { return $options[$value]; } + /** + * Returns the array of options for the widget. + * + * @param \Drupal\Core\Entity\FieldableEntityInterface $entity + * The entity for which to return options. + * + * @return array + * The array of options for the widget. + */ + protected function getOptions(FieldableEntityInterface $entity) { + if (!isset($this->options)) { + $options = parent::getOptions($entity); + + // Add a create option if the widget needs one. + $new_label = $this->getCreateLabel(); + if ($new_label) { + $options['_create'] = $new_label; + } + + $this->options = $options; + } + return $this->options; + } + + /** + * Returns the label for creating a new term. + * @return string + */ + public function getCreateLabel() { + return (string) t('Create...'); + } }