Skip to content

Commit

Permalink
Now compatible with Android (and other)
Browse files Browse the repository at this point in the history
  • Loading branch information
FokkeZB committed Aug 14, 2013
1 parent 6d58900 commit 780e3d5
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 138 deletions.
50 changes: 23 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,22 @@ The widget automatically shows an *ActivityIndicator* in a *TableView*'s *Footer

```javascript
"dependencies": {
"nl.fokkezb.infiniteScroll":"1.1"
"nl.fokkezb.infiniteScroll":"1.2"
}
```

* Add the widget to your *TableView*:

```xml
<TableView dataCollection="myCollection">
<TableView>
<Widget id="is" src="nl.fokkezb.infiniteScroll" onEnd="myLoader" />
</TableView>
```

* In the callback set via `myLoader` you can call `$.is.hide()` to hide the *FooterView* or `$.is.dettach()` to remove it when there are no more rows to load. For example:
* In the callback set via `myLoader` you call either `e.success()`, `e.error()` or `e.done()` optionally passing a custom message:

```javascript
function myLoader() {
function myLoader(e) {
// Length before
var ln = myCollection.length;
Expand All @@ -56,18 +56,12 @@ The widget automatically shows an *ActivityIndicator* in a *TableView*'s *Footer
silent: true,
success: function (col) {

// Reached the end
if (col.length === ln) {
$.is.dettach();
// Just hide
} else {
$.is.hide();
}
// Call "done" if we didn't add anymore models
(col.length === ln) ? e.done() : e.success();
},
error: $.is.hide
error: e.error
});
}
```
Expand All @@ -79,38 +73,40 @@ The widget can be fully styled without touching the widget source. Use the follo
| --------- | ------- |
| `#is` | The view to be added as *FooterView* |
| `#isIndicator` | The *ActivityIndicator* showing during load |
| `#isText` | The message shown when not loading |

## Options
There are no required options to pass via TSS properties or XML attributes, apart from the `onEnd` attribute to bind your callback to the end-event.

If you re-style the widget you might need to change the `height` of the footerView to keep during load. When the *FooterView* is added to your *TableView* its height will be set to `0`. When the user reaches the end of the *TableView* the height is set to the configured height.
There are no required options to pass via TSS properties or XML attributes, apart from the `onEnd` attribute to bind your callback to the end-event. You can change the displayed messages by using the following options:

| Parameter | Type | Default |
| --------- | ---- | ----------- |
| height | `number` | Height of the *FooterView* when shown (default: `50`) |
| msgTap | `string` | Tap to load more... |
| msgDone | `string` | No more to load... |
| msgError | `string` | Tap to try again... |

## Methods
You can also manually show and hide the view or trigger the complete cycle of the widget. You could use this for the first load when your window opens.
You can also manually trigger the loading state of the widget. You could use this for the first load when your window opens.

| Function | Parameters | Usage |
| ---------- | ---------- | ----- |
| Function | Parameters | Usage
| ---------- | ---------- |
| setOptions | `object` | Set any of the options
| load | | Manually trigger show + the `end` event
| show | | Show the *FooterView*
| hide | | Hide the *FooterView*
| dettach | | Remove the *FooterView*
| attach | | Re-add the *FooterView* after removal
| load | | Manually trigger the `end` event and loading state
| state | `state`, `string` | Manually set the state. The first argument should be one of the exported `SUCCESS`, `DONE` and `ERROR` constants. The second optional argument is a custom message to display instead of the message belonging to the state.
| dettach | | Manually set the `DONE` state and remove the scroll listener

## Changelog
* 1.2:
* Now compatible with Android (and other OS)
* View will now always show since Android doesn't support removing it :(
* 1.1:
* Renamed to nl.fokkezb.infiniteScroll
* From now on declared in the XML view instead of the controller!
* Splitted `init` into `setOptions` and `attach`
* Renamed `remove` to `dettach`
* Renamed `trigger` to `load` to not interfere with BackBone
* 1.0.1:
* Fixed for Alloy 1.0GA
* 1.0: Initial version
itial version

## License

Expand Down
210 changes: 109 additions & 101 deletions controllers/widget.js
Original file line number Diff line number Diff line change
@@ -1,133 +1,141 @@
var args = arguments[0] || {};

var options = {
height: 50
msgTap: L('isTap', 'Tap to load more...'),
msgDone: L('isDone', 'No more to load...'),
msgError: L('isError', 'Tap to try again...')
};

var attached = false;
var loading = false;
var shown = false;

var item = null;
var offset = null;

