canvas-lms/public/javascripts/jquery.instructure_misc_plu...

426 lines
14 KiB
JavaScript

/**
* Copyright (C) 2011 Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
define([
'i18n!instructure',
'jquery' /* $ */,
'str/htmlEscape',
'compiled/behaviors/authenticity_token',
'jquery.ajaxJSON' /* ajaxJSON */,
'jqueryui/dialog',
'jquery.scrollToVisible' /* scrollToVisible */,
'vendor/jquery.ba-hashchange' /* hashchange */,
'vendor/jquery.scrollTo' /* /\.scrollTo/ */
], function(I18n, $, htmlEscape, authenticity_token) {
$.fn.setOptions = function(prompt, options) {
var result = prompt ? "<option value=''>" + htmlEscape(prompt) + "</option>" : "";
if (options == null) {
options = [];
}
options.forEach( function(opt) {
var optHtml = htmlEscape(opt);
result += "<option value=\"" + optHtml + "\">" + optHtml + "</option>";
});
return this.html($.raw(result));
}
// this function is to prevent you from doing all kinds of expesive operations on a
// jquery object that doesn't actually have any elements in it
// it is similar and inspired by http://www.slideshare.net/paul.irish/perfcompression (slide #42)
// to use it do something like:
// $("a .bunch #of .nodes").ifExists(function(orignalQuery){
// // 'this' points to the original jquery object (in this case, $("a .bunch #of .nodes") );
// // orignalQuery is the same as 'this';
// this.slideUp().dialog().show();
// });
$.fn.ifExists = function(func){
this.length && func.call(this, this);
return this;
};
// Returns the width of the browser's scroll bars.
$.fn.scrollbarWidth = function() {
var $div = $('<div style="width:50px;height:50px;overflow:hidden;position:absolute;top:-200px;left:-200px;"><div style="height:100px;"></div>').appendTo(this),
$innerDiv = $div.find('div');
// Append our div, do our calculation and then remove it
var w1 = $innerDiv.innerWidth();
$div.css('overflow-y', 'scroll');
var w2 = $innerDiv.innerWidth();
$div.remove();
return (w1 - w2);
};
// Simple animation for dimming an element's opacity
$.fn.dim = function(speed) {
return this.animate({opacity: 0.4}, speed);
};
$.fn.undim = function(speed) {
return this.animate({opacity: 1.0}, speed);
};
// Helper for deleting objects from the DOM and db.
// url: URL to pass DELETE message. If none provided,
// behaves as if the request were a success. Useful for testing.
// message: Confirmation message
// cancelled: Function to handle cancel.
// confirmed: Function to handle confirm, before submit.
// success: What to do on success. If none provided, fades
// out the element and removes it from the DOM.
// error: Error.
// dialog: If present, do a jquery.ui.dialog instead of a confirm(). If an
// object, it will be merged into the dialog options.
$.fn.confirmDelete = function(options) {
var options = $.extend({}, $.fn.confirmDelete.defaults, options);
var $object = this;
var $dialog = null;
var result = true;
options.noMessage = options.noMessage || options.no_message;
var onContinue = function() {
if (!result) {
if (options.cancelled && $.isFunction(options.cancelled)) {
options.cancelled.call($object);
}
return;
}
if (!options.confirmed) {
options.confirmed = function() {
$object.dim();
};
}
options.confirmed.call($object);
if (options.url) {
if (!options.success) {
options.success = function(data) {
$object.fadeOut('slow', function() {
$object.remove();
});
};
}
var data = options.prepareData ? options.prepareData.call($object, $dialog) : {};
data.authenticity_token = authenticity_token();
$.ajaxJSON(options.url, "DELETE", data, function(data) {
options.success.call($object, data);
}, function(data, request, status, error) {
if (options.error && $.isFunction(options.error)) {
options.error.call($object, data, request, status, error);
} else {
$.ajaxJSON.unhandledXHRs.push(request);
}
});
} else {
if (!options.success) {
options.success = function() {
$object.fadeOut('slow', function() {
$object.remove();
});
};
}
options.success.call($object);
}
}
if (options.message && !options.noMessage && !$.skipConfirmations) {
if (options.dialog) {
result = false;
var dialog_options = typeof(options.dialog) == "object" ? options.dialog : {};
$dialog = $(options.message).dialog($.extend({}, {
modal: true,
close: onContinue,
buttons: [
{
text: I18n.t('#buttons.cancel', 'Cancel'),
click: function() { $(this).dialog('close'); } // ; onContinue();
}, {
text: I18n.t('#buttons.delete', 'Delete'),
'class': 'btn-primary',
click: function() { result = true; $(this).dialog('close'); }
}
]
}, dialog_options));
return;
} else {
result = confirm(options.message);
}
}
onContinue();
};
$.fn.confirmDelete.defaults = {
message: I18n.t('confirms.default_delete_thing', "Are you sure you want to delete this?")
};
// Watches the given element's location.href for any changes
// to the fragment ("#...") and calls the provided function
// when there are any.
// $(document).fragmentChange(function(event, hash) { alert(hash); });
$.fn.fragmentChange = function(fn) {
if(fn && fn !== true) {
var query = (window.location.search || "").replace(/^\?/, "").split("&");
// The URL can hard-code a hash regardless of what's
// actually shown in the hash by specifying a query
// parameter, hash=some_hash
var query_hash = null;
for (var i = 0; i < query.length; i++) {
var item = query[i]
if (item && item.indexOf("hash=") === 0) {
query_hash = "#" + item.substring(5);
}
}
this.bind('document_fragment_change', fn);
var $doc = this;
var found = false;
// Can only be used on the root document,
// will not work on an iframe, for example.
for (var i = 0; i < $._checkFragments.fragmentList.length; i++) {
var obj = $._checkFragments.fragmentList[i];
if(obj.doc[0] == $doc[0]) {
found = true;
}
}
if(!found) {
$._checkFragments.fragmentList.push({
doc: $doc,
fragment: ""
});
}
$(window).bind('hashchange', $._checkFragments);
setTimeout(function() {
if(query_hash && query_hash.length > 0) {
$doc.triggerHandler('document_fragment_change', query_hash);
} else if($doc && $doc[0] && $doc[0].location && $doc[0].location.hash.length > 0) {
$doc.triggerHandler('document_fragment_change', $doc[0].location.hash);
}
}, 500);
} else {
this.triggerHandler('document_fragment_change', this[0].location.hash);
}
return this;
};
$._checkFragments = function() {
var list = $._checkFragments.fragmentList;
for (var idx = 0; idx < list.length; idx++) {
var obj = list[idx];
var $doc = obj.doc;
if($doc[0].location.hash != obj.fragment) {
$doc.triggerHandler('document_fragment_change', $doc[0].location.hash);
obj.fragment = $doc[0].location.hash;
$._checkFragments.fragmentList[idx] = obj;
}
}
};
$._checkFragments.fragmentList = [];
// Triggers a click only if the anchor tag isn't disabled.
$.fn.clickLink = function() {
var $obj = this.eq(0);
if(!$obj.hasClass('disabled_link')) {
$obj.click();
}
};
// jQuery supposedly has this built-in, but I haven't
// had much success with it.
$.fn.showIf = function(bool) {
if ($.isFunction(bool)) {
return this.each(function(index) {
$(this).showIf(bool.call(this));
});
}
if (bool) {
this.show();
} else {
this.hide();
}
return this;
};
$.fn.disableIf = function(bool) {
if ($.isFunction(bool)) { bool = bool.call(this); }
this.prop('disabled', !!bool);
return this;
};
$.fn.indicate = function(options) {
options = options || {};
var $indicator;
if(options == "remove") {
$indicator = this.data('indicator');
if($indicator) {
$indicator.remove();
}
return;
}
$(".indicator_box").remove();
var offset = this.offset();
if(options && options.offset) {
offset = options.offset;
}
var width = this.width();
var height = this.height();
var zIndex = (options.container || this).zIndex();
$indicator = $(document.createElement('div'));
$indicator.css({
width: width + 6,
height: height + 6,
top: offset.top - 3,
left: offset.left - 3,
zIndex: zIndex + 1,
position: 'absolute',
display: 'block',
"-moz-border-radius": 5,
opacity: 0.8,
border: "2px solid #870",
backgroundColor: "#fd0"
});
$indicator.addClass('indicator_box');
$indicator.mouseover(function() {
$(this).stop().fadeOut('fast', function() {
$(this).remove();
});
});
if(this.data('indicator')) {
this.indicate('remove');
}
this.data('indicator', $indicator);
$("body").append($indicator);
if(options && options.singleFlash) {
$indicator.hide().fadeIn().animate({opacity: 0.8}, 500).fadeOut('slow', function() {
$(this).remove();
});
} else {
$indicator.hide().fadeIn().animate({opacity: 0.8}, 500).fadeOut('slow').fadeIn('slow').animate({opacity: 0.8}, 2500).fadeOut('slow', function() {
$(this).remove();
});
}
if(options && options.scroll) {
$("html,body").scrollToVisible($indicator);
}
};
$.fn.hasScrollbar = function(){
return this.length && (this[0].clientHeight < this[0].scrollHeight);
};
$.fn.log = function (msg) {
console.log("%s: %o", msg, this);
return this;
};
$.fn.chevronCrumbs = function(options) {
return this.each(function() {
$(this).show()
.addClass("chevron-crumbs")
.children().not("#hide-scratch")
.addClass('chevron-crumb')
.append('<span class="chevron-outer"><span class="chevron-inner"></span></span>')
.filter(".active").prev().addClass("before-active");
});
};
// this is used if you want to fill the browser window with something inside #content but you want to also leave the footer and header on the page.
$.fn.fillWindowWithMe = function(options){
var opts = $.extend({minHeight: 400}, options),
$this = $(this),
$wrapper_container = $('#wrapper-container'),
$main = $('#main'),
$not_right_side = $('#not_right_side'),
$window = $(window),
$toResize = $(this).add(opts.alsoResize);
function fillWindowWithThisElement(){
$toResize.height(0);
var spaceLeftForThis = $window.height()
- ($wrapper_container.offset().top + $wrapper_container.outerHeight())
+ ($main.height() - $not_right_side.height()),
newHeight = Math.max(400, spaceLeftForThis);
$toResize.height(newHeight);
if ($.isFunction(opts.onResize)) {
opts.onResize.call($this, newHeight);
}
}
fillWindowWithThisElement();
$window
.unbind('resize.fillWindowWithMe')
.bind('resize.fillWindowWithMe', fillWindowWithThisElement);
return this;
};
$.fn.autoGrowInput = function(o) {
o = $.extend({
maxWidth: 1000,
minWidth: 0,
comfortZone: 70
}, o);
this.filter('input:text').each(function(){
var minWidth = o.minWidth || $(this).width(),
val = '',
input = $(this),
testSubject = $('<tester/>').css({
position: 'absolute',
top: -9999,
left: -9999,
width: 'auto',
fontSize: input.css('fontSize'),
fontFamily: input.css('fontFamily'),
fontWeight: input.css('fontWeight'),
letterSpacing: input.css('letterSpacing'),
whiteSpace: 'nowrap'
}),
check = function() {
setTimeout(function() {
if (val === (val = input.val())) {return;}
// Enter new content into testSubject
testSubject.text(val);
// Calculate new width + whether to change
var testerWidth = testSubject.width(),
newWidth = (testerWidth + o.comfortZone) >= minWidth ? testerWidth + o.comfortZone : minWidth,
currentWidth = input.width(),
isValidWidthChange = (newWidth < currentWidth && newWidth >= minWidth)
|| (newWidth > minWidth && newWidth < o.maxWidth);
// Animate width
if (isValidWidthChange) {
input.width(newWidth);
}
});
};
testSubject.insertAfter(input);
$(this).bind('keyup keydown blur update change', check);
});
return this;
};
return $;
});