Skip to content

Commit

Permalink
Add unloadView support. This support includes support for config opti…
Browse files Browse the repository at this point in the history
…ons on a view to alwaysAutoUnload:(the view will be unloaded after afterDeactivate every time the view is shown), neverAutoUnload(the view will never be unloaded automatically, no matter how many views are loaded), and use an autoUnloadCount(is set to a number which is used to determine how many views per constraint to leave loaded when transitioning). Fixes issue ibm-js#3
  • Loading branch information
edchat committed Nov 8, 2013
1 parent 944c007 commit 3c566a1
Show file tree
Hide file tree
Showing 9 changed files with 233 additions and 40 deletions.
8 changes: 8 additions & 0 deletions ViewBase.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ define(["require", "dojo/when", "dojo/on", "dojo/dom-attr", "dojo/dom-style", "d
this.children = {};
this.selectedChildren = {};
this.loadedStores = {};
this.transitionCount = 0;

// skipNodeCache: [protected] Boolean (from dijit._TemplatedMixin)
// If using a cached widget template nodes poses issues for a
// particular widget class, it can set this property to ensure
// that its template is always re-built from a string
this._skipNodeCache = true; // use true to avoid Detached domNodes for each view created.

// private
this._started = false;
lang.mixin(this, params);
Expand Down
15 changes: 10 additions & 5 deletions controllers/BorderLayout.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,19 @@ function(declare, domAttr, domStyle, LayoutBase, BorderContainer, StackContainer

this.app.log("in app/controllers/BorderLayout.initLayout event.view.constraint=",event.view.constraint);
var constraint = event.view.constraint; // constraint holds the region for this view, center, top etc.

if(event.view.parent.id == this.app.id){ // If the parent of this view is the app we are working with the BorderContainer
var reg = registry.byId(event.view.parent.id+"-"+constraint);
if(reg){ // already has a stackContainer, just create the contentPane for this view and add it to the stackContainer.
var cp1 = new ContentPane({id:event.view.id+"-cp-"+constraint});
cp1.addChild(event.view); // important to add the widget to the cp before adding cp to BorderContainer for height
reg.addChild(cp1);
bc.addChild(reg);
var cp1 = registry.byId(event.view.id+"-cp-"+constraint);
if(!cp1){
cp1 = new ContentPane({id:event.view.id+"-cp-"+constraint});
cp1.addChild(event.view); // important to add the widget to the cp before adding cp to BorderContainer for height
reg.addChild(cp1);
bc.addChild(reg);
}else{
cp1.domNode.appendChild(event.view.domNode);
}
}else{ // need a contentPane
// this is where the region (constraint) is set for the BorderContainer's StackContainer
var noSplitter = this.app.borderLayoutNoSplitter || false;
Expand Down
12 changes: 6 additions & 6 deletions controllers/Layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@ function(declare, lang, array, win, query, domGeom, domAttr, domStyle, registry,
this.app.log("in Layout resizeSelectedChildren calling resizeSelectedChildren calling _doResize for w.selectedChildren[hash].id="+w.selectedChildren[hash].id);
this._doResize(w.selectedChildren[hash]);
// Call resize on child widgets, needed to get the scrollableView to resize correctly initially
array.forEach(w.selectedChildren[hash].domNode.children, function(child){
if(registry.byId(child.id) && registry.byId(child.id).resize){
registry.byId(child.id).resize();
}
});
// array.forEach(w.selectedChildren[hash].domNode.children, function(child){
// if(registry.byId(child.id) && registry.byId(child.id).resize){
// registry.byId(child.id).resize();
// }
// });

this.resizeSelectedChildren(w.selectedChildren[hash]);
}
Expand Down Expand Up @@ -187,7 +187,7 @@ function(declare, lang, array, win, query, domGeom, domAttr, domStyle, registry,
}
}
// We don't need to layout children if this._contentBox is null for the operation will do nothing.
if(view._contentBox){
if(view._contentBox && view._active !== false){
layout.layoutChildren(view.domNode, view._contentBox, children);
}
}
Expand Down
2 changes: 1 addition & 1 deletion controllers/LayoutBase.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ function(lang, declare, has, win, config, domAttr, topic, domStyle, constraints,
this._doResize(this.app.selectedChildren[hash]);
}
}

},

initLayout: function(event){
Expand Down
94 changes: 88 additions & 6 deletions controllers/Load.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
define(["require", "dojo/_base/lang", "dojo/_base/declare", "dojo/on", "dojo/Deferred", "dojo/when", "dojo/dom-style", "../Controller"],
function(require, lang, declare, on, Deferred, when, domStyle, Controller, View){
define(["require", "dojo/_base/lang", "dojo/_base/declare", "dojo/on", "dojo/Deferred", "dojo/when", "dojo/dom-style",
"dojo/_base/array", "dojo/dom-construct", "dijit/registry", "../Controller"],
function(require, lang, declare, on, Deferred, when, domStyle, array, domConstruct, registry, Controller, View){
// module:
// dojox/app/controllers/Load
// summary:
// Bind "app-load" event on dojox/app application instance.
// Load child view and sub children at one time.

var MODULE = "dapp/controllers/Load";

return declare(Controller, {


Expand All @@ -21,7 +24,8 @@ define(["require", "dojo/_base/lang", "dojo/_base/declare", "dojo/on", "dojo/Def
// {event : handler}
this.events = {
"app-init": this.init,
"app-load": this.load
"app-load": this.load,
"app-unload-view": this.unloadView
};
},

Expand Down Expand Up @@ -53,12 +57,12 @@ define(["require", "dojo/_base/lang", "dojo/_base/declare", "dojo/on", "dojo/Def
// The return value cannot return directly.
// If the caller need to use the return value, pass callback function in event parameter and process return value in callback function.

this.app.log("in app/controllers/Load event.viewId="+event.viewId+" event =", event);
this.app.log("in app/controllers/Load app-load event.viewId="+event.viewId+" event =", event);
var views = event.viewId || "";
var viewArray = [];
// create an array from the diff views in event.viewId (they are separated by +)
var parts = views.split('+');
while(parts.length > 0){
while(parts.length > 0){
var viewId = parts.shift();
viewArray.push(viewId);
}
Expand Down Expand Up @@ -166,6 +170,7 @@ define(["require", "dojo/_base/lang", "dojo/_base/declare", "dojo/on", "dojo/Def
// returns:
// If view exist, return the view object.
// Otherwise, create the view and return a dojo.Deferred instance.
var F = MODULE+":createChild";

var id = parent.id + '_' + childId;

Expand All @@ -179,7 +184,7 @@ define(["require", "dojo/_base/lang", "dojo/_base/declare", "dojo/on", "dojo/Def
if(params){
view.params = params;
}
this.app.log("in app/controllers/Load createChild view is already loaded so return the loaded view with the new parms ",view);
this.app.log("logLoadViews:",F,"view is already loaded so return the loaded view with the new parms for ["+view.id+"]");
return view;
}
var def = new Deferred();
Expand Down Expand Up @@ -328,6 +333,83 @@ define(["require", "dojo/_base/lang", "dojo/_base/declare", "dojo/on", "dojo/Def
}
viewNames = parts.join('+');
return viewNames;
},

unloadView: function(event){
// summary:
// Response to dojox/app "unload-view" event.
// If a view has children loaded the view and any children of the view will be unloaded.
//
// example:
// Use trigger() to trigger "app-unload-view" event, and this function will response the event. For example:
// | this.trigger("app-unload-view", {"view":view, "callback":function(){...}});
//
// event: Object
// app-unload-view event parameter. It should be like this: {"view":view, "callback":function(){...}}
var F = MODULE+":unloadView";

var view = event.view || {};
var parent = view.parent || this.app;
var viewId = view.id;
this.app.log("logLoadViews:",F," app-unload-view called for ["+viewId+"]");

if(!parent || !view || !viewId){
console.warn("unload-view event for view with no parent or with an invalid view with view = ", view);
return;
}

if(parent.selectedChildren[viewId]){
console.warn("unload-view event for a view which is still in use so it can not be unloaded for view id = " + viewId + "'.");
return;
}

if(!parent.children[viewId]){
console.warn("unload-view event for a view which was not found in parent.children[viewId] for viewId = " + viewId + "'.");
return;
}

this.unloadChild(parent, view);

if(event.callback){
event.callback();
}
},

unloadChild: function(parent, viewToUnload){
// summary:
// Unload the view, and all of its child views recursively.
// Destroy all children, destroy all widgets, destroy the domNode, remove the view from the parent.children,
// then destroy the view.
//
// parent: Object
// parent of this view.
// viewToUnload: Object
// the view to be unloaded.
var F = MODULE+":unloadChild";
this.app.log("logLoadViews:",F," unloadChild called for ["+viewToUnload.id+"]");

for(var child in viewToUnload.children){
this.app.log("logLoadViews:",F," calling unloadChild for for ["+child+"]");
this.unloadChild(viewToUnload, viewToUnload.children[child]); // unload children then unload the view itself
}
if(viewToUnload.domNode){
// destroy all widgets, then destroy the domNode, then destroy the view.
var widList = registry.findWidgets(viewToUnload.domNode);
this.app.log("logLoadViews:",F," before destroyRecursive loop registry.length = ["+registry.length+"] for view =["+viewToUnload.id+"]");
for(var wid in widList){
widList[wid].destroyRecursive();
}
this.app.log("logLoadViews:",F," after destroyRecursive loop registry.length = ["+registry.length+"] for view =["+viewToUnload.id+"]");
this.app.log("logLoadViews:",F," calling domConstruct.destroy for the view ["+viewToUnload.id+"]");
domConstruct.destroy(viewToUnload.domNode);
}

delete parent.children[viewToUnload.id]; // remove it from the parents children
if(viewToUnload.destroy){
this.app.log("logLoadViews:",F," calling destroy for the view ["+viewToUnload.id+"]");
viewToUnload.destroy(); // call destroy for the view.
}
viewToUnload = null;
}
});
});
94 changes: 79 additions & 15 deletions controllers/Transition.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ define(["require", "dojo/_base/lang", "dojo/_base/declare", "dojo/has", "dojo/on
// "app-transition" event parameter. It should be like: {"viewId": viewId, "opts": opts}
var F = MODULE+":transition";
this.app.log(LOGKEY,F,"New Transition event.viewId=["+event.viewId+"]");
this.app.log("logLoadViews:",F,"New Transition event.viewId=["+event.viewId+"]");
this.app.log(F,"event.viewId=["+event.viewId+"]","event.opts=",event.opts);

var viewsId = event.viewId || "";
Expand Down Expand Up @@ -204,7 +205,7 @@ define(["require", "dojo/_base/lang", "dojo/_base/declare", "dojo/has", "dojo/on

this.proceeding = true;

this.app.log(F+" calling trigger load", transitionEvt);
this.app.log("logLoadViews:",F," emit app-load for ["+ transitionEvt.viewId+"]");
if(!transitionEvt.opts){
transitionEvt.opts = {};
}
Expand Down Expand Up @@ -439,11 +440,11 @@ define(["require", "dojo/_base/lang", "dojo/_base/declare", "dojo/has", "dojo/on
}

if(current && current._active){
this._handleBeforeDeactivateCalls(currentSubViewArray, this.nextLastSubChildMatch || next, current, data, subIds);
this._handleBeforeDeactivateCalls(currentSubViewArray, this.nextLastSubChildMatch || next, current, data);
}
if(next){
this.app.log(F+" calling _handleBeforeActivateCalls next name=[",next.name,"], parent.name=[",next.parent.name,"]");
this._handleBeforeActivateCalls(nextSubViewArray, this.currentLastSubChildMatch || current, data, subIds);
this._handleBeforeActivateCalls(nextSubViewArray, this.currentLastSubChildMatch || current, data);
}
if(!removeView){
var nextLastSubChild = this.nextLastSubChildMatch || next;
Expand Down Expand Up @@ -481,30 +482,94 @@ define(["require", "dojo/_base/lang", "dojo/_base/declare", "dojo/has", "dojo/on
}

// Add call to handleAfterDeactivate and handleAfterActivate here!
this._handleAfterDeactivateCalls(currentSubViewArray, this.nextLastSubChildMatch || next, current, data, subIds);
this._handleAfterActivateCalls(nextSubViewArray, removeView, this.currentLastSubChildMatch || current, data, subIds);
this._handleAfterDeactivateCalls(currentSubViewArray, this.nextLastSubChildMatch || next, current, data);
this._checkForViewsToUnload(currentSubViewArray, nextSubViewArray, this.nextLastSubChildMatch || next);
this._handleAfterActivateCalls(nextSubViewArray, removeView, this.currentLastSubChildMatch || current, data);
}));
return result; // dojo/promise/all
}
},


_checkForViewsToUnload: function(subs, nextSubViewArray, next){
// summary:
// Call _handleViewUnloads for each of the current views which may need to be unloaded
var F = MODULE+":_checkForViewsToUnload";
for(var i = 0; i < subs.length; i++){
var v = subs[i];
if (v && nextSubViewArray.indexOf(v) < 0) {
if (this._handleViewUnloads(v, next)) { // call handleViewUnloads and if true return
return;
}
}
}
},

_handleViewUnloads: function(viewToUnload, next){
// summary:
// Check to see if this view or one of it's siblings need to be unloaded, and unload it.
//
// view: Object
// the view
var F = MODULE+":_handleViewUnloads";
if(!viewToUnload || viewToUnload == next){
return;
}
this.app.log("logLoadViews:",F," _handleViewUnloads called for ["+viewToUnload.id+"]");

// if alwaysAutoUnload is true unload the viewToUnload view and its children
if(viewToUnload.alwaysAutoUnload){
var params = {};
params.view = viewToUnload;
this.app.log("logLoadViews:",F," viewToUnload.alwaysAutoUnload true emit app-unload-view for ["+viewToUnload.id+"]");
this.app.emit("app-unload-view", params);

return true;
}
//if autoUnloadCount is set we need to check other sibling views to see if any views should be unloaded.
var parent = viewToUnload.parent;
if(parent.autoUnloadCount && Object.keys(parent.children).length > parent.autoUnloadCount){
var constraint = viewToUnload.constraint;// || "center";
var type = typeof(constraint);
var hash = (type == "string" || type == "number") ? constraint : constraint.__hash;
for(var otherChildKey in parent.children){
var otherChild = parent.children[otherChildKey];
var otherChildConstraint = otherChild.constraint;// || "center";
var childtype = typeof(otherChildConstraint);
var childhash = (childtype == "string" || childtype == "number") ? otherChildConstraint : otherChildConstraint.__hash;
if(hash == childhash && otherChild !== next){
if(!otherChild.neverAutoUnload && otherChild.transitionCount >= parent.autoUnloadCount ||
(otherChild.transitionCount > 0 && otherChild.alwaysAutoUnload)){
var params = {};
params.view = otherChild;
this.app.log("logLoadViews:",F," over unload count emit app-unload-view for ["+otherChild.id+"]");
this.app.emit("app-unload-view", params);
return true;
}
}
}

}
},


_handleMatchingViews: function(subs, next, current, parent, data, removeView, doResize, subIds, currentSubNames, toId, forceTransitionNone, opts){
// summary:
// Called when the current views and the next views match
var F = MODULE+":_handleMatchingViews";

this._handleBeforeDeactivateCalls(subs, this.nextLastSubChildMatch || next, current, data, subIds);
this._handleBeforeDeactivateCalls(subs, this.nextLastSubChildMatch || next, current, data);
// this is the order that things were being done before on a reload of the same views, so I left it
// calling _handleAfterDeactivateCalls here instead of after _handleLayoutAndResizeCalls
this._handleAfterDeactivateCalls(subs, this.nextLastSubChildMatch || next, current, data, subIds);
this._handleBeforeActivateCalls(subs, this.currentLastSubChildMatch || current, data, subIds);
this._handleAfterDeactivateCalls(subs, this.nextLastSubChildMatch || next, current, data);
this._handleBeforeActivateCalls(subs, this.currentLastSubChildMatch || current, data);
var nextLastSubChild = this.nextLastSubChildMatch || next;
var trans = this._getTransition(nextLastSubChild, parent, toId, opts, forceTransitionNone)
this._handleLayoutAndResizeCalls(subs, removeView, doResize, subIds, trans);
this._handleAfterActivateCalls(subs, removeView, this.currentLastSubChildMatch || current, data, subIds);
this._handleAfterActivateCalls(subs, removeView, this.currentLastSubChildMatch || current, data);
},

_handleBeforeDeactivateCalls: function(subs, next, current, /*parent,*/ data, /*removeView, doResize,*/ subIds/*, currentSubNames*/){
_handleBeforeDeactivateCalls: function(subs, next, current, data){
// summary:
// Call beforeDeactivate for each of the current views which are about to be deactivated
var F = MODULE+":_handleBeforeDeactivateCalls";
Expand All @@ -520,25 +585,24 @@ define(["require", "dojo/_base/lang", "dojo/_base/declare", "dojo/has", "dojo/on
}
},

_handleAfterDeactivateCalls: function(subs, next, current, data, subIds){
_handleAfterDeactivateCalls: function(subs, next, current, data){
// summary:
// Call afterDeactivate for each of the current views which have been deactivated
var F = MODULE+":_handleAfterDeactivateCalls";
if(current && current._active){
//now we need to loop forwards thru subs calling afterDeactivate
for(var i = 0; i < subs.length; i++){
var v = subs[i];
if(v && v.beforeDeactivate && v._active){
if(v && v.afterDeactivate && v._active){
this.app.log(LOGKEY,F,"afterDeactivate for v.id="+v.id);
v.afterDeactivate(next, data);
v._active = false;
}
}

}
},

_handleBeforeActivateCalls: function(subs, current, data, subIds){
_handleBeforeActivateCalls: function(subs, current, data){
// summary:
// Call beforeActivate for each of the next views about to be activated
var F = MODULE+":_handleBeforeActivateCalls";
Expand Down Expand Up @@ -594,7 +658,7 @@ define(["require", "dojo/_base/lang", "dojo/_base/declare", "dojo/has", "dojo/on
},


_handleAfterActivateCalls: function(subs, removeView, current, data, subIds){
_handleAfterActivateCalls: function(subs, removeView, current, data){
// summary:
// Call afterActivate for each of the next views which have been activated
var F = MODULE+":_handleAfterActivateCalls";
Expand Down
Loading

0 comments on commit 3c566a1

Please sign in to comment.