jquery.controller-api

阎坚成
2023-12-01

jQuery.Controller  class     

inherits: jQuery.Class

plugin: jquery/controller

download: jQuery.Controller

test: qunit.html

Source

jQuery.Controller helps create organized, memory-leak free, rapidlyperforming jQuery widgets. Its extreme flexibility allows it to serve as both atraditional View and a traditional Controller.

This means it is used to create things like tabs, grids, and contextmenusas well as organizing them into higher-order business rules.

Controllers make your code deterministic, reusable, organized and can tearthemselves down auto-magically. Read about the theorybehind controller and a walkthroughof its features on Jupiter's blog. Get Startedwith jQueryMX also has a great walkthrough.

Controller inherits from $.Class andmakes heavy use of event delegation.Make sure you understand these concepts before using it.

Basic Example

Instead of

$(function(){

 $('#tabs').click(someCallbackFunction1)

 $('#tabs .tab').click(someCallbackFunction2)

 $('#tabs .delete click').click(someCallbackFunction3)

});

do this

$.Controller('Tabs',{

 click: function() {...},

 '.tab click' : function() {...},

 '.delete click' : function() {...}

})

$('#tabs').tabs();

Tabs Example

Demo

HTML

<ul id="tabs" class="ui-helper-clearfix"'="">

 <li><a href="#tab1">Tab 1</a></li>

 <li><a href="#tab2">Tab 2</a></li>

 <li><a href="#tab3">Tab 3</a></li>

</ul>

<div id="tab1" class="tab">Tab 1 Content</div>

<div id="tab2" class="tab">Tab 2 Content</div>

<div id="tab3" class="tab">Tab 3 Content</div>

Source

steal("jquery/controller", function(){

// create a new Tabs class

$.Controller("Tabs",{

 

 // initialize widget

 init : function(el){

   

    // activatethe first tab

    $(el).children("li:first").addClass('active')

   

    // hide theother tabs

    var tab = this.tab;

    this.element.children("li:gt(0)").each(function(){

      tab($(this)).hide()

    })

 },

 

 // helper function finds the tabfor a given li

 tab : function(li){

    return $(li.find("a").attr("href"))

 },

 

 // hides old active tab, showsnew one

 "li click" : function(el, ev){

    ev.preventDefault();

    this.tab(this.find('.active').removeClass('active')).hide()

    this.tab(el.addClass('active')).show();

 }

})

 

// adds the controller to the element

$("#tabs").tabs();

})

Using Controller

Controller helps you build and organize jQuery plugins. It can be used tobuild simple widgets, like a slider, or organize multiple widgets intosomething greater.

To understand how to use Controller, you need to understand the typicallifecycle of a jQuery widget and how that maps to controller's functionality:

A controller class is created.

$.Controller("MyWidget",

{

 defaults :  {

    message : "Remove Me"

 }

},

{

 init : function(rawEl, rawOptions){

    this.element.append(

       "<div>"+this.options.message+"</div>"

      );

 },

 "div click" : function(div, ev){

    div.remove();

 } 

})

This creates a $.fn.my_widget jQuery helper function that can be used to create a newcontroller instance on an element. Find more information hereabout the plugin gets created and the rules around its name.

An instance of controller is created on an element

$('.thing').my_widget(options)// calls new MyWidget(el, options)

This calls newMyWidget(el, options) on each '.thing' element.

When a new Classinstance is created, it calls the class's prototype setup and init methods.Controller's setupmethod:

The controller responds to events

Typically, Controller event handlers are automatically bound. However,there are multiple ways to listento events with a controller.

Once an event does happen, the callback function is always called with'this' referencing the controller instance. This makes it easy to use helperfunctions and save state on the controller.

The widget is destroyed

If the element is removed from the page, the controller's jQuery.Controller.prototype.destroymethod is called. This is a great place to put any additional teardownfunctionality.

You can also teardown a controller programatically like:

$('.thing').my_widget('destroy');

Todos Example

Lets look at a very basic example - a list of todos and a button you wantto click to create a new todo. Your HTML might look like:

<div id='todos'>

 <ol>

  <li class="todo">Laundry</li>

  <li class="todo">Dishes</li>

  <li class="todo">Walk Dog</li>

 </ol>

 <a class="create">Create</a>

</div>

To add a mousover effect and create todos, your controller might looklike:

