Skip to content

Commit dcac7f9

Browse files
committed
WIP: slidebox
1 parent 7d000de commit dcac7f9

23 files changed

+1202
-975
lines changed

config/build.config.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ module.exports = {
4747
'js/utils/tap.js',
4848
'js/utils/activator.js',
4949
'js/utils/utils.js',
50+
'js/utils/list.js',
5051
'js/utils/keyboard.js',
5152
'js/utils/viewport.js',
5253

@@ -57,12 +58,7 @@ module.exports = {
5758
'js/views/listView.js',
5859
'js/views/modalView.js',
5960
'js/views/sideMenuView.js',
60-
'js/views/sliderView.js',
6161
'js/views/toggleView.js',
62-
63-
// Controllers
64-
'js/controllers/viewController.js',
65-
'js/controllers/sideMenuController.js',
6662
],
6763

6864
angularIonicFiles: [
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
IonicModule
2+
.controller('$ionSlideBox', [
3+
'$scope',
4+
'$element',
5+
'$$ionicAttachDrag',
6+
SlideBoxController
7+
]);
8+
9+
/*
10+
* This can be abstracted into a controller that will work for views, tabs, and slidebox.
11+
*/
12+
function SlideBoxController(scope, element, $$ionicAttachDrag) {
13+
var self = this;
14+
var slideList = ionic.Utils.list([]);
15+
var selectedIndex = -1;
16+
var slidesParent = angular.element(element[0].querySelector('.slider-slides'));
17+
18+
// Successful slide requires velocity to be greater than this amount
19+
var SLIDE_SUCCESS_VELOCITY = (1 / 4); // pixels / ms
20+
var SLIDE_TRANSITION_DURATION = 250; //ms
21+
22+
$$ionicAttachDrag(scope, element, {
23+
getDistance: function() { return slidesParent.prop('offsetWidth'); },
24+
onDrag: onDrag,
25+
onDragEnd: onDragEnd
26+
});
27+
28+
self.element = element;
29+
self.isRelevant = isRelevant;
30+
self.left = left;
31+
self.right = right;
32+
33+
// Methods calling straight back to Utils.list
34+
self.at = slideList.at;
35+
self.count = slideList.count;
36+
self.indexOf = slideList.indexOf;
37+
self.isInRange = slideList.isInRange;
38+
self.loop = slideList.loop;
39+
self.delta = slideList.delta;
40+
41+
self.add = add;
42+
self.remove = remove;
43+
self.move = move;
44+
self.shown = shown;
45+
self.select = select;
46+
self.onDrag = onDrag;
47+
self.onDragEnd = onDragEnd;
48+
49+
// ***
50+
// Public Methods
51+
// ***
52+
53+
// Gets whether the given index is relevant to selected
54+
// That is, whether the given index is left, shown, or right
55+
function isRelevant(index) {
56+
return slideList.isRelevant(index, selectedIndex);
57+
}
58+
59+
// Gets the index to the left of the given slide, default selectedIndex
60+
function left(index) {
61+
return slideList.previous(arguments.length ? index : selectedIndex);
62+
}
63+
64+
// Gets the index to the right of the given slide, default selectedIndex
65+
function right(index) {
66+
return slideList.next(arguments.length ? index : selectedIndex);
67+
}
68+
69+
/*
70+
* Add/remove/move slides
71+
*/
72+
function add(slide, index) {
73+
var newIndex = slideList.add(slide, index);
74+
slide.onAdded(slidesParent);
75+
76+
if (selectedIndex === -1) {
77+
self.select(newIndex);
78+
} else if (newIndex === self.left() || newIndex === self.right()) {
79+
// if the new slide is adjacent to selected, refresh the selection
80+
enqueueRefresh();
81+
}
82+
}
83+
function remove(slide) {
84+
var index = self.indexOf(slide);
85+
if (index === -1) return;
86+
87+
var isSelected = self.shown() === index;
88+
slideList.remove(index);
89+
slide.onRemoved();
90+
91+
if (isSelected) {
92+
self.select( self.isInRange(selectedIndex) ? selectedIndex : selectedIndex - 1 );
93+
}
94+
}
95+
function move(slide, targetIndex) {
96+
var index = self.indexOf(slide);
97+
if (index === -1) return;
98+
99+
// If the slide is current, right, or left, save so we can re-select after moving.
100+
var isRelevant = self.isRelevant(targetIndex);
101+
slideList.remove(index);
102+
slideList.add(slide, targetIndex);
103+
104+
if (isRelevant) {
105+
enqueueRefresh();
106+
}
107+
}
108+
109+
function shown() {
110+
return selectedIndex;
111+
}
112+
113+
/*
114+
* Select and change slides
115+
*/
116+
function select(newIndex, transitionDuration) {
117+
if (!self.isInRange(newIndex)) return;
118+
119+
var delta = self.delta(selectedIndex, newIndex);
120+
121+
slidesParent.css(
122+
ionic.CSS.TRANSITION_DURATION,
123+
(transitionDuration || SLIDE_TRANSITION_DURATION) + 'ms'
124+
);
125+
selectedIndex = newIndex;
126+
127+
if (self.isInRange(selectedIndex) && Math.abs(delta) > 1) {
128+
// if the new slide is > 1 away, then it is currently not attached to the DOM.
129+
// Attach it in the position from which it will slide in.
130+
self.at(newIndex).setState(delta > 1 ? 'right' : 'left');
131+
// Wait one frame so the new slide can 'settle' in its new place and
132+
// be ready to properly transition in
133+
ionic.requestAnimationFrame(doSelect);
134+
} else {
135+
doSelect();
136+
}
137+
138+
function doSelect() {
139+
// If a new selection has happened before this frame, abort.
140+
if (selectedIndex !== newIndex) return;
141+
scope.$evalAsync(function() {
142+
if (selectedIndex !== newIndex) return;
143+
arrangeSlides(newIndex);
144+
});
145+
}
146+
}
147+
148+
function onDrag(percent) {
149+
var target = self.at(percent > 0 ? self.right() : self.left());
150+
var current = self.at(self.shown());
151+
152+
target && target.transform(percent);
153+
current && current.transform(percent);
154+
}
155+
156+
function onDragEnd(percent, velocity) {
157+
var nextIndex = -1;
158+
if (Math.abs(percent) > 0.5 || velocity > SLIDE_SUCCESS_VELOCITY) {
159+
nextIndex = percent > 0 ? self.right() : self.left();
160+
}
161+
var transitionDuration = Math.min(
162+
slidesParent.prop('offsetWidth') / (3.5 * velocity),
163+
SLIDE_TRANSITION_DURATION
164+
);
165+
166+
// Select a new slide if it's avaiable
167+
self.select(
168+
self.isInRange(nextIndex) ? nextIndex : self.shown(),
169+
transitionDuration
170+
);
171+
}
172+
173+
// ***
174+
// Private Methods
175+
// ***
176+
177+
var oldSlides;
178+
function arrangeSlides(newShownIndex) {
179+
var newSlides = {
180+
left: self.at(self.left(newShownIndex)),
181+
shown: self.at(newShownIndex),
182+
right: self.at(self.right(newShownIndex))
183+
};
184+
185+
newSlides.left && newSlides.left.setState('left');
186+
newSlides.shown && newSlides.shown.setState('shown');
187+
newSlides.right && newSlides.right.setState('right');
188+
189+
if (oldSlides) {
190+
var oldShown = oldSlides.shown;
191+
var delta = self.delta(self.indexOf(oldSlides.shown), self.indexOf(newSlides.shown));
192+
if (Math.abs(delta) > 1) {
193+
// If we're changing by more than one slide, we need to manually transition
194+
// the current slide out and then put it into its new state.
195+
oldShown.setState(delta > 1 ? 'left' : 'right').then(function() {
196+
oldShown.setState(
197+
newSlides.left === oldShown ? 'left' :
198+
newSlides.right === oldShown ? 'right' :
199+
'detached'
200+
);
201+
});
202+
} else {
203+
detachIfUnused(oldSlides.shown);
204+
}
205+
//Additionally, we need to detach both of the old slides.
206+
detachIfUnused(oldSlides.left);
207+
detachIfUnused(oldSlides.right);
208+
}
209+
210+
function detachIfUnused(oldSlide) {
211+
if (oldSlide && oldSlide !== newSlides.left &&
212+
oldSlide !== newSlides.shown &&
213+
oldSlide !== newSlides.right) {
214+
oldSlide.setState('detached');
215+
}
216+
}
217+
218+
oldSlides = newSlides;
219+
}
220+
221+
// When adding/moving slides, we sometimes need to refresh
222+
// the currently shown slides to reflect new data.
223+
// We don't want to refresh more than once per digest cycle,
224+
// so we do this.
225+
function enqueueRefresh() {
226+
if (!enqueueRefresh.queued) {
227+
enqueueRefresh.queued = true;
228+
scope.$$postDigest(function() {
229+
self.select(selectedIndex);
230+
enqueueRefresh.queued = false;
231+
});
232+
}
233+
}
234+
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
IonicModule
2+
.controller('$ionSlide', [
3+
'$scope',
4+
'$element',
5+
'$q',
6+
SlideController
7+
]);
8+
9+
function SlideController(scope, element, $q) {
10+
var self = this;
11+
12+
scope.$on('$destroy', function() {
13+
element.removeData();
14+
detachSlide();
15+
});
16+
element.on(ionic.CSS.TRANSITIONEND, onTransitionEnd);
17+
18+
self.element = element;
19+
20+
self.onAdded = onAdded;
21+
self.onRemoved = onRemoved;
22+
23+
self.transform = transform;
24+
25+
self.state = '';
26+
self.setState = setState;
27+
28+
// ***
29+
// Public Methods
30+
// ***
31+
32+
function onAdded(parentElement) {
33+
self.parentElement = parentElement;
34+
35+
// Set default state
36+
self.setState('detached');
37+
}
38+
function onRemoved() {
39+
self.setState('detached');
40+
}
41+
42+
var isTransforming;
43+
function transform(percent) {
44+
if (!isTransforming) {
45+
self.element.addClass('no-animate');
46+
isTransforming = true;
47+
}
48+
49+
var startPercent = self.state === 'left' ? -1 :
50+
self.state === 'right' ? 1 :
51+
0;
52+
self.element.css(
53+
ionic.CSS.TRANSFORM,
54+
'translate3d(' + (100 * (startPercent - percent)) + '%, 0, 0)'
55+
);
56+
}
57+
58+
function setState(newState) {
59+
if (newState !== self.state) {
60+
self.state && self.element.attr('slide-previous-state', self.state);
61+
self.element.attr('slide-state', newState);
62+
}
63+
self.element.css(ionic.CSS.TRANSFORM, '');
64+
self.element.removeClass('no-animate');
65+
isTransforming = false;
66+
67+
switch(newState) {
68+
case 'detached':
69+
detachSlide();
70+
break;
71+
case 'left':
72+
case 'right':
73+
case 'shown':
74+
attachSlide();
75+
break;
76+
}
77+
78+
self.previousState = self.state;
79+
self.state = newState;
80+
81+
return getTransitionPromise();
82+
}
83+
84+
// ***
85+
// Private Methods
86+
// ***
87+
88+
function attachSlide() {
89+
if (!self.element[0].parentNode) {
90+
self.parentElement.append(self.element);
91+
ionic.Utils.reconnectScope(scope);
92+
}
93+
}
94+
95+
function detachSlide() {
96+
// Don't use self.element.remove(), that will destroy the element's data
97+
var parent = self.element[0].parentNode;
98+
if (parent) {
99+
parent.removeChild(self.element[0]);
100+
ionic.Utils.disconnectScope(scope);
101+
}
102+
}
103+
104+
var transitionDeferred;
105+
function getTransitionPromise() {
106+
// If we aren't transitioning to or from shown, there's no transition, so instantly resolve.
107+
if (self.previousState !== 'shown' && self.state !== 'shown') {
108+
return $q.when();
109+
}
110+
111+
// Interrupt current promise if a new state was set.
112+
transitionDeferred && transitionDeferred.reject();
113+
transitionDeferred = $q.defer();
114+
115+
return transitionDeferred.promise;
116+
}
117+
118+
function onTransitionEnd(ev) {
119+
if (ev.target !== element[0]) return; //don't let the event bubble up from children
120+
transitionDeferred && transitionDeferred.resolve();
121+
}
122+
123+
}

js/angular/directive/infiniteScroll.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,6 @@ IonicModule
119119
});
120120

121121
$scope.$on('$destroy', function() {
122-
console.log(scrollCtrl);
123122
if(scrollCtrl && scrollCtrl.$element)scrollCtrl.$element.off('scroll', checkBounds);
124123
});
125124

0 commit comments

Comments
 (0)