/**
* @author Ed Spencer
* @class Ext.Controller
* @extends Ext.util.Observable
*
* @constructor
*/
Ext.Controller = Ext.extend(Ext.util.Observable, {
constructor: function(config) {
this.addEvents(
/**
* @event instance-created
* Fired when a new model instance has been successfully created by this controller
* @param {Ext.data.Model} instance The newly-created model instance
*/
'instance-created',
/**
* @event instance-creation-failed
* Fired when an attempt at saving a new instance failed
* @param {Ext.data.Model} instance The instance that could not be saved
* @param {Object} errors The set of errors (if any) that caused the failure
*/
'instance-creation-failed',
/**
* @event instance-updated
* Fired when an existing model instance has been successfully updated by this controller
* @param {Ext.data.Model} instance The instance that was updated
*/
'instance-updated',
/**
* @event instance-update-failed
* Fired when an update to existing model instance could not be successfully completed
* @param {Ext.data.Model} instance The instance that could not be updated
* @param {Object} errors The set of errors (if any) that caused the failure
*/
'instance-update-failed',
/**
* @event instance-destroyed
* Fired when an existing instance has been successfully destroyed by this controller
* @param {Ext.data.Model} instance The instance that was destroyed
*/
'instance-destroyed',
/**
* @event instance-destruction-failed
* Fired when an existing instance could not be destroyed
* @param {Ext.data.Model} instance The instance that could not be destroyed
* @param {Object} errors The set of errors (if any) that caused the failure
*/
'instance-destruction-failed'
);
Ext.Controller.superclass.constructor.call(this, config);
Ext.apply(this, config || {});
if (typeof this.model == 'string') {
this.model = Ext.ModelMgr.getModel(this.model);
}
},
index: function() {
this.render('index', {
listeners: {
scope : this,
edit : this.edit,
build : this.build,
create : this.onCreateInstance,
destroy: this.onDestroyInstance
}
});
},
/**
* Renders the edit form for a given model instance
* @param {Ext.data.Model} instance The instance to edit
*/
edit: function(instance) {
var view = this.render('edit', {
listeners: this.getEditListeners()
});
view.loadModel(instance);
},
/**
* Callback automatically tied to the index view's 'build' event. By default this just renders the registered
* 'build' view
*/
build: function() {
this.render('build', {
listeners: this.getBuildListeners()
});
},
/**
* Saves a phantom Model instance via its configured Proxy. Fires the 'instance-created' event if successful,
* the 'instance-creation-failed' event if not.
* @param {Object} data The data to create the instance from
* @param {Object} options Optional options object containing callbacks for success and failure plus optional scope
*/
create: function(data, options) {
options = options || {};
var model = this.getModel(),
instance = new model(data),
successCb = options.success,
failureCb = options.failure,
scope = options.scope || this;
instance.save({
scope : this,
success: function(instance) {
if (typeof successCb == 'function') {
successCb.call(scope, instance);
}
this.fireEvent('instance-created', instance);
},
failure: function(instance, errors) {
if (typeof failureCb == 'function') {
failureCb.call(scope, instance, errors);
}
this.fireEvent('instance-creation-failed', instance, errors);
}
});
},
/**
* Updates an existing model instance by applying optional updates to it and attempting to save
* @param {Ext.data.Model} instance The existing instance
* @param {Object} updates Optional additional updates to apply to the instance before saving
* @param {Object} options success and failure callback functions
*/
update: function(instance, updates, options) {
options = options || {};
var successCb = options.success,
failureCb = options.failure,
scope = options.scope || this;
if (Ext.isObject(updates)) {
instance.set(updates);
}
instance.save({
scope : this,
success: function(instance) {
if (typeof successCb == 'function') {
successCb.call(scope, instance);
}
this.fireEvent('instance-updated', instance);
},
failure: function(instance, errors) {
if (typeof failureCb == 'function') {
failureCb.call(scope, instance, errors);
}
this.fireEvent('instance-update-failed', instance, errors);
}
});
},
/**
* Destroys one or more existing, previously saved model instances
* @param {Ext.data.Model} instance The model instance to destroy
* @param {Object} options success and failure callbacks
*/
destroy: function(instance, options) {
options = options || {};
var successCb = options.success,
failureCb = options.failure,
scope = options.scope || this;
instance.destroy({
scope : this,
success: function(instance) {
if (typeof successCb == 'function') {
successCb.call(scope, instance);
}
this.fireEvent('instance-destroyed', instance);
},
failure: function(instance, errors) {
if (typeof failureCb == 'function') {
failureCb.call(scope, instance, errors);
}
this.fireEvent('instance-destruction-failed', instance, errors);
}
});
},
/**
* Returns the listeners to attach to the view rendered by the {@link #build} action. By default this returns listeners
* for save and cancel, but this can be overridden
* @return {Object} listeners
*/
getBuildListeners: function() {
return {
scope : this,
save : this.onCreateInstance,
cancel: this.onCancelBuild
};
},
/**
* Returns the listeners to attach to the view rendered by the {@link #edit} action. By default this returns listeners
* for save and cancel, but this can be overridden
* @return {Object} listeners
*/
getEditListeners: function() {
return {
scope : this,
save : this.onUpdateInstance,
cancel: this.onCancelEdit
};
},
/**
* Handler for the 'cancel' event fired by an {@link #edit} view. By default this just closes the view
* @param {Ext.Component} view The edit form
*/
onCancelEdit: function(view) {
return this.closeView(view);
},
/**
* Handler for the 'cancel' event fired by an {@link #build} view. By default this just closes the view
* @param {Ext.Component} view The build form
*/
onCancelBuild: function(view) {
return this.closeView(view);
},
/**
* Callback automatically tied to the index view's 'create' event. By default this just calls the controller's
* create function with the data and some basic callbacks to handle errors or show success. Can be overridden
* to provide custom behavior
* @param {Ext.View} view The view instance that fired the event
*/
onCreateInstance: function(view) {
this.create(view.getValues(), {
scope : this,
success: function(instance) {
this.closeView(view);
},
failure: function(instance, errors) {
console.log('fail');
}
});
},
/**
* Callback automatically tied to the index view's 'update' event. By default this just calls the controller's
* update function with the data and some basic callbacks to handle errors or show success. Can be overridden
* to provide custom behavior
* @param {Ext.Component} view The view instance that fired the event
*/
onUpdateInstance: function(view) {
this.update(view.getRecord(), view.getValues(), {
scope : this,
success: function(instance) {
this.closeView(view);
},
failure: function(instance, errors) {
}
});
},
/**
* Callback automatically tied to the index view's 'destroy' event. By default that just calls the controller's
* destroy function with the model instance and some basic callbacks to handle errors or show success. Can be
* overridden to provide custom behavior.
* @param {Ext.data.Model} instance The instance to destroy
* @param {Ext.View} view The view instance that fired the event
*/
onDestroyInstance: function(instance, view) {
this.destroy(instance, {
scope : this,
success: function(instance) {
},
failure: function(instance, errors) {
}
});
},
/**
* Sets the default container that components rendered using {@link #render} will be added to.
* In many applications there is a fixed navigation panel and a content panel - the content
* panel would usually form the render target in this type of setup.
* @param {Ext.Container} target The container to add rendered components to
*/
setRenderTarget: function(target) {
/**
* @property renderTarget
* @type Ext.Container
* The current {@link #setRenderTarget render target}. Read only
*/
Ext.Controller.renderTarget = target;
},
/**
* Renders a given view based on a registered name
* @param {String} viewName The name of the view to render
* @param {Object} config Optional config object
* @return {Ext.View} The view instance
*/
render: function(config, target) {
var Controller = Ext.Controller,
application = this.application,
profile = application ? application.currentProfile : undefined,
profileTarget, view;
Ext.applyIf(config, {
profile: profile
});
view = Ext.ComponentMgr.create(config);
if (target !== false) {
//give the current Ext.Profile a chance to set the target
profileTarget = profile ? profile.getRenderTarget(config, application) : target;
if (target == undefined) {
target = profileTarget || (application ? application.defaultTarget : undefined);
}
if (typeof target == 'string') {
target = Ext.getCmp(target);
}
if (target != undefined && target.add) {
if (profile) {
profile.beforeLayout(view, target, application);
}
target.add(view);
if (target.layout && target.layout.setActiveItem) {
target.layout.setActiveItem(view);
}
target.doComponentLayout();
if (profile) {
profile.afterLayout(view, target, application);
}
}
}
return view;
},
/**
* This function allows you to add listeners to a view
* in a convenient way
*/
control : function(view, actions, itemName) {
if (!view || !view.isView) {
throw 'Trying to control a view that doesnt exist';
}
var item = itemName ? view.refs[itemName] : view,
key, value, name, child, listener;
if (!item) {
throw "No item called " + itemName + " found inside the " + view.name + " view.";
}
for (key in actions) {
value = actions[key];
// If it is an object, it can either be a listener with a handler defined
// in which case the key is the event name, or it can be an object containing
// listener(s), in which case key will be the items name
if (Ext.isObject(value) && !value.fn) {
this.control(view, value, key);
}
else {
// Now hopefully we can be sure that key is an event name. We will loop over all
// child items and enable bubbling for this event
if (item.refs) {
for (name in item.refs) {
child = item.refs[name];
if (child.isObservable && child.events[key]) {
child.enableBubble(key);
}
}
}
if (!value.fn) {
listener = {};
listener[key] = value;
listener.scope = this;
}
else {
listener = value;
if (listener.scope === undefined) {
listener.scope = this;
}
}
// Finally we bind the listener on this item
item.addListener(listener);
}
}
return view;
},
/**
* Returns the constructor for the model type linked to this controller
* @return {Ext.data.Model} The model constructor
*/
getModel: function() {
return Ext.ModelMgr.getModel(this.model);
},
/**
* @private
* Used internally whenever we want to remove a component from its parent container. See onCancelEdit and onCancelBuild
* @param {Ext.Component} view The component to close
*/
closeView: function(view) {
var ownerCt = view.ownerCt;
if (ownerCt) {
ownerCt.remove(view);
ownerCt.doLayout();
ownerCt.setActiveItem(ownerCt.items.last());
}
}
});