├── .gitmodules ├── .versions ├── History.md ├── README.md ├── demo ├── .meteor │ ├── .finished-upgraders │ ├── .gitignore │ ├── .id │ ├── packages │ ├── platforms │ ├── release │ └── versions ├── client │ ├── FlexScrollView.jade │ ├── FlexScrollView.js │ ├── FlexScrollview.css │ ├── TabBar.css │ ├── TabBar.jade │ ├── TabBar.js │ ├── demo.css │ ├── demo.jade │ └── demo.js ├── lib │ └── smart.require └── packages │ ├── .gitignore │ └── gadicohen:fview-flex ├── lib ├── FlexScrollView.js ├── TabBar.js └── pre.js └── package.js /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/famous-flex"] 2 | path = lib/famous-flex 3 | url = https://github.com/IjzerenHein/famous-flex 4 | [submodule "lib/famous-refresh-loader"] 5 | path = lib/famous-refresh-loader 6 | url = https://github.com/IjzerenHein/famous-refresh-loader 7 | [submodule "lib/famous-autosizetextarea"] 8 | path = lib/famous-autosizetextarea 9 | url = https://github.com/IjzerenHein/famous-autosizetextarea 10 | 11 | -------------------------------------------------------------------------------- /.versions: -------------------------------------------------------------------------------- 1 | base64@1.0.3 2 | binary-heap@1.0.3 3 | blaze@2.1.2 4 | blaze-tools@1.0.3 5 | callback-hook@1.0.3 6 | check@1.0.5 7 | coffeescript@1.0.6 8 | ddp@1.1.0 9 | deps@1.0.7 10 | ejson@1.0.6 11 | gadicohen:famous-views@0.2.0 12 | gadicohen:fview-flex@0.0.9 13 | geojson-utils@1.0.3 14 | html-tools@1.0.4 15 | htmljs@1.0.4 16 | id-map@1.0.3 17 | jag:pince@0.0.5 18 | jquery@1.11.3_2 19 | json@1.0.3 20 | local-test:gadicohen:fview-flex@0.0.9 21 | logging@1.0.7 22 | meteor@1.1.6 23 | minifiers@1.1.5 24 | minimongo@1.0.8 25 | mongo@1.1.0 26 | observe-sequence@1.0.6 27 | ordered-dict@1.0.3 28 | pierreeric:cssc@1.0.4 29 | random@1.0.3 30 | reactive-dict@1.1.0 31 | reactive-var@1.0.5 32 | retry@1.0.3 33 | sdecima:javascript-detect-element-resize@0.5.3 34 | spacebars-compiler@1.0.6 35 | templating@1.1.1 36 | tinytest@1.0.5 37 | tracker@1.0.7 38 | underscore@1.0.3 39 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | ## vNEXT 2 | 3 | ## v0.0.8 4 | 5 | * TabBar: `fview.index()` -> `fview.selectedTab()`, and `fview.selectedTab(valueToSet)` 6 | * TabBar: `selectedTab` attribute in template declaration, from a reactive helper 7 | 8 | ## v0.0.7 9 | 10 | * Update to latest famous-flex, famous-autosizetextarea, famous-refresh-loader 11 | * Famous-views wrapper for TabBar 12 | * Extra Flex modules: LayoutNodeManager, LayoutController, 13 | AnimationController, TabBarController 14 | 15 | ## v0.0.6 16 | 17 | * Fix for AutosizeTextareaSurface.prototype error (deps) 18 | 19 | ## v0.0.5 20 | 21 | * add HeaderFooterLayout 22 | * add LayoutDockHelper 23 | * add DatePicker & TabBar 24 | * add useful views RefreshLoader & AutosizeTextareaSurface 25 | * bugfixes 26 | 27 | A massive thanks to @ShawnOceanHu for his work on *all of the above*, including 28 | sample code in the README. 29 | 30 | ## v0.0.4 31 | 32 | * Bugfix: don't pipe events from parent ContainerSurface AND child surfaces, 33 | as the double piping breaks fluid flicking. Instead, do one or the other, 34 | and also, change the way we pipe ContainerSurface to work better on Mac. 35 | 36 | * Bump famous-flex, work correctly with now deprecated GridLayout. 37 | thanks @ShawnOceanHu. 38 | 39 | ## v0.0.3 40 | 41 | * Bugfix: don't bail on `Cannot set property '_isDirty' of null` on removedAt 42 | if the containing view destroyed before defer time. 43 | 44 | ## v0.0.2 45 | 46 | * famono support 47 | * Demo: .noselect on surfaces makes it a tiny bit easier (but still pretty 48 | hard) to "flick" with mouse 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fview-flex 2 | 3 | So IjzerenHein's [famous-flex](https://github.com/IjzerenHein/famous-flex) 4 | is pretty friggin' awesome, and without a doubt the best community component for 5 | Famo.us by a substantial distance. This is the start of a 6 | [famous-views](http://famous-views.meteor.com/) wrapper for it (for Meteor). 7 | 8 | **VERY EARLY RELEASE!!! Feel free to play around but don't use this for 9 | anything serious.** Works with both `mjn:famous` and `raix:famono` (since 10 | v0.0.2). 11 | 12 | [Live demo](http://fview-flex.meteor.com/) 13 | 14 | Too add to your project, just `meteor add gadicohen:fview-flex`. To clone from 15 | github, see **Development** below (in short, make sure to submodule init/update). 16 | 17 | ## Usage: 18 | 19 | ```jade 20 | +ContainerSurface perspective=500 overflow="hidden" 21 | +FlexScrollView layout="WheelLayout" direction="Y" layoutOptions=layoutOptions 22 | +famousEach surfaces 23 | +Surface style=style 24 | | #{_id} 25 | ``` 26 | 27 | Note: [Beware of overflow: hidden, it breaks z-translation quite badly](https://github.com/Famous/famous/issues/493) - if you don't need it, don't use it. 28 | 29 | **Attributes**: 30 | 31 | * All attributes are parsed using famous-views and given on init (i.e. you can give either a string or [fview string](http://famous-views.meteor.com/views/README) in the template, or use a template helper to give an actual value). So you can use any option mentioned 32 | in the famous-flex docs. 33 | * `layout`, `direction`, `layoutOptions` are all reactive, see the 34 | [demo source](https://github.com/gadicc/fview-flex/tree/master/demo/client) 35 | for a good example. 36 | * If none of your layoutOptions are reactive, you can use straight JSON 37 | directly from the template, e.g. 38 | `layoutOptions='{ "itemSize": [50,90], "diameter": 500, "radialOpacity": 0 }'`. 39 | * No problem to use a helper to return your own custom layout function to 40 | `layout`. 41 | 42 | You can also use `{{#FlexLayoutController}}` directly if you don't need 43 | scrolling. 44 | 45 | ## Special behaviour 46 | 47 | * If the FlexScrollView's immediate parent is a ContainerSurface, we'll set up 48 | all the event piping for you. Otherwise, by default, we'll still automatically 49 | pipe all children to it, just like in regular ScrollView with famousEach. But 50 | generally you'll want to use a ContainerSurface to ensure smooth scrolling even 51 | when going over "gaps" between the Surfaces. 52 | 53 | ## Progress 54 | 55 | **famous-views** is developed in my spare time. **fview-flex** is developed in 56 | my spare time from famous-views :) I'm not sure how quickly I'll get the full 57 | list below implemented, however, I'll definitely prioritize according to demand. 58 | [Open an issue](https://github.com/gadicc/fview-flex/issues) for feature / 59 | prioritization requests, and I'll try get to these first, especially if there 60 | are a lot of `+1`'s. 61 | 62 | Basically you can use all the components of famous-flex right now, through js if not available for templates. 63 | 64 | * **Standard Layouts** ([full docs](https://github.com/IjzerenHein/famous-flex#standard-layouts)) 65 | * *--- Non-scrollable ---* 66 | * [x] GridLayout 67 | * [x] ProportionalLayout 68 | * [x] HeaderFooterLayout (compared to famo.us' HeaderFooterLayout, this could update the header/footer size dynamically) 69 | * [x] NavBarLayout 70 | * [x] TabBarLayout 71 | * *--- Scrollable ----* 72 | * [x] ListLayout ([x] stickyHeaders) 73 | * [x] CollectionLayout 74 | * [x] WheelLayout 75 | 76 | * **LayoutHelpers(layout literals)** 77 | * [ ] LayoutDockHelper (add template support for dock literals) 78 | 79 | * **Widgets** 80 | * [ ] DatePicker (add template support) 81 | * [ ] TabBar (add template support) 82 | 83 | * **LayoutControllers**, **LayoutHelpers**, etc. 84 | 85 | A DatePicker example(will add template support in the future :): 86 | ``` 87 | var datePicker = new Flex.DatePicker({ 88 | date: new Date(), 89 | perspective: 500, 90 | wheelLayout: { 91 | itemSize: 25, 92 | diameter: 100, 93 | radialOpacity: -0.5 94 | }, 95 | createRenderables: { 96 | top: true, 97 | bottom: true 98 | }, 99 | classes: ['transparent'] 100 | }); 101 | datePicker.setComponents([ 102 | new Flex.DatePicker.Component.Month(), 103 | new Flex.DatePicker.Component.Day(), 104 | new Flex.DatePicker.Component.Year() 105 | ]); 106 | 107 | datePicker.on('datechange', function(event) { 108 | console.log('date-changed to: ' + event.date.toString()); 109 | }); 110 | ``` 111 | 112 | A HeaderFooterLayout example: 113 | ``` 114 | {{#FlexLayoutController layout='HeaderFooterLayout' layoutOptions=layoutOptions}} 115 | {{#Surface target="header" size="[undefined, 40]"}} 116 | {{/Surface}} 117 | {{#Surface target="content"}} 118 | {{/Surface}} 119 | {{#Surface target="footer" size="[undefined, 80]"}} 120 | {{/Surface}} 121 | {{/FlexLayoutController}} 122 | ``` 123 | 124 | A LayoutDockHelper example: 125 | ``` 126 | The template: 127 | 128 | 132 | 133 | In JS: 134 | 135 | Template.dockExample.helpers({ 136 | 'layout': function() { 137 | return { 138 | dock: [ 139 | ['fill', 'background'], 140 | ['left', undefined, 8], 141 | ['top', undefined, 8], 142 | ['right', undefined, 8], 143 | ['bottom', undefined, 8], 144 | ['right', 'send', undefined, 1], 145 | ['fill', 'input', 1] 146 | ] 147 | }; 148 | }, 149 | 'dataSource': function() { 150 | return { 151 | background: backgroundSurface, 152 | input: inputSurface, 153 | send: sendSurface 154 | }; 155 | }, 156 | 'layoutOptions': function() { 157 | return { 158 | margins: [0, 0, 0, 0] 159 | }; 160 | } 161 | }); 162 | ``` 163 | I think future release can add support for layout literals(and other common layouts) like this: 164 | ``` 165 | {{#FlexLayoutController layout='HeaderFooterLayout/ListLayout/Dock/or whatever layout' margin="[10, 10, 10, 10]"}} 166 | {{#Surface target="left" size="[undefined, 100]"}} 167 | {{>templateA}} 168 | {{/Surface}} 169 | {{#Surface target="right" size="[undefined, 100]"}} 170 | {{>templateB}} 171 | {{/Surface}} 172 | {{/FlexLayoutController}} 173 | ``` 174 | 175 | BTW, you can use RefreshLoader & AutosizeTextareaSurface like this: 176 | ``` 177 | var loader = new Flex.RefreshLoader(...); 178 | ``` 179 | 180 | ## Development 181 | 182 | If you plan to contribute, you probably want to clone your own fork. 183 | See https://help.github.com/articles/using-pull-requests/ for more details. 184 | 185 | ```bash 186 | $ git clone https://github.com/gadicc/fview-flex.git 187 | $ cd fview-flex 188 | $ git submodule init 189 | $ git submodule update 190 | ``` 191 | 192 | The last two lines are necessary because we don't include all the original 193 | fmaous-flex etc code in the repo, rather, we include them as submodules and 194 | can update / checkout a particular commit whenever we want. If you don't 195 | you'll see errors like: 196 | 197 | ```bash 198 | => Errors while initializing project: 199 | 200 | While building package gadicohen:fview-flex: 201 | error: File not found: lib/famous-flex/src/LayoutUtility.js 202 | error: File not found: lib/famous-flex/src/LayoutContext.js 203 | ``` 204 | 205 | etc. -------------------------------------------------------------------------------- /demo/.meteor/.finished-upgraders: -------------------------------------------------------------------------------- 1 | # This file contains information which helps Meteor properly upgrade your 2 | # app when you run 'meteor update'. You should check it into version control 3 | # with your project. 4 | 5 | notices-for-0.9.0 6 | notices-for-0.9.1 7 | 0.9.4-platform-file 8 | notices-for-facebook-graph-api-2 9 | -------------------------------------------------------------------------------- /demo/.meteor/.gitignore: -------------------------------------------------------------------------------- 1 | local 2 | .famono-repos 3 | .famono-base 4 | -------------------------------------------------------------------------------- /demo/.meteor/.id: -------------------------------------------------------------------------------- 1 | # This file contains a token that is unique to your project. 2 | # Check it into your repository along with the rest of this directory. 3 | # It can be used for purposes such as: 4 | # - ensuring you don't accidentally deploy one app on top of another 5 | # - providing package authors with aggregated statistics 6 | 7 | 8x1sei1y0x01i1726n2q 8 | -------------------------------------------------------------------------------- /demo/.meteor/packages: -------------------------------------------------------------------------------- 1 | # Meteor packages used by this project, one per line. 2 | # Check this file (and the other files in this directory) into your repository. 3 | # 4 | # 'meteor add' and 'meteor remove' will edit this file for you, 5 | # but you can also edit it by hand. 6 | 7 | meteor-platform 8 | autopublish 9 | insecure 10 | 11 | #mjn:famous 12 | raix:famono 13 | 14 | gadicohen:famous-views 15 | gadicohen:fview-flex 16 | 17 | mquandalle:jade@=0.2.9 18 | meteorhacks:flow-router 19 | 20 | gadicohen:snippets 21 | chuangbo:marked@=0.3.2_4 22 | 23 | -------------------------------------------------------------------------------- /demo/.meteor/platforms: -------------------------------------------------------------------------------- 1 | server 2 | browser 3 | -------------------------------------------------------------------------------- /demo/.meteor/release: -------------------------------------------------------------------------------- 1 | METEOR@1.1.0.2 2 | -------------------------------------------------------------------------------- /demo/.meteor/versions: -------------------------------------------------------------------------------- 1 | autopublish@1.0.3 2 | autoupdate@1.2.1 3 | base64@1.0.3 4 | binary-heap@1.0.3 5 | blaze@2.1.2 6 | blaze-tools@1.0.3 7 | boilerplate-generator@1.0.3 8 | callback-hook@1.0.3 9 | check@1.0.5 10 | chuangbo:marked@0.3.2_4 11 | coffeescript@1.0.6 12 | cosmos:browserify@0.1.3 13 | ddp@1.1.0 14 | deps@1.0.7 15 | ejson@1.0.6 16 | fastclick@1.0.3 17 | gadicohen:famous-views@0.2.0 18 | gadicohen:fview-flex@0.0.9 19 | gadicohen:prism@1.0.4 20 | gadicohen:snippets@0.0.5 21 | geojson-utils@1.0.3 22 | html-tools@1.0.4 23 | htmljs@1.0.4 24 | http@1.1.0 25 | id-map@1.0.3 26 | insecure@1.0.3 27 | jag:pince@0.0.6 28 | jquery@1.11.3_2 29 | json@1.0.3 30 | launch-screen@1.0.2 31 | livedata@1.0.13 32 | logging@1.0.7 33 | meteor@1.1.6 34 | meteor-platform@1.2.2 35 | meteorhacks:flow-router@1.7.0 36 | minifiers@1.1.5 37 | minimongo@1.0.8 38 | mobile-status-bar@1.0.3 39 | mongo@1.1.0 40 | mquandalle:jade@0.2.9 41 | observe-sequence@1.0.6 42 | ordered-dict@1.0.3 43 | pierreeric:cssc@1.0.4 44 | raix:famono@0.9.27 45 | random@1.0.3 46 | reactive-dict@1.1.0 47 | reactive-var@1.0.5 48 | reload@1.1.3 49 | retry@1.0.3 50 | routepolicy@1.0.5 51 | sdecima:javascript-detect-element-resize@0.5.3 52 | session@1.1.0 53 | spacebars@1.0.6 54 | spacebars-compiler@1.0.6 55 | templating@1.1.1 56 | tracker@1.0.7 57 | ui@1.0.6 58 | underscore@1.0.3 59 | url@1.0.4 60 | webapp@1.2.0 61 | webapp-hashing@1.0.3 62 | -------------------------------------------------------------------------------- /demo/client/FlexScrollView.jade: -------------------------------------------------------------------------------- 1 | template(name="FlexScrollViewDemo") 2 | +HeaderFooterLayout id="FlexScrollViewDemo" headerSize=100 3 | +Surface target="header" template="fsvHeader" 4 | +ContainerSurface target="content" perspective=500 overflow="hidden" 5 | +FlexScrollView layout=layout direction=direction layoutOptions=layoutOptions 6 | +famousEach surfaces 7 | +Surface style=style class="noselect" 8 | | #{name} 9 | 10 | template(name="fsvHeader") 11 | div.hb 12 | b Collection: 13 | button(class="surfaceAction",data-action="remove") - 14 | | #{surfaceCount} 15 | button(class="surfaceAction",data-action="add") + 16 | | (random)   17 | select(id="orderPicker") 18 | option(value="1") Ascending 19 | option(value="-1",selected=isOrderDescending) Descending 20 | div.hb 21 | b Template: 22 | | {{dstache}}#FlexScrollView layout=" 23 | select(id="layoutPicker") 24 | each layouts 25 | option(value=this,selected=isLayoutSelected) #{this} 26 | | " direction=" 27 | select(id="directionPicker") 28 | each directions 29 | option(value=value,selected=isDirectionSelected) #{key} 30 | | " layoutOptions='{ 31 | each layoutOptions 32 | label 33 | |   "#{this}": " 34 | input(name=this,class="picker",value=layoutOptionValue,size="5") 35 | | " 36 | | }' }} 37 | div.hb 38 | b GitHub: 39 | a(href="http://github.com/gadicc/fview-flex/") Project 40 | | | 41 | a(href="http://github.com/gadicc/fview-flex/tree/master/demo") Demo Source 42 | | ; note: obviously all attributes above are reactive! -------------------------------------------------------------------------------- /demo/client/FlexScrollView.js: -------------------------------------------------------------------------------- 1 | FlowRouter.route('/FlexScrollView', { name: 'FlexScrollViewDemo' } ); 2 | 3 | // TODO, make session vars template state vars? 4 | 5 | var layoutOptions = { 6 | GridLayout: [ 'cells', 'margins', 'spacing' ], 7 | ProportionalLayout: [ 'ratios' ], 8 | ListLayout: [ 'itemSize', 'margins', 'spacing' ], 9 | CollectionLayout: [ 'itemSize', 'justify', 'margins', 'spacing' ], 10 | // FullScreen: [ 'margins', 'spacing' ], 11 | WheelLayout: [ 'itemSize', 'diameter', 'radialOpacity' ] 12 | }; 13 | 14 | Session.setDefault('cells', [4,4]); 15 | Session.setDefault('margins', [10,10,10,10]); 16 | Session.setDefault('ratios', [1,2,3,1]); 17 | Session.setDefault('justify', [0,0]); 18 | Session.setDefault('diameter', 500); 19 | Session.setDefault('radialOpacity', 0); 20 | // set in autorun below 21 | // Session.setDefault('itemSize', [90,90]); 22 | // Session.setDefault('spacing', [10,10]); 23 | 24 | // Switch between scalar/array for itemSize when necessary w/ history 25 | var itemSizeOld = [ [150,150], 70 ]; 26 | var spacingOld = [ [10,10], 2 ]; 27 | Tracker.autorun(function(c) { 28 | var type = Session.equals('layout', 'WheelLayout') 29 | || Session.equals('layout', 'ListLayout') ? 1 : 0; 30 | if (!c.firstRun) { 31 | itemSizeOld[type?0:1] = Tracker.nonreactive(function() { 32 | return Session.get('itemSize'); 33 | }); 34 | spacingOld[type?0:1] = Tracker.nonreactive(function() { 35 | return Session.get('spacing'); 36 | }); 37 | } 38 | Session.set('itemSize', itemSizeOld[type]); 39 | Session.set('spacing', spacingOld[type]); 40 | }); 41 | 42 | Surfaces = new Meteor.Collection(null); 43 | 44 | Meteor.startup(function() { 45 | for (var i=0; i < 60; i++) 46 | Surfaces.insert({ 47 | name: '#'+(i+1), 48 | index: i, 49 | style: "background: hsl(" + (i * 360 / 60) + ", 100%, 50%)", 50 | show: Math.random() < 0.4 51 | }); 52 | }); 53 | 54 | Template.FlexScrollViewDemo.helpers({ 55 | layout: function() { 56 | return Session.get('layout'); 57 | }, 58 | 59 | layoutOptions: function() { 60 | var out = {}; 61 | _.each(layoutOptions[Session.get('layout')], function(option) { 62 | out[option] = Session.get(option); 63 | }); 64 | return out; 65 | }, 66 | 67 | direction: function() { 68 | return Session.get('direction'); 69 | }, 70 | 71 | surfaces: function() { 72 | return Surfaces.find({ show: true }, 73 | { sort: { index: Session.get('order') } } ); 74 | } 75 | }); 76 | 77 | Session.setDefault('direction', 0); 78 | Session.setDefault('layout', 'CollectionLayout'); 79 | Session.setDefault('order', 1); 80 | 81 | Template.fsvHeader.events({ 82 | 'change input.picker': function(event) { 83 | Session.set(this.valueOf(), JSON.parse(event.currentTarget.value)); 84 | }, 85 | 'click .surfaceAction': function(event) { 86 | var add = event.currentTarget.getAttribute('data-action') === 'add'; 87 | var sids = _.pluck(Surfaces.find({show:!add}, {fields:{_id:1}}).fetch(), '_id'); 88 | var id = sids[Math.floor((Math.random()-0.001) * sids.length)]; 89 | Surfaces.update(id, { $set: { show: add }}); 90 | }, 91 | 'change #layoutPicker': function(event) { 92 | Session.set('layout', event.currentTarget.value); 93 | }, 94 | 'change #directionPicker': function(event) { 95 | Session.set('direction', parseInt(event.currentTarget.value)); 96 | }, 97 | 'change #orderPicker': function(event) { 98 | Session.set('order', parseInt(event.currentTarget.value)); 99 | } 100 | }); 101 | 102 | Template.fsvHeader.helpers({ 103 | surfaceCount: function() { 104 | return Surfaces.find({show:true}).count(); 105 | }, 106 | 107 | layouts: _.keys(layoutOptions), 108 | 109 | directions: (function() { 110 | var out = []; 111 | for (key in famous.utilities.Utility.Direction) 112 | if (key !== 'Z') // ok this got a bit stupid :) 113 | out.push({key: key, value: famous.utilities.Utility.Direction[key]}); 114 | return out; 115 | })(), 116 | 117 | layoutOptions: function() { 118 | return layoutOptions[ Session.get('layout') ]; 119 | }, 120 | layoutOptionValue: function() { 121 | return JSON.stringify( Session.get( this.valueOf() ) ); 122 | }, 123 | 124 | isLayoutSelected: function() { 125 | return Session.equals('layout', this.valueOf()); 126 | }, 127 | isDirectionSelected: function() { 128 | return Session.equals('direction', this.value); 129 | }, 130 | isOrderDescending: function() { 131 | return Session.equals('order', -1); 132 | } 133 | }); 134 | -------------------------------------------------------------------------------- /demo/client/FlexScrollview.css: -------------------------------------------------------------------------------- 1 | input.picker { 2 | text-align: center; 3 | } 4 | 5 | .previewCS { 6 | border: 1px solid black; 7 | overflow: hidden; 8 | margin: 5px; 9 | } 10 | 11 | .hb { 12 | padding: 2px; 13 | } 14 | .hb:nth-child(1) { background: #88a } 15 | .hb:nth-child(2) { background: #a8a } 16 | .hb:nth-child(3) { background: #aa8 } 17 | -------------------------------------------------------------------------------- /demo/client/TabBar.css: -------------------------------------------------------------------------------- 1 | /* 2 | * src: https://github.com/IjzerenHein/famous-flex-tabbar/blob/master/src/styles.css 3 | */ 4 | 5 | .ff-tabbar.item.small { 6 | font-size: 12px; 7 | } 8 | .ff-tabbar.item .icon { 9 | font-size: 24px; 10 | } 11 | 12 | 13 | /* tabbar white */ 14 | .ff-tabbar.background.white { 15 | background-color: #ECECEC; 16 | } 17 | .ff-tabbar.item.white { 18 | color: #333333; 19 | } 20 | .ff-tabbar.selectedItemOverlay.white { 21 | border-bottom: 4px solid #1185c3; 22 | } 23 | 24 | 25 | /* tabbar black */ 26 | .ff-tabbar.background.black { 27 | background-color: #101010; 28 | } 29 | .ff-tabbar.item.black { 30 | color: #f7f3f7; 31 | } 32 | .ff-tabbar.selectedItemOverlay.black { 33 | border-bottom: 6px solid #30b6e7; 34 | } 35 | .ff-tabbar.spacer.black:after { 36 | content: ""; 37 | background-color: #333333; 38 | width: 100%; 39 | top: 10px; 40 | bottom: 10px; 41 | position: absolute; 42 | } 43 | 44 | /* tabbar blue */ 45 | .ff-tabbar.background.blue { 46 | background-color: #f5f5f5; 47 | } 48 | .ff-tabbar.item.blue { 49 | color: #818181; 50 | } 51 | .ff-tabbar.item.blue.selected { 52 | color: #1185c3; 53 | } 54 | .ff-tabbar.selectedItemOverlay.blue { 55 | border-bottom: 6px solid #1185c3; 56 | } 57 | 58 | 59 | /* tabbar orange */ 60 | .ff-tabbar.background.orange { 61 | background-color: #f5f5f5; 62 | } 63 | .ff-tabbar.item.orange { 64 | color: #818181; 65 | } 66 | .ff-tabbar.item.orange.selected { 67 | color: #f5f5f5; 68 | background-color: #E14610; 69 | } 70 | .ff-tabbar.selectedItemOverlay.orange { 71 | border-bottom: none; 72 | } -------------------------------------------------------------------------------- /demo/client/TabBar.jade: -------------------------------------------------------------------------------- 1 | template(name="TabBarDemo") 2 | +Surface id="TabBarDemo" class="padded" 3 | +markdown 4 | | # TabBar 5 | | ## Famous-Views Wrapper 6 | | Here's a full example, combining a `RenderController`, which probably covers 7 | | most use cases: 8 | +snippet lang="spacebars" 9 | | <template name="tabBarDemo"> 10 | | {{dstache}}SequentialLayout}} 11 | | {{dstache}}#TabBar selectedTab=fromRouteParam _onRender="tabBarRender" createRenderables=createRenderables layoutController=layoutController}} 12 | | {{dstache}}>tabHeader tabId="surface1" text="Surface 1"}} 13 | | {{dstache}}>tabHeader tabId="surface2" text="Surface 2"}} 14 | | {{dstache}}/TabBar}} 15 | | {{dstache}}#RenderController prerender=true showId=fromRouteParam}} 16 | | {{dstache}}>Surface id="tab1" template="surface1"}} 17 | | {{dstache}}>Surface id="tab2" template="surface2"}} 18 | | {{dstache}}/RenderController}} 19 | | {{dstache}}/SequentialLayout}} 20 | | </template> 21 | | 22 | | <template name="tabHeader"> 23 | | {{dstache}}#Surface tabId=tabId class="ff-widget ff-tabbar images item black tabHeader"}} 24 | | {{dstache}}text}} 25 | | {{dstache}}/Surface}} 26 | | </template> 27 | else 28 | if currentLang "jade" 29 | | template(name="tabBarDemo") 30 | | +SequentialLayout 31 | | +TabBar selectedTab=fromRouteParam _onRender="tabBarRender" class="black" createRenderables=createRenderables layoutController=layoutController 32 | | +tabHeader tabId="tab1" text="Tab 1" 33 | | +tabHeader tabId="tab2" text="Tab 2" 34 | | +RenderController prerender=true showId=fromRouteParam 35 | | +Surface id="tab1" template="surface1" 36 | | +Surface id="tab2" template="surface2" 37 | | 38 | | template(name="tabHeader") 39 | | +Surface tabId=tabId class="ff-widget ff-tabbar images item black tabHeader" 40 | | | #{text} 41 | +snippet lang="javascript" 42 | | Template.tabBarDemo.helpers({ 43 | | fromRouteParam: function() { 44 | | return FlowRouter.getParam('tabId'); 45 | | }, 46 | | layoutController: { 47 | | flowOptions: { spring: { dampingRatio: 0.2, period: 500 } } 48 | | }, 49 | | createRenderables: { 50 | | background: true, selectedItemOverlay: true 51 | | }, 52 | | tabBarRender: function() { 53 | | var fview = FView.from(this); 54 | | this.autorun(function(c) { 55 | | FlowRouter.setParams({ tab: fview.selectedTab() }); 56 | | }); 57 | | } 58 | | }); 59 | +markdown 60 | | **In Template TabBar Component**: 61 | | 1. `selectedTab` attribute - sets the current tab from a reactive helper 62 | | 1. `_onRender` new/experimental shortuct in famous-views 0.1.33 63 | | 1. Every child has a `tabId` or `id` to name it. Without it, we use indexes (0, 1, 2, etc) 64 | | 65 | | **In Code/Helpers**: 66 | | 1. `tabBarRender` "helper" to change route on tab change 67 | | 1. `selectedTab()` method - reactively returns the current tab 68 | | 1. `selectedTab(value)` method - sets the current tab (not used in example) 69 | | 70 | | ## TabBar Component 71 | | 72 | | * **[Full docs](https://github.com/IjzerenHein/famous-flex/blob/master/docs/widgets/TabBar.md)** 73 | | * [Upstream demo](https://rawgit.com/IjzerenHein/famous-flex-tabbar/master/dist/index.html) 74 | | and [source](https://github.com/IjzerenHein/famous-flex-tabbar) 75 | | * Also available via JS: `Flex.TabBar`, `Flex.TabBarLayout`, `Flex.TabBarController` -------------------------------------------------------------------------------- /demo/client/TabBar.js: -------------------------------------------------------------------------------- 1 | FlowRouter.route('/TabBar', { name: 'TabBarDemo' } ); -------------------------------------------------------------------------------- /demo/client/demo.css: -------------------------------------------------------------------------------- 1 | .noselect { 2 | -webkit-touch-callout: none; 3 | -webkit-user-select: none; 4 | -khtml-user-select: none; 5 | -moz-user-select: none; 6 | -ms-user-select: none; 7 | user-select: none; 8 | } 9 | 10 | .ff-tabbar.tabHeader { 11 | font-size: 120%; 12 | font-family: verdana; 13 | line-height: 1.65em; 14 | } 15 | 16 | .padded { 17 | padding: 15px; 18 | overflow: auto; 19 | } -------------------------------------------------------------------------------- /demo/client/demo.jade: -------------------------------------------------------------------------------- 1 | head 2 | title demo 3 | 4 | body 5 | +famousContext id="mainCtx" 6 | +HeaderFooterLayout headerSize=45 7 | +TabBar target="header" selectedTab=currentRouteName _onRender="tabBarRender" class="black" createRenderables=createRenderables layoutController=layoutController 8 | +tabHeader tabId="FlexScrollViewDemo" text="FlexScrollView" 9 | +tabHeader tabId="TabBarDemo" text="TabBar" 10 | +RenderController target="content" prerender=true showId=currentRouteName 11 | +FlexScrollViewDemo 12 | +TabBarDemo 13 | 14 | template(name="TabBarInitHack") 15 | 16 | template(name="layout1") 17 | +famousContext id="mainCtx" 18 | +HeaderFooterLayout headerSize=120 19 | +Modifier target="header" 20 | +TabBar createRenderables=createRenderables 21 | +Surface template="tabHeader" 22 | +RenderController target="content" 23 | +yield 24 | 25 | template(name="tabHeader") 26 | +Surface tabId=tabId class="ff-widget ff-tabbar images item black tabHeader" 27 | | {{text}} -------------------------------------------------------------------------------- /demo/client/demo.js: -------------------------------------------------------------------------------- 1 | FView.ready(function() { 2 | // Famono: load famo.us shims and CSS 3 | famous.polyfills; 4 | famous.core.famous; // CSS 5 | }); 6 | 7 | FlowRouter.route('/', { 8 | action: function() { 9 | FlowRouter.go('/FlexScrollView'); 10 | } 11 | }); 12 | 13 | //var getSession = function(sessionVar) { return Session.get('direction') } 14 | 15 | Template.registerHelper('dstache', function() { 16 | return '{{'; 17 | }); 18 | 19 | Template.body.helpers({ 20 | currentRouteName: function() { 21 | return FlowRouter.getRouteName(); 22 | }, 23 | layoutController: { 24 | flowOptions: { 25 | spring: { 26 | dampingRatio: 0.2, 27 | period: 500 28 | } 29 | } 30 | }, 31 | createRenderables: { 32 | background: true, 33 | selectedItemOverlay: true 34 | }, 35 | tabBarRender: function() { 36 | var fview = FView.from(this); 37 | this.autorun(function(c) { 38 | FlowRouter.go(fview.selectedTab()); 39 | }); 40 | } 41 | }); 42 | 43 | /* 44 | Template.TabBarInitHack.onRendered(function() { 45 | // Since we're a non-fview template, the fview we get here is our parent's 46 | var fview = FView.from(this); 47 | 48 | this.autorun(function(c) { 49 | // only reactive dep 50 | var tabId = fview.selectedTab(); 51 | 52 | // give precendence to route, not initial tab index 53 | if (c.firstRun) 54 | return; 55 | 56 | console.log(tabId, FlowRouter.current().route.name); 57 | if (tabId && tabId !== FlowRouter.current().route.name /* non-reactive *//*) 58 | FlowRouter.go(tabId); 59 | }); 60 | }); 61 | */ -------------------------------------------------------------------------------- /demo/lib/smart.require: -------------------------------------------------------------------------------- 1 | { 2 | "famous": { 3 | "git": "https://github.com/Famous/famous.git", 4 | "root": "src" 5 | }, 6 | "famous.polyfills": { 7 | "git": "https://github.com/Famous/polyfills.git" 8 | }, 9 | "library": { 10 | "git": "https://github.com/raix/library.git" 11 | }, 12 | "famous-polyfills": { 13 | "alias": "famous.polyfills" 14 | } 15 | } -------------------------------------------------------------------------------- /demo/packages/.gitignore: -------------------------------------------------------------------------------- 1 | gadicohen:famous-views 2 | -------------------------------------------------------------------------------- /demo/packages/gadicohen:fview-flex: -------------------------------------------------------------------------------- 1 | ../.. -------------------------------------------------------------------------------- /lib/FlexScrollView.js: -------------------------------------------------------------------------------- 1 | var superDirty = function(/* arguments */) { 2 | var self = this; 3 | 4 | // _super() is always the 2nd last argument to addedAt/movedTo/removedAt 5 | arguments[arguments.length-2](); 6 | 7 | // only after _super()'s deferred stuff 8 | famous.core.Engine.defer(function() { 9 | // could be destroyed by the time this runs 10 | if (self.view) 11 | self.view._isDirty = true; 12 | }); 13 | }; 14 | 15 | var extension = { 16 | 17 | add: function(child_fview, child_options) { 18 | var flexView = this.view; 19 | var target = child_options.target; 20 | if (target) { 21 | var dataSource = {}; 22 | dataSource[target] = child_fview.view; 23 | if (_.isEmpty(flexView.getDataSource())) { 24 | flexView.setDataSource(dataSource); 25 | } else { 26 | dataSource = _.extend(flexView.getDataSource(), dataSource); 27 | flexView.setDataSource(dataSource); 28 | } 29 | 30 | flexView.reflowLayout(); 31 | 32 | return child_fview; 33 | } 34 | 35 | if (child_fview._view.name == 'FlexScrollView') { 36 | this.view.push(child_fview.view, undefined, false); 37 | } else { 38 | this.view.push(child_fview.view); 39 | } 40 | }, 41 | 42 | create: function(options) { 43 | options = _.extend({ 44 | flow: true, // smoothly animates renderables when changing the layout 45 | direction: undefined, // 0 = X, 1 = Y, undefined = use default from selected layout-function 46 | autoPipeEvents: true, 47 | mouseMove: true, 48 | nodeSpring: { 49 | dampingRatio: 0.6, 50 | period: 600 51 | } 52 | }, options); 53 | 54 | if (typeof options.layout === 'string') { 55 | if (Flex[options.layout]) 56 | options.layout = Flex[options.layout]; 57 | else 58 | throw new Error("[FlexLayoutController] No such Layout " + options.layout); 59 | } 60 | 61 | var node = new this._view.constructor(options); 62 | 63 | /* 64 | * If no dataSource is specified, expect a typical famousEach setup 65 | */ 66 | if (!options.dataSource) { 67 | // Quick hack for init order 68 | node.sequenceFrom = function(source) { 69 | node.setDataSource(source); 70 | }; 71 | } 72 | 73 | // auto pipe to a parent ContainSurface 74 | if (this.parent._view && this.parent._view.name === 'ContainerSurface') 75 | this.container = this.parent.view; 76 | if (this.container) { 77 | /* BUG: when using like this(in ContainerSurface an FlexScrollView is embedded): 78 | * {{#EdgeSwapper target="content" modifier="StateModifier" id="homeBody"}} 79 | * {{#ContainerSurface id="homeBodyContainer"}} 80 | * {{>showContent name=currentContent}} 81 | * {{/ContainerSurface}} 82 | * {{/EdgeSwapper}} 83 | * 84 | * below code would break scrolling when currentContent is changed. Seems like, 85 | * input events were not piped to FlexScrollView. 86 | * 87 | *//*----------------------------------------------------------------------------*//* 88 | node.subscribe(this.container); 89 | EventHandler.setInputHandler(this.container, node); 90 | EventHandler.setOutputHandler(this.container, node); 91 | */ 92 | this.parent.view.pipe(node); 93 | } 94 | 95 | return node; 96 | }, 97 | 98 | famousCreatedPost: function() { 99 | if (!this.container) 100 | this.pipeChildrenTo = this.parent.pipeChildrenTo ? 101 | [ this.view, this.parent.pipeChildrenTo[0] ] : 102 | [ this.view ]; 103 | }, 104 | 105 | attrUpdate: function(key, value, oldValue, data, firstTime) { 106 | 107 | if (key === 'layout') { 108 | if (typeof value === 'string') { 109 | if (Flex[value]) 110 | return this.view.setLayout(Flex[value]); 111 | else 112 | throw new Error("[FlexLayoutController] No such Layout " + value); 113 | } else 114 | return this.view.setLayout(value); 115 | } 116 | 117 | if (key === 'direction') 118 | return this.view.setDirection(value); 119 | 120 | if (key === 'layoutOptions') 121 | return this.view.setLayoutOptions(value); 122 | 123 | }, 124 | 125 | addedAt: superDirty, 126 | movedTo: superDirty, 127 | removedAt: superDirty 128 | }; 129 | 130 | var EventHandler = null; 131 | FView.ready(function() { 132 | EventHandler = famous.core.EventHandler; 133 | 134 | FView.registerView('FlexLayoutController', Flex.LayoutController, extension); 135 | FView.registerView('FlexScrollView', Flex.FlexScrollView, extension); 136 | }); 137 | -------------------------------------------------------------------------------- /lib/TabBar.js: -------------------------------------------------------------------------------- 1 | var Map = function() { 2 | this.idToIdx = {}; 3 | this.idxToId = {}; 4 | }; 5 | Map.prototype.add = function(idx, id) { 6 | this.idToIdx[id] = idx; 7 | this.idxToId[idx] = id; 8 | }; 9 | Map.prototype.toId = function(idx) { 10 | return this.idxToId[idx] !== undefined ? this.idxToId[idx] : idx; 11 | }; 12 | Map.prototype.toIdx = function(id) { 13 | return this.idToIdx[id] !== undefined ? this.idToIdx[id] : id; 14 | }; 15 | 16 | FView.ready(function() { 17 | FView.registerView('TabBar', Flex.TabBar, { 18 | 19 | create: function(options) { 20 | var fview = this; 21 | 22 | fview.properties = new ReactiveDict(); 23 | fview.selectedTab = function(value) { 24 | if (value) 25 | return this.properties.set('selectedTab', value); 26 | else 27 | return this.properties.get('selectedTab'); 28 | }; 29 | 30 | fview.items = []; 31 | fview.itemMap = new Map(); 32 | 33 | if (options.class) { 34 | options.classes = options.class.split(' '); 35 | delete options.class; 36 | } 37 | 38 | if (!options.createRenderables) 39 | options.createRenderables = {}; 40 | 41 | if (!options.createRenderables.item) { 42 | options.createRenderables.item = function(id, data) { 43 | // TODO https://github.com/IjzerenHein/famous-flex/blob/master/src/widgets/TabBar.js#L183 44 | 45 | // by default we'll accept full Surfaces / whatever 46 | return data.surface || data.view || data.node; 47 | } 48 | } 49 | 50 | var tabBar = new fview._view.constructor(options); 51 | 52 | tabBar.on('tabchange', function(event) { 53 | fview.properties.set('selectedTab', fview.itemMap.toId(event.index)); 54 | }); 55 | 56 | return tabBar; 57 | }, 58 | 59 | add: function(child_fview, child_options) { 60 | var fview = this; 61 | 62 | if (child_options.tabId) 63 | fview.itemMap.add(fview.items.length, child_options.tabId); 64 | else if (child_options.id) 65 | fview.ItemMap.add(fview.items.length, child_options.id); 66 | 67 | var selectedTab = fview.selectedTab(); 68 | 69 | // This won't be undefined if the app code initted it already, e.g. from router 70 | if (fview.items.length === 0 && selectedTab === undefined) 71 | fview.properties.set('selectedTab', child_options.tabId || child_options.id || 0); 72 | 73 | fview.items.push(child_fview); 74 | fview.view.setItems(fview.items); 75 | 76 | if (selectedTab === (child_options.tabId || fview.selectedTab || fview.items.length-1)) 77 | fview.view.setSelectedItemIndex(fview.items.length-1); 78 | }, 79 | 80 | attrUpdate: function(key, value, oldValue, data, firstTime) { 81 | var fview = this; 82 | 83 | if (key === 'selectedTab') { 84 | // On init, just the reactive var, setSelectedItemIndex called in add() 85 | if (firstTime) 86 | fview.properties.set('selectedTab', value); 87 | else 88 | fview.view.setSelectedItemIndex(fview.itemMap.toIdx(value)); 89 | } 90 | } 91 | 92 | }); 93 | }); -------------------------------------------------------------------------------- /lib/pre.js: -------------------------------------------------------------------------------- 1 | Flex = window.Flex = {}; 2 | 3 | var modules = [ 4 | 'famous-flex/src/LayoutUtility', 5 | 'famous-flex/src/LayoutContext', 6 | 'famous-flex/src/LayoutNode', 7 | 'famous-flex/src/LayoutNodeManager', 8 | 'famous-flex/src/FlowLayoutNode', 9 | 'famous-flex/src/helpers/LayoutDockHelper', 10 | 'famous-flex/src/LayoutController', 11 | 'famous-flex/src/AnimationController', 12 | 13 | 'famous-flex/src/LayoutNodeManager', 14 | 'famous-flex/src/LayoutController', 15 | 'famous-flex/src/ScrollController', 16 | 'famous-flex/src/VirtualViewSequence', 17 | 18 | 'famous-flex/src/layouts/CollectionLayout', 19 | 'famous-flex/src/layouts/GridLayout', 20 | 'famous-flex/src/layouts/ListLayout', 21 | 'famous-flex/src/layouts/ProportionalLayout', 22 | 'famous-flex/src/layouts/WheelLayout', 23 | 'famous-flex/src/layouts/HeaderFooterLayout', 24 | 'famous-flex/src/layouts/TabBarLayout', 25 | 26 | 'famous-flex/src/widgets/DatePickerComponents', 27 | 'famous-flex/src/widgets/DatePicker', 28 | 'famous-flex/src/widgets/TabBar', 29 | 'famous-flex/src/widgets/TabBarController', 30 | 31 | 'famous-refresh-loader/RefreshLoader', 32 | 'famous-autosizetextarea/AutosizeTextareaSurface', 33 | 34 | 'famous-flex/src/FlexScrollView' 35 | ]; 36 | 37 | require = function(obj) { 38 | //console.log('r', obj); 39 | obj = obj.split('/'); 40 | if (obj[0] === 'famous') { 41 | var s, ret = window; 42 | while (s=obj.shift()) 43 | ret = ret[s]; 44 | return ret; 45 | } else if (obj[obj.length-1].split('.')[0] === 'polyfill') { 46 | } else { 47 | var moduleName = obj[obj.length-1]; 48 | var ret = Flex[moduleName]; 49 | if (!ret) 50 | console.warn('require ' + moduleName + ' (not found) - needed by (probably) ' + currentDefine); 51 | return ret; 52 | } 53 | } 54 | 55 | var moduleCount = 0; 56 | function functionName(fun) { 57 | var moduleName = modules[moduleCount++]; 58 | return moduleName.split('/').pop(); 59 | } 60 | 61 | var currentDefine = null; 62 | define = function(func) { 63 | FView.ready(function() { 64 | //console.log('d', func); 65 | var exports = {}, module = {}; 66 | currentDefine = func.toString().match(/module.exports = (\w+);/); 67 | if (currentDefine) currentDefine = currentDefine[1]; 68 | func(require, exports, module); 69 | 70 | //if (typeof module.exports === 'function') { 71 | var moduleName = functionName(module.exports); 72 | Flex[moduleName] = module.exports; 73 | // console.log('d', moduleName); 74 | //} else { 75 | // console.log('doh', typeof module.exports, module.exports); 76 | //} 77 | }); 78 | }; 79 | 80 | // imports 81 | if (Package['raix:famono']) { 82 | FView.ready(function() { 83 | famous.surfaces.TextareaSurface; // views/AutosizeTextareaSurface 84 | famous.transitions.Easing; // widgets/TabBarController 85 | }); 86 | } -------------------------------------------------------------------------------- /package.js: -------------------------------------------------------------------------------- 1 | Package.describe({ 2 | name: 'gadicohen:fview-flex', 3 | version: '0.0.9', 4 | summary: 'IjzerenHein\'s famous-flex for famous-views', 5 | git: 'https://github.com/gadicc/fview-flex.git' 6 | }); 7 | 8 | Package.onUse(function(api) { 9 | // api.versionsFrom('1.0.3.1'); 10 | api.use('reactive-dict@1.0.4', 'client'); 11 | 12 | api.use('mjn:famous@0.3.5', 'client', { weak: true }); 13 | api.use('raix:famono@0.9.27', 'client', { weak: true }); 14 | 15 | api.use('gadicohen:famous-views@0.2.0', 'client'); 16 | 17 | // custom require/define funcs 18 | api.addFiles('lib/pre.js', 'client'); 19 | 20 | // ALWAYS copy this exactly into pre.js on update. for now. 21 | var modules = [ 22 | 'famous-flex/src/LayoutUtility', 23 | 'famous-flex/src/LayoutContext', 24 | 'famous-flex/src/LayoutNode', 25 | 'famous-flex/src/LayoutNodeManager', 26 | 'famous-flex/src/FlowLayoutNode', 27 | 'famous-flex/src/helpers/LayoutDockHelper', 28 | 'famous-flex/src/LayoutController', 29 | 'famous-flex/src/AnimationController', 30 | 31 | 'famous-flex/src/LayoutNodeManager', 32 | 'famous-flex/src/LayoutController', 33 | 'famous-flex/src/ScrollController', 34 | 'famous-flex/src/VirtualViewSequence', 35 | 36 | 'famous-flex/src/layouts/CollectionLayout', 37 | 'famous-flex/src/layouts/GridLayout', 38 | 'famous-flex/src/layouts/ListLayout', 39 | 'famous-flex/src/layouts/ProportionalLayout', 40 | 'famous-flex/src/layouts/WheelLayout', 41 | 'famous-flex/src/layouts/HeaderFooterLayout', 42 | 'famous-flex/src/layouts/TabBarLayout', 43 | 44 | 'famous-flex/src/widgets/DatePickerComponents', 45 | 'famous-flex/src/widgets/DatePicker', 46 | 'famous-flex/src/widgets/TabBar', 47 | 'famous-flex/src/widgets/TabBarController', 48 | 49 | 'famous-refresh-loader/RefreshLoader', 50 | 'famous-autosizetextarea/AutosizeTextareaSurface', 51 | 52 | 'famous-flex/src/FlexScrollView' 53 | ]; 54 | 55 | for (var i=0; i < modules.length; i++) 56 | api.addFiles('lib/' + modules[i] + '.js', 'client'); 57 | 58 | // add the css files 59 | api.addFiles('lib/famous-flex/src/widgets/styles.css', 'client'); 60 | 61 | // famous-views wrappers for famous-flex 62 | api.addFiles([ 63 | 'lib/FlexScrollView.js', 64 | 'lib/TabBar.js' 65 | ], 'client'); 66 | 67 | api.export('Flex', 'client'); 68 | }); 69 | 70 | Package.onTest(function(api) { 71 | api.use('tinytest'); 72 | api.use('gadicohen:fview-flex'); 73 | }); 74 | --------------------------------------------------------------------------------