map observable on nested observables

Hi, I would like to map a filter containing two conditions, which values may be modified by the user:

var array = Observable({
  color: 0,
  favorite: Observable(true),
  discount: Observable(true)
}, 
{
 color: 1,
 favorite: Observable(false),
 discount: Observable(true)
}, 
{
 color: 2,
 favorite: Observable(false),
 discount: Observable(false)
});

var filter = Observable(
{
  color: Observable(0),          // Possible values = {'ALL', 0, 1, 2}
  type: Observable('FAVORITES')  // Possible values = {'NONE', 'FAVORITES', 'DISCOUNT'}
});

var filteredArray = filter.map(function(currentFilter){
  return array.where(function(item){
    return (currentFilter.value.color.value === 'ALL' || currentFilter.value.color.value === item.color)
        && (
             currentFilter.value.type.value === 'NONE' ||
            (currentFilter.value.type.value === 'FAVORITES' && item.favorite.value) &&
            (currentFilter.value.type.value === 'DISCOUNT' && item.discount.value)
           )
  });
}).inner();

I export filteredArray and use it in Each.

I tried splitting the two filtering conditions in two simple Observables and mapping two times. First one to obtain a helper array filtered on the first condition (color), then re-mapping this array on the second condition to obtain the final array.
It works but cannot use it as it freezes the animations that are activated right after.

Thanks for your help

Hi!

To map over multiple observables, you can use an observable function:

var filteredArray = Observable(function() {
     var cond1 = filter1.value;
     var cond2 = filter2.value;
     return array.where(function() { ... use cond1 and cond2 ... })
}).inner();

We are working on cleaner patterns for this in the future.

Sorry for the late reply!

EDIT: As of 0.26 this actually doesn’t work, see below

Thanks for your reply Anders,

However, I’m getting the following error message:

ERROR:
Name: Error: Observable(): cannot create new observables while evaluating dependency function :where
Error message: Uncaught Error: Observable(): cannot create new observables while evaluating dependency function :where
File name: FuseJS/Observable.js
Line number: 197
Source line: throw new Error(“Observable(): cannot create new observables while evaluating dependency function :” + x);
JS stack trace: Error: Observable(): cannot create new observables while evaluating dependency function :where
at Observable._assertNoDependence (FuseJS/Observable.js:197:8)
at Observable.where (FuseJS/Observable.js:809:7)
at null._func (Preview/ColorPicker.js:129:19)
at evaluate (FuseJS/Observable.js:135:18)
at depChanged (FuseJS/Observable.js:119:16)
at obs.beginSubscriptions (FuseJS/Observable.js:167:3)
at Observable.addSubscriber (FuseJS/Observable.js:255:8)
at res.beginSubscriptions (FuseJS/Observable.js:1084:8)
at Observable.addSubscriber (FuseJS/Observable.js:255:8)

The original array is defined in another .js (such as in Hikr example).
I recently uploaded to v0.26 and performed a uno clean.

