How to check if the editor is empty? - lexical

I have two editor instances.
One is editable
Second one is read only for preview what user is typed
I'm copied editor state beetwen those editors - wtih no problems
But i want to hide second editor when first one is empty. I'm trying something like this but it always return false
...
function onChange(editorState) {
console.log(editorState.isEmpty())
}
function Editor() {
...
return (
<>
<LexicalComposer initialConfig={editorConfig}>
<ToolbarPlugin />
<RichTextPlugin
contentEditable={<ContentEditable className="editor-input" />}
placeholder={<Placeholder />}
ErrorBoundary={LexicalErrorBoundary}
/>
<OnChangePlugin onChange={onChange} />
<LexicalComposer />
)
}

There are, unfortunately, currently several different ways to do this, depending on exactly what you mean by "empty". You can us EditorState.isEmpty, but this is checking to see if the _nodeMap contains only a single node (the root) and the selection is null - so it's really about emptiness in the sense that the EditorState is in the same state it would be in had it just been initialized. However, this is might not be what you want - usually a visually empty editor will have a RootNode with a single ParagraphNode child, and you may or may not care if the Selection is null. So, you could look at using ElementNode.isEmpty:
$getRoot().isEmpty()
This checks if the RootNode has 0 children. Once again, in a typical editor, that might return false in a lot of cases where you would expect it to return true (like if the editor is initialized with an empty ParagraphNode under the RootNode). So, you could do:
const root = $getRoot();
const isEmpty = root.getFirstChild().isEmpty() && root.getChildrenSize === 1
However, this wouldn't account for whitespace - which you might also care about. If you want to consider an editor with only whitespace as empty, then you might want to use $isRootTextContentEmpty from #lexical/text. You can also look at the implementation of this function to see how you might go about tweaking it for your use case.

Related

Validate prop with function as input in redux form

i have a question about the validate prop.
Suppose we (.....)
In the component we define this:
<Field>
validate = {validate}
</Field>
Why cant we write validate={validate(value)} or why is not correct to write something like this: validate={()=> validate(value)}
Thanks!
The validate prop on Field takes a function (or an array of functions) which should be used to validate the incoming field value. So you can't write validate={ someCustomValidator(value) } unless the someCustomValidator function is a function you have defined that in turn returns a function.
Using validate={()=> someCustomValidator(value)} should work, but doesn't make much sense in the context of your example (where does value come from?). If you use validate={(value)=> someCustomValidator(value)} instead, that makes more sense, but this is problematic because this will create a new function each time the component with the Field re-renders. And according to the documentation:
Note: if the validate prop changes the field will be re-registered.
which is probably not what you want to happen.
So, using
// validation
const someCustomValidator = value => {
// give error roughly half of the time
return Math.random() < 0.5 ?
undefined : // validation is ok
'Validation failed'; // this will be available in meta.error
}
// somewhere else
<Field validate={someCustomValidator} />
is the correct way of using it. But note that Field doesn't internally know what to do to display potential validation errors. That you have to solve yourself. See https://redux-form.com/7.0.4/examples/fieldlevelvalidation/ for an example.

How to call a function in data-binding when lots of parameters are involved

