Setting visibility based on ux:Property values

Hello there! I am really loving Fuse, but have come across a problem I’m unable to resolve from looking through the site and forum posts.

I have a custom component (currently defined in my MainView.ux because the ux:Property tag doesn’t seem to work yet when the component is in a separate file).

I’m trying to set visiblity of a Grid (I’ve also tried a StackPanel) within my component based on a Property value I pass in. My JS for the component looks like this.

var Observable = require("FuseJS/Observable");

var isFormatSmall = Observable("Collapsed");
if(Format.value == "Small") { isFormatSmall.value = "Visible"; }

module.exports = {
  isFormatSmall: isFormatSmall
};

Where Format is the name of the Property I am using to pass in as an attribute “Small” (or ‘Large’, which is my default)

<string ux:Property="Format" ux:Value="Large" />

and isFormatSmall is bound to the Visiblity of the Grid.

<Grid ColumnCount="2" Visibility="{isFormatSmall}">

I’ve also tried using ‘self.Format’ and ‘Property self.Format’ as the left operand.

I get the following error (I’ll post the entire stack trace if you want, but it’s huge):

Exception: System.Exception: Removing an invalid container from Layout
  at Fuse.Layouts.Layout.RemoveContainer (Fuse.Controls.Panel element) [0x00000] in <filename unknown>:0

If I remove the if() statement, everything builds fine, and the Visibility is Collapsed on my Grid as I’d expect, but I obviously don’t get the behavior I want.

Thoughts?

Here is the ux structure around the Grid:

<StackPanel ux:Class="CardBase" ux:Name="self"
            Margin="10,5" Padding="10,5" ItemSpacing="5"
            Alignment="Top">
    <Rectangle ux:Binding="Appearance" Fill="#FFF" CornerRadius="5" />
    <DropShadow Angle="90" Distance="1" Size="5" />

    <string ux:Property="Format" ux:Value="Large" />
    <string ux:Property="CardType" ux:Value="Offer" />
    <string ux:Property="NumPeople" ux:Value="110" />

    <JavaScript>
      var Observable = require("FuseJS/Observable");

      var isFormatSmall = Observable("Collapsed");

      if(Format.value == "Small") { isFormatSmall.value = "Visible"; }

      module.exports = {
          isFormatSmall: isFormatSmall
      };
    </JavaScript>

    <Grid ColumnCount="2" Visibility="{isFormatSmall}">
        <StackPanel Orientation="Horizontal" Margin="5,0,5,5">
            <Images.OfferCard.TypeBar.icnOffers Width="8" Height="8" Margin="0,0,10,0" />
            <Text Value="{Property self.CardType}" Alignment="Left" Font="HelveticaNeue_Light" FontSize="10" TextColor="#87C024" />
        </StackPanel>
        <StackPanel Orientation="Horizontal" Alignment="Right" Margin="5,0,5,5">
            <Text Value="{Property self.NumPeople}" Font="HelveticaNeue_Light" FontSize="9" TextColor="#979797" />
            <Text Value=" People" Font="HelveticaNeue_Light" FontSize="9" TextColor="#979797" />
        </StackPanel>
    </Grid>
</StackPanel>

And then I call it using:

<StackPanel>
    <Each Items="{cardList}">
        <CardBase Format="{format}"
                  CardType="{type}"
                  NumPeople="{numPeople}" />
    </Each>
</StackPanel>

Hey,

Now that I see the full structure I see some things that might be in your way, but I’m not completely sure if they will fix your issue: First, you shouldn’t keep the Javascript inside the item that you are going to duplicate within the Each loop. You can set it apart and evaluate the isFormatSmall in other ways.

For example:

<App>
    <DockPanel>
        <JavaScript>
            var Observable = require("FuseJS/Observable");
            var isFormatSmall = Observable("Collapsed");

            module.exports = {
                  isFormatSmall: isFormatSmall
            };
          </JavaScript>
  ...
  </DockPanel>
