spfx - onpropertychange event - spfx

I have created cascading dropdown. I need to load dropdown based on parent dropdown selection. I am trying to use onpropertychange event. but I am getting error on super.onpropertychange saying {Property 'onPropertyChange' does not exist on type 'BaseClientSideWebPart'.}
please let us know what I havve missed.
protected onPropertyChange(propertyPath: string, newValue: any):void{
if(propertyPath === "listDropDown"){
// Change only when drop down changes
super.onPropertyChange(propertyPath,newValue);
// Clears the existing data
this.properties.ItemsDropDown = undefined;
this.onPropertyChange('ItemsDropDown', this.properties.ItemsDropDown);
// Get/Load new items data
this.GetItems();
}
else {
// Render the property field
super.onPropertyChange(propertyPath, newValue);
}
}

Instead of onPropertyChange, perhaps you mean onPropertyFieldChanged from the BaseWebPart class?
The error message is accurate - web parts don't have a method called onPropertyChange. The above sounds like the closest match for what you are trying to do. Note that it takes not two arguments, but three: propertyPath, oldValue, and newValue.

Related

RxSwift MVVM tableview/collectionview with user input cells representing state

I have one request for any of you. I want to create collectionview/tableview which will have user inputs in cells. Mixture of values from those inputs would represent state. I want to observe that state and if some conditions are met, I want to recreate all cells. I have created very simple app, where I demonstrate how i tried to implement it, but I’m getting reentrancy warning and I would love to find out your ideas/best practicies for this. Here is the repo you can checkout.
https://github.com/beretis/CollectionViewTest
PS: Im using RxData sources, and I would love to know exactly what is causing this reentrancy (I have my idea)
I sent a pull request your way.
The key to answer this question is to have two Observables in your view model. One that represents the programatic state of each cell (the stuff that the user doesn't input) and one that represents the user input state of each cell. You connect the data from these two Observables using some sort of ID value (I use UUID.) So for your specific example, the view model for the collection should look like this:
typealias CellID = UUID
struct StaticCellState {
let id: CellID
let placeholder: String
}
struct CollectionViewModel {
let cells: Observable<[StaticCellState]>
let cellStates: Observable<[CellID: String]>
}
The cells observable contains the placeholder and cell ID. This is the data that the cell uses when it is configured and doesn't change for the life of that configuration (It might change if the cell is reused.) It is only updated if you want to add/remove a cell or change the placeholder value of a particular cell.
The cellStates observable contains the latest user input values and is updated every time the user types into one of the cells' text fields.
Then you configure your cells by passing in the information for that cell from both observables:
let dataSource = RxCollectionViewSectionedReloadDataSource<SectionOfCustomData>(
configureCell: { dataSource, collectionView, indexPath, item in
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as? SomeCell else { return UICollectionViewCell() }
let output = cell.configure(with: item, initial: viewModel.cellStates.map { $0[item.id]! })
output
.bind(to: itemEdit)
.disposed(by: cell.disposeBag)
return cell
})

CE Update Event: any way to pass before/after property values to a workflow?

I've configured a FileNet workflow subscription on Add, Update and Delete events. The workflow calls a Java component to send a notification message (to a third party).
We would like to see "before" and "after" property values in the notification message for "Update" events.
The "Event" object that triggers the subscription has a "Modified Properties" member, so I was hoping I could just create a corresponding "ModifiedProperties" string array in the workflow, and have the subscription map "Update.ModifiedProperties = ModifiedProperties". Unfortunately, the Event's "ModifiedProperties" only gives the NEW value, not the "before" value.
<= So I don't see any way to get "before/after" values directly from the subscription...
It looks like the "UpdateEvent" object also has an "OriginalObject" member ... and I might be able to use the Java API to get the "before" value from the OriginalObject.
Q: Does this sound plausible method for getting the before/after document property values?
Q: Any ideas how to pass the "OriginalObject" object from the subscription to the workflow, so the Java component can use it?
The target platform is P8 5.2.1; I'm developing on P8 5.5.
You are right, the only way to the original values is through the OriginalObject object. And the quickest way to get data to a workflow is using a subscribable object.
Therefore, a solution to your problem is to define a custom object containing the properties describing the new and the old property values. You create this custom object in a custom event handler triggered on an update event from the document. Here you can populate the properties of the custom object using the original object:
Document document = (Document) event.get_OriginalObject();;
Iterator<?> iterator = event.get_ModifiedProperties().iterator();
while (iterator.hasNext()) {
String modifiedProperty = (String) iterator.next();
// TODO: Fetch the values from the original object
// and set them on the custom object. The details depend
// on the data structure you choose.
}
Next you create a Workflow subscription triggered on the creation of the custom object. You can map the properties of your custom object to the data fields of your workflow. In the workflow that is started you can define an attachment and specify that the custom object is the initiating attachment. Using the CE_Operation queue methods you can now and delete the custom object when your processing is finished.
if(objEvent instanceof UpdateEvent) { try { String strModifiedProperties = ""; UpdateEvent updateEvent = (UpdateEvent) objEvent; StringList propertyNames = updateEvent.get_ModifiedProperties(); Iterator iterModifiedProps = propertyNames.iterator(); while(iterModifiedProps.hasNext()) { String modifiedProperty = (String) iterModifiedProps.next(); strModifiedProperties = strModifiedProperties+modifiedProperty+","; } strModifiedProperties = strModifiedProperties.substring(0, strModifiedProperties.lastIndexOf(",")); } catch (Exception e) { System.out.println("onEvent : Exception while executing UpdateEvent: "+e.getMessage()); } }

Passing parameters from Command to Converter

I defined a new type of model element as a plug-in; let's refer to it as Foo. A Foo node in the model should translate to a section element in the view. So far, so good. I managed to do that by defining simple conversion rules. I also managed to define a new FooCommand that transforms (renames) selected blocks to Foo.
I got stuck trying to have attributes on those Foo model nodes be translated to attributes on the view elements (and vice-versa). Suppose Foos have an attribute named fooClass which should map to the view element's class attribute.
<Foo fooClass="green-foo"> should map to/from <section class="green-foo">
I can successfully receive parameters in FooCommand, but I can't seem to set them on the blocks being processed by the command:
execute(options = {}) {
const document = this.editor.document;
const fooClass = options.fooClass;
document.enqueueChanges(() => {
const batch = options.batch || document.batch();
const blocks = (options.selection || document.selection).getSelectedBlocks();
for (const block of blocks) {
if (!block.is('foo')) {
batch.rename(block, 'foo');
batch.setAttribute(block, 'fooClass', fooClass);
}
}
});
}
Below is the code for the init function in the Foo plugin, including the model→view and view→model conversions:
init() {
const editor = this.editor;
const doc = editor.document;
const data = editor.data;
const editing = editor.editing;
editor.commands.add('foo', new FooCommand(editor));
doc.schema.registerItem('foo', '$block');
buildModelConverter().for(data.modelToView, editing.modelToView)
.fromElement('foo')
.toElement(modelElement => {
const fooClass = modelElement.item.getAttribute('fooClass'));
return new ContainerElement('section', {'class': fooClass});
});
buildViewConverter().for(data.viewToModel)
.fromElement('section')
.toElement(viewElement => {
let classes = Array.from(viewElement.getClassNames());
let modelElement = new ModelElement('foo', {'fooClass': classes[0]});
return modelElement;
});
}
When I try to run the command via
editor.execute('foo', { fooClass: 'green-foo' })
I can see that the green-foo value is available to FooCommand, but the modelElement in the model→view conversion, on the other hand, has no fooClass attribute.
I'm sure I'm missing the point here and misusing the APIs. I'd be really thankful if someone could shed some light on this issue. I can provide more details, as needed.
Follow-up after initial suggestions
Thanks to #Reinmar and #jodator for their suggestion regarding configuring the document schema to allow for the custom attribute. I really thought that would have taken care of it, but no. It may have been a necessary step anyway, but I'm still unable to get the attribute value from the model element during the model→view conversion.
First, let me add an important piece of information I had left out: the CKEditor5's version I'm working with is 1.0.0-alpha2. I am aware several of the APIs are bound to change, but I would still like to get things working with the present version.
Model→view conversion
If I understand it correctly, one can either pass a string or a function to the toElement call. A question about using the latter: what exactly are the parameters passed to the function? I assumed it would be the model element (node?) to be converted. Is that the case? If so, why is the attribute set on that node via batch.setAttribute (inside a document.enqueueChanges) not available when requested? Should it be?
A sequencing problem?
Additional testing seems to indicate there's some kind of order-of-execution issue happening. I've observed that, even though the attribute is not available when I first try to read it from the modelElement parameter, it will be so if I read it again later. Let me try to illustrate the situation below. First, I'll modify the conversion code to make it use some dummy value in case the attribute value is not available when read:
buildModelConverter().for(data.modelToView, editing.modelToView)
.fromElement('foo')
.toElement(modelElement => {
let fooClass = modelElement.item.getAttribute('fooClass') || 'naught';
let viewElement = new ContainerElement('section');
viewElement.setAttribute('class', fooClass);
return viewElement;
});
Now I reload the page and execute the following instructions on the console:
c = Array.from(editor.document.getRoot().getChildren());
c[1].is('paragraph'); // true
// Changing the node from 'paragraph' to 'foo' and adding an attribute
// 'fooClass' with value 'green-foo' to it.
editor.document.enqueueChanges(() => {
const batch = editor.document.batch();
batch.rename(c[1], 'foo');
batch.setAttribute(c[1], 'fooClass', 'green-foo');
return batch;
});
c[1].is('paragraph'); // false
c[1].is('foo'); // true
c[1].hasAttribute('fooClass'); // true
c[1].getAttribute('fooClass'); // 'green-foo'
Even though it looks like the expected output is being produced, a glance at the generated view element shows the problem:
<section class="naught"/>
Lastly, even if I try to reset the fooClass attribute on the model element, the change is not reflected on the view element. Why is that? Shouldn't changes made via enqueueChanges cause the view to update?
Sorry for the very long post, but I'm trying to convey as many details as I can. Here's hoping someone will spot my mistake or misunderstanding of how the CKEditor 5's API actually works.
View not updating?
I turned to Document's events and experimented with the changesDone event. It successfully addresses the "timing" issue, as it consistently triggers only after all changes have been processed. Still, the problem of the view not updating in response to a change in the model remains. To make it clear, the model does change, but the view does not reflect that. Here is the call:
editor.document.enqueueChanges(() => editor.document.batch().setAttribute(c[1], 'fooClass', 'red-foo'));
To be 100% sure I wrote the whole feature myself. I use the 1.0.0-beta.1 API which is completely different than what you had.
Basically – it works. It isn't 100% correct yet, but I'll get to that.
How to convert an element+attribute pair?
The thing when implementing a feature which needs to convert element + attribute is that it requires handling the element and attribute conversion separately as they are treated separately by CKEditor 5.
Therefore, in the code below you'll find that I used elementToElement():
editor.conversion.elementToElement( {
model: 'foo',
view: 'section'
} );
So a converter between model's <foo> element and view's <section> element. This is a two-way converter so it handles upcasting (view -> model) and downcasting (model -> view) conversion.
NOTE: It doesn't handle the attribute.
Theoretically, as the view property you could write a callback which would read the model element's attribute and create view element with this attribute set too. But that wouldn't work because such a configuration would only make sense in case of downcasting (model -> view). How could we use that callback to downcast a view structure?
NOTE: You can write converters for downcast and upcast pipelines separately (by using editor.conversion.for()), in which case you could really use callbacks. But it doesn't really make sense in this case.
The attribute may change independently!
The other problem is that let's say you wrote an element converter which sets the attribute at the same time. Tada, you load <section class=ohmy> and gets <foo class=ohmy> in your model.
But then... what if the attribute will change in the model?
In the downcast pipeline CKEditor 5 treats element changes separately from attribute changes. It fires them as separate events. So, when your FooCommand is executed on a heading it calls writer.rename() and we get the following events in DowncastDispatcher:
remove with <heading>
insert:section
But then the attribute is changed too (writer.setAttribute()), so we also get:
setAttibute:class:section
The elementToElement() conversion helper listens to insert:section event. So it's blind to setAttribute:class:selection.
Therefore, when you change the value of the attribute, you need the attributeToAttribute() conversion.
Sequencing
I didn't want to reply to your question before we released 1.0.0-beta.1 because 1.0.0-beta.1 brought the Differ.
Before 1.0.0-beta.1 all changes were converted immediately when they were applied. So, rename() would cause immediate remove and insert:section events. At this point, the element that you got in the latter one wouldn't have the class attribute set yet.
Thanks to the Differ we're able to start the conversion once all the changes are applied (after change() block is executed). This means that the insert:section event is fired once the model <foo> element has the class attribute set already. That's why you could write a callback-based converters... bur you shouldn't :D
The code
import { downcastAttributeToAttribute } from '#ckeditor/ckeditor5-engine/src/conversion/downcast-converters';
import { upcastAttributeToAttribute } from '#ckeditor/ckeditor5-engine/src/conversion/upcast-converters';
class FooCommand extends Command {
execute( options = {} ) {
const model = this.editor.model;
const fooClass = options.class;
model.change( writer => {
const blocks = model.document.selection.getSelectedBlocks();
for ( const block of blocks ) {
if ( !block.is( 'foo' ) ) {
writer.rename( block, 'foo' );
writer.setAttribute( 'class', fooClass, block );
}
}
} );
}
}
class FooPlugin extends Plugin {
init() {
const editor = this.editor;
editor.commands.add( 'foo', new FooCommand( editor ) );
editor.model.schema.register( 'foo', {
allowAttributes: 'class',
inheritAllFrom: '$block'
} );
editor.conversion.elementToElement( {
model: 'foo',
view: 'section'
} );
editor.conversion.for( 'upcast' ).add(
upcastAttributeToAttribute( {
model: 'class',
view: 'class'
} )
);
editor.conversion.for( 'downcast' ).add(
downcastAttributeToAttribute( {
model: 'class',
view: 'class'
} )
);
// This should work but it does not due to https://github.com/ckeditor/ckeditor5-engine/issues/1379 :(((
// EDIT: The above issue is fixed and will be released in 1.0.0-beta.2.
// editor.conversion.attributeToAttribute( {
// model: {
// name: 'foo',
// key: 'class'
// },
// view: {
// name: 'section',
// key: 'class'
// }
// } );
}
}
This code works quite well, except the fact that it converts the class attribute on any possible element that has it. That's because I had to use very generic downcastAttributeToAttribute() and upcastAttributeToAttribute() converters because of a bug that I found (EDIT: it's fixed and will be available in 1.0.0-beta.2). The commented out piece of code is how you it should be defined if everything worked fine and it will work in 1.0.0-beta.2.
It's sad that we missed such a simple case, but that's mainly due to the fact that all our features... are much more complicated than this.

