incorporated AMD into the main application

- added AMD support for Template, but it still
  exports to window.Template until we convert all
  recent stuff into modules

- converted objectCollection, CustomList, and
  courseList to modules

- added specs for objectCollection

Change-Id: Ib4ca4e25374c656b7b583ec7284c37672c117d2a
Reviewed-on: https://gerrit.instructure.com/6545
Reviewed-by: Ryan Florence <ryanf@instructure.com>
Tested-by: Hudson <hudson@instructure.com>
This commit is contained in:
Ryan Florence 2011-10-27 16:09:53 -06:00
parent 8ec102aa0e
commit cc4522fc04
17 changed files with 754 additions and 576 deletions

View File

@ -1,7 +1,7 @@
# A client-side templating wrapper. Templates are compiled with the rake task
# `$ rake jst:compile` or automatically using the guard gem `$ guard`.
# Don't call the templating object directly (like Handlebars), use this class.
class @Template
class Template
# If called w/o `new`, it will return the HTML string immediately.
# i.e. `Template(test, {foo: 'bar'})` => '<div>bar</div>'
@ -44,3 +44,11 @@ class @Template
toElement: (locals) ->
html = @toHTML locals
jQuery('<div/>').html(html)
# export to window until we convert new stuff to modules
@Template = Template
# register as module (will eventually put this at the head of the file)
define 'compiled/Template', ->
Template

View File

@ -0,0 +1,7 @@
require [
'compiled/widget/courseList'
], (courseList) ->
# eventually we'll be requiring packages, like "common" etc.
# but this is a simple start
courseList.init()

View File

@ -3,55 +3,56 @@
# Returns an array with extra methods. It uses direct property injection so our
# prototypes are still clean, but we get a nice OO syntax for these kinds
# of arrays.
define 'compiled/util/objectCollection', ->
(array) ->
@objectCollection = (array) ->
# The usual indexOf support if not present
unless array.indexOf
array.indexOf = (needle) ->
for item, index in array
index if item is needle
-1
# The usual indexOf support if not present
unless array.indexOf
array.indexOf = (needle) ->
# Can find a specific element by a property ie:
# arr = arrayOfObjects([{id: 1}, {id: 2}])
# arr.findBy('id', 1) //> {id: 1}
array.findBy = (prop, value) ->
for item, index in array
index if item is needle
-1
return item if (item[prop] is value)
false
# Can find a specific element by a property ie:
# arr = arrayOfObjects([{id: 1}, {id: 2}])
# arr.findBy('id', 1) //> {id: 1}
array.findBy = (prop, value) ->
for item, index in array
return item if (item[prop] is value)
false
array.eraseBy = (prop, value) ->
item = array.findBy(prop, value)
array.erase(item)
array.eraseBy = (prop, value) ->
item = array.findBy(prop, value)
array.erase(item)
# Inserts an item into an array at a specific index
array.insert = (item, index = 0) ->
array.splice(index, 0, item)
# Inserts an item into an array at a specific index
array.insert = (item, index = 0) ->
array.splice(index, 0, item)
# erases an item from an array, if it exists
array.erase = (victim) ->
for prospect, index in array
array.splice(index, 1) if prospect is victim
# erases an item from an array, if it exists
array.erase = (victim) ->
for prospect, index in array
array.splice(index, 1) if prospect is victim
# Sort an array of of objects by object property, Supports sorting by strings
# and numbers
array.sortBy = do ->
sorters =
string: (a, b) ->
if a < b
-1
else if a > b
1
else
0
# Sort an array of of objects by object property, Supports sorting by strings
# and numbers
array.sortBy = do ->
sorters =
string: (a, b) ->
if a < b
-1
else if a > b
1
else
0
number: (a, b) ->
a - b
number: (a, b) ->
a - b
(prop) ->
type = typeof array[0][prop] or 'string'
array.sort (a, b) ->
return sorters[type](a[prop], b[prop])
(prop) ->
type = typeof array[0][prop] or 'string'
array.sort (a, b) ->
return sorters[type](a[prop], b[prop])
return array
return array

View File

