Best methadology for single page/ multi screen design

I know this is a bit of a general query, but I've been trying a couple of things recently around this and have had mixed success. I want to go back to the drawing board and find the best approach.

Problem: Design an app with a navigation panel on left of screen, which contains a list (like the Sampler or Flickr samples). Simply put - when an option is selected from the list, a new screen is loaded into the main section/panel to its right.

I've tried to do this by having different custom kinds, and trying to find a way to load/unload them dependant on list selection. I initially done this by defining the kinds (screens) in the main kind (right hand panel) as hidden. Then dependant on the list option selected, I show/hide the relevant one(s). This caused me issues with my layout. Should I be doing something around create/destroy?

I noticed the Sampler app takes the approach of having everything in separate html/js files and loading those in dependant on list selection.

Is there a way to do this as per my first example - i.e. a bunch of custom created kinds/components that each represent a screen, which are loaded/unloaded into a main panel dependant on a list selection? Sounds like the cleanest and nicest solution, but I'm struggling for a solid example.

I've gone through all the samples/examples and can't find something quite like this. I'm wondering would anyone have an example of something like this - would be a life saver as I'm struggling a bit to figure out the best way to do this :) Applogies if this is a very basic question - I just want to figure out the best practice here for a work app I'm building. Thanks!

Comments

  • Do you want to defer loading and parsing the JS for the screens until requested or just not render them until requested?

    The former could be implemented with the RemoteControl kind I created (see blog post for details). The latter is probably a just a matter of creating new components contained by the Panels instance as needed. The unloading might be a bit tricky to keep the animations and indices right.
  • Hi Ryan,

    Had a play with your RemoteControl kind on jsfiddle. It's very cool! That could be exactly the type of thing I'm looking for. I'm going to have a play with the code today to see if I can do what it is I want to achieve. Question - would there be any issue using this in a commercial app?

    Been following you on Twitter for a while - I will be paying much more attention to your tweets/blogs from now on - really good stuff that :)

    Again, thanks so much! Will keep you posted here as to how I go with this.

    Cheers,

    Frank.
  • Been having a play with your code. I encountered a problem - probably a pretty fundamental thing, so sorry - but I done the following to the "Select a Sample" section:
    
    {kind:"onyx.Picker", onChange:"pickerChanged", components:[
    	{content:"CheckBoxSample", href:"../navtest1/lib/onyx/samples/CheckboxSample.js", remoteKind:"onyx.sample.CheckboxSample"}
    ]}
    
    And I get this error. Am I missing something, perhaps in the package.js file?

    image
  • re: using in a commercial app - no issues. i probably should have put a license string somewhere but it's free to use.

    i added the onyx sample to the fiddle and it works so there's probably an issue on your side. My first guess would be you're missing the two patched files for the enyo loader (loader.js and boot.js). you can snag them from the fiddle resources or the ENYO-1825 branch of my enyo fork.

    https://raw.github.com/ryanjduffy/enyo/ENYO-1825/loader.js
    https://raw.github.com/ryanjduffy/enyo/ENYO-1825/source/boot/boot.js
  • Firstly, thanks for allowing this to be used commercially. I'll see how I go with my prototype, will all depend on that whether it could become something that we sell.

    Secondly - your suggestions were absolutely spot on! Working perfectly now.

    Again, want to stress how thankful I am for your help Ryan. Much appreciated.
  • enyo.kind({
    	name:"enyo.RemoteControlScrim",
    	kind:"onyx.Scrim",
    	classes:"onyx-scrim-translucent",
    	style:"text-align:center",
    	showing:false,
    	components:[
    		{kind:"onyx.Spinner", style:"position:absolute;top:50%;margin-top:-30px;display:inline-block"}
    	]
    });
    
    enyo.kind({
    	name: "enyo.RemoteControl",
    	kind: "Control",
    	loadState: 0,
    	scrimKind:"enyo.RemoteControlScrim",
    	published:{
    		animate:true
    	},
    	events:{
    		onLoad:"",
    		onLoadFailed:""
    	},
    	components:[
    		{name:"ani", kind:"Animator", startValue:65, endValue:1, onStep:"aniStep", onStop:"aniStop", onEnd:"aniStop"}
    	],
    	getScrim:function() {
    		// defer scrim creation ...
    		return this.$.scrim || this.createComponent({name:"scrim", kind:this.scrimKind || "Control", owner:this}).render();
    	},
    	refresh: function() {
    		this.setLoadState(0);
    		this.load();
    	},
    	setLoadState:function(state) {
    		this.loadState = state;
    		if(this.loadState == 1) {
    			this.getScrim().show();
    		} else if(this.loadState == 2) {	// what about errors?
    			if(this.animate) {
    				this.$.ani.play();
    			} else {
    				this.aniStop();
    			}
    		}
    	},
    	load: function() {
    		if(this.href && this.loadState == 0) {
    			this.setLoadState(1);
    			//enyo.job(this.id+"_load", enyo.bind(this, function() {
    				enyo.load([this.href], enyo.bind(this, "loaded"));
    			//}), 2000);
    		}
    	},
    	loaded: function(block) {
    		// should be null unless my pull is changed to always created the failed member
    		if(!block.failed || block.failed.length === 0) {
    			this.setLoadState(2);
    			this.remote = this.createComponent({kind:this.testingKind, classes:(this.fit) ? "enyo-fit" : ""}).render();
    
    			this.doLoad({remote:this.remote});
    		} else {
    			this.setLoadState(-1);
    			this.doLoadFailed({failed:block.failed});
    		}
    	},
    	aniStep:function(source) {
    		this.getScrim().applyStyle("opacity", source.value/100)
    	},
    	aniStop:function(source) {
    		this.getScrim().hide();
    	}
    });
    
    enyo.kind({
    	name: "enyo.LazyPanels",
    	kind: "enyo.Panels",
    	defaultKind: "enyo.RemoteControl",
    	initComponents:function() {
    		for(var i=0,c;c=this.components[i];i++) {
    			var ctor = enyo.constructorForKind(c.kind || "enyo.Control");
    			if(!ctor && c.href) {
    				c.testingKind = c.kind;
    				c.kind = this.defaultKind;
    			}
    		}
    
    		this.inherited(arguments);
    	},
    	indexChanged:function(oldIndex) {
    		var panel = this.getClientControls()[this.index];
    		if(panel instanceof enyo.RemoteControl) {
    			panel.load();				
    		}
    
    		this.inherited(arguments);
    	}
    });
    
    enyo.kind({
    	name: "App",
    	kind: "FittableColumns", 
    	classes: "enyo-fit", 
    	components: [
    	// This is the main panel, that has all the other panels inside of it and allows the main panel to slide
    	{kind: "LazyPanels", name:"mainPanels", classes:"panels enyo-fit", arrangerKind: "CollapsingArranger", components: [
    	// This is the secondary panel, the one that is on the far left that will house the buttons
    	{kind: "Panels", name:"contentPanels", arrangerKind:"CollapsingArranger", draggable:false, classes:"panels enyo-fit",   style: "width: 20%;", onTransitionFinish: "contentTransitionComplete", components: [
    			{kind: "FittableRows", components: [
    			// This is the button that is at the top of the panel on the left of the page
    			{kind: "onyx.Button", name: "testingButton", content: "Testing button", style: "width: 100%;", ontap: "testingButtonTap"},
    			{kind: "onyx.Button", name: "getIndexOfPanelsButton", content: "Get Index", style: "width: 100%;", ontap: "getIndexOfPanelsButtonTap"},
    			{kind: "onyx.Button", name:"Testing a new page", content:"Testing the new page", style: "width: 100%;", ontap: "testingNewPageButtonTap"},
    			{fit: true},
    			{fit: true, components: [
    			// This is the toolbar that is at the bottom of the left panel
    			{kind: "onyx.Toolbar", style: "height: 40px;"}
    			]}
    		]}
    		]},
    		// This is the MAIN panel, the one that will house all of the information
    		{kind: "FittableRows", fit: true, components: [
    			{fit: true, classes: "fittable-sample-fitting-color"},
    			{fit: true, components: [
    				{content: "Thank you for using the WJGM Radio application built on the EnyoJS Framework! We are still in the middle of building our application, so please bear with us as we fix things up and make them work!"},
    			// This is the toolbar on the main panel
    			{kind: "onyx.Toolbar", style: "height: 40px;", ontap:"toggleFullScreen", components: [
    				// This is the grabber for the main panel, to show that it can be colapsed. Though a tap anywhere will colapse the panel
    				{kind: "onyx.Grabber"}
    			]}
    		]}
    	]},
    
    	]},	
    ],	
    	create: function() {
    		this.inherited(arguments);
    	},
    	index:0,
    	showPage: function(source) {
    		this.$.mainPanels.setIndex(source.index);
    	},
    	// This is the function to toggle the colapse of the main panel
    	toggleFullScreen: function() {
    		this.$.mainPanels.setIndex(this.$.mainPanels.index ? 0 : 1);
    	},
    	// This is the function to toggle the content of the testingButton, obviously this isn't the final function of the button, but it's a start
    	testingButtonTap: function() {
    		this.$.testingButton.setContent("Testing Button was tapped!!!");
    	},
    	getIndexOfPanelsButtonTap: function() {
    		var index = this.$.mainPanels.setIndex(0);
    		this.log(index);
    	},
    	sourceEvents: function(source, event) {
    		if(!this.hasNode()) return;
    
    		var testingKind = "Testing";
    		var e = event.selected;
    		this.$.testingKind.setValue(e.testingKind || "");
    	},
    	testingNewPageButtonTap: function() {
    		var kind = this.$.testingKind.getValue();
    
    		if(kind) {
    			this.index++;
    			this.$.mainPanels.createComponent({kind:"enyo.RemoteControl", testingKind:kind, owner:this}).render();
    			this.$.mainPanels.refresh();
    			this.$.mainPanels.setIndex(this.index);
    			this.log(this.index++);
    			this.log("testingKind = " + kind);
    		}
    	}
    });
    I have a kind that is named Testing, which is my source/ folder, and named in my package.js file.. But I keep getting the error
    Uncaught TypeError: Cannot call method 'getValue' of undefined
    when I click on my button. Any idea? It's been far too long since I've touched Enyo code, or JavaScript code in general. Lol.

    Thanks in advance!
  • this.$.testingKind is looking for a actual component named "testingKind". Do you want this.testingKind or this.$[testingKind] instead?
  • Even if I change it to this
    
    sourceEvents: function(source, event) {
    		if(!this.hasNode()) return;
    
    		var e = event.selected;
    		this.remoteKind.setValue(e.remoteKind || "");
    	},
    testingNewPageButtonTap: function() {
    		//var kind = this.remoteKind.getValue();
    		var kind = this.remoteKind.getValue();
    
    
    		if(kind) {
    			this.index++;
    			this.$.mainPanels.createComponent({kind:"enyo.RemoteControl", remoteKind:kind, owner:this}).render();
    			this.$.mainPanels.refresh();
    			this.$.mainPanels.setIndex(this.index);
    			this.log(this.index++);
    			this.log("remoteKind = " + kind);
    		}
    	}
    It still doesn't want to get the value. I set the remoteKind using this
    			{kind: "onyx.Button", name:"Testing a new page", content:"Testing the new page", style: "width: 100%;", ontap: "testingNewPageButtonTap", remoteKind: "Testing"},
    
    on my button in the main content panel
  • You don't need to use getValue. Your remoteKind property is a string, just use it.
  • Take a look at this fiddle and see if it clarifies things for you. The button's ontap handler is updated to pull the name of the remote kind from the triggering control (your button in this case) and assumes that the source file has the same name.
        testingNewPageButtonTap: function (sender) {
            this.index++;
            var remoteKind = sender.remoteKind;
            this.$.mainPanels.createComponent({
                kind: "enyo.RemoteControl",
                remoteKind: remoteKind,
                owner: this,
                href: "source/"+remoteKind+".js"
            }).render();
            this.$.mainPanels.reflow();
            this.$.mainPanels.setIndex(this.index);
        }
    That said, if you're already including that file using enyo.depends(), you don't need to use the enyo.RemoteControl kind I shared above. You can either declare that panel in the mainPanels components array or create it at runtime:
        testingNewPageButtonTap: function (sender) {
            this.index++;
            var remoteKind = sender.remoteKind;
            this.$.mainPanels.createComponent({
                kind: remoteKind,
                owner: this
            }).render();
            this.$.mainPanels.reflow();
            this.$.mainPanels.setIndex(this.index);
        }
Sign In or Register to comment.