Send and update data between pages

I’m working with the app which starts on the page (CompanyPage.ux) list of companies. The part of template looks like this:

<Each Items="{companies}">
	<Rectangle Height="1" Color="#666" />
	<Panel Clicked="{goToDetails}" Background="#FFF">
		<Text FontSize="20" Value="{name}" />
	</Panel>	
</Each>

JS code looks like this:

var companies = Context.companies;

function goToDetails(arg) {
	var company = arg.data;
	router.push("employee", company);
}

At EmployeePage user will see the list of company’s details like name, image and description and the list of employees who works for the company, example:

<Panel Background="#EFEFEF">
	<Button Text="< Back" Margin="15" Alignment="Left" Clicked="{cancel}" />
	<Button Text="" Margin="15" Alignment="Right" >
		<Image File="../Assets/edit.png" />
		<Clicked Handler="{edit}" />
	</Button>
	<h1 Margin="0,10,0,10" TextAlignment="Center" Value="{company_name}" />
</Panel>
<Rectangle Height="1" Color="#ccc" />
<Grid ColumnCount="3" Margin="0,15,0,15">
	<Rectangle CornerRadius="50" Width="96" Height="96" Column="0">
		<ImageFill Url="{company_img}" StretchMode="UniformToFill"/>
	</Rectangle>
	<Text Column="1" ColumnSpan="2" Value="{company_description}" TextWrapping="Wrap" Margin="0,3,0,3" />
</Grid>
<Rectangle Height="1" Color="#ccc" />
<h2 Margin="0,10,0,10" TextAlignment="Center" Value="Employees"/>
<Each Items="{employees}">
	<Rectangle Height="1" Color="#ccc" />
	<Panel Clicked="{goToEmployee}" Background="#FFF">
		<Text FontSize="20" Value="{name}" />
	</Panel>	
</Each>

JS code looks like this:

var company = this.Parameter;
company.subscribe(module);

function edit() {
	router.push("editCompany", company.value);
}

function cancel() {
	router.goBack();
	Context.refreshCompanies();
	setTimeout(cleanElectricians, 300);
}

var company_name = company.map(function(x) { return x.name; });
var company_img = company.map(function(x) { return x.avatar_urls['96']; });
var company_description = company.map(function(x) { return x.description; });

I tried to use args on edit function but it gets a data about the first company’s employee and values from .map function. So if I would like to try send data to next page with args it would look something like that

router.push("editCompany", {company_name: data.company_name.value, company_description: data.company_description.value});

My EditCompany page looks like this:

<Page ux:Class="EditCompanyPage" Background="#EFEFEF">
	<Router ux:Dependency="router" />
	<JavaScript File="EditCompany.js" />
	<DockPanel ux:Name="appDock">
		<ScrollView>
			<StackPanel>
				<Panel Background="#EFEFEF">
					<Button Text="< Back" Margin="15" Alignment="Left" Clicked="{cancel}" />
					<h1 Margin="0,10,0,10" TextAlignment="Center" Value="{company_name}" />
				</Panel>
				<Rectangle Height="1" Color="#ccc" />
				<Text Margin="10,10,10,0">Description:</Text>
				<TextView Padding="5" Margin="10,5,10,0" Height="100" Background="#FFF" Value="{company_description}" TextWrapping="Wrap" />
				<MyButton Text="Oppdater" Alignment="Right" Clicked="{update}" />
			</StackPanel>
		</ScrollView>
	</DockPanel>
</Page>

JS Code:

var Observable = require("FuseJS/Observable");
var API = require("Modules/Backend.js");
var Context = require("Modules/Context");

var companies = Context.companies;

var company = this.Parameter;
company.subscribe(module);

function update() {
	var user_id = company.value.id;
	API.updateUser(user_id, company_description.value).then(function(updatedCompany) {
		//TO DO
	});
}

function cancel() {
	router.goBack();
}

var company_name = company.map(function(x) { return x.value.name; });
var company_description = company.map(function(x) { return x.description; });

module.exports = {
	company_name: company_name,
	company_description: company_description,

	update: update,
	cancel: cancel
}

I wonder about what is the best practice to send the same data between 2 or more pages. At the same time I can’t wrap my head around how to update data at third page and show changes on the second page. I tried the method from hikes tutorial where I update Context.companies using for loop and replaceAt() function, but if I understood right so changes won’t be shown at second page since data comes from this.Parameter.

The best practice in this case would probably be only sending an ID between the pages.

The data for a particular item can live in your model (Context?), and when you pass an ID of an item along with a router action, then you can just ask your model for the other properties you need to show (or manipulate) on a given screen.

Then, whenever you change the data right in the model, it will update in whatever other screens that reactively pull data from the model.

Hope this helps!

Yes, it helped a lot! I have created an Observable in Context.js file which get filled/replaced with data when user click on a company and then I get data from this Observable on details and edit page.

var comp = Observable();

function getCompany(id) {
	API.getUser(id).then(function(selectedCompany) {
		comp.replaceAll([selectedCompany]);
	}).catch(function(error) {
		console.log("Couldn't get company: " + error);
	});
}

Then I do something like this on details page

var company_name = Context.comp.map(function(x) { return x.name; });
var company_img = Context.comp.map(function(x) { return x.avatar_urls['96']; });
var company_description = Context.comp.map(function(x) { return x.description; });

On edit page

var company_name = Context.comp.map(function(x) { return x.name });
var company_description = Context.comp.map(function(x) { return x.description; });

function update() {
	var user_id = data.value.id;
	API.updateUser(user_id, company_description.value).then(function() {
		Context.getCompany(user_id);
	}).then(function(){
		router.goBack();
	});
}

Is it a right way to do it? I’m trying to figure out what is the best practice here because I have a problem with data showing on details page. When user switch between companies so it’s possible to see that name and description of previous company get replaced with the new one on the details page.

Yes, in general this approach seems to be right. I would perhaps keep a list of companies in an Observable list, and have the “currently selected” company as a component-local variable that maps to the list and picks the company by its id.

Something along these lines, which uses both the id parameter passed to router, and accesses that conceptual Observable list of companies (pseudo-code, not tested):

var companyData = this.Param.flatMap(function(p) {
    return Context.companies.where(function(c) { return p.id == c.id; });
});

As for removing old instances of pages that show the “previous” company data, there are several ways to approach this. One can be clearing the data from a Deactivated that you put on the page:

<JavaScript>
function clearLocalData() {
    // .. select a blank company or clear your component-local Observables in some other way
}
module.exports = {
    clearLocalData: clearLocalData
};
</JavaScript>
...
<Deactivated>
    <Callback Handler="{clearLocalData}" />
</Deactivated>

Another way could be only navigating to the page once it has done rendering. You can achieve that by playing around with DeferFreeze.

And finally, for the lazy ones, there’s a way to completely remove previously spawned instances of pages, but this of course comes at a cost in terms of performance, since the pages wouldn’t get reused. See Navigator.Reuse for details.

Perfect! Thanks for you help.

var companyData = this.Parameter.flatMap(function(p) {
	return Context.companies.where(function(c) {
		return p.id == c.id;
	});
});

Just tested this right now and it works much faster than my way where I create separate Observable with selected company. I belive this causes the delay when details page getting loaded since I send an extra fetch to fill Context.comp Observable instead getting company directly from Context.companies.

Thanks for your help again. Keep up the good work.