in Famo.us, how do you pipe events to parent view from inside parent view - famo.us

I have a custom view that contains a surface. I am needing to pipe the surface events to the parent view. I can do this easily from outside the view, but how do I do this from inside the view? Here is my custom view with the code that does NOT work:
define([
"famous/core/view",
"famous/core/Surface",
"famous/modifiers/StateModifier"
], function(View, Surface, StateModifier){
function _createContainer() {
var self = this;
var container = new Surface({
classes: ['blue-bg'],
content: 'HERE IS A LOVELY BIT OF CONTENT FOR MY SURFACE'
});
// THIS DOESN'T WORK, BUT ILLUSTRATES WHAT I'M NEEDING TO DO:
container.pipe(self);
self.containerNode.add(container);
self.form = container;
}
function FormView(){
var self = this;
View.apply(self, arguments);
var containerMod = new StateModifier({
size: self.options.size
});
self.containerNode = self.add(containerMod);
_createContainer.call(self);
}
FormView.prototype = Object.create(View.prototype);
FormView.prototype.constructor = FormView;
FormView.DEFAULT_OPTIONS = {
size: [300, 800]
};
return FormView;
});
Here is example code that does work, but that I want to do from inside the view:
var myView = new View();
mainContext.add(myView);
var surface = new Surface({
size: [100, 100],
content: 'click me',
properties: {
color: 'white',
textAlign: 'center',
backgroundColor: '#FA5C4F'
}
});
myView.add(surface);
surface.pipe(myView);

Inside your custom view FormView you need to pipe to the view's event handler. This will allow the view's events to be seen by a Scrollview when they are added to surfaces.
Change
container.pipe(self);
to
container.pipe(self._eventOutput);

Related

Do famo.us layouts like SequentialLayout participate in the render tree?

When using a SequentialLayout in trying to apply StateModifiers to Surface objects that had been added to a layout, it looks like some unexpected behavior happens:
When applying transformations via setTransform on a StateModifier, I expect to see the transformation applied from the origin of the Surface in question.
Instead, the transform is applied from an origin of 0,0 in relation to the parent SequentialLayout
Given the code below, the above behavior seems to make no logical sense (for context, I am working on a sorting algorithms demo, using famo.us):
/* globals define */
define(function(require, exports, module) {
'use strict';
// import dependencies
var Engine = require('famous/core/Engine');
var Modifier = require('famous/core/Modifier');
var Transform = require('famous/core/Transform');
var Surface = require('famous/core/Surface');
var StateModifier = require('famous/modifiers/StateModifier');
var SequentialLayout = require('famous/views/SequentialLayout');
// create the main context
var mainContext = Engine.createContext();
// your app here
var surfaces = [];
// Sorter
var Sort = require('sort');
var arr = [100,25,20,15,30,-20,-10,10,0];
var min = Math.min.apply(null, arr);
var base_dims = [ 50, 50 ];
arr.forEach(function(el) {
surfaces.push(new Surface({
content: el,
size: base_dims.map(function(d) { return d + (el - min); }),
properties: {
backgroundColor: 'rgb(240, 238, 233)',
textAlign: 'center',
padding: '5px',
border: '2px solid rgb(210, 208, 203)',
marginTop: '50px',
marginLeft: '50px'
}
}));
});
var sequentialLayout = new SequentialLayout({
direction: 0,
itemSpacing:20
});
sequentialLayout.sequenceFrom(surfaces);
mainContext.add(sequentialLayout);
var swap_modifiers = [
new StateModifier({}), new StateModifier({})
];
Sort.bubble_sort_iterative(arr, function(first_swap_index, second_swap_index) {
swap_modifiers[0].setTransform(
Transform.translate(300, 0, 0),
{ duration : 750, curve: 'linear' }
);
swap_modifiers[1].setTransform(
Transform.translate(300, 0, 0),
{ duration : 750, curve: 'linear' }
);
mainContext.add(swap_modifiers[0]).add(surfaces[first_swap_index]);
mainContext.add(swap_modifiers[1]).add(surfaces[second_swap_index]);
});
});
A surface has no origin, a (state-)modifier has an origin. Since you don't provide any origin vaue, the default value is set up, which is [0, 0]. See more:
http://famo.us/university/lessons/#/famous-101/positioning/8
Think of your SequentialLayout as a Render Node in your tree. Adding surfaces to SequentialLayout is in essence adding individual nodes to that tree branch. SequentialLayout happens to be adding each item at the same level in the tree.
Sort.bubble_sort_iterative(... changes the location of the surfaces by adding them to the mainContext of your application. This is the same level as the sequentialLayout and makes their origin the same origin as the sequentialLayout. Not what you wanted!
Remember: Adding a modifier to a context will make that context the parent node.
Without knowing the specifics of the above code, we know that we can add a View rather than surfaces to the sequentialLayout and could transition the View's modifiers within each of those items without changing their location in the render tree.
A simple code example of views in the sequential layout:
arr.forEach(function(el) {
var surfSize = base_dims.map(function(d) { return d + (el - min); });
console.log(size);
var view = new View();
view.mod = new StateModifier({ size: surfSize });
view.surface = new Surface({
content: el,
size: [undefined, undefined],
properties: {
backgroundColor: 'rgb(240, 238, 233)',
textAlign: 'center',
padding: '5px',
border: '2px solid rgb(210, 208, 203)',
marginTop: '50px',
marginLeft: '50px'
}
});
view.add(view.mod).add(view.surface);
surfaces.push(view);
});
Trying to swap out the views from one to the other will give you some unexpected results. It would be better to just swap out the options and content values.

Famo.us scrollview positioning

In my context I have a scroll view, and I'm trying to position the child elements within the view using origin/align properties in a state modifier. However for some reason, when I scroll to the bottom, the last surface isn't displayed correctly.
I can see this is because I'm using origin/align but I'm not sure on the correct way to position child elements within a scroll view? If someone could point me in the right direction that would be great.
Thanks
Code:
main.js
// Create the main context
var mainContext = Engine.createContext();
// Create scroll view
var scrollView = new Scrollview();
var surfaces = [];
scrollView.sequenceFrom(surfaces);
// Create logo
var logoNode = new RenderNode();
var logo = new ImageSurface({
size: [150, 112],
content: 'img/logo.png',
classes: ['logo']
});
// Center logo within context, center and set opacity
var modifier = new StateModifier({
align: [0.5, 0.05],
origin: [0.5, 0.05],
});
logoNode.add(modifier).add(logo);
logo.pipe(scrollView);
surfaces.push(logoNode);
var tribesLength = Object.keys(tribes).length;
for (var t = 0; t < tribesLength; t++) {
var tribe = new TribesView({tribes: tribes, tribe: t});
tribe.pipe(scrollView);
surfaces.push(tribe);
}
mainContext.add(scrollView);
TribesView.js
function TribesView() {
View.apply(this, arguments);
_displayTribe.call(this);
}
TribesView.prototype = Object.create(View.prototype);
TribesView.prototype.constructor = TribesView;
TribesView.DEFAULT_OPTIONS = {
tribes: {},
tribe: 0,
};
function _displayTribe() {
var tribes = this.options.tribes;
var tribe = this.options.tribe;
var node = new RenderNode();
var surface = new Surface({
size: [, 100],
content: tribes[tribe]['name'],
properties: {
background: tribes[tribe]['bg'],
color: 'blue'
}
});
var modifier = new StateModifier({
origin: [0, 0.1],
align: [0, 0.1]
});
node.add(modifier).add(surface);
surface.pipe(this._eventOutput);
this.add(node);
}
module.exports = TribesView;
The problem comes as you suspected, from the use of..
var modifier = new StateModifier({
origin: [0, 0.1],
align: [0, 0.1]
});
in the _displayTribe function. You have to remember that TribeView although labeled a view is nothing representative of something visual on screen. That means when you add this modifier to a surface inside a view, view thinks it is one place, which will be laid out in scrollview, and the modifier will put it in another place (in your case too low on screen).
It is difficult to give you a clear example, because I do not have the data or images or anything to make this look halfway good. If you want to use modifiers within your TribeViews, take a look at chain modifier. I have found it helpful for creating a sort of container surface without using a containerSurface.
Here is what I did to _displayTribe to give the content text an offset relative to the view..
function _displayTribe() {
var tribes = this.options.tribes;
var tribe = this.options.tribe;
var surface = new Surface({
size: [undefined, 100],
properties: {
color: 'blue',
border:'1px solid black'
}
});
this.add(surface)
var text = new Surface({
size:[undefined,true],
content: "Helloo",
})
chain = new ModifierChain()
var containerModifier = new StateModifier({
size: [undefined, 100],
});
var modifier = new StateModifier({
origin: [0, 0.1],
align: [0, 0.1]
});
chain.addModifier(modifier)
chain.addModifier(containerModifier)
this.add(chain).add(text);
surface.pipe(this._eventOutput);
}
I removed anything 'asset' related since it was not available to me. The first surface I added to the view completely unmodified. This allows us to see where scrollview is placing our view. For the text surface, I am using true sizing, and creating a ModifierChain to simulate the container as I previously mentioned. You do this by defining two modifiers, one for the size of the container, and the other for positioning in the container, then chain them together.
Lots of information, Hope this helps!

How to implement Slide to delete in Famo.us with proper event handling

I'm trying to implement a slide to delete. As part of that I have a layer with opacity set to 0 the idea being I'm trying to set several if clauses to gradual change the opacity of the surface so that the word Delete gentle appears as you slide it to the left. At this point I just have it switching at 10pixels for testing. The functions fire but the opacity doesn't change. I think it has something to do with not being piped/event handling being done properly on my part. Any Ideas?
var SnapTransition = require("famous/transitions/SnapTransition");
Transitionable.registerMethod('snap', SnapTransition);
var CSS = require("css/recentActivityCSS");
var Ctrl = require("controllers/recentActivityCtrl");
var homeContentWrap = new Scrollview();
var recentActivities = [];
var ContainerSize = [undefined, 100];
homeContentWrap.sequenceFrom(recentActivities);
for (var i = 0; i < Ctrl.recentActivityList.length; i++) {
var recentActivitiesContainer = new ContainerSurface({
size: ContainerSize,
properties: CSS.recentActivitiesContainer,
});
var redLayer = new Surface({
size: ContainerSize,
content: 'DELETE',
properties: CSS.redLayer,
});
var draggable = new Draggable({
xRange: [-120, 5],
yRange: [0, 0],
});
var textContainer = new ContainerSurface({
size: ContainerSize,
properties: CSS.textContainer,
});
var mod = new Modifier({});
node = new RenderNode(draggable);
node.add(mod).add(textContainer);
textContainer.pipe(draggable);
textContainer.pipe(homeContentWrap);
var opacityMod = new StateModifier({
opacity: 0
});
recentActivitiesContainer.add(node);
recentActivitiesContainer.add(opacityMod).add(redLayer);
recentActivities.push(recentActivitiesContainer);
var trans = {
method: 'snap',
period: 100,
dampingRatio: 0.3,
velocity: 5
};
draggable.on('start', function() {});
draggable.on('update', function() {
var position = this.getPosition();
if (position[0] > (-10)) {
opacityMod.halt();
opacityMod.setOpacity(0, { curve: 'easeOut', duration: 10 });
} else {
opacityMod.halt();
opacityMod.setOpacity(1, { curve: 'easeOut', duration: 10 });
}
});
draggable.on('end', function(){
var position = this.getPosition();
if (position[0] < (-100)) {
alert('delete');
}
this.setPosition([0,0,0], trans);
});
There are a couple of things I did to the draggable 'update' function to achieve what you have described.
1) You need to bind objects to your update function or else you have no real reference to them. When you use opacityMod in your 'update' function, you only alter the last cells opacityMod. Since binding will change the meaning of 'this', I also bind draggable.
2) You say you want a gradual fade. This approach is not going to give you anything gradual. You need to take the position of the draggable and calculate an opacity based on that value. To start, I declare two new variables for fadeStart and fadeEnd, that represent the positions of the draggable X position 0 and 1 opacity respectively.
Also you probably do not need the transition in your setOpacity, but I kept it in anyway.
Here is the updated 'update' function.. Good Luck!
fadeStart = -10;
fadeEnd = -100;
draggable.on('update', function() {
var draggable = this[0];
var opacityMod = this[1];
var position = draggable.getPosition();
if ( position[0] > fadeStart ) {
opacityMod.halt();
opacityMod.setOpacity(0, { curve: 'easeOut', duration: 10 });
} else if ( position[0] > fadeEnd ) {
opacity = (position[0] - fadeStart) / ( fadeEnd - fadeStart );
opacityMod.halt();
opacityMod.setOpacity(opacity, { curve: 'easeOut', duration: 10 });
} else {
opacityMod.halt();
opacityMod.setOpacity(1, { curve: 'easeOut', duration: 10 });
}
}.bind([draggable,opacityMod]));
John has clearly answered this question above, but I wanted to show an alternate approach to the problem. I've seen questions here and in the #famous irc where people are having eventing problems. I've also seen a number of people struggling with binding or the lack of it. And finally, if you work out the whole slide thing here, shouldn't you be able to put that behind you and simply drop it in elsewhere? With that in mind I wrote a program that simply puts several images into a scrollview. Then I wrote a function called createSlidePanel that encapsulated the slide functionality and then to enable the fade-in of the word "delete" I created a second helper function createModifyingView. This approach appears to hit all three points above. I broke the eventing problems down into smaller more manageable units. It completely eliminated the need for this and binding. And finally, the two helper functions can be reused.
Here is my version of "main.js" which contains fundamentally "application" behavior:
/* globals define */
define(function(require, exports, module) {
'use strict';
// import dependencies
var Engine = require('famous/core/Engine');
var ImageSurface = require('famous/surfaces/ImageSurface');
var Surface = require('famous/core/Surface');
var Scrollview = require('famous/views/Scrollview');
var SnapTransition = require('famous/transitions/SnapTransition');
var Transitionable = require('famous/transitions/Transitionable');
var createSlidePanel = require('SlidePanel');
var createModifyingView = require('ModifyingView');
Transitionable.registerMethod('snap', SnapTransition);
var trans = {
method: 'snap',
period: 100,
dampingRatio: 0.3,
velocity: 5
};
var dataSource = [
'http://www.outerspaceuniverse.org/wp-content/uploads/2009/07/outer-space1.jpg',
'http://wallpoper.com/images/00/39/95/84/outer-space_00399584.jpg',
'http://static1.businessinsider.com/image/508c649e69beddb270000005/the-only-reason-private-space-flight-isnt-laughed-at-is-nasas-11-billion-infusion.jpg'
];
var images = [];
var slideOptions = {
drag: {
xRange: [-120, 5],
projection: 'x',
},
view: {
size:[300,300]
}
};
var mainContext = Engine.createContext();
var scrollView = new Scrollview();
mainContext.add(scrollView);
dataSource.forEach(function(url,i,urls) {
var img = new ImageSurface({
content: url,
size: [300,300]
});
var dlt = new Surface({
size:[300,300],
content: 'DELETE',
properties: {
color: 'red',
zIndex: 4,
lineHeight: '200px',
fontSize:'60px'
}
});
var modView = createModifyingView();
modView.modifier.setOpacity(0);
modView.add(dlt);
var elem = createSlidePanel(slideOptions);
elem.addSlide(img)
elem.addStill(modView);
elem._eventOutput.pipe(scrollView);
elem.on('slideupdate',slideUpdateHandler);
elem.on('slideend',slideEndHandler);
images.push(elem);
});
function slideUpdateHandler(eventInfo) {
var ratio = (eventInfo.data.position[0]-slideOptions.drag.xRange[1])/(slideOptions.drag.xRange[0]-slideOptions.drag.xRange[1]);
if(ratio>.2) {
eventInfo.source.stillElements[0].modifier.setOpacity(ratio);
} else {
eventInfo.source.stillElements[0].modifier.setOpacity(0);
}
}
function slideEndHandler(eventInfo) {
if (eventInfo.data.position[0] < (-100)) {
alert('delete');
}
eventInfo.source.modifier.setPosition([0,0,0], trans);
eventInfo.source.stillElements[0].modifier.setOpacity(0);
}
scrollView.sequenceFrom(images);
});
The slide functionality is here in "SlidePanel.js":
/* globals define */
define(function(require, exports, module) {
'use strict';
// import dependencies
var Modifier = require('famous/core/Modifier');
var View = require('famous/core/View');
var Draggable = require('famous/modifiers/Draggable');
function createSlidePanel(options) {
options = options || {};
var slidePanel = new View(options.view);
slidePanel.slideElements = [];
slidePanel.stillElements = [];
slidePanel.modifier = new Draggable(options.drag);
var node = slidePanel._add(slidePanel.modifier);
slidePanel.addSlide = function addSlide(renderable) {
node.add(renderable);
renderable.pipe(slidePanel._eventOutput);
renderable.pipe(slidePanel.modifier);
slidePanel.slideElements.push(renderable);
}
slidePanel.addStill = function addStill(renderable) {
slidePanel.add(renderable);
renderable.pipe(slidePanel._eventOutput);
renderable.pipe(slidePanel.modifier);
slidePanel.stillElements.push(renderable);
}
slidePanel.modifier.on('start',function(data) {
slidePanel._eventOutput.emit('slidestart',{source:slidePanel,data:data});
});
slidePanel.modifier.on('update',function(data) {
slidePanel._eventOutput.emit('slideupdate',{source:slidePanel,data:data});
});
slidePanel.modifier.on('end',function(data) {
slidePanel._eventOutput.emit('slideend',{source:slidePanel,data:data});
});
slidePanel.modifier.activate();
return slidePanel;
}
module.exports = createSlidePanel;
});
And here is the "ModifyingView.js" code:
/* globals define */
define(function(require, exports, module) {
'use strict';
// import dependencies
var Modifier = require('famous/core/Modifier');
var View = require('famous/core/View');
function createModifyingView(options) {
options = options || {};
var view = new View(options);
view.modifier = new Modifier();
var node = view._add(view.modifier);
view.add = function add(renderable) {
node.add(renderable);
view._eventOutput.subscribe(renderable);
};
view.setPosition = function setPosition(/* passthrough */) {
view.modifier.setPosition(arguments);
};
view.setOpacity = function setOpacity(/* passthrough */) {
view.modifier.setOpacity(arguments)
}
//view.modifier.setPosition([0,0,0]);
return view;
}
module.exports = createModifyingView;
});
Several Notes:
Obviously, one of the main changes here is the functional pattern which makes all references explicit and leaves no question of binding.
Yes this is more code than the original, partly because it is complete with all of the require statements and the list of images, but also because it just is. The trade-off here is that you may get more bang for the buck if you reuse the helpers.
The ModifyingView pattern is one I use quite a bit. This comes up so often whether I'm building a login form with eight surfaces interacting in ways the main program need know nothing about, or a simple surface fading in and out, that I have a code snippet which defines a view, a modifier, a statemodifier (one of which I usually delete,) a surface and much of the common code to tie them together.
I'm specifically not recommending the "options" management used in this code, but it suffices for the example.

famo.us: can I animate the header/footer heights of a header footer layout?

I want to have my header and footer almost take up the entire screen (there will just be a thin line left in the middle which will contain a textbox. If the user enters the right password, I want the textbox to disappear and the header and footer to gradually get shorter (making more room for content to appear in the center of the screen).
Is it possible to apply a transition to the height of the header and footer on a HeaderFooterLayout?
How do I show a typical password box where the characters all show as *'s?
Like many animations that are not supported by default, you can add a transition by using the Transitionable class.. Here is an example that expands the header when you click it..
var Engine = require("famous/core/Engine");
var Surface = require("famous/core/Surface");
var HeaderFooterLayout = require("famous/views/HeaderFooterLayout");
var Transitionable = require("famous/transitions/Transitionable");
var Easing = require("famous/transitions/Easing");
var mainContext = Engine.createContext();
var layout = new HeaderFooterLayout({
headerSize: 100,
footerSize: 50
});
var header = new Surface({
size: [undefined, undefined],
content: "Header",
classes: ["red-bg"],
properties: {
lineHeight: "100px",
textAlign: "center"
}
})
var open = false;
header.on("click",function(){
var transition = {duration: 400, curve: Easing.inOutQuad };
var start = open ? 200 : 100 ;
var end = open ? 100 : 200 ;
open = !open;
var transitionable = new Transitionable(start);
var prerender = function(){ layout.setOptions({ headerSize: transitionable.get()} ) };
var complete = function(){ Engine.removeListener('prerender', prerender) };
Engine.on('prerender', prerender);
transitionable.set(end, transition, complete);
});
layout.header.add(header);
layout.content.add(new Surface({
size: [undefined, undefined],
content: "Content",
classes: ["grey-bg"],
properties: {
lineHeight: window.innerHeight - 150 + 'px',
textAlign: "center"
}
}));
layout.footer.add(new Surface({
size: [undefined, 50],
content: "Footer",
classes: ["red-bg"],
properties: {
lineHeight: "50px",
textAlign: "center"
}
}));
mainContext.add(layout);
As for the password field, you simply create an InputSurface and set it's type to password..
inputSurface = new InputSurface({
size:[200,60],
type: 'password'
});
^ Watch out for performance issues when using Transitionable on headerSize. Especially iPhones with iOS 7 seem to be acting glitchy.
You can also animate header / footer size via CSS transitions, although it's a bit of a bubblegum fix and has it's pitfalls:
var headerContainer = new ContainerSurface({
size: [undefined, 50],
classes: ['my-header']
});
layout.header.add(headerContainer);
headerContainer.setSize([undefined,300]);
Then in CSS:
.my-header { transition: 200ms all; }

How to implement swipe action in ScrollView Famous

I'm trying to implement the typical swipe left event to trigger some custom action using a scrollview in famo.us. The thing is that I missing something and I can't get it done. I manage to implement a Draggable modifier in each scrollview item, so the items can be dragged (X axis), but now I can't be able to capture the event of the draggable modifier in order to trigger the actions.
Here is my ListView class:
define(function(require, exports, module) {
// Imports
var View = require('famous/core/View');
var Surface = require('famous/core/Surface');
var Utility = require('famous/utilities/Utility');
var ScrollView = require('famous/views/ScrollView');
var ViewSequence = require('famous/core/ViewSequence');
var Draggable = require('famous/modifiers/Draggable');
var RenderNode = require('famous/core/RenderNode');
var EventHandler = require('famous/core/EventHandler');
function ListView() {
View.apply(this, arguments);
this.items = [];
this.scrollView = new ScrollView({
direction: Utility.Direction.Y,
margin: 100000
});
this.viewSequence = new ViewSequence(this.items);
this.scrollView.sequenceFrom(this.viewSequence);
this._add(this.scrollView);
};
ListView.prototype = Object.create(View.prototype);
ListView.prototype.constructor = ListView;
ListView.prototype.setContent = function(data) {
for (var i = 0; i < data.length; i++) {
var item = new Surface({
size: [undefined, 60],
content: 'Item ' + data[i],
classes: ['listview-item']
});
var draggable = new Draggable({
xRange: [-100, 100],
yRange: [0, 0]
});
var node = new RenderNode(draggable);
node.add(item);
draggable.on('click', function() {
console.log('emit swipe')
this._eventOutput.emit('swipe');
}.bind(this)); // This Doesn't work
item.on('click', function() {
console.log('emit swipe')
this._eventOutput.emit('swipe');
}.bind(this)); // Neither this
item.pipe(draggable);
item.pipe(this.scrollView);
this.items.push(node);
}
};
module.exports = ListView;
});
Now App Class where I include my ListView:
define(function(require, exports, module) {
...
// Custom Views
var ListView = require('views/ListView');
function App() {
View.apply(this, arguments);
this.layout = new HeaderFooterLayout({
headerSize: 70,
});
...
this.list = new ListView();
this.list.pipe(this._eventInput);
this._eventInput.on('swipe', this.swipeListItem.bind(this)) // Trying to captute swipe event
this.list.setContent([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]);
this.layout.content.add(this.list);
....
this._add(this.layout);
};
App.prototype = Object.create(View.prototype);
App.prototype.constructor = App;
App.DEFAULT_OPTIONS = {};
App.prototype.swipeListItem = function() {
console.log('Item Swiped!');
};
module.exports = App;
});
I don't know what I missing or if there is a better way to implement a swipe gesture in famo.us, if someone knows something about it would be helpful.
Thanks in advance. =)
It looks like you want to use the 'start' event for the draggable modifier..
draggable.on('start', function() {
console.log('emit drag start')
this._eventOutput.emit('swipe');
}.bind(this));
Draggable also emits 'update', and 'end' and each of these handlers take a parameter that returns the position of the draggable
draggable.on('update', function(e) {
// Do something on update
var pos = e.position;
});
draggable.on('end', function(e) {
// Do something on end
var pos = e.position;
});
Hope this helps!