</App>

<StackPanel ux:Class="CardBase" ... >
    <Match Value="{isFormatSmall}">
        <Case String="Small">
            <Change someTarget.Visilibity="Visible" />
        </Case>
        <Case String="Large">
            ...
        </Case>
    </Match>
       ...
</StackPanel>

Hi

When you define ux:Properties, they won’t be available to you in JavaScript, so the manipulation you are attempting here won’t work. We are working hard at improving ux:Property, but i’m not sure this case will ever be fully covered since it relies on synchronous communication between UX and JS (which is something we are avoiding for performance reasons).

I would suggest you drop the step of having your component interpret the string into “change visibility”-commands, and instead do that as part of your data-generation. That way you can data-bind the visibility directly.

Can I use Match on a ux:Property within my custom component?

<StackPanel ux:Class="CardBase" ux:Name="self">    <string ux:Property="Format" ux:Value="Large" />

    <Match Value="{Property self.Format}">
      <Case String="Small">
          <Change Target="CardSmall.Visibility" Value="Visible" />
      </Case>
      <Case String="Large">
          <Change Target="CardLarge.Visibility" Value="Visible" />
      </Case>
    </Match>

    <!-- Small format layout -->
    <Grid ux:Name="CardSmall" Visibility="Collapsed">

    </Grid>
    <!-- end Small format layout -->

    <!-- Large format layout -->
    <StackPanel ux:Name="CardLarge" Visibility="Collapsed">

    </StackPanel>
    <!-- end Large format layout -->
</StackPanel>

<StackPanel>
    <Each Items="{cardList}">
        <CardBase Format="{format}" />
    </Each>
</StackPanel>

I basically am trying to make a custom component with a Large and Small layout and be able to pass in an attribute to define which I use (for each item in my loop)

This code is giving me an odd error:

.build/Simulator/Local53/Cache/GeneratedCode/MainView.g.uno(288,51): E1068: Expected ',' or ';'
.build/Simulator/Local53/Cache/GeneratedCode/MainView.g.uno(442,51): E1068: Expected ',' or ';'

I come from a Flex world so I’m trying to ‘unlearn what I have learned’ and adapt to the Fuse style… :slight_smile:

Actually, I found that this may work better. And it fits with the way I wanted to inherit from my CardBase component.

<StackPanel>
    <Each Items="{cardList}">
        <Panel>
            <Match Value="{format}">
                <Case String="Small">
                    <CardSmall />
                </Case>
                <Case String="Large">
                    <CardLarge />
                </Case>
            </Match>
        </Panel>
    </Each>
</StackPanel>

I’m going to try this for a bit and see if I can get this to work the way I need it to. Apologies if this is what you were referring to from the get-go. :slight_smile:

Still not quite getting where I need to be. Here is my entire app at the moment:

<App Theme="NativeWithFallback">
    <JavaScript>
        function CardBase() {
            var format;
            var numPeople;
            var cardType;        }
    var Observable = require(&quot;FuseJS/Observable&quot;);
    var cardList = Observable();

    var testCard1 = new CardBase();
        testCard1.format = &quot;Large&quot;;
        testCard1.numPeople = 105;
        testCard1.cardType = &quot;Offer&quot;;

    cardList.add(testCard1);

    var testCard2 = new CardBase();
        testCard2.format = &quot;Small&quot;;
        testCard2.numPeople = 97;
        testCard2.cardType = &quot;Offer&quot;;

    cardList.add(testCard2);

    module.exports = {
      cardList: cardList
    }
&lt;/JavaScript&gt;

&lt;StackPanel ux:Class=&quot;CardBase&quot; ux:Name=&quot;self&quot;
            Margin=&quot;10,5&quot; Padding=&quot;10,5&quot; ItemSpacing=&quot;5&quot; Alignment=&quot;Top&quot;&gt;
    &lt;Rectangle ux:Binding=&quot;Appearance&quot; Fill=&quot;#FFF&quot; CornerRadius=&quot;5&quot; /&gt;
    &lt;DropShadow Angle=&quot;90&quot; Distance=&quot;1&quot; Size=&quot;5&quot; /&gt;

    &lt;string ux:Property=&quot;CardType&quot; ux:Value=&quot;Offer&quot; /&gt;
    &lt;string ux:Property=&quot;NumPeople&quot; ux:Value=&quot;110&quot; /&gt;
&lt;/StackPanel&gt;

&lt;CardBase ux:Class=&quot;CardSmall&quot; ux:Name=&quot;self&quot;&gt;
    &lt;Grid ColumnCount=&quot;2&quot;&gt;
          &lt;StackPanel Orientation=&quot;Horizontal&quot; Margin=&quot;5,0,5,5&quot;&gt;
            &lt;Images.OfferCard.TypeBar.icnOffers Width=&quot;8&quot; Height=&quot;8&quot; Margin=&quot;0,0,10,0&quot; /&gt;
            &lt;Text Value=&quot;{Property self.CardType}&quot; Alignment=&quot;Left&quot; Font=&quot;HelveticaNeue_Light&quot; FontSize=&quot;10&quot; TextColor=&quot;#87C024&quot; /&gt;
          &lt;/StackPanel&gt;
          &lt;StackPanel Orientation=&quot;Horizontal&quot; Alignment=&quot;Right&quot; Margin=&quot;5,0,5,5&quot;&gt;
            &lt;Text Value=&quot;{Property self.NumPeople}&quot; Font=&quot;HelveticaNeue_Light&quot; FontSize=&quot;9&quot; TextColor=&quot;#979797&quot; /&gt;
            &lt;Text Value=&quot; People&quot; Font=&quot;HelveticaNeue_Light&quot; FontSize=&quot;9&quot; TextColor=&quot;#979797&quot; /&gt;
          &lt;/StackPanel&gt;
    &lt;/Grid&gt;

    &lt;Grid ColumnCount=&quot;2&quot; CellSpacing=&quot;5&quot;&gt;
          &lt;Images.OfferCard.PromoImage /&gt;
          &lt;Text TextWrapping=&quot;Wrap&quot; Font=&quot;HelveticaNeue_Light&quot; FontSize=&quot;15&quot; TextColor=&quot;#5D5E5E&quot; Value=&quot;Test Small&quot; /&gt;
    &lt;/Grid&gt;
&lt;/CardBase&gt;

&lt;CardBase ux:Class=&quot;CardLarge&quot; ux:Name=&quot;self&quot;&gt;
    &lt;Grid ColumnCount=&quot;2&quot;&gt;
        &lt;StackPanel Orientation=&quot;Horizontal&quot; Margin=&quot;5,0,5,5&quot;&gt;
            &lt;Images.OfferCard.TypeBar.icnOffers Width=&quot;8&quot; Height=&quot;8&quot; Margin=&quot;0,0,10,0&quot; /&gt;
            &lt;Text Value=&quot;{Property self.CardType}&quot; Alignment=&quot;Left&quot; Font=&quot;HelveticaNeue_Light&quot; FontSize=&quot;10&quot; TextColor=&quot;#87C024&quot; /&gt;
        &lt;/StackPanel&gt;
        &lt;StackPanel Orientation=&quot;Horizontal&quot; Alignment=&quot;Right&quot; Margin=&quot;5,0,5,5&quot;&gt;
            &lt;Text Value=&quot;{Property self.NumPeople}&quot; Font=&quot;HelveticaNeue_Light&quot; FontSize=&quot;9&quot; TextColor=&quot;#979797&quot; /&gt;
            &lt;Text Value=&quot; People&quot; Font=&quot;HelveticaNeue_Light&quot; FontSize=&quot;9&quot; TextColor=&quot;#979797&quot; /&gt;
        &lt;/StackPanel&gt;
    &lt;/Grid&gt;

    &lt;StackPanel&gt;
        &lt;Images.OfferCard.PromoImage /&gt;
        &lt;Text TextWrapping=&quot;Wrap&quot; Font=&quot;HelveticaNeue_Light&quot; FontSize=&quot;15&quot; TextColor=&quot;#5D5E5E&quot; Value=&quot;Test Large&quot; /&gt;
    &lt;/StackPanel&gt;
