How to access item in Observable array in JS?

Hello!

I have an Observable that looks like this when using JSON.strinigfy in console log illustrated here. It’s a single row Observable array named Happening with parameters like id, title, text, etc… as you can see. Using Value="{Happening.title}" in UX works fine, but then trying to grab the same variable in JS seems to be more confusing.

So far I’ve tried:
Happening.id
Happening.id.value
Happening.value.id
Happening._values.id
but all of them return undefined.

How do I reference to an item in this array?

Hi Ferdinand,

from a quick glance, it seems that what you have there is an Observable inside of an Observable.

So, if that is the result of some reactive operation, you might need to chain .inner() to the end of whatever that operation is.

However, to be able to help, we will need to see some of your JavaScript code!

Ferdinand,

aside from what might be going on in your code that produces the nested Observables, iterating over observable lists is fairly simple. As an example, you could use a forEach operator - but that will be of little value if you’re not creating / using a proper observable list.

Thanks for the quick response! Aah yeah that’s true, but how come it still works with the UX?

Happening gets assigned here from a total list of happenings with .where():

Happening.value = Happenings.where({id: SelectedHappeningId.value});

I tried adding .inner() at the end of the .where() but it didn’t do anything.

Ferdinand, you’re using Observables in a bit weird way.

You see, the .where() reactive operator returns an Observable. And you’re assigning it to the value of another Observable. Why would you do that in the first place?

Instead of that weird structure, you could just have:

var selectedHappening = SelectedHappeningId.flatMap(function(x) {
    return Happenings.where({id: x});
});

I strongly suggest you should spend some more time reading about Observables. For example, this section in the Observable docs explains the flatMap + where combo I used in the snippet above.

Yeah I see. I just thought it would make sense to do it that way. But anyway, I tried to replace it like this:

Happening = SelectedHappeningId.flatMap(function(x) {return Happenings.where({id: x});});

But it doesn’t assign to Happening. The code I’ve posted is from inside an if statement and the Happening observable is declared on the top of the page, so I would like to avoid creating a new observable within the if statement.

No, you can’t do it that way either :slight_smile:

What you describe is the following:

Here, Happening is a new Observable
... yada yada, code ...
if (something happens) {
    make the Happening be a new, completely different Observable
}

Also, “doesn’t assign” is not a valid error report or debugging result. How did you debug that and what allows you to draw this conclusion? Did you make sure the Observable gets consumed?

Ferdinand, it would be much better if you could simply post a complete code example of what it is you’re trying to make and we could take it from there.

Also, I said this before and I’ll say it again - you need to read about Observables.

By not assigned I mean that when I do console.log(JSON.stringify(Happening)) after Happening = SelectedHappeningId.flatMap(function(x) {return Happenings.where({id: x});}) it’s an empty Observable array, it therefore couldn’t have assigned any values to Happening.

Ok maybe I’ve gotten lost in translation here.

Happenings list is created
Happening is created

Happenings are retrieved from database as JSON and assigned to the Happenings observable

(UX) The user clicks on a list item (a happening), that selects it and user is routed to the edit page

An if statement checks whether the user is editing or adding a new happening
    If the user is editing a happening, it gets the Happening in question from Happenings observable and assigns that entry to the Happening observable

(UX) UX uses the Happening observable to fill input fields in the form (Happening.title, Happening.text, etc...)
(UX) The user does changes to the fields and clicks the 'save' button

function save() {
    grabs Happening items, packs them into a JS array and sends to server for SQL (update/insert) through PHP $_POST[]
}

And here’s the whole code:

var Observable = require('FuseJS/Observable');
var HappeningContext = require('Modules/HappeningContext');
var UserContext = require('Modules/UserContext');

var EventId = null;

Happenings = Observable();
NoHappenings = Observable(true);
Loading = Observable(true);
IsHost = Observable(false);
FormTitle = Observable();
FormMode = Observable();

SelectedHappeningId = Observable();
SelectedEventId = Observable();

NewHappening = Observable({event_id: SelectedEventId.value, title: "handsome title", text: "", from: {date: "", time: ""}, to: {date: "", time: ""}});

this.Parameter.onValueChanged(module, function(param) {

	IsHost.value = param.isHost;
	EventId = param.eventId;
	loadHappenings();
});

function loadHappenings() {

	Happenings.clear();
	UserContext.getStorageUser().then(function(user_result) {

		HappeningContext.getHappenings(EventId, user_result.id).then(function(happenings_result) {

			if (happenings_result !== false) {

				Happenings.replaceAll(happenings_result);
				NoHappenings.value = false;
			}

			Loading.value = false;
		});
	});
}

function editHappening(){

	if (IsHost.value) {
		happeningsRouter.push("editHappening");
	}

	FormMode.value = "edit";
	FormTitle.value = "Edit Happening";
    Happening.value = Happenings.where({id: SelectedHappeningId.value});

}

function addHappening(){

	if (IsHost.value) {
    	Happening.clear();
		happeningsRouter.push("addHappening");
	}

	FormMode.value = "add";
	FormTitle.value = "Add Happening";
}

