Shopware 6: sw-inherit-wrapper property currentValue does not update on select - plugins

I've a custom entity enterprise, which is assigned to product.extensions.myPlugin.enterpriseId.
Now i want to select the enterprise within Shopware Vue-Component sw-entity-single-select wrapped by sw-inherit-wrapper:
<sw-inherit-wrapper
v-model="product.extensions.myPlugin.enterpriseId"
:has-parent="false"
:inherited-value="null">
<template #content="{currentValue, updateCurrentValue, isInherited}">
<sw-entity-single-select
class="enterprise-name"
:key="isInherited"
:value="currentValue"
entity="enterprise"
:placeholder="$tc('inputEnterprise.placeholder')"
show-clearable-button
allow-entity-creation
:entity-creation-label="$tc('inputEnterprise.labelCreation')"
#change="updateCurrentValue"></sw-entity-single-select>
</template>
</sw-inherit-wrapper>
This template ist part of a modal.
On select a enterprise from given list, the <sw-entity-single-select__selection-text> does not update.
Step out step in into the template (close and reopen the parent modal), the enterprise is set and printed out on the <sw-entity-single-select__selection-text>.
So the entity extension was updated but not the property currentValue in <sw-entity-single-select__selection-text>
As example i follow the sw_product_basic_form_manufacturer_field
Question: Why property currentValue does not update in <sw-entity-single-select> on select from its list?
EDIT #1
The extension is set in overridden component sw-product-detail like this:
// sw-product-detail/index.js
...
Component.override('sw-product-detail', {
...
computed: {
myPluginRepository() {
return this.repositoryFactory.create('my_plugin');
},
}
watch: {
product() {
if(this.product.id) {
this.__setMyPluginExtension()
}
}
},
methods: {
...
__setMyPluginExtension() {
let extension = this.product.extensions.myPlugin ?? null;
if(null === extension) {
extension =
this.myPluginRepository.create(Shopware.Context.api);
extension.enterpriseId = null
}
this.product.extensions.myPlugin = extension;
},
}
}
Inside the overridden child component sw-product-detail-base the modal is loaded.
I think, the extension is known when the component is loaded and rendered. I've check it with a debug breakpoint inside the created() in the custom component, the extension is known like defined in parent component.
Even more, if i set extension.enterpriseId a real default value instead null then the enterprise is printed out in <sw-entity-single-select__selection-text>, what me indicate, the extension is known.
Btw, i used the change method, but i'm looking for my error...

Best guess without knowing more of the stack would be that this.product.extensions.myPlugin.enterpriseId isn't reactive. That would be the case if the property doesn't exists when the product is originally set and it is declared like this:
this.product.extensions.myPlugin = {
enterpriseId: 'asdf'
};
Any further changes to enterpriseId would then not be reactive and the view would subsequently not be updated when it changes.
To be safe you could add a #change listener to sw-inherit-wrapper and update the property reactively:
onEnterpriseIdChange(enterpriseId) {
if (!this.product.extensions.myPlugin) {
this.$set(this.product.extensions, 'myPlugin', {});
}
if (!this.product.extensions.myPlugin.enterpriseId) {
this.$set(this.product.extensions.myPlugin, 'enterpriseId', enterpriseId);
}
}

As addition of #dneustadt answers:
...
const extension = this.myPluginRepository.create(Shopware.Context.api);
extension.enterpriseId= null
this.$set(this.product.extensions, 'myPlugin', extension)
...
Use the repository create() method instead create a simple object because of required method entity.getEntityName() in changeset-generator.data.js

Related

How to give a fixed Uid to my Action

Hy,
I'm trying to call my action with allways a fixed Uid (configured by TS) so I could put a plugin on my page to register for a specific Event. And don't have to go over a Event List click the Event click register.
I tried the following which did not work out:
public function newAction(
\XYZ\xyz\Domain\Model\Registration $newRegistration = NULL,
\XYZ\xyz\Domain\Model\Event $event = 'DD8B2164290B40DA240D843095A29904'
)
The next didn't one work either!
public function newAction(
\XYZ\xyz\Domain\Model\Registration $newRegistration = NULL,
\XYZ\xyz\Domain\Model\Event $event = Null
) {
$myinstance = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(
'XYZ\\xyz\\Domain\\Model\\Event'
);
$event = $myinstance->findByUid('DD8B2164290B40DA240D843095A29904');
.......
}
So I was woundering is there a way to give my fixed Uid to the action?
In TYPO3 calling Extbase actions is done in the routing and dispatching components - to pass anything from the outside that is different from a numeric uid value a custom property TypeConverter would have to be implemented that transforms a particular string pattern into a value domain object of type Event.
However, there's a simpler approach by using configuration:
1) Provide configuration in TypoScript
Extbase uses a strong naming convention based on the extension name and optionally the plugin name. Thus, either tx_myextension or tx_myextension_someplugin can be used - latter is more specific for for according somePlugin. Besides that settings are automatically forwarded and provided in an Extbase controller context - accessible by $this->settings.
plugin.tx_xyz {
settings {
newActionEventIdentifier = DD8B2164290B40DA240D843095A29904
}
}
2) Retrieve data via repository
\XYZ\xyz\Domain\Repository\EventRepository
Use a dedicated EventRepository::findByIdentifier(string) method to retrieve the data. The property names are just assumptions since there are no explicit mentions how exactly the event data is persisted and whether it is persisted in a relational DBMS at all.
<?php
namespace XYZ\xyz\Domain\Repository;
class EventRepository
{
public function findByIdentifier($identifier)
{
$query = $this->createQuery();
$query->matching(
$query->equals('event_id', $identifier)
);
return $query->execute();
}
}
3) Putting all together in the according controller
The $event property was removed from the action since that entity is pre-defined and cannot be submitted from the outside (and to support the string to Event entity transformation a custom TypeConverter would be required as mentioned earlier).
public function newAction(
\XYZ\xyz\Domain\Model\Registration $newRegistration = null
) {
$event = $this->eventRepository->findByIdentifier(
$this->settings['newActionEventIdentifier']
);
if ($event === null) {
throw new \RuntimeException('No event found', 1522070079);
}
// the regular controller tasks
$this->view->assign(...);
}

Create ControlProperty for custom UIControl

Is it possible to make extension of structure Reactive, where base class is my custom control inherited from UIControl?
When I'm trying following code:
extension Reactive where Base: CustomControl {
public var value: ControlProperty<CGFloat> {
return CustomControl.rx.value(
self.base,
getter: { customControl in
customControl.customProperty
},
setter: { customControl, value in
customControl.customProperty = value
}
)
}
}
I'm getting following error:
Instance member "value" cannot be used on type 'Reactive<CustomControl>'
I will be grateful if you provide me any explanation.
You can check this link: https://github.com/ReactiveX/RxSwift/issues/991
The method value isn't public so you need to create an public version of that. After that, it'll be possible create the extension for your custom control.

Sequelize Hooks - does the funcs expose the Models as in the associations?

When creating a hook I want to know if the models variable is exposed in the function like in the assosiations:
...
classMethods: {
associate: function ( models ) {
users.belongsTo( models.roles, { foreignKey: 'role' } );
}
}
...
What I'd like to do is to update the a value of another model, first I have to update the current amount of product adding the amount purchased (any tips?), so, I need the models var.
hooks : {
afterCreate: function ( inventory, options ) {
//What does the `options` value has?
}
}
One workaround suggested was to simply require the model...
Also I would like to know if afterCreate would work for bulkCreate? Or does it necessary has to be afterBulkCreate?
afterCreate is triggered after each creation. afterBulkCreate is triggered after the whole set has been created. So if you are creating 10 items, afterCreate would be called 10 times, after each item was created. But afterBulkCreate would only be called once.
This doesn't seem to be documented throughly enough but I'd suggest just creating a hook and then doing console.log(arguments) inside the hook to see which arguments are beeing passed in. But I'd suggest to just require the model like you said.
The models variable is not exposed in the function. The solution I found when I need to update the value of another model is the following:
Define a function to get the hooks that receives the models:
var getHooks = function(models) {
return {
afterCreate: function(item, options) {
// Can access the models:
models.roles.create({...});
},
beforeCreate: function(item, options) {
// Code here
}
};
};
And set the hooks functions in the associate method:
classMethods: {
associate: function ( models ) {
users.belongsTo( models.roles, { foreignKey: 'role' } );
var hooks = getHooks(models);
for(var name in hooks) {
users.addHook(name, hooks[name]);
}
}
}

How make permission for change attribute in Sails.js?

For example: we have the model Profile. Each user profile has attribute alias_name that can be set only once (when you create), and can not be changed (when you update).
Of course I can override action .update() in the controller and remove the attribute from req.body. But then lost all the magic Blueprint API.
More I can create special policies that will remove the attribute from req.body. But not sure it's right.
Perhaps it should be made in the method .beforeUpdate() in the Profile model?
How better to do? Share your experiences?
You can use approach with custom validation rule for this attribute
// /api/models/profile.js
module.exports = {
attributes: {
alias_name: {
type: 'string',
nonEditable: true
}
},
// ...
types: {
nonEditable: function(prop) {
return prop === null;
}
}
}
Thus, this fields can be changed only once (in case you have not defaultsTo specified for alias_name).

Entity Framework - Automatically Adding Unattached Items

I use EF 5 and Code First.
Ive got a main ViewModel that exposes a Translation Property. A Translation consists out of 1 to n Translation Items. The Translation Property is bound to a Usercontrol. When the Translation is created and Added via _context.Translations.Add(t) in the Viewmodel also all TranslationItems inside the Translation are saved and stored correctly in the DB.
But when i try to Add a new TranslationItem (t.TranslationItems.Add) to a existing Translation it isn't saved in the DB. context.ObjectStateManager.GetObjectStateEntries(EntityState.Added) also returns no elements so i guess the new Translation Items is still unattached. Since the TranslationItem is added inside the Child Usercontrol, i can't access the current Datacontext To set the Entity State to Added.
What can i do?
Edit:
public class Translation : BaseEntity, IValidatableObject
{
private List<TranslationItem> _translations;
public virtual List<TranslationItem> Translations
{
get{
return _translations;
}
set
{
_translations = value;
}
}s
In ViewModel:
Translation = new Translation(); or
Translation = repo.GetTranslation(1);
Binding to custom Usercontrol:
<Views:TranslationTextInput Translations="{Binding Translation}"/>
In code behind of UserControl:
Trans.Translations.Add(new TranslationItem() { Text = "", Lcid = new CultureInfo("en").LCID });
Save function:
public void Update(Translation t)
{
if (t.Id == 0)
_context.Units.Add(t);
_context.SaveChanges();
}
I create the Context in the Viewmodel Constructor and pass it to the repository class.
The ChangeTracker should automatically grab all it's children if the Translation is properly attached. However, you could try attaching all of the TranslationItems inside the Translation if you're sure that the Translation is attached to the context.
I'm not sure of the DbSet name for TranslationItems, change that to whatever you named it.
public void Update(Translation t)
{
if (t.Id == 0)
_context.Units.Add(t);
foreach (var ti in t.Translations)
{
if (_context.Entry(ti).State == EntityState.Detached)
{
_context.TranslationItems.Attach(ti);
}
}
_context.SaveChanges();
}