(Same error if I use:

 var filteredArray = Observable(function() {
     var cond1 = filter1.value;
     var cond2 = filter2.value;
     return array.where(function() { return true; });
}).inner();

OK, try this:

filter1.map(function(f1) {
    return filter2.map(function(f2) {
         return array.where(function() { .. use f1 and f2 .. })
    }).inner()
}).inner()

Thanks for the quick response.
It’s getting better: no runtime error now, but I am getting no results if I use one of the filters.
Using return true; inside the where returns the items in the original array.

marcmelo8@gmail.com wrote:

Thanks for the quick response.
It’s getting better: no runtime error now, but I am getting no results if I use one of the filters.
Using return true; inside the where returns the items in the original array.

Actually, changing the where condition in some ways displays the entire array (as if no filter was applied), and changing the filter value has no effect

Hi!

The above approach should work, so if it doesn’t I can’t help without seeing a complete test case.

Anders Lassen wrote:

Hi!

The above approach should work, so if it doesn’t I can’t help without seeing a complete test case.

Hi Anders, Ok, where can I upload it?

Ideally create an isolated test case you can post here in the forum :slight_smile:

Ok!

The code will display: the entire array and content (top of the screen), the current filter values (color & second filter), the filtered array and the filtering options (second filter then colors to filter).

Project structure:

MainView.ux
/Data
    Data.js
    Context.js
/MyViews
    FirstView.ux
    MyJava.js

unoproj:

{
  "RootNamespace":"",
  "Packages": [
  	"Fuse",
  	"FuseJS"
  ],
  "Includes": [
    "*",
    "Data/*.js:Bundle",
  ]
}

MainView.ux:

<App>

	<ClientPanel>
		<MyViews.FirstView />
	</ClientPanel>

</App>

Data.js:

function Item(id, parentColor, discount, favorite, recommended){
	this.id = id;
	this.parentColor = parentColor;
	this.discount = discount;
	this.favorite = favorite;
	this.recommended = recommended;
}

var COLOR_TYPES = {
	PINK: '#FFDCE7',
	RED: '#C20032',
	PURPLE: '#561235',
	BLUE: '#0033FF',
	GREEN: '#89FAA6',
	ALL: '#FFFFFF'
};

var array = [

	new Item(0, COLOR_TYPES.PINK, true, false, false),
	new Item(1, COLOR_TYPES.PINK, false, true, true),
	new Item(2, COLOR_TYPES.PINK, false, false, false),
	new Item(3, COLOR_TYPES.RED, true, true, true),
	new Item(4, COLOR_TYPES.RED, true, true, false),
	new Item(5, COLOR_TYPES.PURPLE, false, false, true),
	new Item(6, COLOR_TYPES.BLUE, true,  true, true),
	new Item(7, COLOR_TYPES.GREEN, true, false, true)
];

function getArray() {
	return new Promise(function(resolve, reject) {
		setTimeout(function() {
			resolve(array);
		}, 0);
	});
}

module.exports = {
	getArray: getArray
};

Context.js:

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

var array = Observable();

Data.getArray()
	.then(function(newArray) {
		array.replaceAll(newArray);
	})
	.catch(function(error) {
		console.log("Couldn't get array: " + error);
	});

module.exports = {
	array: array
};

MyJava.js:

var Observable = require('FuseJS/Observable');
var Context = require("../Data/Context");

var COLOR_TYPES = {
	PINK: '#FFDCE7',
	RED: '#C20032',
	PURPLE: '#561235',
	BLUE: '#0033FF',
	GREEN: '#89FAA6',
	ALL: '#FFFFFF'
};

var colors = Observable(
{
	name: "all",
	color: COLOR_TYPES.ALL
},
{
	name: "pink",
	color: COLOR_TYPES.PINK
},
{
	name: "red",
	color: COLOR_TYPES.RED
},
{
	name: "purple",
	color: COLOR_TYPES.PURPLE
},
{
	name: "blue",
	color: COLOR_TYPES.BLUE
},
{
	name: "green",
	color: COLOR_TYPES.GREEN
});

var selectedColor = Observable(COLOR_TYPES.ALL);

function setSelectedColor(x){
	selectedColor.value = x.data.color;
} 


var FILTER_TYPES = {
	NONE: 'none',
	FAVORITES: 'favorites',
	RECOMMENDED: 'recommended',
	DEALS: 'deals'
};

var filterButtons = Observable(
{
	text: Observable("All"),
	type: Observable(FILTER_TYPES.NONE)
},
{
	text: Observable("Favorites"),
	type: Observable(FILTER_TYPES.FAVORITES)
},
{
	text: Observable("Recommended"),
	type: Observable(FILTER_TYPES.RECOMMENDED)
},
{
	text: Observable("Deals"),
	type: Observable(FILTER_TYPES.DEALS)
});

var activeFilter = Observable(FILTER_TYPES.NONE);

function setActiveFilter(x){
	activeFilter.value = x.data.type.value;
} 


var array = Context.array;

var filteredArray = selectedColor.map(function(f1) {
    return activeFilter.map(function(f2) {
         return array.where(function(item) { 
         	return ( 
         		(item.parentColor == f1.value || f1.value == COLOR_TYPES.ALL )
         		&&
         		(
         			f2.value == FILTER_TYPES.NONE ||
         			(f2.value == FILTER_TYPES.DEALS && item.discount ) ||
         			(f2.value == FILTER_TYPES.FAVORITES && item.favorite ) ||
         			(f2.value == FILTER_TYPES.RECOMMENDED && item.recommended ) 
         			)
         		);
          });
    }).inner();
}).inner();


module.exports = {

	colors: colors,
	selectedColor: selectedColor,
	
	array: array,
	filteredArray: filteredArray,

	filterButtons: filterButtons,
	activeFilter: activeFilter,

	setSelectedColor: setSelectedColor,
	setActiveFilter: setActiveFilter

};

FirstView.ux:

<Panel ux:Class="MyViews.FirstView">

	<JavaScript File="MyJava.js" />

	<StackPanel Alignment="Top" >
		<Text Value="Full Array:" FontSize="20" />
		<StackPanel Orientation="Horizontal" ItemSpacing="10" >
			<Text Value="Id" />
			<Text Value="Color" />
			<Text Value="Discount" />
			<Text Value="Favorite" />
			<Text Value="Recommended" />
		</StackPanel>
		<Each Items="{array}" >
			<StackPanel Orientation="Horizontal" ItemSpacing="10" >
				<Text Value="{id}" />
				<Text Value="{parentColor}" />
				<Text Value="{discount}" />
				<Text Value="{favorite}" />
				<Text Value="{recommended}" />
			</StackPanel>
		</Each>

		<Text Value="Filter values:" FontSize="20" />
		<StackPanel Orientation="Horizontal" >
			<Text Value="Selected color: " />
			<Text Value="{selectedColor}" />
		</StackPanel>
		<StackPanel Orientation="Horizontal" >
			<Text Value="Selected filter: " />
			<Text Value="{activeFilter}" />
		</StackPanel>
	</StackPanel>

	<StackPanel Alignment="Center">
		<Text Value="Filtered array:" FontSize="20" />
		<Each Items="{filteredArray}" >
			<Text Value="{id}" />
		</Each>
		<WhileEmpty Items="{filteredArray}">
		    <Text>No results</Text>
		</WhileEmpty>
	</StackPanel>

	<StackPanel Alignment="Bottom" >
		<StackPanel Orientation="Horizontal" ItemSpacing="30" >
			<Each Items="{filterButtons}" >
				<Button Text="{text}" Clicked="{setActiveFilter}" />
			</Each>
		</StackPanel>
		<StackPanel Orientation="Horizontal" >
			<Each Items="{colors}" >
				<Rectangle Width="60" Height="60" Color="{color}" Clicked="{setSelectedColor}" />
			</Each>
		</StackPanel>
	</StackPanel>

</Panel>

Thanks for your support,

Hi!

I didn’t run your code, but I can see that you are using .value on f1 and f2. You shouldn’t (need to) do that, the values passed in are already the leaf values. Does that help?

We are working on some new observable operators (.combine and .combineLatest) which will make this nicer in the future.

Thanks a lot Anders, it’s working now! :smiley: