Files
ionic-framework/js/ext/angular/src/service/ionicView.js
2014-01-20 09:47:18 -06:00

422 lines
13 KiB
JavaScript

angular.module('ionic.service.view', ['ui.router'])
.run( ['$rootScope', '$state', '$location', '$document',
function( $rootScope, $state, $location, $document) {
// init the variables that keep track of the view history
$rootScope.$viewHistory = {
histories: { root: { historyId: 'root', parentHistoryId: null, stack: [], cursor: -1 } },
backView: null,
forwardView: null,
currentView: null
};
$rootScope.$on('viewState.changeHistory', function(e, data) {
if(!data) return;
var hist = (data.historyId ? $rootScope.$viewHistory.histories[ data.historyId ] : null );
if(hist && hist.cursor > -1 && hist.cursor < hist.stack.length) {
// the history they're going to already exists
// go to it's last view in its stack
var view = hist.stack[ hist.cursor ];
return view.go(data);
}
// this history does not have a URL, but it does have a uiSref
// figure out its URL from the uiSref
if(!data.url && data.uiSref) {
data.url = $state.href(data.uiSref);
}
if(data.url) {
// don't let it start with a #, messes with $location.url()
if(data.url.indexOf('#') === 0) {
data.url = data.url.replace('#', '');
}
if(data.url !== $location.url()) {
// we've got a good URL, ready GO!
$location.url(data.url);
}
}
});
// Set the document title when a new view is shown
$rootScope.$on('viewState.viewEnter', function(e, data) {
if(data && data.title) {
$document[0].title = data.title;
}
});
}])
.factory('$ionicViewService', ['$rootScope', '$state', '$location', '$window', '$injector',
function( $rootScope, $state, $location, $window, $injector) {
var $animate = $injector.has('$animate') ? $injector.get('$animate') : false;
var View = function(){};
View.prototype.initialize = function(data) {
if(data) {
for(var name in data) this[name] = data[name];
return this;
}
return null;
};
View.prototype.go = function(opts) {
if(this.stateName) {
return $state.go(this.stateName, this.stateParams);
}
if(this.url && this.url !== $location.url()) {
if($rootScope.$viewHistory.backView === this) {
return $window.history.go(-1);
} else if($rootScope.$viewHistory.forwardView === this) {
return $window.history.go(1);
}
$location.url(this.url);
return;
}
return null;
};
View.prototype.destory = function() {
if(this.scope) {
this.scope.destory && this.scope.destory();
this.scope = null;
}
};
function createViewId(stateId) {
return ('_' + stateId + '_' + Math.round(Math.random() * 99999999)).replace(/\./g, '_');
}
return {
register: function(containerScope) {
var viewHistory = $rootScope.$viewHistory,
currentStateId = this.getCurrentStateId(),
hist = this._getHistory(containerScope),
currentView = viewHistory.currentView,
backView = viewHistory.backView,
forwardView = viewHistory.forwardView,
rsp = {
viewId: null,
navAction: null,
navDirection: null,
historyId: hist.historyId
};
if(currentView &&
currentView.stateId === currentStateId &&
currentView.historyId === hist.historyId) {
// do nothing if its the same stateId in the same history
rsp.navAction = 'noChange';
return rsp;
}
if(backView && backView.stateId === currentStateId) {
// they went back one, set the old current view as a forward view
rsp.viewId = backView.viewId;
rsp.navAction = 'moveBack';
if(backView.historyId === currentView.historyId) {
// went back in the same history
rsp.navDirection = 'back';
}
} else if(forwardView && forwardView.stateId === currentStateId) {
// they went to the forward one, set the forward view to no longer a forward view
rsp.viewId = forwardView.viewId;
rsp.navAction = 'moveForward';
if(forwardView.historyId === currentView.historyId) {
rsp.navDirection = 'forward';
}
var parentHistory = this._getParentHistoryObj(containerScope);
if(forwardView.historyId && parentHistory.scope) {
// if a history has already been created by the forward view then make sure it stays the same
parentHistory.scope.$historyId = forwardView.historyId;
rsp.historyId = forwardView.historyId;
}
} else if(currentView && currentView.historyId !== hist.historyId &&
hist.cursor > -1 && hist.stack.length > 0 && hist.cursor < hist.stack.length &&
hist.stack[hist.cursor].stateId === currentStateId) {
// they just changed to a different history and the history already has views in it
rsp.viewId = hist.stack[hist.cursor].viewId;
rsp.navAction = 'moveBack';
} else {
// set a new unique viewId
rsp.viewId = createViewId(currentStateId);
if(currentView) {
// set the forward view if there is a current view (ie: if its not the first view)
currentView.forwardViewId = rsp.viewId;
// its only moving forward if its in the same history
if(hist.historyId === currentView.historyId) {
rsp.navDirection = 'forward';
}
rsp.navAction = 'newView';
// check if there is a new forward view
if(forwardView && currentView.stateId !== forwardView.stateId) {
// they navigated to a new view but the stack already has a forward view
// since its a new view remove any forwards that existed
var forwardsHistory = this._getView(forwardView.historyId);
if(forwardsHistory) {
// the forward has a history
for(var x=forwardsHistory.stack.length - 1; x >= forwardView.index; x--) {
// starting from the end destory all forwards in this history from this point
forwardsHistory.stack[x].destory();
forwardsHistory.stack.splice(x);
}
}
}
} else {
// there's no current view, so this must be the initial view
rsp.navAction = 'initialView';
}
// add the new view to the stack
viewHistory.histories[rsp.viewId] = this.createView({
viewId: rsp.viewId,
index: hist.stack.length,
historyId: hist.historyId,
backViewId: (currentView && currentView.viewId ? currentView.viewId : null),
forwardViewId: null,
stateId: currentStateId,
stateName: this.getCurrentStateName(),
stateParams: this.getCurrentStateParams(),
url: $location.url()
});
// add the new view to this history's stack
hist.stack.push(viewHistory.histories[rsp.viewId]);
}
this.setNavViews(rsp.viewId);
hist.cursor = viewHistory.currentView.index;
return rsp;
},
setNavViews: function(viewId) {
var viewHistory = $rootScope.$viewHistory;
viewHistory.currentView = this._getView(viewId);
viewHistory.backView = this._getBackView(viewHistory.currentView);
viewHistory.forwardView = this._getForwardView(viewHistory.currentView);
$rootScope.$broadcast('$viewHistory.historyChange', {
showBack: (viewHistory.backView && viewHistory.backView.historyId === viewHistory.currentView.historyId)
});
},
registerHistory: function(scope) {
scope.$historyId = 'h' + Math.round(Math.random() * 99999999999);
},
createView: function(data) {
var newView = new View();
return newView.initialize(data);
},
getCurrentView: function() {
return $rootScope.$viewHistory.currentView;
},
getBackView: function() {
return $rootScope.$viewHistory.backView;
},
getForwardView: function() {
return $rootScope.$viewHistory.forwardView;
},
getNavDirection: function() {
return $rootScope.$viewHistory.navDirection;
},
getCurrentStateName: function() {
return ($state && $state.current ? $state.current.name : null);
},
isCurrentStateNavView: function(navView) {
return ($state &&
$state.current &&
$state.current.views &&
$state.current.views[navView] ? true : false);
},
getCurrentStateParams: function() {
var rtn;
if ($state && $state.params) {
for(var key in $state.params) {
if($state.params.hasOwnProperty(key)) {
rtn = rtn || {};
rtn[key] = $state.params[key];
}
}
}
return rtn;
},
getCurrentStateId: function() {
var id;
if($state && $state.current && $state.current.name) {
id = $state.current.name;
if($state.params) {
for(var key in $state.params) {
if($state.params.hasOwnProperty(key) && $state.params[key]) {
id += "_" + key + "=" + $state.params[key];
}
}
}
return id;
}
// if something goes wrong make sure its got a unique stateId
return 'r' + Math.round(Math.random() * 9999999);
},
_getView: function(viewId) {
return (viewId ? $rootScope.$viewHistory.histories[ viewId ] : null );
},
_getBackView: function(view) {
return (view ? this._getView(view.backViewId) : null );
},
_getForwardView: function(view) {
return (view ? this._getView(view.forwardViewId) : null );
},
_getHistory: function(scope) {
var histObj = this._getParentHistoryObj(scope);
if( !$rootScope.$viewHistory.histories[ histObj.historyId ] ) {
// this history object exists in parent scope, but doesn't
// exist in the history data yet
$rootScope.$viewHistory.histories[ histObj.historyId ] = {
historyId: histObj.historyId,
parentHistoryId: this._getParentHistoryObj(histObj.scope.$parent).historyId,
stack: [],
cursor: -1
};
}
return $rootScope.$viewHistory.histories[ histObj.historyId ];
},
_getParentHistoryObj: function(scope) {
var parentScope = scope;
while(parentScope) {
if(parentScope.hasOwnProperty('$historyId')) {
// this parent scope has a historyId
return { historyId: parentScope.$historyId, scope: parentScope };
}
// nothing found keep climbing up
parentScope = parentScope.$parent;
}
// no history for for the parent, use the root
return { historyId: 'root', scope: $rootScope };
},
transition: function(opts) {
if(!opts || !opts.enteringElement) return;
if (opts.leavingScope) {
opts.leavingScope.$destroy();
opts.leavingScope = null;
}
// use the directive's animation attribute first
// if it doesn't exist, then use the given animation
var animationClass = opts.animation || getAnimationClass();
if($animate && animationClass && opts.doAnimation !== false && opts.navDirection) {
// set the animation we're gonna use
this.setAnimationClass(opts.parentElement, animationClass, opts.navDirection);
opts.enteringElement.addClass('ng-enter');
// disable any pointer-events from being able to fire
document.body.classList.add('disable-pointer-events');
// start the animations
if(opts.leavingElement) {
$animate.leave(opts.leavingElement, function() {
// re-enable pointer-events
document.body.classList.remove('disable-pointer-events');
});
}
$animate.enter(opts.enteringElement, opts.parentElement);
} else {
// no animation, just plain ol' add/remove DOM elements
if(opts.leavingElement) {
opts.leavingElement.remove();
}
opts.parentElement.append(opts.enteringElement);
}
function getAnimationClass(){
// go up the ancestors looking for an animation value
var climbScope = opts.enteringScope;
while(climbScope) {
if(climbScope.animation) {
return climbScope.animation;
}
climbScope = climbScope.$parent;
}
}
},
setAnimationClass: function(element, animationClass, navDirection) {
// add the animation we're gonna use
element[0].classList.add(animationClass);
if(navDirection === 'back') {
// animate backward
element[0].classList.add('reverse');
} else {
// defaults to animate forward
// make sure the reverse class isn't already added
element[0].classList.remove('reverse');
}
},
clearHistory: function() {
var historyId, x, view,
histories = $rootScope.$viewHistory.histories,
currentView = $rootScope.$viewHistory.currentView;
for(historyId in histories) {
if(histories[historyId].stack) {
histories[historyId].stack = [];
histories[historyId].cursor = -1;
}
if(currentView.historyId === historyId) {
currentView.backViewId = null;
currentView.forwardViewId = null;
histories[historyId].stack.push(currentView);
} else if(histories[historyId].destroy) {
histories[historyId].destroy();
}
}
this.setNavViews(currentView.viewId);
}
};
}]);