flowy icon indicating copy to clipboard operation
flowy copied to clipboard

Conditional Blocks

Open johndkn opened this issue 5 years ago • 23 comments

Hi Alyssa X,

Thanks for the amazing work ! Flowy looks awesome.

Would it be possible to create "Conditional Blocks" (one with one input and two outputs) ?

Screenshot example : image

Thank you in advance

johndkn avatar Dec 23 '19 17:12 johndkn

How would you see it best implemented w/ the current mechanics? Special blocks that instead of being able to have infinite children, have 2 children max, each for a different outcome? Doable for sure, just trying to think of the best UX for this sort of scenario (also because in this case the order of the "children blocks" is important, and you should be able to choose if you put it on the left or the right side, something that isn't possible w/ the current engine).

alyssaxuu avatar Dec 24 '19 12:12 alyssaxuu

Thanks for your reply :)

I gave it a lot of thought since yesterday, and maybe the best UX is to have instead "Filtering Blocks" in which we could implement conditional logic.

Example : image

johndkn avatar Dec 24 '19 12:12 johndkn

There are dozens of flowchart type scripts out there packed with numerous features and UI/UX artifacts. What's significant about Flowy is its very vanilla-like simplicity. If it's going to be a library encapsulated into other larger projects, it's important to keep simplicity in mind to keep options open for unforeseen behavior types. Here's a possible litmus test to consider when adopting new behavior: Could these blocks and relationships be translated 1:1 into a textual format (ie. Markdown) for documentation? The current "If ___ is from ___ then ___" or "When ___ days have pass" statements inside these blocks should be simple enough to create conditional prompts for developers to add time delays, pending tasks, logic dependencies, and from another issue groups without adding artifacts that could increase user complexity.

I guess we're at a fork. Does Flowy build additional visual UI/UX artifacts to denote behavior or does it leave it as-is in its current textual state and leave it to the beholder to decide.

fqure avatar Dec 24 '19 17:12 fqure

We solved this with a block controller that owns how parent/child relationships get setup, e.g. what children can snap to what parent and some parents are 'decision' types.

And, my vote would be to keep flowy simple per @fqure

image

robhoward avatar Jan 29 '20 00:01 robhoward

This is awesome @robhoward! Can you give us more details on how you achieved this?

johndkn avatar Feb 06 '20 15:02 johndkn

Sure, @johndkn - hopefully I can explain it in a way that makes sense :)

We created a "blockBase" that wraps flowly. For example, it manages an internal array of blocks and the loading/saving. It also handles all the logic of what block and snap to what block/etc. We started with the example @alyssaxuu put together and took it further. Then we added functions for getting blocks by id/etc.

Each block is a separate object. For example, below is the "Yes" block.

The blockBase then renders all the loaded blocks into the UI by blockType. It places them in the UI by group, e.g. Trigger, Condition, Action. It then calls methods on the the block when it is dragged, rendered, needs to show properties, check parents, etc.

We're so happy with how this turned out. We always had workflow in our system, but we couldn't find a solid, simple way to visualize it. So much so, we just updated our website with a screen shot highlighting it :)

https://www.dailystory.com/ (just scroll down slightly)

And we also made some updates to flowy itself. I'd like to try to get those submitted as pull requests soon.

// ┌──────────────────────────────────────────────────────────────────── // │ Decision block Yes // └──────────────────────────────────────────────────────────────────── var blockYes = {

// ┌────────────────────────────────────────────────────────────────────
// │ Required - unique name of the block matches enum AutomationDecisionType.Yes
// └────────────────────────────────────────────────────────────────────
name: "Yes",

// ┌────────────────────────────────────────────────────────────────────
// │ Required - friendly name of the block, shown in messages
// └────────────────────────────────────────────────────────────────────
friendlyName: "Yes",

// ┌────────────────────────────────────────────────────────────────────
// │ Required - list of parents this block can snap to. Empty array means all
// └────────────────────────────────────────────────────────────────────
allowedParents: ['HasOpenedEmail', 'HasClickedEmail', 'HasRepliedToTextMessage', 'HasTextMessageReply','HasCustomRule'],

// ┌────────────────────────────────────────────────────────────────────
// │ Required - type of block
// └────────────────────────────────────────────────────────────────────
blockType: 'Condition',

// ┌────────────────────────────────────────────────────────────────────
// │ Required - does this block have properties
// └────────────────────────────────────────────────────────────────────
hasProperties: false,

// ┌────────────────────────────────────────────────────────────────────
// │ Required - validates the block, called on save
// └────────────────────────────────────────────────────────────────────
validate: function (props) {
    return { status: true };
},

// ┌────────────────────────────────────────────────────────────────────
// │ Required - renders HTML for block in toolbar
// └────────────────────────────────────────────────────────────────────
getToolbarBlock: function () {
    return `<div class="blockelem create-flowy noselect">
                    <input type="hidden" name="blockelemtype" class="blockelemtype" value="${this.name}">                            
                        <div class="blockin">
                            <div class="blockico"><span></span><i class="fad fa-thumbs-up"></i></div>
                            <div class="blocktext"><p class="blocktitle">${this.friendlyName}</p><p class="blockdesc">Used with a condition</p></div>
                        </div>
                </div>`;
},

// ┌────────────────────────────────────────────────────────────────────
// │ Required - renders HTML when block is placed in designer
// └────────────────────────────────────────────────────────────────────
renderDrag: function (elem, parent) {
    elem.innerHTML += `<div class='blockyinfo-basic'><p class='blockyname-basic'>${this.friendlyName}</p></div>`;
    elem.classList.add('decision-yes');
}

}; blockBase.register(blockYes);

robhoward avatar Feb 06 '20 20:02 robhoward

Woow! Thanks @robhoward for these details! This "wrapper" seems like an amazing solution to organize the blocks and the logic. Would love to see this in a pull request.

johndkn avatar Feb 08 '20 15:02 johndkn

Thanks for the contribution @robhoward . Do you plan on creating a pull request? I want to use flowy but I really need the conditional blocks feature.

fj-vega avatar Mar 26 '20 14:03 fj-vega

Thank you @robhoward , I think it's an amazing idea. With this you can create "virtual" blocks that group things together. You can even decide to hide the children block if you dont want to show, amazing.

yellow1912 avatar Apr 16 '20 15:04 yellow1912

I am considering adding the functionality of creating "grouped blocks", for example, as shown by @robhoward in this image (which can be used for conditional blocks):

image

That way it could be dragged from the "panel" as a single unit, and have two outputs prebuilt.

I don't think I can come up with another way to implement conditional/logic blocks differently. Adding multiple arrows to the output of a block would be really tricky to implement with the current library, as it would cause issues with spacing and centering.

I'm going to explore the grouping functionalty for the next release (which will be bundled with new features and improvements), but it is not guaranteed yet.

alyssaxuu avatar Apr 24 '20 16:04 alyssaxuu

Just my $0.02 on this as we have this in production now:

An action, such as "text message replied to" can evaluate to true or false. We're finding that in some cases customers only care about one condition vs both.

So, as long as the group block could have the concept of removing one of the items in the group on the design surface.

robhoward avatar Apr 24 '20 16:04 robhoward

Just my $0.02 on this as we have this in production now:

An action, such as "text message replied to" can evaluate to true or false. We're finding that in some cases customers only care about one condition vs both.

So, as long as the group block could have the concept of removing one of the items in the group on the design surface.

Interesting. I suppose that could be possible by adding an optional parameter when creating blocks that allows it to be broken apart by the user (or else locked together).

alyssaxuu avatar Apr 24 '20 16:04 alyssaxuu

Possibly a new view to create a group block that is saved to the global block list to save space on the main view while keeping easy usability for those that don't need group blocks?

fqure avatar Apr 24 '20 17:04 fqure

Are conditional blocks as described by @johndkn planned anytime soon?

aadityak avatar Jun 26 '20 20:06 aadityak

@alyssaxuu are you planning to implement conditional blocks anytime soon?

@robhoward have you implement conditional blocks and submit a pull request?

Here is my way.

I have a "Class" who is loading modules window.Automation. Each module has his own functions but have same requirements info. All forms elements is linked with his hidden input.

window.ConditionSource = {
    init: function () {
        this.info = {
            class: 'ConditionSource',
            name: 'source',
            title: 'Source',
            blocks: {
                canvas: '...../block.html'
            },
            selects: [
                {name: 'ConditionSource_selectState', hidden: 'source_state'},
                {name: 'ConditionSource_selectSource', hidden: 'source_id', populate: true, function: 'selectSource'}
            ]
        }
    },
....
}

I have a test button to match with the first contact in DB. Success = green, failed = red. if success but his parent is false = red

WIP, have to make block allowParent, etc...

I didn't want a popup to enter settings. I wanted to do all in the canvas, to be faster.

check video img

cdebattista avatar Sep 29 '20 23:09 cdebattista

HS does a good job with having the arrows pre-rendered for decision points. Labels on the arrows would be amazing! https://blog.hubspot.com/customers/sales-management-pain-points-workflow-automation

papeventures avatar Jan 09 '21 20:01 papeventures

Here is my way.

I have a "Class" who is loading modules window.Automation. Each module has his own functions but have same requirements info. All forms elements is linked with his hidden input.

window.ConditionSource = {
    init: function () {
        this.info = {
            class: 'ConditionSource',
            name: 'source',
            title: 'Source',
            blocks: {
                canvas: '...../block.html'
            },
            selects: [
                {name: 'ConditionSource_selectState', hidden: 'source_state'},
                {name: 'ConditionSource_selectSource', hidden: 'source_id', populate: true, function: 'selectSource'}
            ]
        }
    },
....
}

I have a test button to match with the first contact in DB. Success = green, failed = red. if success but his parent is false = red

WIP, have to make block allowParent, etc...

I didn't want a popup to enter settings. I wanted to do all in the canvas, to be faster.

check video img

@cdebattista amazing work! Can you be kind enough to jump start toward the similar direction. I am trying to build similar application (user flow). Any help/ direction would be really appreciated.

jatinderbhola avatar Mar 24 '21 01:03 jatinderbhola

@robhoward I want to have only one child when you drop the block like you have in DailyStory, not like this one: multipleParents Can you help me with this? Thanks

loralll0 avatar Dec 13 '21 10:12 loralll0

Hi @laura040796 unfortunately my version of flowy is pretty much completely re-written, so I'm not sure how much help the code would be. But I can tell you how it works:

During the snapping event a check is done to see if the block is allowed to snap to the parent:

            // check if can snap these blocks together
            if (!blockBase.allowedToSnapToParent(drag, parent, first))
                return false;

The allowedToSnapToParent method walks through the conditions to determine if snapping will return true/false. For example:

    // Is this parent in the child's allowed parents list
    if (isParentInChildAllowedList()) {
        allowed_to_snap = true;
    } else {
        DsUtility.statusMessage(`'${child_block.friendlyName}' cannot be used with '${parent_block.friendlyName}'`);
        allowed_to_snap = false;
    }

    // Is this child in the parent's allowed childs list
    if (isChildInParentAllowedList()) {
        allowed_to_snap = true;
    } else {
        DsUtility.statusMessage(`'${child_block.friendlyName}' cannot be used with '${parent_block.friendlyName}'`);
        allowed_to_snap = false;
    }

    // Does the parent allow children
    if (allowed_to_snap && parent_block && undefined !== parent_block.allowChildren && !parent_block.allowChildren) {
        DsUtility.statusMessage(`'${parent_block.friendlyName}' cannot have additional steps`);
        allowed_to_snap = false;
    }

    // Does this parent already have children?
    if (allowed_to_snap) {
        if (isChildAllowedInParent(parent, child_block)) {
            allowed_to_snap = true;
        } else {
            allowed_to_snap = true;
        }
    }

robhoward avatar Dec 13 '21 15:12 robhoward

@robhoward is possible see your version?

jhaineymilevis avatar May 19 '22 22:05 jhaineymilevis

@jhaineymilevis unfortunately it's pretty much been re-written from the original flowy at this point. What are you trying to solve for?

robhoward avatar May 23 '22 10:05 robhoward