$.Controller('Todos',{

 ".todo mouseover" : function( el, ev ) {

    el.css("backgroundColor","red")

 },

 ".todo mouseout" : function( el, ev ) {

    el.css("backgroundColor","")

 },

 ".create click" : function() {

    this.find("ol").append("<li class='todo'>New Todo</li>");

 }

})

Now that you've created the controller class, you've must attach the eventhandlers on the '#todos' div by creating anew controller instance. There are 2 ways of doing this.

//1. Create a new controller directly:

new Todos($('#todos'));

//2. Use jQuery function

$('#todos').todos();

Controller Initialization

It can be extremely useful to add an init method with setup functionalityfor your widget.

In the following example, I create a controller that when created, willput a message as the content of the element:

$.Controller("SpecialController",

{

 init: function( el, message ) {

    this.element.html(message)

 }

})

$(".special").special("Hello World")

Removing Controllers

Controller removal is built into jQuery. So to remove a controller, youjust have to remove its element:

$(".special_controller").remove()

$("#containsControllers").html("")

It's important to note that if you use raw DOM methods (innerHTML, removeChild), the controllers won't be destroyed.

If you just want to remove controller functionality, call destroy on thecontroller instance:

$(".special_controller").controller().destroy()

Accessing Controllers

Often you need to get a reference to a controller, there are a few ways ofdoing that. For the following example, we assume there are 2 elements with className="special".

//creates 2 foo controllers

$(".special").foo()

 

//creates 2 bar controllers

$(".special").bar()

 

//gets all controllers on all elements:

$(".special").controllers() //-> [foo, bar,foo, bar]

 

//gets only foo controllers

$(".special").controllers(FooController) //->[foo, foo]

 

//gets all bar controllers

$(".special").controllers(BarController) //->[bar, bar]

 

//gets first controller

$(".special").controller() //-> foo

 

//gets foo controller via data

$(".special").data("controllers")["FooController"] //-> foo

Calling methods on Controllers

Once you have a reference to an element, you can call methods on it.However, Controller has a few shortcuts:

//creates foo controller

$(".special").foo({name: "value"})

 

//calls FooController.prototype.update

$(".special").foo({name: "value2"})

 

//calls FooController.prototype.bar

$(".special").foo("bar","something I want to pass")

These methods let you call one controller from another controller.

Listening To Events  page    

Controllers make creating and tearing down event handlersextremely easy. The tearingdown of event handlers is especially important inpreventing memory leaks in long lived applications.

Automatic Binding

When a newcontroller is created, contoller checks its prototype methods for functionsthat are named like event handlers. It binds these functions to thecontroller's elementwith event delegation. When the controller is destroyed (or it's element isremoved from the page), controller will unbind its event handlersautomatically.

For example, each of the following controller's functionswill automatically bound:

$.Controller("Crazy",{
 
  // listens to all clicks on this element
  "click" : function(el, ev){},
 
  // listens to all mouseovers on 
  // li elements withing this controller
  "li mouseover" : function(el, ev){}
 
  // listens to the window being resized
  "{window} resize" : function(window, ev){}
})

Controller will bind function names with spaces, standardDOM events, and event names in $.event.special.

In general, Controller will know automatically when to bindevent handler functions except for one case - event names without selectorsthat are not in $.event.special.

But to correct for this, you just need to add the functionto the listensToproperty. Here's how:

 $.Controller("MyShow",{
   listensTo: ["show"]
 },{
   show: function( el, ev ) {
     el.show();
   }
 })
 $('.show').my_show().trigger("show");

Callback parameters

Event handlers bound with controller are called back withthe element and the event as parameters. this refers to the controllerinstance. For example:

$.Controller("Tabs",{
 
  // li - the list element that was clicked
  // ev - the click event
  "li click" : function(li, ev){
     this.tab(li).hide()
  },
  tab : function(li){
    return $(li.find("a").attr("href"))
  }
})

Templated Event Bindings

One of Controller's most powerful features is templatedevent handlers. You can parameterize the event name, the selector, or event theroot element.

Templating event names and selectors:

Often, you want to make a widget's behavior configurable. Acommon example is configuring which event a menu should show a sub-menu (ex: onclick or mouseenter). The following controller lets you configure when a menushould show sub-menus:

The following makes two buttons. One says hello on click,the other on a 'tap' event.

$.Controller("Menu",{
  "li {showEvent}" : function(el){
    el.children('ul').show()
  }
})
 
$("#clickMe").menu({showEvent : "click"});
$("#touchMe").menu({showEvent : "mouseenter"});

$.Controller replaces value in {} with valuesin a controller's options.This means we can easily provide a default showEvent value andcreate a menu without providing a value like:

$.Controller("Menu",
{
  defaults : {
    showEvent : "click"
  }
},
{
  "li {showEvent}" : function(el){
    el.children('ul').show()
  }
});
 
$("#clickMe").menu(); //defaults to using click

Sometimes, we might might want to configure our widget touse different elements. The following makes the menu widget's buttonelements configurable:

$.Controller("Menu",{
  "{button} {showEvent}" : function(el){
    el.children('ul').show()
  }
})
 
$('#buttonMenu').menu({button: "button"});

Templating the root element.

Finally, controller lets you bind to objects outside of thecontroller'selement.

The following listens to clicks on the window:

$.Controller("HideOnClick",{
  "{window} click" : function(){
    this.element.hide()
  }
})

The following listens to Todos being created:

$.Controller("NewTodos",{
  "{App.Models.Todo} created" : function(Todo, ev, newTodo){
    this.element.append("newTodos.ejs", newTodo)
  }
});

But instead of making NewTodos only work with the Todomodel, we can make it configurable:

$.Controller("Newbie",{
  "{model} created" : function(Model, ev, newItem){
    this.element.append(this.options.view, newItem)
  }
});
 
$('#newItems').newbie({
  model: App.Models.Todo,
  view: "newTodos.ejs"
})

How Templated events work

When looking up a value to replace {},controller first looks up the item in the options, then it looks up the valuein the window object. It does not use eval to look up the object. Instead ituses jQuery.String.getObject.

Subscribing to OpenAjax messages and custom bindings

The jquery/controller/subscribe plugin allows controllersto listen to OpenAjax.hub messages like:

$.Controller("Listener",{
  "something.updated subscribe" : function(called, data){
 
  }
})

You can create your own binders by adding to jQuery.Controller.static.processors.

Manually binding to events.

The [jQuery.Controller.prototype.bind] and jQuery.Controller.prototype.delegatemethods let you listen to events on other elements. These event handlers willbe unbound when the controller instance is destroyed.

The generated jQuery plugin  page     

When you create a controller, it creates a jQuery plugin that can be usedto:

  • Create controllers on an element or elements
  • Call controller methods
  • Update a controller

For example, the following controller:

$.Controller("My.Widget",{

 say : function(){

     alert(this.options.message);

 }

})

creates a jQuery.fn.my_widget method that you can use like:

// create my_widget on each .thing

$(".thing").my_widget({message : "Hello"})

 

// alerts "Hello"

$(".thing").my_widget("say");

 

// updates the message option

$(".thing").my_widget({message : "World"});

 

// alerts "World"

$(".thing").my_widget("say");

Note that in every case, the my_widget plugin returns the original jQuerycollection for chaining ($('.thing')). If you want to get a valuefrom a controller, use the jQuery.fn.controllersor jQuery.fn.controller.

Creating controllers

When a controller's jQuery plugin helper is used on a jQuery collection,it goes to each element and tests if it has a controller instance on theelement. If it does not, it creates one.

It calls newYourController with the element and anyadditional arguments you passed to the jQuery plugin helper. So for example,say there are 2 elements in $('.thing').

This:

$(".thing").my_widget({message : "Hello"})

Does the exact same thing as:

var things = $('.thing'),

    options = {message : "Hello"};

new My.Widget(things[0],options);

new My.Widget(things[1],options);

Note, when a new Class is created, it calls your class's prototype setup andinit methods. Read controller'ssetup for the details on what happens when a new controller is created.

Calling methods on controllers

Once a Controller is already on an element, you can call methods on itwith the same jQuery helper. The first param to the helper is the name of themethod, the following params are passed to the jQuery function. For example:

$.Controller("Adder",{

 sum : function(first, second, third){

     this.element.text(first+second+third);

 }

})

 

// add an adder to the page

$("#myadder").adder()

 

// show the sum of 1+2+3

$("#myadder").adder("sum",1,2,3);

Naming

By default, a controller's jQuery helper is the controller name:

  • underscored
  • "." replaced with "_"
  • with Controllers removed.

Here are some examples:

$.Controller("Foo")                 // -> .foo()

$.Controller("Foo.Bar")             // -> .foo_bar()

$.Controller("Foo.Controllers.Bar") // ->.foo_bar()

You can overwrite the Controller's default name by setting a staticpluginName property:

$.Controller("My.Tabs",

{

 pluginName: "tabs"

},

{ ... })

 

$("#tabs").tabs()

jQuery.Controller.prototype.delegate  function    

Source

Delegate will delegate on an elememt and will be undelegated when thecontroller is removed. This is a good way to delegate on elements not in acontroller's element.

Example:

// calls function when the any 'a.foo' is clicked.

this.delegate(document.documentElement,'a.foo', 'click', function(ev){

 //do something

})

API

controller.delegate(element,selector, eventName, func) -> Integer

element {optional:HTMLElement|jQuery.fn} defaults to this.element

the element to delegate from

selector {String}

the css selector

eventName {String}

the event to bind to

func {Function|String}

A callback function or the String name of a controller function. If acontroller function name is given, the controller function is called back withthe bound element and event as the first and second parameter. Otherwise thefunction is called back like a normal bind.

returns {Integer}

The id of the binding in this._bindings

jQuery.Controller.prototype.destroy  function    

Source

Destroy unbinds and undelegates all event handlers on this controller, andprevents memory leaks. This is called automatically if the element is removed.You can overwrite it to add your own teardown functionality:

$.Controller("ChangeText",{

 init : function(){

    this.oldText = this.element.text();

    this.element.text("Changed!!!")

 },

 destroy : function(){

    this.element.text(this.oldText);

    this._super(); //Always call this!

})

Make sure you always call _super when overwriting controller'sdestroy event. The base destroy functionality unbinds all event handlers thecontroller has created.

You could call destroy manually on an element with ChangeText added like:

$("#changed").change_text("destroy");

API

controller.destroy() ->undefined

returns {undefined}

jQuery.Controller.prototype.element  attribute    

Source

The controller instance's delegated element. This is set by setup.It is a jQuery wrapped element.

For example, if I add MyWidget to a '#myelement' element like:

$.Controller("MyWidget",{

 init : function(){

    this.element.css("color","red")

 }

})

 

$("#myelement").my_widget()

MyWidget will turn #myelement's font color red.

Using a different element.

Sometimes, you want a different element to be this.element. A very commonexample is making progressively enhanced form widgets.

To change this.element, overwrite Controller's setup method like:

$.Controller("Combobox",{

 setup : function(el, options){

     this.oldElement = $(el);

     var newEl = $('<div/>');

     this.oldElement.wrap(newEl);

     this._super(newEl, options);

 },

 init : function(){

     this.element //-> the div

 },

 ".option click" : function(){

    // eventhandler bound on the div

 },

 destroy : function(){

     var div = this.element; //save reference

     this._super();

     div.replaceWith(this.oldElement);

 }

}

jQuery.Controller.prototype.find  function     

Source

Queries from the controller's element.

".destroy_all click" : function() {

  this.find(".todos").remove();

}

API

controller.find(selector) ->jQuery.fn

selector {String}

selection string

returns {jQuery.fn}

returns the matched elements

jQuery.Controller.prototype.init  function     

Source

Implement this.

API

controller.init()

jQuery.Controller.prototype.options  attribute    

Source

Options are used to configure an controller. They are the 2nd argumentpassed to a controller (or the first argument passed to the controller'sjQuery plugin).

For example:

$.Controller('Hello')

 

var h1 = new Hello($('#content1'), {message: 'World'} );

equal( h1.options.message , "World" )

 

var h2 = $('#content2').hello({message: 'There'})

                       .controller();

equal( h2.options.message , "There" )

Options are merged with defaultsin setup.

For example:

$.Controller("Tabs",

{

  defaults : {

     activeClass: "ui-active-state"

  }

},

{

  init : function(){

     this.element.addClass(this.options.activeClass);

  }

})

 

$("#tabs1").tabs()                         // adds 'ui-active-state'

$("#tabs2").tabs({activeClass : 'active'}) // adds 'active'

Options are typically updated by calling update;

jQuery.Controller.prototype.setup  function    

Source

Setup is where most of controller's magic happens. It does the following:

1. Sets this.element

The first parameter passed to new Controller(el, options) is expected tobe an element. This gets converted to a jQuery wrapped element and set as this.element.

2. Adds the controller's name to the element's className.

Controller adds it's plugin name to the element's className for easierdebugging. For example, if your Controller is named "Foo.Bar", itadds "foo_bar" to the className.

3. Saves the controller in $.data

A reference to the controller instance is saved in $.data. You can findinstances of "Foo.Bar" like:

$("#el").data("controllers")['foo_bar'].

Binds event handlers

Setup does the event binding described in ListeningTo Events.

API

controller.setup(element,options) -> Array

element {HTMLElement}

the element this instance operates on.

options {optional:Object}

option values for the controller. These get added to this.options andmerged with defaults.

returns {Array}

return an array if you wan to change what init is called with. By defaultit is called with the element and options passed to the controller.

jQuery.Controller.prototype.update  function    

Source

Update extends this.optionswith the options argument and rebinds all events. It basicallyre-configures the controller.

For example, the following controller wraps a recipe form. When the formis submitted, it creates the recipe on the server. When the recipe is created, it resets the form with a new instance.

$.Controller('Creator',{

 "{recipe} created" : function(){

    this.update({recipe : new Recipe()});

    this.element[0].reset();

    this.find("[type=submit]").val("Create Recipe")

 },

 "submit" : function(el, ev){

    ev.preventDefault();

    var recipe = this.options.recipe;

    recipe.attrs( this.element.formParams());

    this.find("[type=submit]").val("Saving...")

    recipe.save();

 }

});

$('#createRecipes').creator({recipe : new Recipe()})

Demo

HTML

<form class="creator" action=""id="createRecipes">

        <input name="name" type="text">

        <input value="Create Recipe" type="submit">

</form>

Source

steal("jquery/controller",

      "jquery/model",

          "jquery/dom/form_params",

          "jquery/dom/fixture",

          function(){

               

$.fixture.delay = 2000;

$.fixture("POST /recipes",function(){

        return {};

})

 

$.Model('Recipe',{

        create: "/recipes"

},{});

 

$.Controller('Creator',{

 "{recipe} created" : function(){

    this.update({recipe : new Recipe()});

        this.element[0].reset();

        this.find("[type=submit]").val("Create Recipe")

 },

 "submit" : function(el, ev){

       ev.preventDefault();

    var recipe = this.options.recipe;

    recipe.attrs( this.element.formParams());

        this.find("[type=submit]").val("Saving...")

    recipe.save();

 }

});

 

$('#createRecipes').creator({recipe : new Recipe()})

 

})

Update is called if a controller's jQueryhelper is called on an element that already has a controller instance ofthe same type.

For example, a widget that listens for model updates and updates it's htmlwould look like.

$.Controller('Updater',{

 // when the controller iscreated, update the html

 init : function(){

    this.updateView();

 },

 

 // update the html with atemplate

 updateView : function(){

    this.element.html( "content.ejs",

                       this.options.model);

 },

 

 // if the model is updated

 "{model} updated" : function(){

    this.updateView();

 },

 update : function(options){

    // makesure you call super

    this._super(options);

 

    this.updateView();

 }

})

 

// create the controller

// this calls init

$('#item').updater({model:recipe1});

 

// later, update that model

// this calls "{model} updated"

recipe1.update({name: "something new"});

 

// later, update the controller with a new recipe

// this calls update

$('#item').updater({model:recipe2});

 

// later, update the new model

// this calls "{model} updated"

recipe2.update({name: "something newer"});

NOTE: If you overwrite update, you probably need to call this._super.

Example

$.Controller("Thing",{

 init: function( el, options ) {

    alert( 'init:'+this.options.prop )

 },

 update: function( options ) {

    this._super(options);

    alert('update:'+this.options.prop)

 }

});

$('#myel').thing({prop: 'val1'}); // alerts init:val1

$('#myel').thing({prop: 'val2'}); // alerts update:val2

API

controller.update(options) ->undefined

options {Object}

A list of options to merge with this.options.Often, this method is called by the jQueryhelper function.

returns {undefined}

jQuery.Controller.prototype.view  function     

tags: view

plugin: jquery/controller/view

Source

Renders a View template with the controller instance. If the firstargument is not supplied, it looks for a view in /views/controllername/actionname.ejs.If data is not provided, it uses the controller instance as data.

TasksController =$.Controller.extend('TasksController',{

 click: function( el ) {

    // renderswith views/tasks/click.ejs

    el.html( this.view() )

    // renderswith views/tasks/under.ejs

    el.after( this.view("under", [1,2]) );

    // renderswith views/tasks/under.micro

    el.after( this.view("under.micro", [1,2]) );

    // renderswith views/shared/top.ejs

    el.before( this.view("shared/top", {phrase: "hi"}) );

 }

})

API

controller.view(view, data,myhelpers) -> String

view {optional:String}

The view you are going to render. If a view isn't explicity given thisfunction will try to guess at the correct view as show in the example codeabove.

data {optional:Object}

data to be provided to the view. If not present, the controller instanceis used.

myhelpers {optional:Object}

an object of helpers that will be available in the view. If not presentthis controller class's "Helpers" property will be used.

returns {String}

the rendered result of the view.

jQuery.Controller.static.defaults  attribute    

Source

A object of name-value pairs that act as default values for a controller'soptions.

$.Controller("Message",

{

 defaults : {

    message : "Hello World"

 }

},{

 init : function(){

    this.element.text(this.options.message);

 }

})

 

$("#el1").message(); //writes "HelloWorld"

$("#el12").message({message: "hi"}); //writes hi

In setupthe options passed to the controller are merged with defaults. This is not a deepmerge.

jQuery.Controller.static.listensTo  attribute    

Source

An array of special events this controller listens too. You only need toadd event names that are whole words (ie have no special characters).

$.Controller('TabPanel',{

 listensTo : ['show']

},{

 'show' : function(){

    this.element.show();

 }

})

 

$('.foo').tab_panel().trigger("show");

jQuery.Controller.static.pluginName  attribute    

Source

Setting the pluginName property allows you to change the jQuery plugin helpername from its default value.

$.Controller("Mxui.Layout.Fill",{

 pluginName: "fillWith"

},{});

 

$("#foo").fillWith();

jQuery.Controller.static.processors  attribute    

Source

An object of {eventName : function} pairs that Controller uses to hook upevents auto-magically. A processor function looks like:

jQuery.Controller.processors.

 myprocessor = function( el,event, selector, cb, controller ) {

     //el - thecontroller's element

     //event -the event (myprocessor)

     //selector- the left of the selector

     //cb - thefunction to call

     //controller- the binding controller

 };

This would bind anything like: "foo~3242 myprocessor".

The processor must return a function that when called, unbinds the eventhandler.

Controller already has processors for the following events:

  • change
  • click
  • contextmenu
  • dblclick
  • focusin
  • focusout
  • keydown
  • keyup
  • keypress
  • mousedown
  • mouseenter
  • mouseleave
  • mousemove
  • mouseout
  • mouseover
  • mouseup
  • reset
  • resize
  • scroll
  • select
  • submit

Listen to events on the document or window with templated event handlers:

$.Controller('Sized',{

 "{window} resize" : function(){

    this.element.width(this.element.parent().width() / 2);

 }

});

 

$('.foo').sized();

jQuery.Controller.static.processors.subscribe  function    

plugin: jquery/controller/subscribe

Source

Adds OpenAjax.Hub subscribing to controllers.

$.Controller("Subscriber",{

 "recipe.updatedsubscribe" : function(called, recipe){

 

 },

 "todo.* subscribe" : function(called, todo){

 

 }

})

You should typically be listening to jQuery triggered events whencommunicating between controllers. Subscribe should be used for listening tomodel changes.

API

This is the call signiture for the processor, not the controllersubscription callbacks.

API

$.Controller.processors.subscribe(el,event, selector, cb, controller) -> undefined

el {HTMLElement}

the element being bound. This isn't used.

event {String}

the event type (subscribe).

selector {String}

the subscription name

cb {String}

the callback function's name

controller {}

returns {undefined}

jQuery.Controller.static.setup  function     

Source

Does 2 things:

  • Creates a jQuery helper for this controller.
  • Calculates and caches which functions listen for events.

jQuery Helper Naming Examples

"TaskController" -> $().task_controller()

"Controllers.Task" -> $().controllers_task()

API

$.Controller.setup() ->undefined

returns {undefined}

 

 类似资料: