famo.us: add an item to main context after an animation completes - famo.us

I have an animation that takes 1 sec to complete, and then I want to show some text boxes for username/password and a login button. Is there some sort of "oncomplete" event for an animation?

Yes, there most certainly is..
It will be the third argument in your setTransform or equivalent animation function..
Check it out..
EDIT:
I did misunderstand your question. There is no event that is emitted, only the ability to set the completion handler.
var Engine = require("famous/core/Engine");
var Surface = require("famous/core/Surface");
var StateModifier = require("famous/modifiers/StateModifier");
var Transform = require("famous/core/Transform");
var Easing = require("famous/transitions/Easing");
var context = Engine.createContext();
var surface = new Surface({
size:[200,200],
properties: { backgroundColor: 'green' }
})
surface.state = new StateModifier({ origin:[0,0.5] });
var completionFunction = function(){ console.log("Complete!"); };
var xOffset = 0;
context.add(surface.state).add(surface);
surface.on('click', function(){
transition = { duration:1000, curve:Easing.inOutQuad };
xOffset += 100;
surface.state.setTransform(Transform.translate(xOffset,0,0),transition, completionFunction )
});

There is no built in 'oncomplete' event, but assuming that you're using some sort of Transitionable, you can do whatever you want via the the callback argument of Transitionable#set:
https://famo.us/docs/0.1.1/transitions/Transitionable/#set
If you prefer to work with events, you can use an EventEmitter to emit one via the callback yourself.

Related

famo.us scale and rescale, save scale data from event to event

I have a simple problem I am stuck on. I want to implement a ScaleSync on an ImageSurface. Everytime I end the scale action the data value is reset to 1 and if I perform another scale the ImageSurfaces starts transforming from 1.
define(function(require, exports, module) {
var Engine = require('famous/core/Engine');
var Modifier = require('famous/core/Modifier');
var Transform = require('famous/core/Transform');
var ImageSurface = require('famous/surfaces/ImageSurface');
var Surface = require('famous/core/Surface');
var ScaleSync = require("famous/inputs/ScaleSync");
var mainContext = Engine.createContext();
var start = 1;
var update = 0;
var end = 0;
var growShrink = "";
var objectScale = 1;
var scaleSync = new ScaleSync();
var logo = new ImageSurface({
size: [200, 200],
content: '/content/images/famous_logo.png',
classes: ['backfaceVisibility']
});
var centerModifier = new Modifier({
align: [0.5, 0.5],
origin: [0.5, 0.5]
});
logo.pipe(scaleSync);
scaleSync.on("update", function(data) {
objectScale = data.scale
centerModifier.setTransform(
Transform.scale( objectScale, objectScale, 1)
);
});
scaleSync.on("start", function(){
start ++;
});
scaleSync.on("end", function() {
end++;
});
mainContext.add(centerModifier).add(logo);
});
I tried achieving this using an Accumulator, but I think that's for different purposes. I think I have to somehow alter the data in the on.update callback, but have no clue how to do that.
Your help is much appreciated!
I found the solution. Hope this helps anybody with similar issues. The modifier should have a scale variable that is referenced as follows:
var scale = 1;
var centerModifier = new Modifier({
align: [0.5, 0.5],
origin: [0.5, 0.5],
transform: function(){
return Transform.scale(scale, scale, 1);
}
});
And the state of the variable is changed on event update (I divide data.delta by 10 to normalize the value):
scaleSync.on("update", function(data) {
scale += data.delta / 10;
});

Built-in Popup Modal

Is there any built-in support for modals in Famous?
I completed the Famous University and looked through the documentation but didn't see anything.
I am about to roll my own, but I figured I should ask first.
You are going to want to use the Lightbox object. Lightbox is like a RenderController that has state. This means you can define, how views come in an out of view both by transition and transform. Here is a very basic example of the modal type you see often in iOS..
Hope it helps!
var Engine = require('famous/core/Engine');
var Surface = require('famous/core/Surface');
var Transform = require('famous/core/Transform');
var Modifier = require('famous/core/Modifier');
var Lightbox = require('famous/views/Lightbox');
var Easing = require('famous/transitions/Easing');
var context = Engine.createContext();
var surface = new Surface({
size:[200,200],
properties:{
backgroundColor:'green',
color:'white',
lineHeight:'200px',
textAlign:'center'
}
});
surface.on('click',function(){ openModal() });
context.add(new Modifier({origin:[0.5,0.5]})).add(surface);
var modal = new Surface({
size:[500,500],
properties:{
backgroundColor:'red'
}
})
modal.on('click',function(){ hideModal() });
modal.lightbox = new Lightbox({
inTransform: Transform.translate(0,500,0),
outTransform: Transform.translate(0,500,0),
inTransition: {duration:1000, curve:Easing.outElastic},
outTransition: {duration:200, curve:Easing.inOutQuad},
});
context.add(new Modifier({origin:[0.5,0.5]})).add(modal.lightbox);
function openModal(){
modal.lightbox.show(modal);
}
function hideModal(){
modal.lightbox.hide();
}

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.

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!