function show() {

if (!attached || shown) {
return false;
}

shown = true;

$.isIndicator.show();
$.is.height = options.height;

return true;
var loading = false,
position = null;

init();

function init() {

// delete special args
delete args.__parentSymbol;
delete args.__itemTemplate;
delete args.$model;

// set args as options
setOptions(args);

// set default text & remove indicator
$.isText.text = options.msgTap;
$.is.remove($.isIndicator);

// listen to scroll
__parentSymbol.addEventListener('scroll', onScroll);

return;
}

function hide() {

if (!attached || !shown) {
return false;
}

$.is.height = 0;
$.isIndicator.hide();

shown = false;
loading = false;

return true;
function state(state, message) {

// remove indicator
$.isIndicator.hide();
$.is.remove($.isIndicator);

// set custom message
if (message) {
$.isText.text = message;

// set state message
} else {

if (state === 0 || state === false) {
$.isText.text = options.msgError;
} else if (state === -1) {
$.isText.text = options.msgDone;
} else if (state === 1 || state === true) {
$.isText.text = options.msgTap;
} else {
throw Error('Pass a valid state');
}
}

// add text
$.is.add($.isText);

loading = false;

return true;
}

function load() {

if (!attached || loading) {
return false;
}

loading = true;

show();

$.trigger('end');

return true;
}

function scrollListener(e) {

if (OS_ANDROID) {
var triggerLoad = triggerLoad || (item && e.firstVisibleItem > item) && (e.totalItemCount < e.firstVisibleItem + e.visibleItemCount);
item = e.firstVisibleItem;

} else if (OS_IOS) {
var triggerLoad = triggerLoad || (offset && e.contentOffset.y > offset) && (e.contentOffset.y + e.size.height > e.contentSize.height);
offset = e.contentOffset.y;
}

if (triggerLoad) {
load();
}

return;
}
if (loading) {
return false;
}

function setOptions(_properties) {
_.extend(options, _properties);
}
loading = true;

// remove text
$.is.remove($.isText);

function attach(set) {

if (attached) {
return false;
}

$.is.height = 0;
__parentSymbol.footerView = $.is;
// add indicator
$.is.add($.isIndicator);
$.isIndicator.show();

init();
// trigger listener to load
$.trigger('end', {
success: function (msg) { return state(exports.SUCCESS, msg); },
error: function (msg) { return state(exports.ERROR, msg); },
done: function (msg) { return state(exports.DONE, msg); },
});

return true;
return true;
}

function init() {
__parentSymbol.addEventListener('scroll', scrollListener);

attached = true;
loading = false;
shown = false;
function onScroll(e) {
var triggerLoad;

item = null;
offset = null;
if (OS_ANDROID) {

return;
}
// last item shown
triggerLoad = (position && e.firstVisibleItem > position && e.totalItemCount <= (e.firstVisibleItem + e.visibleItemCount));

function dettach() {
// remember position
position = e.firstVisibleItem;

if (!attached) {
return false;
}
} else if (OS_IOS) {

__parentSymbol.footerView = null;
// last pixel shown
triggerLoad = (position && e.contentOffset.y > position) && (e.contentOffset.y + e.size.height > e.contentSize.height);

__parentSymbol.removeEventListener('scroll', scrollListener);
// remember position
position = e.contentOffset.y;
}

attached = false;
// trigger
if (triggerLoad) {
load();
}

return true;
return;
}

delete args.__parentSymbol;
function dettach() {

setOptions(args);
// set as done
state(exports.DONE);

init();
// remove listener
__parentSymbol.removeEventListener('scroll', onScroll);

return;
}

function setOptions(_properties) {
_.extend(options, _properties);
}

exports.SUCCESS = 1;
exports.ERROR = 0;
exports.DONE = -1;

exports.setOptions = setOptions;
exports.show = show;
exports.hide = hide;
exports.load = load;
exports.dettach = dettach;
exports.attach = attach;
exports.state = state;
exports.dettach = dettach;
17 changes: 14 additions & 3 deletions styles/widget.tss
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
".is": {
height: 0
top: 0,

width: Ti.UI.FILL,
height: 50,
}

".isIndicator": {
style: Ti.UI.ActivityIndicatorStyle.DARK
style: Ti.UI.ActivityIndicatorStyle.DARK
}

".isIndicator[platform=ios]": {
style: Ti.UI.iPhone.ActivityIndicatorStyle.DARK
style: Ti.UI.iPhone.ActivityIndicatorStyle.DARK
}

".isText": {
color: '#777',
font: {
fontSize: 13,
fontWeight: 'bold'
}
}
11 changes: 6 additions & 5 deletions views/widget.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<Alloy>
<FooterView>
<View id="is" class="stl">
<ActivityIndicator id="isIndicator" class="isIndicator" />
</View>
</FooterView>
<FooterView>
<View id="is" class="is">
<Label id="isText" class="isText" />
<ActivityIndicator id="isIndicator" class="isIndicator" />
</View>
</FooterView>
</Alloy>
Loading

0 comments on commit 780e3d5

Please sign in to comment.