@ -1,221 +1,221 @@
###
requires:
js!requires:
- vendor/jquery-1.6.4.js
- compiled/util/objectCollection.js
- vendor/publisher.js
- compiled/Template.js
- jst/CustomList/courseList/wrapper.js
- jst/CustomList/courseList/content.js
- jQuery.ajaxJSON
###
class @CustomList
define 'compiled/widget/CustomList', [
'compiled/util/objectCollection'
'compiled/Template'
], (objectCollection, Template) ->
options:
animationDuration: 200
model: 'Course'
dataAttribute: 'id'
wrapper: 'courseList/wrapper'
content: 'courseList/content'
url: '/favorites'
appendTarget: 'body',
resetCount: 12
class CustomList
options:
animationDuration: 200
model: 'Course'
dataAttribute: 'id'
wrapper: 'courseList/wrapper'
content: 'courseList/content'
url: '/favorites'
appendTarget: 'body',
resetCount: 12
constructor: (selector, items, options) ->
@options = jQuery.extend {}, @options, options
@appendTarget = jQuery @options.appendTarget
@element = jQuery selector
@targetList = @element.find '> ul'
@wrapper = jQuery Template @options.wrapper, {}
@sourceList = @wrapper.find '> ul'
@contentTemplate = new Template @options.content
@ghost = jQuery('<ul/>').addClass('customListGhost')
@requests = { add: {}, remove: {} }
@doc = jQuery document.body
@isOpen = false
constructor: (selector, items, options) ->
@options = jQuery.extend {}, @options, options
@appendTarget = jQuery @options.appendTarget
@element = jQuery selector
@targetList = @element.find '> ul'
@wrapper = jQuery Template @options.wrapper, {}
@sourceList = @wrapper.find '> ul'
@contentTemplate = new Template @options.content
@ghost = jQuery('<ul/>').addClass('customListGhost')
@requests = { add: {}, remove: {} }
@doc = jQuery document.body
@isOpen = false
@attach()
@setItems items
@attach()
@setItems items
open: ->
@wrapper.appendTo(@appendTarget).show()
setTimeout => # css3 animation
@element.addClass('customListEditing')
, 1
open: ->
@wrapper.appendTo(@appendTarget).show()
setTimeout => # css3 animation
@element.addClass('customListEditing')
, 1
close: ->
@wrapper.hide 0, =>
@teardown()
@element.removeClass('customListEditing');
@resetList() if @pinned.length is 0
close: ->
@wrapper.hide 0, =>
@teardown()
@element.removeClass('customListEditing');
@resetList() if @pinned.length is 0
attach: ->
@element.delegate '.customListOpen', 'click', jQuery.proxy(this, 'open')
@wrapper.delegate '.customListClose', 'click', jQuery.proxy(this, 'close')
@wrapper.delegate '.customListRestore', 'click', jQuery.proxy(this, 'reset')
@wrapper.delegate 'a', 'click.customListTeardown', (event) ->
event.preventDefault()
@wrapper.delegate(
'.customListItem',
'click.customListTeardown',
jQuery.proxy(this, 'sourceClickHandler')
)
attach: ->
@element.delegate '.customListOpen', 'click', jQuery.proxy(this, 'open')
@wrapper.delegate '.customListClose', 'click', jQuery.proxy(this, 'close')
@wrapper.delegate '.customListRestore', 'click', jQuery.proxy(this, 'reset')
@wrapper.delegate 'a', 'click.customListTeardown', (event) ->
event.preventDefault()
@wrapper.delegate(
'.customListItem',
'click.customListTeardown',
jQuery.proxy(this, 'sourceClickHandler')
)
teardown: ->
@wrapper.detach()
teardown: ->
@wrapper.detach()
add: (id, element) ->
item = @items.findBy('id', id)
clone = element.clone().hide()
item.element = clone
add: (id, element) ->
item = @items.findBy('id', id)
clone = element.clone().hide()
item.element = clone
element.addClass 'on'
element.addClass 'on'
@pinned.push item
@pinned.sortBy('shortName')
index = @pinned.indexOf(item) + 1
target = @targetList.find("li:nth-child(#{index})")
if target.length isnt 0
clone.insertBefore target
else
clone.appendTo @targetList
clone.slideDown @options.animationDuration
@animateGhost element, clone
@onAdd item
animateGhost: (fromElement, toElement) ->
from = fromElement.offset()
to = toElement.offset()
clone = fromElement.clone()
from.position = 'absolute'
@ghost.append(clone)
@ghost.appendTo(@doc).css(from).animate to, @options.animationDuration, =>
@ghost.detach().empty()
remove: (item, element) ->
element.removeClass 'on'
@animating = true
@onRemove item
item.element.slideUp @options.animationDuration, =>
item.element.remove()
@pinned.eraseBy 'id', item.id
@animating = false
abortAll: ->
req.abort() for id, req of @requests.add
req.abort() for id, req of @requests.remove
reset: ->
@abortAll()
callback = =>
delete @requests.reset
@requests.reset = jQuery.ajaxJSON(@options.url + '/' + @options.model, 'DELETE', {}, callback, callback)
@resetList()
resetList: ->
defaultItems = @items.slice 0, @options.resetCount
html = @contentTemplate.toHTML { items: defaultItems }
@targetList.empty().html(html)
@setPinned()
onAdd: (item) ->
if @requests.remove[item.id]
@requests.remove[item.id].abort()
return
success = =>
args = [].slice.call arguments
args.unshift(item.id)
@addSuccess.apply(this, args)
error = =>
args = [].slice.call arguments
args.unshift(item.id)
@addError.apply(this, args)
data =
favorite:
context_type: @options.model,
context_id: item.id
req = jQuery.ajaxJSON(@options.url, 'POST', data, success, error)
@requests.add[item.id] = req
onRemove: (item) ->
if @requests.add[item.id]
@requests.add[item.id].abort();
return
success = =>
args = [].slice.call arguments
args.unshift(item.id)
@removeSuccess.apply(this, args)
error = =>
args = [].slice.call arguments
args.unshift(item.id)
@removeError.apply(this, args)
url = @options.url + '/' + item.id
req = jQuery.ajaxJSON(url, 'DELETE', {context_type: @options.model}, success, error)
@requests.remove[item.id] = req
addSuccess: (id) ->
delete @requests.add[id]
addError: (id) ->
delete @requests.add[id]
removeSuccess: (id) ->
delete @requests.remove[id]
removeError: (id) ->
delete @requests.remove[id]
setItems: (items) ->
@items = objectCollection items
@items.sortBy 'shortName'
html = @contentTemplate.toHTML items: @items
@sourceList.html html
@setPinned()
setPinned: ->
@pinned = objectCollection []
@element.find('> ul > li').each (index, element) =>
element = jQuery element
id = element.data('id')
item = @items.findBy('id', id)
return unless item
item.element = element
@pinned.push item
@pinned.sortBy('shortName')
@wrapper.find('ul > li').removeClass('on')
index = @pinned.indexOf(item) + 1
target = @targetList.find("li:nth-child(#{index})")
for item in @pinned
match = @wrapper.find("ul > li[data-id=#{item.id}]")
match.addClass 'on'
if target.length isnt 0
clone.insertBefore target
else
clone.appendTo @targetList
sourceClickHandler: (event) ->
@checkElement jQuery event.currentTarget
clone.slideDown @options.animationDuration
@animateGhost element, clone
@onAdd item
checkElement: (element) ->
# DOM and data get out of sync for atomic clicking, hence @animating
return if @animating or @requests.reset
id = element.data 'id'
item = @pinned.findBy 'id', id
animateGhost: (fromElement, toElement) ->
from = fromElement.offset()
to = toElement.offset()
clone = fromElement.clone()
from.position = 'absolute'
if item
@remove item, element
else
@add id, element
@ghost.append(clone)
@ghost.appendTo(@doc).css(from).animate to, @options.animationDuration, =>
@ghost.detach().empty()
remove: (item, element) ->
element.removeClass 'on'
@animating = true
@onRemove item
item.element.slideUp @options.animationDuration, =>
item.element.remove()
@pinned.eraseBy 'id', item.id
@animating = false
abortAll: ->
req.abort() for id, req of @requests.add
req.abort() for id, req of @requests.remove
reset: ->
@abortAll()
callback = =>
delete @requests.reset
@requests.reset = jQuery.ajaxJSON(@options.url + '/' + @options.model, 'DELETE', {}, callback, callback)
@resetList()
resetList: ->
defaultItems = @items.slice 0, @options.resetCount
html = @contentTemplate.toHTML { items: defaultItems }
@targetList.empty().html(html)
@setPinned()
onAdd: (item) ->
if @requests.remove[item.id]
@requests.remove[item.id].abort()
return
success = =>
args = [].slice.call arguments
args.unshift(item.id)
@addSuccess.apply(this, args)
error = =>
args = [].slice.call arguments
args.unshift(item.id)
@addError.apply(this, args)
data =
favorite:
context_type: @options.model,
context_id: item.id
req = jQuery.ajaxJSON(@options.url, 'POST', data, success, error)
@requests.add[item.id] = req
onRemove: (item) ->
if @requests.add[item.id]
@requests.add[item.id].abort();
return
success = =>
args = [].slice.call arguments
args.unshift(item.id)
@removeSuccess.apply(this, args)
error = =>
args = [].slice.call arguments
args.unshift(item.id)
@removeError.apply(this, args)
url = @options.url + '/' + item.id
req = jQuery.ajaxJSON(url, 'DELETE', {context_type: @options.model}, success, error)
@requests.remove[item.id] = req
addSuccess: (id) ->
delete @requests.add[id]
addError: (id) ->
delete @requests.add[id]
removeSuccess: (id) ->
delete @requests.remove[id]
removeError: (id) ->
delete @requests.remove[id]
setItems: (items) ->
@items = objectCollection items
@items.sortBy 'shortName'
html = @contentTemplate.toHTML items: @items
@sourceList.html html
@setPinned()
setPinned: ->
@pinned = objectCollection []
@element.find('> ul > li').each (index, element) =>
element = jQuery element
id = element.data('id')
item = @items.findBy('id', id)
return unless item
item.element = element
@pinned.push item
@wrapper.find('ul > li').removeClass('on')
for item in @pinned
match = @wrapper.find("ul > li[data-id=#{item.id}]")
match.addClass 'on'
sourceClickHandler: (event) ->
@checkElement jQuery event.currentTarget
checkElement: (element) ->
# DOM and data get out of sync for atomic clicking, hence @animating
return if @animating or @requests.reset
id = element.data 'id'
item = @pinned.findBy 'id', id
if item
@remove item, element
else
@add id, element

View File

@ -1,13 +1,16 @@
###
requires:
- CustomList => widget/CustomList.js
- js!vendor/jquery-1.6.4.js
###
define 'compiled/widget/courseList', ['compiled/widget/CustomList'], (CustomList) ->
jQuery ->
menu = jQuery '#menu_enrollments'
init: ->
jQuery ->
menu = jQuery '#menu_enrollments'
return if menu.length is 0 # :(
return if menu.length is 0 # :(
jQuery.getJSON '/all_menu_courses', (enrollments) ->
window.courseList = new CustomList '#menu_enrollments', enrollments,
appendTarget: '#menu_enrollments'
jQuery.getJSON '/all_menu_courses', (enrollments) ->
window.courseList = new CustomList '#menu_enrollments', enrollments,
appendTarget: '#menu_enrollments'

View File

@ -95,7 +95,7 @@ javascripts:
- public/javascripts/jst/courseList/content.js
- public/javascripts/compiled/widget/CustomList.js
- public/javascripts/compiled/widget/courseList.js
- public/javascripts/compiled/main.js
assignmentMuter:
- public/javascripts/compiled/AssignmentMuter.js
- public/javascripts/jst/mute_dialog.js

View File

@ -1,5 +1,6 @@
(function() {
this.Template = (function() {
var Template;
Template = (function() {
function Template(name, locals) {
this.name = name;
this.locals = locals;
@ -20,4 +21,8 @@
};
return Template;
})();
this.Template = Template;
define('compiled/Template', function() {
return Template;
});
}).call(this);

View File

@ -0,0 +1,5 @@
(function() {
require(['compiled/widget/courseList'], function(courseList) {
return courseList.init();
});
}).call(this);

View File

@ -1,71 +1,73 @@
(function() {
this.objectCollection = function(array) {
if (!array.indexOf) {
array.indexOf = function(needle) {
var index, item, _len, _results;
_results = [];
define('compiled/util/objectCollection', function() {
return function(array) {
if (!array.indexOf) {
array.indexOf = function(needle) {
var index, item, _len, _results;
_results = [];
for (index = 0, _len = array.length; index < _len; index++) {
item = array[index];
_results.push(item === needle ? index : void 0);
}
return _results;
};
-1;
}
array.findBy = function(prop, value) {
var index, item, _len;
for (index = 0, _len = array.length; index < _len; index++) {
item = array[index];
_results.push(item === needle ? index : void 0);
if (item[prop] === value) {
return item;
}
}
return false;
};
array.eraseBy = function(prop, value) {
var item;
item = array.findBy(prop, value);
return array.erase(item);
};
array.insert = function(item, index) {
if (index == null) {
index = 0;
}
return array.splice(index, 0, item);
};
array.erase = function(victim) {
var index, prospect, _len, _results;
_results = [];
for (index = 0, _len = array.length; index < _len; index++) {
prospect = array[index];
_results.push(prospect === victim ? array.splice(index, 1) : void 0);
}
return _results;
};
-1;
}
array.findBy = function(prop, value) {
var index, item, _len;
for (index = 0, _len = array.length; index < _len; index++) {
item = array[index];
if (item[prop] === value) {
return item;
}
}
return false;
};
array.eraseBy = function(prop, value) {
var item;
item = array.findBy(prop, value);
return array.erase(item);
};
array.insert = function(item, index) {
if (index == null) {
index = 0;
}
return array.splice(index, 0, item);
};
array.erase = function(victim) {
var index, prospect, _len, _results;
_results = [];
for (index = 0, _len = array.length; index < _len; index++) {
prospect = array[index];
_results.push(prospect === victim ? array.splice(index, 1) : void 0);
}
return _results;
};
array.sortBy = (function() {
var sorters;
sorters = {
string: function(a, b) {
if (a < b) {
return -1;
} else if (a > b) {
return 1;
} else {
return 0;
array.sortBy = (function() {
var sorters;
sorters = {
string: function(a, b) {
if (a < b) {
return -1;
} else if (a > b) {
return 1;
} else {
return 0;
}
},
number: function(a, b) {
return a - b;
}
},
number: function(a, b) {
return a - b;
}
};
return function(prop) {
var type;
type = typeof array[0][prop] || 'string';
return array.sort(function(a, b) {
return sorters[type](a[prop], b[prop]);
});
};
})();
return array;
};
};
return function(prop) {
var type;
type = typeof array[0][prop] || 'string';
return array.sort(function(a, b) {
return sorters[type](a[prop], b[prop]);
});
};
})();
return array;
};
});
}).call(this);

View File

@ -1,257 +1,256 @@
(function() {
/*
requires:
js!requires:
- vendor/jquery-1.6.4.js
- compiled/util/objectCollection.js
- vendor/publisher.js
- compiled/Template.js
- jst/CustomList/courseList/wrapper.js
- jst/CustomList/courseList/content.js
- jQuery.ajaxJSON
*/
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
this.CustomList = (function() {
CustomList.prototype.options = {
animationDuration: 200,
model: 'Course',
dataAttribute: 'id',
wrapper: 'courseList/wrapper',
content: 'courseList/content',
url: '/favorites',
appendTarget: 'body',
resetCount: 12
};
function CustomList(selector, items, options) {
this.options = jQuery.extend({}, this.options, options);
this.appendTarget = jQuery(this.options.appendTarget);
this.element = jQuery(selector);
this.targetList = this.element.find('> ul');
this.wrapper = jQuery(Template(this.options.wrapper, {}));
this.sourceList = this.wrapper.find('> ul');
this.contentTemplate = new Template(this.options.content);
this.ghost = jQuery('<ul/>').addClass('customListGhost');
this.requests = {
add: {},
remove: {}
define('compiled/widget/CustomList', ['compiled/util/objectCollection', 'compiled/Template'], function(objectCollection, Template) {
var CustomList;
return CustomList = (function() {
CustomList.prototype.options = {
animationDuration: 200,
model: 'Course',
dataAttribute: 'id',
wrapper: 'courseList/wrapper',
content: 'courseList/content',
url: '/favorites',
appendTarget: 'body',
resetCount: 12
};
this.doc = jQuery(document.body);
this.isOpen = false;
this.attach();
this.setItems(items);
}
CustomList.prototype.open = function() {
this.wrapper.appendTo(this.appendTarget).show();
return setTimeout(__bind(function() {
return this.element.addClass('customListEditing');
}, this), 1);
};
CustomList.prototype.close = function() {
this.wrapper.hide(0, __bind(function() {
return this.teardown();
}, this));
this.element.removeClass('customListEditing');
if (this.pinned.length === 0) {
return this.resetList();
function CustomList(selector, items, options) {
this.options = jQuery.extend({}, this.options, options);
this.appendTarget = jQuery(this.options.appendTarget);
this.element = jQuery(selector);
this.targetList = this.element.find('> ul');
this.wrapper = jQuery(Template(this.options.wrapper, {}));
this.sourceList = this.wrapper.find('> ul');
this.contentTemplate = new Template(this.options.content);
this.ghost = jQuery('<ul/>').addClass('customListGhost');
this.requests = {
add: {},
remove: {}
};
this.doc = jQuery(document.body);
this.isOpen = false;
this.attach();
this.setItems(items);
}
};
CustomList.prototype.attach = function() {
this.element.delegate('.customListOpen', 'click', jQuery.proxy(this, 'open'));
this.wrapper.delegate('.customListClose', 'click', jQuery.proxy(this, 'close'));
this.wrapper.delegate('.customListRestore', 'click', jQuery.proxy(this, 'reset'));
this.wrapper.delegate('a', 'click.customListTeardown', function(event) {
return event.preventDefault();
});
return this.wrapper.delegate('.customListItem', 'click.customListTeardown', jQuery.proxy(this, 'sourceClickHandler'));
};
CustomList.prototype.teardown = function() {
return this.wrapper.detach();
};
CustomList.prototype.add = function(id, element) {
var clone, index, item, target;
item = this.items.findBy('id', id);
clone = element.clone().hide();
item.element = clone;
element.addClass('on');
this.pinned.push(item);
this.pinned.sortBy('shortName');
index = this.pinned.indexOf(item) + 1;
target = this.targetList.find("li:nth-child(" + index + ")");
if (target.length !== 0) {
clone.insertBefore(target);
} else {
clone.appendTo(this.targetList);
}
clone.slideDown(this.options.animationDuration);
this.animateGhost(element, clone);
return this.onAdd(item);
};
CustomList.prototype.animateGhost = function(fromElement, toElement) {
var clone, from, to;
from = fromElement.offset();
to = toElement.offset();
clone = fromElement.clone();
from.position = 'absolute';
this.ghost.append(clone);
return this.ghost.appendTo(this.doc).css(from).animate(to, this.options.animationDuration, __bind(function() {
return this.ghost.detach().empty();
}, this));
};
CustomList.prototype.remove = function(item, element) {
element.removeClass('on');
this.animating = true;
this.onRemove(item);
return item.element.slideUp(this.options.animationDuration, __bind(function() {
item.element.remove();
this.pinned.eraseBy('id', item.id);
return this.animating = false;
}, this));
};
CustomList.prototype.abortAll = function() {
var id, req, _ref, _ref2, _results;
_ref = this.requests.add;
for (id in _ref) {
req = _ref[id];
req.abort();
}
_ref2 = this.requests.remove;
_results = [];
for (id in _ref2) {
req = _ref2[id];
_results.push(req.abort());
}
return _results;
};
CustomList.prototype.reset = function() {
var callback;
this.abortAll();
callback = __bind(function() {
return delete this.requests.reset;
}, this);
this.requests.reset = jQuery.ajaxJSON(this.options.url + '/' + this.options.model, 'DELETE', {}, callback, callback);
return this.resetList();
};
CustomList.prototype.resetList = function() {
var defaultItems, html;
defaultItems = this.items.slice(0, this.options.resetCount);
html = this.contentTemplate.toHTML({
items: defaultItems
});
this.targetList.empty().html(html);
return this.setPinned();
};
CustomList.prototype.onAdd = function(item) {
var data, error, req, success;
if (this.requests.remove[item.id]) {
this.requests.remove[item.id].abort();
return;
}
success = __bind(function() {
var args;
args = [].slice.call(arguments);
args.unshift(item.id);
return this.addSuccess.apply(this, args);
}, this);
error = __bind(function() {
var args;
args = [].slice.call(arguments);
args.unshift(item.id);
return this.addError.apply(this, args);
}, this);
data = {
favorite: {
context_type: this.options.model,
context_id: item.id
CustomList.prototype.open = function() {
this.wrapper.appendTo(this.appendTarget).show();
return setTimeout(__bind(function() {
return this.element.addClass('customListEditing');
}, this), 1);
};
CustomList.prototype.close = function() {
this.wrapper.hide(0, __bind(function() {
return this.teardown();
}, this));
this.element.removeClass('customListEditing');
if (this.pinned.length === 0) {
return this.resetList();
}
};
req = jQuery.ajaxJSON(this.options.url, 'POST', data, success, error);
return this.requests.add[item.id] = req;
};
CustomList.prototype.onRemove = function(item) {
var error, req, success, url;
if (this.requests.add[item.id]) {
this.requests.add[item.id].abort();
return;
}
success = __bind(function() {
var args;
args = [].slice.call(arguments);
args.unshift(item.id);
return this.removeSuccess.apply(this, args);
}, this);
error = __bind(function() {
var args;
args = [].slice.call(arguments);
args.unshift(item.id);
return this.removeError.apply(this, args);
}, this);
url = this.options.url + '/' + item.id;
req = jQuery.ajaxJSON(url, 'DELETE', {
context_type: this.options.model
}, success, error);
return this.requests.remove[item.id] = req;
};
CustomList.prototype.addSuccess = function(id) {
return delete this.requests.add[id];
};
CustomList.prototype.addError = function(id) {
return delete this.requests.add[id];
};
CustomList.prototype.removeSuccess = function(id) {
return delete this.requests.remove[id];
};
CustomList.prototype.removeError = function(id) {
return delete this.requests.remove[id];
};
CustomList.prototype.setItems = function(items) {
var html;
this.items = objectCollection(items);
this.items.sortBy('shortName');
html = this.contentTemplate.toHTML({
items: this.items
});
this.sourceList.html(html);
return this.setPinned();
};
CustomList.prototype.setPinned = function() {
var item, match, _i, _len, _ref, _results;
this.pinned = objectCollection([]);
this.element.find('> ul > li').each(__bind(function(index, element) {
var id, item;
element = jQuery(element);
id = element.data('id');
CustomList.prototype.attach = function() {
this.element.delegate('.customListOpen', 'click', jQuery.proxy(this, 'open'));
this.wrapper.delegate('.customListClose', 'click', jQuery.proxy(this, 'close'));
this.wrapper.delegate('.customListRestore', 'click', jQuery.proxy(this, 'reset'));
this.wrapper.delegate('a', 'click.customListTeardown', function(event) {
return event.preventDefault();
});
return this.wrapper.delegate('.customListItem', 'click.customListTeardown', jQuery.proxy(this, 'sourceClickHandler'));
};
CustomList.prototype.teardown = function() {
return this.wrapper.detach();
};
CustomList.prototype.add = function(id, element) {
var clone, index, item, target;
item = this.items.findBy('id', id);
if (!item) {
clone = element.clone().hide();
item.element = clone;
element.addClass('on');
this.pinned.push(item);
this.pinned.sortBy('shortName');
index = this.pinned.indexOf(item) + 1;
target = this.targetList.find("li:nth-child(" + index + ")");
if (target.length !== 0) {
clone.insertBefore(target);
} else {
clone.appendTo(this.targetList);
}
clone.slideDown(this.options.animationDuration);
this.animateGhost(element, clone);
return this.onAdd(item);
};
CustomList.prototype.animateGhost = function(fromElement, toElement) {
var clone, from, to;
from = fromElement.offset();
to = toElement.offset();
clone = fromElement.clone();
from.position = 'absolute';
this.ghost.append(clone);
return this.ghost.appendTo(this.doc).css(from).animate(to, this.options.animationDuration, __bind(function() {
return this.ghost.detach().empty();
}, this));
};
CustomList.prototype.remove = function(item, element) {
element.removeClass('on');
this.animating = true;
this.onRemove(item);
return item.element.slideUp(this.options.animationDuration, __bind(function() {
item.element.remove();
this.pinned.eraseBy('id', item.id);
return this.animating = false;
}, this));
};
CustomList.prototype.abortAll = function() {
var id, req, _ref, _ref2, _results;
_ref = this.requests.add;
for (id in _ref) {
req = _ref[id];
req.abort();
}
_ref2 = this.requests.remove;
_results = [];
for (id in _ref2) {
req = _ref2[id];
_results.push(req.abort());
}
return _results;
};
CustomList.prototype.reset = function() {
var callback;
this.abortAll();
callback = __bind(function() {
return delete this.requests.reset;
}, this);
this.requests.reset = jQuery.ajaxJSON(this.options.url + '/' + this.options.model, 'DELETE', {}, callback, callback);
return this.resetList();
};
CustomList.prototype.resetList = function() {
var defaultItems, html;
defaultItems = this.items.slice(0, this.options.resetCount);
html = this.contentTemplate.toHTML({
items: defaultItems
});
this.targetList.empty().html(html);
return this.setPinned();
};
CustomList.prototype.onAdd = function(item) {
var data, error, req, success;
if (this.requests.remove[item.id]) {
this.requests.remove[item.id].abort();
return;
}
item.element = element;
return this.pinned.push(item);
}, this));
this.wrapper.find('ul > li').removeClass('on');
_ref = this.pinned;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
item = _ref[_i];
match = this.wrapper.find("ul > li[data-id=" + item.id + "]");
_results.push(match.addClass('on'));
}
return _results;
};
CustomList.prototype.sourceClickHandler = function(event) {
return this.checkElement(jQuery(event.currentTarget));
};
CustomList.prototype.checkElement = function(element) {
var id, item;
if (this.animating || this.requests.reset) {
return;
}
id = element.data('id');
item = this.pinned.findBy('id', id);
if (item) {
return this.remove(item, element);
} else {
return this.add(id, element);
}
};
return CustomList;
})();
success = __bind(function() {
var args;
args = [].slice.call(arguments);
args.unshift(item.id);
return this.addSuccess.apply(this, args);
}, this);
error = __bind(function() {
var args;
args = [].slice.call(arguments);
args.unshift(item.id);
return this.addError.apply(this, args);
}, this);
data = {
favorite: {
context_type: this.options.model,
context_id: item.id
}
};
req = jQuery.ajaxJSON(this.options.url, 'POST', data, success, error);
return this.requests.add[item.id] = req;
};
CustomList.prototype.onRemove = function(item) {
var error, req, success, url;
if (this.requests.add[item.id]) {
this.requests.add[item.id].abort();
return;
}
success = __bind(function() {
var args;
args = [].slice.call(arguments);
args.unshift(item.id);
return this.removeSuccess.apply(this, args);
}, this);
error = __bind(function() {
var args;
args = [].slice.call(arguments);
args.unshift(item.id);
return this.removeError.apply(this, args);
}, this);
url = this.options.url + '/' + item.id;
req = jQuery.ajaxJSON(url, 'DELETE', {
context_type: this.options.model
}, success, error);
return this.requests.remove[item.id] = req;
};
CustomList.prototype.addSuccess = function(id) {
return delete this.requests.add[id];
};
CustomList.prototype.addError = function(id) {
return delete this.requests.add[id];
};
CustomList.prototype.removeSuccess = function(id) {
return delete this.requests.remove[id];
};
CustomList.prototype.removeError = function(id) {
return delete this.requests.remove[id];
};
CustomList.prototype.setItems = function(items) {
var html;
this.items = objectCollection(items);
this.items.sortBy('shortName');
html = this.contentTemplate.toHTML({
items: this.items
});
this.sourceList.html(html);
return this.setPinned();
};
CustomList.prototype.setPinned = function() {
var item, match, _i, _len, _ref, _results;
this.pinned = objectCollection([]);
this.element.find('> ul > li').each(__bind(function(index, element) {
var id, item;
element = jQuery(element);
id = element.data('id');
item = this.items.findBy('id', id);
if (!item) {
return;
}
item.element = element;
return this.pinned.push(item);
}, this));
this.wrapper.find('ul > li').removeClass('on');
_ref = this.pinned;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
item = _ref[_i];
match = this.wrapper.find("ul > li[data-id=" + item.id + "]");
_results.push(match.addClass('on'));
}
return _results;
};
CustomList.prototype.sourceClickHandler = function(event) {
return this.checkElement(jQuery(event.currentTarget));
};
CustomList.prototype.checkElement = function(element) {
var id, item;
if (this.animating || this.requests.reset) {
return;
}
id = element.data('id');
item = this.pinned.findBy('id', id);
if (item) {
return this.remove(item, element);
} else {
return this.add(id, element);
}
};
return CustomList;
})();
});
}).call(this);

View File

@ -1,17 +1,23 @@
(function() {
/*
requires:
- CustomList => widget/CustomList.js
*/ jQuery(function() {
var menu;
menu = jQuery('#menu_enrollments');
if (menu.length === 0) {
return;
}
return jQuery.getJSON('/all_menu_courses', function(enrollments) {
return window.courseList = new CustomList('#menu_enrollments', enrollments, {
appendTarget: '#menu_enrollments'
});
});
- js!vendor/jquery-1.6.4.js
*/ define('compiled/widget/courseList', ['compiled/widget/CustomList'], function(CustomList) {
return {
init: function() {
return jQuery(function() {
var menu;
menu = jQuery('#menu_enrollments');
if (menu.length === 0) {
return;
}
return jQuery.getJSON('/all_menu_courses', function(enrollments) {
return window.courseList = new CustomList('#menu_enrollments', enrollments, {
appendTarget: '#menu_enrollments'
});
});
});
}
};
});
}).call(this);

View File

@ -1,4 +1,4 @@
curl = require = {
baseUrl: '/public/javascripts/compiled',
baseUrl: '/public/javascripts',
apiName: 'require'
};

View File

@ -1,18 +1,17 @@
define [
'js!vendor/jquery-1.6.4.js!order'
# non-amd dependencies, once everything is a module you'll rarely require
# more than one thing in a spec
'js!vendor/jquery-1.6.4.js'
'js!jquery.ajaxJSON.js!order'
'js!i18n.js!order'
'js!vendor/handlebars.vm.js!order'
'js!compiled/handlebars_helpers.js!order'
'js!compiled/Template.js!order'
'js!jst/courseList/wrapper.js!order'
'js!jst/courseList/content.js!order'
'js!compiled/util/objectCollection.js!order'
'js!compiled/widget/CustomList.js!order'
], ->
# module dependencies
'compiled/widget/CustomList'
], (a, b, c, d, e, f, g, CustomList)->
module 'CustomList',
setup: ->

View File

@ -0,0 +1,62 @@
define ['compiled/util/objectCollection'], (objectCollection) ->
module 'objectCollection',
setup: ->
arrayOfObjects = [
{id: 1, name: 'foo'}
{id: 2, name: 'bar'}
{id: 3, name: 'baz'}
{id: 4, name: 'quux'}
]
@collection = objectCollection arrayOfObjects
test 'indexOf', ->
needle = @collection[2]
index = @collection.indexOf needle
equal index, 2, 'should find the correct index'
test 'findBy', ->
byId = @collection.findBy 'id', 1
equal @collection[0], byId, 'should find the first item by id'
byName = @collection.findBy 'name', 'bar'
equal @collection[1], byName, 'should find the second item by name'
test 'eraseBy', ->
originalLength = @collection.length
equal @collection[0].id, 1, 'first item id should be 1'
@collection.eraseBy 'id', 1
equal @collection.length, originalLength - 1, 'collection length should less by 1'
equal @collection[0].id, 2, 'first item id should be 2, since first is erased'
test 'insert', ->
corge = {id: 5, name: 'corge'}
@collection.insert corge
equal @collection[0], corge, 'should insert at index 0 by default'
grault = {id: 6, name: 'grault'}
@collection.insert grault, 2
equal @collection[2], grault, 'should insert at an arbitrary index'
test 'erase', ->
originalLength = @collection.length
@collection.erase @collection[0]
equal @collection[0].name, 'bar', 'should erase first item by reference, second item becomes first'
equal @collection.length, originalLength - 1, 'should decrease length'
test 'sortBy', ->
@collection.sortBy 'name'
equal @collection[0].name, 'bar'
equal @collection[1].name, 'baz'
equal @collection[2].name, 'foo'
equal @collection[3].name, 'quux'
@collection.sortBy 'id'
equal @collection[0].id, 1
equal @collection[1].id, 2
equal @collection[2].id, 3
equal @collection[3].id, 4

View File

@ -1,6 +1,6 @@
(function() {
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
define(['js!vendor/jquery-1.6.4.js!order', 'js!jquery.ajaxJSON.js!order', 'js!i18n.js!order', 'js!vendor/handlebars.vm.js!order', 'js!compiled/handlebars_helpers.js!order', 'js!compiled/Template.js!order', 'js!jst/courseList/wrapper.js!order', 'js!jst/courseList/content.js!order', 'js!compiled/util/objectCollection.js!order', 'js!compiled/widget/CustomList.js!order'], function() {
define(['js!vendor/jquery-1.6.4.js', 'js!jquery.ajaxJSON.js!order', 'js!i18n.js!order', 'js!vendor/handlebars.vm.js!order', 'js!compiled/handlebars_helpers.js!order', 'js!jst/courseList/wrapper.js!order', 'js!jst/courseList/content.js!order', 'compiled/widget/CustomList'], function(a, b, c, d, e, f, g, CustomList) {
module('CustomList', {
setup: function() {
var index, items;

View File

@ -0,0 +1,80 @@
(function() {
define(['compiled/util/objectCollection'], function(objectCollection) {
module('objectCollection', {
setup: function() {
var arrayOfObjects;
arrayOfObjects = [
{
id: 1,
name: 'foo'
}, {
id: 2,
name: 'bar'
}, {
id: 3,
name: 'baz'
}, {
id: 4,
name: 'quux'
}
];
return this.collection = objectCollection(arrayOfObjects);
}
});
test('indexOf', function() {
var index, needle;
needle = this.collection[2];
index = this.collection.indexOf(needle);
return equal(index, 2, 'should find the correct index');
});
test('findBy', function() {
var byId, byName;
byId = this.collection.findBy('id', 1);
equal(this.collection[0], byId, 'should find the first item by id');
byName = this.collection.findBy('name', 'bar');
return equal(this.collection[1], byName, 'should find the second item by name');
});
test('eraseBy', function() {
var originalLength;
originalLength = this.collection.length;
equal(this.collection[0].id, 1, 'first item id should be 1');
this.collection.eraseBy('id', 1);
equal(this.collection.length, originalLength - 1, 'collection length should less by 1');
return equal(this.collection[0].id, 2, 'first item id should be 2, since first is erased');
});
test('insert', function() {
var corge, grault;
corge = {
id: 5,
name: 'corge'
};
this.collection.insert(corge);
equal(this.collection[0], corge, 'should insert at index 0 by default');
grault = {
id: 6,
name: 'grault'
};
this.collection.insert(grault, 2);
return equal(this.collection[2], grault, 'should insert at an arbitrary index');
});
test('erase', function() {
var originalLength;
originalLength = this.collection.length;
this.collection.erase(this.collection[0]);
equal(this.collection[0].name, 'bar', 'should erase first item by reference, second item becomes first');
return equal(this.collection.length, originalLength - 1, 'should decrease length');
});
return test('sortBy', function() {
this.collection.sortBy('name');
equal(this.collection[0].name, 'bar');
equal(this.collection[1].name, 'baz');
equal(this.collection[2].name, 'foo');
equal(this.collection[3].name, 'quux');
this.collection.sortBy('id');
equal(this.collection[0].id, 1);
equal(this.collection[1].id, 2);
equal(this.collection[2].id, 3);
return equal(this.collection[3].id, 4);
});
});
}).call(this);

View File

@ -3,6 +3,7 @@ require([
'specs/CustomListSpec',
'specs/invokerSpec',
'specs/jQuery.instructureMiscPluginsSpec',
'specs/userNamePartsSpec'
'specs/userNamePartsSpec',
'specs/objectCollectionSpec'
]);