function save() {

	happeningsRouter.goto("savingHappening");

	if (FormMode.value == "add") {

		// HappeningContext.add(happeningObject).then(function(result){

		// 	console.log("result: " + result);
		// });

	} else if (FormMode.value == "edit") {

		var happeningObject = {

			id: Happening.value.id,
			method: "update",
			event_id: Happening.value.event_id,
			title: Happening.value.title,
			text: Happening.value.text,
			from_time: Happening.value.from.time,
			from_date: Happening.value.from.date,
			to_time: Happening.value.to.time,
			to_date: Happening.value.to.date
		};

		HappeningContext.set(happeningObject).then(function(result){
			if (result) {
				happeningsRouter.goto("happenings");
			} else {
				happeningsRouter.goto("errorPage");
			}
		});
	}

	loadHappenings();
}

function retry() {
	if (FormMode.value == "add") {
		happeningsRouter.goto("addHappening");
	} else if (FormMode.value == "edit") {
		happeningsRouter.goto("editHappening");
	}
}

module.exports = {

	Happenings: Happenings,
	NewHappening: NewHappening,
	NoHappenings: NoHappenings,
	IsHost: IsHost,
	editHappening: editHappening,
	addHappening: addHappening,
	FormTitle: FormTitle,
	SelectedHappeningId: SelectedHappeningId,
	Happening: Happening,
	save: save,
	Loading: Loading,
	FormMode: FormMode,
	retry: retry
};

Okay, a pretty neat promise-based implementation all around, so I’m fairly certain we’ll get this fixed in no time :slight_smile:

Now, let’s take a look at the code block in question:

function editHappening(){

    if (IsHost.value) {
        happeningsRouter.push("editHappening");
    }

    FormMode.value = "edit";
    FormTitle.value = "Edit Happening";
    Happening.value = Happenings.where({id: SelectedHappeningId.value});

}

The first thing that is bad, is that you’re running some code after a router.push(), which supposedly lands the user on another screen. That other screen has a different data context, so whatever you do after that call stays in this data context. Router methods should be last calls in particular actions.

This alone means that you should instead move the business logic for selecting a particular “Happening” to the editHappening page.

Disregarding that, there’s another thing: if you were to do this:

Happening.value = Happenings.where({id: SelectedHappeningId.value});
console.log(JSON.stringify(Happening));

… you would always get an empty observable there. Why? Because .where() is a reactive operator and it gets calculated async. You can not expect the result of it to be available in imperative style.

Hope this helps.

Will the page after .push still have it’s own data context even if it’s part of the same UX code as the rest (Navigator within the UX, not using pre-made page classes)?

And in my case

Happening.value = Happenings.where({id: SelectedHappeningId.value});
console.log(JSON.stringify(Happening));

actually does print and say that the observable has been populated, but as you mentioned earlier that it’s an observable within an observable. Maybe it’s just a coincidence and it just happens to pick it async before the console writes but yeah it would make sense that it’s an async operation.

But could you show me an example of how you would assign to Happening from a given Happenings entry by id (given that Happening is already declared as empty observable on the top of the script) and then further down in the code fetch an item (e.g. ‘title’ or ‘id’) from Happening and print it in console? You can call the arrays and items whatever you want of course if it makes it easier to demonstrate.

PS: I’m sorry to be such a pain and I appreciate your patience

A Navigator usually works with templates, disregarding if you have a separate ux:Class for the pages or not. As it spawns a page instance from a template, that instance obviously has its own data context (even if there’s no JavaScript in it).

Now, Fuse actually tries to help you there, and if it can’t find things in the data context you are in, it tries to look a level up and so forth. This may or may not be “good”, so quite frequently it’s better to have an explicit data context in JavaScript for every page (we call them “viewmodels”).

The fact that your console.log() prints something in that case is expected, because the Observable is already there - so it will probably print the previously assigned value (and not the one being calculated). As you keep going to the edit page and back, you should start to see a pattern there.

As for me, I would do it exactly as suggested in Fuse best practices. Where can you find them? Let me answer it this way…

No worries! we all started somewhere and we’re happy to help, but you need to do your homework. In this case, you should take a look at this section in Fuse Tutorial which explains how to correctly handle multi-page navigation. Taking the whole tutorial won’t hurt, either :slight_smile:

Ok, I have split it up so the Happening is edited/added in a separate UX file with its own JavaScript and it’s working now. Going through the tutorial was the first thing I did when I first started using Fuse. It’s been a while since that so maybe I should take a peek at what I did again.

I’ve taken a different approach to get the Happening from the Happenings list. The id of the selected Happening item is passed by param to the edit screen and it uses it to get the specified Happening item from the database. Each Happening item such as .title and .text is assigned to their own Observable and not a an Observable array. It takes a bit more unnecessary code lines, but it works, for now. I’m working on a pretty big project alone with little time left so I’ll come back to this and try to use an array instead later in the final polish.

Thank you for the help!