Selection of a property in a compartment shape

I have a problem with selecting a property on a compartmentshape of a dsl. What I want to do is:
I have a DSL with one compartmentshape which has many properties in one compartment. Each of this properties has a textfield which is used for saving c# code. I compile this code and add the error tasks to the error list. I added an event handler for the navigate event of the error task. Inside this handler, i would like to select the property of the compartmentshape which is responsible for the error. I tried many things, but didn't succeeded. This is my current selection logic:
public void Select(Rule rule)
{
Library.Field ruleField = rule.Field as Library.Field;
var ruleFieldPresentation = PresentationViewsSubject.GetPresentation(ruleField as ModelElement).FirstOrDefault() as ShapeElement;
VSDiagramView activeDiagramView = Diagram.ActiveDiagramView as VSDiagramView;
if (activeDiagramView != null)
{
var docView = activeDiagramView.DocView;
activeDiagramView.Selection.Clear();
docView.CurrentDiagram.ActiveDiagramView.Selection.Set(new DiagramItem(ruleFieldPresentation));
}
}
The problem seems that an property of the compartmentshape doesn't have a presentationview, because I'm not able to get it.
I would be glad and very grateful if someone can helpe me with this problem.
Thank you
Regards Manuel
I wanted to open an error from the error list. There is a better solution than using the navigation event on an error. The better solution is to add a validation rule to the domain class and add the error with the context to the error list. Than the navigation to the property works out of the box.
context.LogError(errorDescription, "GAIN001RuleCompilationError", Field);

Where does Event.Keys gone?

Did I miss something or the Event.Keys object has gone from mootools since 1.4.0 ?
I cannot get the real value of a key or compare it to the current pressed key :
var modifiers = {
previous: Event.Keys.left,
next: Event.Keys.right
};
switch (evt.code){
case Event.Keys.backspace:
// Do some stuff;
break;
case Event.Keys.delete:
// Do some other stuff
break;
}
Is ther a possibility this object has move to another object or property ?
erm. Under the new API changes, Event is now known as DOMEvent, a MooTools Type, not a Class. Additionally, event definitions are now private behind a closure in a keys array.
https://github.com/mootools/mootools-core/blob/master/Source/Types/DOMEvent.js
There is an API to work with it:
DOMEvent.defineKeys({
38: "up"
});
which, regretfully - is one way: You have no getter for the Event.Keys, you cannot set it w/o going through the API either.
You can redefine them as per the source or store a local var of what they mean. You can also refactor it to puncture it.
You can also do pseudo events, like keydown:left
DOMEvent.definePseudo('left', function(split, fn, args){
// args[0] is the Event instance
if(args[0].key == 'left') fn.apply(this, args);
});
document.getElement("textarea").addEvent("keydown:left", function(e) {
alert("left pressed!");
});
the alert above will only fire if you press the left arrow inside your textarea. Hope this helps...

Resources