At the moment I have this in my template:
WAY 1
<template is="dom-if" if="{{_showCancel(userData.generic.id,info.userId,info._children.gigId.userId,info.accepted,info.cancelled,info.paymentTerms)}}">
And then this in my element:
_showCancel: function(viewerUserId,offerUserId,gigUserId, accepted, cancel, paymentTerms) {
// Offer needs to be accepted and NOT cancelled
if (!info.accepted || info.cancelled) return false;
// Gig owner can ALWAYS cancel an offer
if (viewerUserId == gigUserId ) return true;
// Offer maker can only cancel if the gig involved pre-payment
if (viewerUserId == offerUserId && paymentTerms != 'on-day') return true;
return false;
},
Do I have just too many parameters to this function?
WAY 2
Should I just have something like this instead:
<template is="dom-if" if="{{_showCancel(userData, info)}}">
WAY 3
Although I would want to check if their sub-properties change too... so I would need:
<template is="dom-if" if="{{_showCancel(userData, info, userData.*, info.*)}}">
WAY 4
But then again I probably should just look for the properties and use the value property like so:
<template is="dom-if" if="{{_showCancel(userData.*, info.*)}}">
And then the function would be:
_showCancel: function(userDataObs, infoObs) {
var userData = userDataObs.value;
var info = infoObs.value;
if( !userData || !info) return;
...
Questions:
Do you see any fundamental mistakes with ways 1 to 4?
Is WAY 1 really the best way to go about it? (it feels like it right now)
Is WAY 3 an acceptable pattern?
ADDENDUM
What would I do in cases where I have:
<paper-button
raised
hidden$="{{!_showCancel(item.id,userData.config.usersCategories,userData.config.userCategories,userData.config.usersCategories.*)}}"
>
Cancel
</paper-button>
(NOTE: usersCategories is an Array)
And _showCancel being:
_showCancel: function(categoryId, userCategories) {
for (var k in usersCategories) {
var $ucat = usersCategories[k];
if ($ucat.categoryId == categoryId) {
if ($ucat.status == 'applied' || $ucat.status == 'granted') return true;
}
}
return false;
},
The point being: I want both easy access to usersCategories, but don't want to get the value out of the "strange" array modifiers etc. So, is this a good pattern?
The "Way 1" is the right one. But, you should only reference the variables that you need, and you should always use them as they are defined in your function header.
For example, you use:
{{_showCancel(userData.generic.id,info.userId,info._children.gigId.userId,info.accepted,info.cancelled,info.paymentTerms)}}
with the following function header:
_showCancel: function(viewerUserId,offerUserId,gigUserId, accepted, cancel, paymentTerms).
But then inside the function, you reference info.accepted and info.cancelled, whereas you should used accepted and cancelled.
This is because inside the function, the referenced values will always be up-to-date, whereas referencing variables via this.<variable-name> can sometimes contain older values.
In order for my answer to be complete, I will also explain certain "problems" with other ways.
Way 2: Here, you only reference the Object as a whole. This won't trigger the call via subproperty change, so it won't work as desired.
Way 3 and Way 4 are similar, and both are overkills. With the object.* notation, you listen to all subproperty changes, which you most likely don't want.
tl;dr
Go with "Way 1" and make things simpler by using computed properties.
To do this, change:
<template is="dom-if" if="{{_showCancel(userData.generic.id,info.userId,info._children.gigId.userId,info.accepted,info.cancelled,info.paymentTerms)}}">
To:
<template is="dom-if" if="{{isCanceled}}">
And add the following computed property to the Polymer element:
isCanceled: {
type: Boolean,
computed: '_showCancel(userData.generic.id,info.userId,info._children.gigId.userId,info.accepted,info.cancelled,info.paymentTerms)'
}
You already have _showCancel defined, so this is actually it. The code should work the same as your "Way 1" example, only the dom-if is cleaner. This is especially useful if you re-use the condition on multiple occurences.
If you have any questions, don't hesitate do add a comment about it.

#ngrx/store Ignore first emitted value

store.select() emits previous store state.
Is it possible to subscribe to changes from "this point forward" without getting the previous store value?
If you are not interested in the first emitted value, you should be able to use the skip operator:
store.select(...).skip(1)...
skip operators need piping now, you can use skip like this:
store.pipe(select(...), skip(1));
In terms of the 'hacky' part, it is a standard practice in ngrx to set an initial state with properties set to null. and that value gets emitted initially. so the first value you get will be null in these cases.
Alternatively you could also consider skipwhile(https://www.learnrxjs.io/learn-rxjs/operators/filtering/skipwhile) and use it like this:
store.pipe(select(...), skipWhile(val => val === undefined));
where undefined is the initial value of the property you are interested in. Rather than setting the initial value of the property to undefined, you could use null as the initial value as well, and change the above skipwhile() accordingly.
Just sharing my thoughts (and solution) after reading #Niz's answer.
This is a perfect, practical example of how to utilize the difference between null and undefined. When you initialize your state with null, you're basically saying:
I don't care about differentiating the nullable future state from the
initial one. I don't care if the user is null because he has signed
out or because he just didn't sign in
However, in some cases this could be insufficient. Think about a case when you need an asynchronous call (implemented in effects) in order to know if you have an active user session. Based on the selection result, you should determine whether to show a login modal or redirect to a content page. With initial user state set to null, you'd pop up that modal and then immediately hide it when that asynchronous call returns a session value.
With initial state set to undefined you can make that differentiation, saying:
Initially, I know nothing about my state, then it's undefined. When I know it should be empty, then I'll set it to null.
Therefor, as a practical solution, I set everything on the app's initialState to undefined. In the example above, I need to know if the login modal should be displayed after the asynchronous call resolves. skipWhile(val => val === undefined) will do the job for sure, but repeating it over and over again feels a little tedious. Plus, it's not really descriptive to our use case. I created a rxjs-custom-operators.ts with a shortened implementation:
import { Observable } from "rxjs";
import { skipWhile } from "rxjs/operators";
export const skipInitial = () => {
return <T>(source: Observable <T>): Observable<T> => {
return source.pipe(skipWhile(value => value === undefined));
};
};
Usage:
navigateOnLoad(): void {
this.store.pipe(select(selectAuthUser), skipInitial()).subscribe((authUser: CognitoUser) => {
// Navigate to login if !authUser, else navigate to content...
});
}

Identity in ractive data arrays

I have an object of message streams that looks like this:
ractive.data.messages:
{
stream_id1: {
some_stream_metadata: "foo",
stream: [
{id: "someid1", message: "message1"},
{id: "someid2", message: "message2"}
]
},
stream_id2: {
some_stream_metadata: "bar",
stream: [
{id: "someid3", message: "message3"},
{id: "someid4", message: "message4"}
]
}
}
main_template:
{{#messages[ current_stream_id ]}}
{{>render_message_stream}}
{{/messages[ current_stream_id ]}}
render_message_stream:
{{#stream}}
<div class="stream">
...someotherstuff...
{{>render_message}}
</div>
{{/stream}}
render_message:
<div class="message">
...someotherstuff...
{{message}}
</div>
I change "current_stream_id" to change the rendered stream of messages.
On updates, i change the contents of the message streams like this:
ractive.merge(
"messages.stream_id1.stream",
new_message_stream,
{
compare: function ( item ) { return item.id; }
});
I also tried the compare: true option instead of the function, with the same results:
Ractive always thinks that these two messages belong effectively to the same DOM element, even though they live in a completely different message stream:
ractive.data.messages[ "stream_id1" ].stream[1].message
ractive.data.messages[ "stream_id2" ].stream[1].message
Problems:
When there are intro/outro animations ractive animates always just the end of the messages stream, even when a message in the middle of the stream was deleted, i need help to make ractive understand which messages are identical.
When i change the current_stream_id, ractive does not rerender the complete {{>render_message_stream}} partial, but goes inside the existing dom and changes the {{message}} field in all existing messages, though this might be good for dom element reuse, this triggers a lot of animations that are wrong. (Eg. it triggers intro/outro animations for the last message in the stream if stream1 has one message more than stream2).
One of these issues has a straightforward answer; unfortunately the other one doesn't.
I'll start with the easy one - the fact that
ractive.data.messages[ "stream_id1" ].stream[1].message
ractive.data.messages[ "stream_id2" ].stream[1].message
belong to the same DOM element. You're correct in that Ractive updates the existing elements rather than removing them and creating new ones - this is a core part of its design. In this case that's undesirable behaviour, but you can work around it like so:
// instead of immediately switching to a new stream ID like this...
ractive.set( 'current_stream_id', 'stream_id2' );
// you can set it to a non-existent ID. That will cause the existing DOM
// to be removed. When you set it to an ID that *does* exist, new DOM
// will be created:
ractive.set( 'current_stream_id', null );
ractive.set( 'current_stream_id', 'stream_id2' );
// or, if you'd like the initial transitions to complete first...
ractive.set( 'current_stream_id', null ).then(function () {
ractive.set( 'current_stream_id', 'stream_id2' );
});
The other issue - that merge() isn't merging, but is instead behaving as though you were doing ractive.set('messages.stream_id1.stream', new_message_stream) - is tougher. The problem is that while you and I know that {{#messages[ current_stream_id ]}} equates to messages.stream_id1 when current_stream_id === 'stream_id1, Ractive doesn't.
What it does know is that we have an expression whose value is determined by messages and current_stream_id. When the value of either of those references changes, the expression is re-evaluated, and if that value changes, the DOM gets updated - but using a standard set(). When you do ractive.merge('messages.stream_id1.stream', ...), Ractive updates all the things that depend on keypaths that are 'upstream' or 'downstream' of messages.stream_id1.stream - which includes messages. So that's how the expression knows that it needs to re-evaluate.
It's possible that a future version of Ractive will be able to handle this case in a smarter fashion. Perhaps it could make a note of arrays that are subject to merge operations, and check evaluator results to see if they're identical to one of those arrays, and if so use merge() rather than set(). Perhaps it could analyse the function in some way to see if the {{#messages[ current_stream_id ]}} section should register itself as a dependant of messages.stream_id1 for as long as current_stream_id === 'stream_id1', rather than the internally-generated ${messages-current_stream_id-} keypath.
None of that helps you in the meantime though. The only way to use merge() in your current situation is to have a separate reference that doesn't use an expression, and a bit of magic with pattern observers:
main_template:
{{#current_messages}} <!-- rather than `messages[ current_stream_id ]` -->
{{>render_message_stream}}
{{/current_messages}}
render_message_stream:
{{#current_message_stream}} <!-- rather than `stream` -->
<div class="stream">
{{>render_message}}
</div>
{{/current_message_stream}}
code:
ractive.observe( 'current_stream_id', function ( id ) {
var current_messages = this.get( 'messages.' + id );
this.set( 'current_messages', current_messages );
// hide existing stream, then show new stream
this.set( 'current_message_stream', null ).then(function () {
this.set( 'current_message_stream', current_messages.stream );
});
});
// when ANY message stream changes, we see if it's the current one - if so, we
// perform a merge on the top-level `current_message_stream` array
ractive.observe( 'messages.*.stream', function ( new_stream, old_stream, keypath, id ) {
// the value of any * characters are passed in as extra arguments, hence `id`
if ( id === this.get( 'current_stream_id' ) ) {
this.merge( 'current_message_stream', new_stream, {
compare: function ( item ) {
return item.id;
}
});
}
});
I've set up a JSFiddle demonstrating this. I hope it makes sense, let me know if not - and sorry I didn't get round to answering this question much sooner.

attempting to use Drupal's Rules module and check a truth value

if([node:term] == "Main Stage Theatre"){return TRUE;}
First I have a condition that checks the node creation of an Event.
Now, this second condition i want to check the taxonomy terms, and if it is the right one it will add to the my node queue.
my above piece of code I don't think is correct. Can someone help me with the check a truth value feature?
Your code looks correct, did you include PHP tags? You have to wrap your code in <?php ?> tags in the Truth value textfield.
I have this working with:
<?php if ('[node:term]' == 'Comedy') { return TRUE; } else { return FALSE; } ?>
Note: If you are allowing multiple terms to be selected for an Event node, [node:term] only returns the "top" term.
For that kind of comparison I suggest using "text comparison"; it compares two text fields and evaluates TRUE if the two fields match. You could use a "token" (e.g. [node:term] in your example) in one field and text (e.g. Main Stage Theatre) in the other field. You can also check a box to return true if the two fields do not evaluate as equal and there is another checkbox option to use Regex for the match comparison. I just used it to check the language of the content a comment was left on.

Resources