&lt;/CardBase&gt;

&lt;DockPanel&gt;
    &lt;StatusBarBackground Dock=&quot;Top&quot; /&gt;

    &lt;StackPanel&gt;
        &lt;Each Items=&quot;{cardList}&quot;&gt;
            &lt;Panel&gt;
                &lt;Match Value=&quot;{format}&quot;&gt;
                    &lt;Case String=&quot;Small&quot;&gt;
                        &lt;CardSmall CardType=&quot;{cardType}&quot; NumPeople=&quot;{numPeople}&quot; /&gt;
                    &lt;/Case&gt;
                    &lt;Case String=&quot;Large&quot;&gt;
                        &lt;CardLarge CardType=&quot;{cardType}&quot; NumPeople=&quot;{numPeople}&quot; /&gt;
                    &lt;/Case&gt;
                &lt;/Match&gt;
            &lt;/Panel&gt;
        &lt;/Each&gt;
    &lt;/StackPanel&gt;

&lt;/DockPanel&gt;


Couple of things here:

  1. I have to use a Panel inside the Each statement, before my custom component, it was the only way I could get rid of an error I was seeing (not a big deal, I just don’t know why)

  2. This is giving me a warning, problem with inheritance I think:

    .build/Simulator/Local74/Cache/GeneratedCode/MainView.g.uno(78,19): W0000: 'CardSmall.InitializeUX()' hides inherited member 'CardBase.InitializeUX()' -- use the 'new' modifier if hiding is intentional
    
    
  3. And I am seeing the same error as I originally posted. CardLarge and CardSmall both work, unless I’m using both in the Match/Case Statement.

    Exception: System.Exception: Removing an invalid container from Layout
    at Fuse.Layouts.Layout.RemoveContainer (Fuse.Controls.Panel element) [0x00000] in <filename unknown>:0
    
    

Hmm… it seems like Match isn’t intended to be used this way?

So I guess my real question is basically ‘how would I build components with varying layouts and display them conditionally based on the data I have?’

My thought was either to:

  1. Build a component that shows/hides various layouts and data based on attributes I pass in (from the data received) OR
  2. Build various components I conditionally called based on the data received

Thoughts?

I’ve tried a few things since yesterday, including trying to do some Uno code behind, unfortunatly to no avail.

I went back to try something else that seems like it should work, but I’m struggling with some odd errors.

I have the following in a custom component, defined in my MainView.ux:

<Match Value="{format}">
    <Case String="Small">
        <Change smallFormat.Visibility="Visible" />
    </Case>
    <Case String="Large">
        <Change largeFormat.Visibility="Visible" />
    </Case>
</Match>

<Grid ux:Name="smallFormat" Visibility="Collapsed">
...
</Grid>

<StackPanel ux:Name="largeFormat" Visibility="Collapsed">
...
</StackPanel>

Where I am passing in ‘format’ from a collection of data. This is throwing an error:

 E8001: 'Change' does not have an attached property called 'smallFormat.Visibility', nor does it support implicit set-properties

And I’ve also tried it using the other syntax:

<Change Target="smallFormat.Visibility" Value="Visible" />

But it throws a different error:

E1068: Expected ',' or ';'

And if I go into the referenced code for that error, it does look like it’s incorrectly formed:

var self = new Fuse.Animations.Change`1<Fuse.Elements.Visibility>();

Any thoughts? I feel like I am really close to a solution.

In my experience the best way to do this would be to have

<Change smallFormat.Visibility="Visible" />

inside WhileTrue or WhileFalse statements.

So you would have something like:

<Match Value="{format}">
    <Case String="Small">
          <Toggle Target="setIsSmall" />
      </Case>

    <Case String="Large">
        <Toggle Target="setIsLarge" />
      </Case>
</Match>

And then somewhere else in your app,

<WhileTrue ux:Name="setIsSmall" Value="false">
    <Change smallFormat.Visibility="Visible" />
</WhileTrue>

<WhileTrue ux:Name="setIsLarge" Value="false">
    <Change largeFormat.Visibility="Visible" />
</WhileTrue>

Hope that helps!

Thanks!

For some reason it’s like the ‘format’ value isn’t being passed in or evaluated.

Here is my full app code:

<App Theme="NativeWithFallback">
    <JavaScript>
        var Observable = require("FuseJS/Observable");

        function CardBase() {
            var format;
            var numKlicklers;
            var cardType;
        }

        var cardList = Observable();

        var testCard1 = new CardBase();
            testCard1.format = "Small";
            testCard1.numKlicklers = 105;
            testCard1.cardType = "Offer";
        cardList.add(testCard1);

        var testCard2 = new CardBase();
            testCard2.format = "Large";
            testCard2.numKlicklers = 97;
            testCard2.cardType = "Offer";
        cardList.add(testCard2);

        module.exports = {
            cardList: cardList
        }
    </JavaScript>

    <DockPanel>
        <StatusBarBackground Dock="Top" />
        <ScrollView>
            <StackPanel>
                <Each Items="{cardList}">
                    <StackPanel Margin="10,5" Padding="10,5" ItemSpacing="5" Alignment="Top">
                        <!-- edited here -->
                        <Text Value="{format}" Alignment="Center" FontSize="20" />
                        <Match Value="{format}">
                            <Case String="Small">
                                <Toggle Target="setIsSmall" />
                            </Case>

                            <Case String="Large">
                                <Toggle Target="setIsLarge" />
                            </Case>
                        </Match>

                        <WhileTrue ux:Name="setIsSmall" Value="false">
                            <Change smallFormat.Visibility="Visible" />
                        </WhileTrue>

                        <WhileTrue ux:Name="setIsLarge" Value="false">
                            <Change largeFormat.Visibility="Visible" />
                        </WhileTrue>

                        <Grid ColumnCount="2">
                            <StackPanel Orientation="Horizontal" Margin="5,0,5,5">                                <Assets.OfferCard.TypeBar.icnOffers Width="8" Height="8" Margin="0,0,10,0" />
                                <Text Value="{cardType}" Alignment="Left" FontSize="10" TextColor="#87C024" />
                            </StackPanel>
                            <StackPanel Orientation="Horizontal" Alignment="Right" Margin="5,0,5,5">
                                <Text Value="{numKlicklers}" FontSize="9" TextColor="#979797" />
                                <Text Value=" Klicklers" FontSize="9" TextColor="#979797" />
                            </StackPanel>
                        </Grid>

                        <Grid ux:Name="smallFormat" ColumnCount="2" CellSpacing="5" Visibility="Collapsed">
                            <Assets.TestImageSmall />
                            <Text TextWrapping="Wrap" FontSize="15" TextColor="#5D5E5E" Value="Test Text Small..." />
                        </Grid>

                        <StackPanel ux:Name="largeFormat" ItemSpacing="5" Visibility="Collapsed">
                            <Assets.TestImageLarge />
                            <Text TextWrapping="Wrap" FontSize="15" TextColor="#5D5E5E" Value="Test Text Large..." />
                        </StackPanel>

                    </StackPanel>
                </Each>
            </StackPanel>
        </ScrollView>


    </DockPanel>

</App>

Basically it renders nothing, neither the large or the small format blocks of markup.