'
}; // default options
Lightbox.prototype.getOptions = function(options)
{
var IMAGE = 'image';
options = $.extend(
{}, Lightbox.DEFAULTS, this.$.data(), options);
if (!options[IMAGE])
{
options[IMAGE] = this.$.attr('src') || this.$.attr('href') || this.$.find('img').attr('src');
this.$.data(IMAGE, options[IMAGE]);
}
return options;
};
Lightbox.prototype.init = function()
{
this.bindEvents();
};
Lightbox.prototype.initGroups = function()
{
var groups = this.$.data('groups');
if (!groups)
{
groups = $('[data-toggle="lightbox"][data-group="' + this.options.group + '"], [data-lightbox-group="' + this.options.group + '"]');
this.$.data('groups', groups);
groups.each(function(index)
{
$(this).attr('data-group-index', index);
});
}
this.groups = groups;
this.groupIndex = parseInt(this.$.data('group-index'));
};
Lightbox.prototype.bindEvents = function()
{
var $e = this.$,
that = this;
var options = this.options;
if (!options.image) return false;
$e.modalTrigger(
{
type: 'custom',
name: 'lightboxModal',
position: 'center',
custom: function(e)
{
that.initGroups();
var modal = e.modal,
groups = that.groups,
groupIndex = that.groupIndex;
modal.addClass('modal-lightbox')
.html(options.modalTeamplate.format(options))
.toggleClass('lightbox-with-caption', typeof options.caption == 'string')
.removeClass('lightbox-full')
.data('group-index', groupIndex);
var dialog = modal.find('.modal-dialog'),
winWidth = $(window).width();
$.zui.imgReady(options.image, function()
{
dialog.css(
{
width: Math.min(winWidth, this.width)
});
if (winWidth < (this.width + 30)) modal.addClass('lightbox-full');
e.ready();
});
modal.find('.prev').toggleClass('show', groups.filter('[data-group-index="' + (groupIndex - 1) + '"]').length > 0);
modal.find('.next').toggleClass('show', groups.filter('[data-group-index="' + (groupIndex + 1) + '"]').length > 0);
modal.find('.controller').click(function()
{
var $this = $(this);
var id = modal.data('group-index') + ($this.hasClass('prev') ? -1 : 1);
var $e = groups.filter('[data-group-index="' + id + '"]');
if ($e.length)
{
var image = $e.data('image'),
caption = $e.data('caption');
modal.addClass('modal-loading')
.data('group-index', id)
.toggleClass('lightbox-with-caption', typeof caption == 'string')
.removeClass('lightbox-full');
modal.find('.lightbox-img').attr('src', image);
winWidth = $(window).width();
$.zui.imgReady(image, function()
{
dialog.css(
{
width: Math.min(winWidth, this.width)
});
if (winWidth < (this.width + 30)) modal.addClass('lightbox-full');
e.ready();
});
}
modal.find('.prev').toggleClass('show', groups.filter('[data-group-index="' + (id - 1) + '"]').length > 0);
modal.find('.next').toggleClass('show', groups.filter('[data-group-index="' + (id + 1) + '"]').length > 0);
return false;
});
}
});
};
$.fn.lightbox = function(option)
{
var defaultGroup = 'group' + (new Date()).getTime();
return this.each(function()
{
var $this = $(this);
var options = typeof option == 'object' && option;
if (typeof options == 'object' && options.group)
{
$this.attr('data-lightbox-group', options.group);
}
else if ($this.data('group'))
{
$this.attr('data-lightbox-group', $this.data('group'));
}
else
{
$this.attr('data-lightbox-group', defaultGroup);
}
$this.data('group', $this.data('lightbox-group'));
var data = $this.data('zui.lightbox');
if (!data) $this.data('zui.lightbox', (data = new Lightbox(this, options)));
if (typeof option == 'string') data[option]();
});
};
$.fn.lightbox.Constructor = Lightbox;
$(function()
{
$('[data-toggle="lightbox"]').lightbox();
});
}(jQuery, window, Math));
/* ========================================================================
* ZUI: messager.js
* http://zui.sexy
* ========================================================================
* Copyright (c) 2014 cnezsoft.com; Licensed MIT
* ======================================================================== */
(function($, window)
{
'use strict';
var id = 0;
var template = '
';
var defaultOptions = {
type: 'default',
placement: 'top',
time: 4000,
parent: 'body',
// clear: false,
icon: null,
close: true,
fade: true,
scale: true
};
var lastMessager;
var Messager = function(message, options)
{
var that = this;
that.id = id++;
options = that.options = $.extend(
{}, defaultOptions, options);
that.message = (options.icon ? '
' : '') + message;
that.$ = $(template.format(options)).toggleClass('fade', options.fade).toggleClass('scale', options.scale).attr('id', 'messager-' + that.id);
if (!options.close)
{
that.$.find('.close').remove();
}
else
{
that.$.on('click', '.close', function()
{
that.hide();
});
}
that.$.find('.messager-content').html(that.message);
that.$.data('zui.messager', that);
};
Messager.prototype.show = function(message)
{
var that = this,
options = this.options;
if (lastMessager)
{
if (lastMessager.id == that.id)
{
that.$.removeClass('in');
}
else if (lastMessager.isShow)
{
lastMessager.hide();
}
}
if (that.hiding)
{
clearTimeout(that.hiding);
that.hiding = null;
}
if (message)
{
that.message = (options.icon ? '
' : '') + message;
that.$.find('.messager-content').html(that.message);
}
that.$.appendTo(options.parent).show();
if (options.placement === 'top' || options.placement === 'bottom' || options.placement === 'center')
{
that.$.css('left', ($(window).width() - that.$.width() - 50) / 2);
}
if (options.placement === 'left' || options.placement === 'right' || options.placement === 'center')
{
that.$.css('top', ($(window).height() - that.$.height() - 50) / 2);
}
that.$.addClass('in');
if (options.time)
{
that.hiding = setTimeout(function()
{
that.hide();
}, options.time);
}
that.isShow = true;
lastMessager = that;
};
Messager.prototype.hide = function()
{
var that = this;
if (that.$.hasClass('in'))
{
that.$.removeClass('in');
setTimeout(function()
{
that.$.remove();
}, 200);
}
that.isShow = false;
};
var showMessage = function(message, options)
{
if (typeof options === 'string')
{
options = {
type: options
};
}
var msg = new Messager(message, options);
msg.show();
return msg;
};
var getOptions = function(options)
{
return (typeof options === 'string') ?
{
placement: options
} : options;
};
$.zui({
Messager: Messager,
showMessager: showMessage,
messager:
{
show: showMessage,
primary: function(message, options)
{
return showMessage(message, $.extend(
{
type: 'primary'
}, getOptions(options)));
},
success: function(message, options)
{
return showMessage(message, $.extend(
{
type: 'success',
icon: 'ok-sign'
}, getOptions(options)));
},
info: function(message, options)
{
return showMessage(message, $.extend(
{
type: 'info',
icon: 'info-sign'
}, getOptions(options)));
},
warning: function(message, options)
{
return showMessage(message, $.extend(
{
type: 'warning',
icon: 'warning-sign'
}, getOptions(options)));
},
danger: function(message, options)
{
return showMessage(message, $.extend(
{
type: 'danger',
icon: 'exclamation-sign'
}, getOptions(options)));
},
important: function(message, options)
{
return showMessage(message, $.extend(
{
type: 'important'
}, getOptions(options)));
},
special: function(message, options)
{
return showMessage(message, $.extend(
{
type: 'special'
}, getOptions(options)));
}
}
});
}(jQuery, window));
/* ========================================================================
* ZUI: menu.js
* http://zui.sexy
* ========================================================================
* Copyright (c) 2014 cnezsoft.com; Licensed MIT
* ======================================================================== */
(function($)
{
'use strict';
var Menu = function(element, options)
{
this.$ = $(element);
this.options = this.getOptions(options);
this.init();
};
Menu.DEFAULTS = {
auto: false,
foldicon: 'icon-chevron-right'
};
Menu.prototype.getOptions = function(options)
{
options = $.extend(
{}, Menu.DEFAULTS, this.$.data(), options);
return options;
};
Menu.prototype.init = function()
{
var children = this.$.children('.nav');
children.find('.nav').closest('li').addClass('nav-parent');
children.find('.nav > li.active').closest('li').addClass('active');
children.find('.nav-parent > a').append('
');
this.handleFold();
};
Menu.prototype.handleFold = function()
{
var auto = this.options.auto;
var $menu = this.$;
this.$.on('click', '.nav-parent > a', function(event)
{
if (auto)
{
$menu.find('.nav-parent.show').find('.nav').slideUp(function()
{
$(this).closest('.nav-parent').removeClass('show');
});
$menu.find('.icon-rotate-90').removeClass('icon-rotate-90');
}
var li = $(this).closest('.nav-parent');
if (li.hasClass('show'))
{
li.find('.icon-rotate-90').removeClass('icon-rotate-90');
li.find('.nav').slideUp(function()
{
$(this).closest('.nav-parent').removeClass('show');
});
}
else
{
li.find('.nav-parent-fold-icon').addClass('icon-rotate-90');
li.find('.nav').slideDown(function()
{
$(this).closest('.nav-parent').addClass('show');
});
}
event.preventDefault();
return false;
});
};
$.fn.menu = function(option)
{
return this.each(function()
{
var $this = $(this);
var data = $this.data('zui.menu');
var options = typeof option == 'object' && option;
if (!data) $this.data('zui.menu', (data = new Menu(this, options)));
if (typeof option == 'string') data[option]();
});
};
$.fn.menu.Constructor = Menu;
$(function()
{
$('[data-toggle="menu"]').menu();
});
}(jQuery));
/* ========================================================================
* bootbox.js [master branch]
* http://bootboxjs.com/license.txt
* ========================================================================
* Updates in ZUI:
* 1. Determine client language and apply setting automatically.
* 2. Changed button position.
* ======================================================================== */
// @see https://github.com/makeusabrew/bootbox/issues/180
// @see https://github.com/makeusabrew/bootbox/issues/186
(function(root, factory){
'use strict';
if (typeof define === "function" && define.amd)
{
// AMD. Register as an anonymous module.
define(["jquery"], factory);
}
else if (typeof exports === "object")
{
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory(require("jquery"));
}
else
{
// Browser globals (root is window)
root.bootbox = factory(root.jQuery);
}
}(this, function init($, undefined)
{
'use strict';
// the base DOM structure needed to create a modal
var templates = {
dialog: "
"
}
};
var defaults = {
// default language
locale: judgeClientLang(),
// show backdrop or not
backdrop: true,
// animate the modal in/out
animate: true,
// additional class string applied to the top level dialog
className: null,
// whether or not to include a close button
closeButton: true,
// show the dialog immediately by default
show: true,
// dialog container
container: "body"
};
// our public object; augmented after our private API
var exports = {};
function judgeClientLang()
{
var lang;
if (typeof(config) != 'undefined' && config.clientLang)
{
lang = config.clientLang;
}
else
{
var hl = $('html').attr('lang');
lang = hl ? hl : 'en';
}
return lang.replace('-', '_').toLowerCase();
}
/**
* @private
*/
function _t(key)
{
var locale = locales[defaults.locale];
return locale ? locale[key] : locales.en[key];
}
function processCallback(e, dialog, callback)
{
e.stopPropagation();
e.preventDefault();
// by default we assume a callback will get rid of the dialog,
// although it is given the opportunity to override this
// so, if the callback can be invoked and it *explicitly returns false*
// then we'll set a flag to keep the dialog active...
var preserveDialog = $.isFunction(callback) && callback(e) === false;
// ... otherwise we'll bin it
if (!preserveDialog)
{
dialog.modal("hide");
}
}
function getKeyLength(obj)
{
// @TODO defer to Object.keys(x).length if available?
var k, t = 0;
for (k in obj)
{
t++;
}
return t;
}
function each(collection, iterator)
{
var index = 0;
$.each(collection, function(key, value)
{
iterator(key, value, index++);
});
}
function sanitize(options)
{
var buttons;
var total;
if (typeof options !== "object")
{
throw new Error("Please supply an object of options");
}
if (!options.message)
{
throw new Error("Please specify a message");
}
// make sure any supplied options take precedence over defaults
options = $.extend(
{}, defaults, options);
if (!options.buttons)
{
options.buttons = {};
}
// we only support Bootstrap's "static" and false backdrop args
// supporting true would mean you could dismiss the dialog without
// explicitly interacting with it
options.backdrop = options.backdrop ? "static" : false;
buttons = options.buttons;
total = getKeyLength(buttons);
each(buttons, function(key, button, index)
{
if ($.isFunction(button))
{
// short form, assume value is our callback. Since button
// isn't an object it isn't a reference either so re-assign it
button = buttons[key] = {
callback: button
};
}
// before any further checks make sure by now button is the correct type
if ($.type(button) !== "object")
{
throw new Error("button with key " + key + " must be an object");
}
if (!button.label)
{
// the lack of an explicit label means we'll assume the key is good enough
button.label = key;
}
if (!button.className)
{
if (total == 1 || (total >= 2 && key === 'confirm'))
{
// always add a primary to the main option in a two-button dialog
button.className = "btn-primary";
}
else
{
button.className = "btn-default";
}
}
});
return options;
}
/**
* map a flexible set of arguments into a single returned object
* if args.length is already one just return it, otherwise
* use the properties argument to map the unnamed args to
* object properties
* so in the latter case:
* mapArguments(["foo", $.noop], ["message", "callback"])
* -> { message: "foo", callback: $.noop }
*/
function mapArguments(args, properties)
{
var argn = args.length;
var options = {};
if (argn < 1 || argn > 2)
{
throw new Error("Invalid argument length");
}
if (argn === 2 || typeof args[0] === "string")
{
options[properties[0]] = args[0];
options[properties[1]] = args[1];
}
else
{
options = args[0];
}
return options;
}
/**
* merge a set of default dialog options with user supplied arguments
*/
function mergeArguments(defaults, args, properties)
{
return $.extend(
// deep merge
true,
// ensure the target is an empty, unreferenced object
{},
// the base options object for this type of dialog (often just buttons)
defaults,
// args could be an object or array; if it's an array properties will
// map it to a proper options object
mapArguments(
args,
properties
)
);
}
/**
* this entry-level method makes heavy use of composition to take a simple
* range of inputs and return valid options suitable for passing to bootbox.dialog
*/
function mergeDialogOptions(className, labels, properties, args)
{
// build up a base set of dialog properties
var baseOptions = {
className: "bootbox-" + className,
buttons: createLabels.apply(null, labels)
};
// ensure the buttons properties generated, *after* merging
// with user args are still valid against the supplied labels
return validateButtons(
// merge the generated base properties with user supplied arguments
mergeArguments(
baseOptions,
args,
// if args.length > 1, properties specify how each arg maps to an object key
properties
),
labels
);
}
/**
* from a given list of arguments return a suitable object of button labels
* all this does is normalise the given labels and translate them where possible
* e.g. "ok", "confirm" -> { ok: "OK, cancel: "Annuleren" }
*/
function createLabels()
{
var buttons = {};
for (var i = 0, j = arguments.length; i < j; i++)
{
var argument = arguments[i];
var key = argument.toLowerCase();
var value = argument.toUpperCase();
buttons[key] = {
label: _t(value)
};
}
return buttons;
}
function validateButtons(options, buttons)
{
var allowedButtons = {};
each(buttons, function(key, value)
{
allowedButtons[value] = true;
});
each(options.buttons, function(key)
{
if (allowedButtons[key] === undefined)
{
throw new Error("button key " + key + " is not allowed (options are " + buttons.join("\n") + ")");
}
});
return options;
}
exports.alert = function()
{
var options;
options = mergeDialogOptions("alert", ["ok"], ["message", "callback"], arguments);
if (options.callback && !$.isFunction(options.callback))
{
throw new Error("alert requires callback property to be a function when provided");
}
/**
* overrides
*/
options.buttons.ok.callback = options.onEscape = function()
{
if ($.isFunction(options.callback))
{
return options.callback();
}
return true;
};
return exports.dialog(options);
};
exports.confirm = function()
{
var options;
options = mergeDialogOptions("confirm", ["confirm", "cancel"], ["message", "callback"], arguments);
/**
* overrides; undo anything the user tried to set they shouldn't have
*/
options.buttons.cancel.callback = options.onEscape = function()
{
return options.callback(false);
};
options.buttons.confirm.callback = function()
{
return options.callback(true);
};
// confirm specific validation
if (!$.isFunction(options.callback))
{
throw new Error("confirm requires a callback");
}
return exports.dialog(options);
};
exports.prompt = function()
{
var options;
var defaults;
var dialog;
var form;
var input;
var shouldShow;
var inputOptions;
// we have to create our form first otherwise
// its value is undefined when gearing up our options
// @TODO this could be solved by allowing message to
// be a function instead...
form = $(templates.form);
// prompt defaults are more complex than others in that
// users can override more defaults
// @TODO I don't like that prompt has to do a lot of heavy
// lifting which mergeDialogOptions can *almost* support already
// just because of 'value' and 'inputType' - can we refactor?
defaults = {
className: "bootbox-prompt",
buttons: createLabels("confirm", "cancel"),
value: "",
inputType: "text"
};
options = validateButtons(
mergeArguments(defaults, arguments, ["title", "callback"]), ["cancel", "confirm"]
);
// capture the user's show value; we always set this to false before
// spawning the dialog to give us a chance to attach some handlers to
// it, but we need to make sure we respect a preference not to show it
shouldShow = (options.show === undefined) ? true : options.show;
// check if the browser supports the option.inputType
var html5inputs = ["date", "time", "number"];
var i = document.createElement("input");
i.setAttribute("type", options.inputType);
if (html5inputs[options.inputType])
{
options.inputType = i.type;
}
/**
* overrides; undo anything the user tried to set they shouldn't have
*/
options.message = form;
options.buttons.cancel.callback = options.onEscape = function()
{
return options.callback(null);
};
options.buttons.confirm.callback = function()
{
var value;
switch (options.inputType)
{
case "text":
case "textarea":
case "email":
case "select":
case "date":
case "time":
case "number":
case "password":
value = input.val();
break;
case "checkbox":
var checkedItems = input.find("input:checked");
// we assume that checkboxes are always multiple,
// hence we default to an empty array
value = [];
each(checkedItems, function(_, item)
{
value.push($(item).val());
});
break;
}
return options.callback(value);
};
options.show = false;
// prompt specific validation
if (!options.title)
{
throw new Error("prompt requires a title");
}
if (!$.isFunction(options.callback))
{
throw new Error("prompt requires a callback");
}
if (!templates.inputs[options.inputType])
{
throw new Error("invalid prompt type");
}
// create the input based on the supplied type
input = $(templates.inputs[options.inputType]);
switch (options.inputType)
{
case "text":
case "textarea":
case "email":
case "date":
case "time":
case "number":
case "password":
input.val(options.value);
break;
case "select":
var groups = {};
inputOptions = options.inputOptions || [];
if (!inputOptions.length)
{
throw new Error("prompt with select requires options");
}
each(inputOptions, function(_, option)
{
// assume the element to attach to is the input...
var elem = input;
if (option.value === undefined || option.text === undefined)
{
throw new Error("given options in wrong format");
}
// ... but override that element if this option sits in a group
if (option.group)
{
// initialise group if necessary
if (!groups[option.group])
{
groups[option.group] = $("
").attr("label", option.group);
}
elem = groups[option.group];
}
elem.append("
");
});
each(groups, function(_, group)
{
input.append(group);
});
// safe to set a select's value as per a normal input
input.val(options.value);
break;
case "checkbox":
var values = $.isArray(options.value) ? options.value : [options.value];
inputOptions = options.inputOptions || [];
if (!inputOptions.length)
{
throw new Error("prompt with checkbox requires options");
}
if (!inputOptions[0].value || !inputOptions[0].text)
{
throw new Error("given options in wrong format");
}
// checkboxes have to nest within a containing element, so
// they break the rules a bit and we end up re-assigning
// our 'input' element to this container instead
input = $("
");
each(inputOptions, function(_, option)
{
var checkbox = $(templates.inputs[options.inputType]);
checkbox.find("input").attr("value", option.value);
checkbox.find("label").append(option.text);
// we've ensured values is an array so we can always iterate over it
each(values, function(_, value)
{
if (value === option.value)
{
checkbox.find("input").prop("checked", true);
}
});
input.append(checkbox);
});
break;
}
if (options.placeholder)
{
input.attr("placeholder", options.placeholder);
}
if (options.pattern)
{
input.attr("pattern", options.pattern);
}
// now place it in our form
form.append(input);
form.on("submit", function(e)
{
e.preventDefault();
// Fix for SammyJS (or similar JS routing library) hijacking the form post.
e.stopPropagation();
// @TODO can we actually click *the* button object instead?
// e.g. buttons.confirm.click() or similar
dialog.find(".btn-primary").click();
});
dialog = exports.dialog(options);
// clear the existing handler focusing the submit button...
dialog.off("shown.bs.modal");
// ...and replace it with one focusing our input, if possible
dialog.on("shown.bs.modal", function()
{
input.focus();
});
if (shouldShow === true)
{
dialog.modal("show");
}
return dialog;
};
exports.dialog = function(options)
{
options = sanitize(options);
var dialog = $(templates.dialog);
var innerDialog = dialog.find(".modal-dialog");
var body = dialog.find(".modal-body");
var buttons = options.buttons;
var buttonStr = "";
var callbacks = {
onEscape: options.onEscape
};
each(buttons, function(key, button)
{
// @TODO I don't like this string appending to itself; bit dirty. Needs reworking
// can we just build up button elements instead? slower but neater. Then button
// can just become a template too
buttonStr += "
";
callbacks[key] = button.callback;
});
body.find(".bootbox-body").html(options.message);
if (options.animate === true)
{
dialog.addClass("fade");
}
if (options.className)
{
dialog.addClass(options.className);
}
if (options.size === "large")
{
innerDialog.addClass("modal-lg");
}
if (options.size === "small")
{
innerDialog.addClass("modal-sm");
}
if (options.title)
{
body.before(templates.header);
}
if (options.closeButton)
{
var closeButton = $(templates.closeButton);
if (options.title)
{
dialog.find(".modal-header").prepend(closeButton);
}
else
{
closeButton.css("margin-top", "-10px").prependTo(body);
}
}
if (options.title)
{
dialog.find(".modal-title").html(options.title);
}
if (buttonStr.length)
{
body.after(templates.footer);
dialog.find(".modal-footer").html(buttonStr);
}
/**
* Bootstrap event listeners; used handle extra
* setup & teardown required after the underlying
* modal has performed certain actions
*/
dialog.on("hidden.bs.modal", function(e)
{
// ensure we don't accidentally intercept hidden events triggered
// by children of the current dialog. We shouldn't anymore now BS
// namespaces its events; but still worth doing
if (e.target === this)
{
dialog.remove();
}
});
/*
dialog.on("show.bs.modal", function() {
// sadly this doesn't work; show is called *just* before
// the backdrop is added so we'd need a setTimeout hack or
// otherwise... leaving in as would be nice
if (options.backdrop) {
dialog.next(".modal-backdrop").addClass("bootbox-backdrop");
}
});
*/
dialog.on("shown.bs.modal", function()
{
dialog.find(".btn-primary:first").focus();
});
/**
* Bootbox event listeners; experimental and may not last
* just an attempt to decouple some behaviours from their
* respective triggers
*/
dialog.on("escape.close.bb", function(e)
{
if (callbacks.onEscape)
{
processCallback(e, dialog, callbacks.onEscape);
}
});
/**
* Standard jQuery event listeners; used to handle user
* interaction with our dialog
*/
dialog.on("click", ".modal-footer button", function(e)
{
var callbackKey = $(this).data("bb-handler");
processCallback(e, dialog, callbacks[callbackKey]);
});
dialog.on("click", ".bootbox-close-button", function(e)
{
// onEscape might be falsy but that's fine; the fact is
// if the user has managed to click the close button we
// have to close the dialog, callback or not
processCallback(e, dialog, callbacks.onEscape);
});
dialog.on("keyup", function(e)
{
if (e.which === 27)
{
dialog.trigger("escape.close.bb");
}
});
// the remainder of this method simply deals with adding our
// dialogent to the DOM, augmenting it with Bootstrap's modal
// functionality and then giving the resulting object back
// to our caller
$(options.container).append(dialog);
dialog.modal(
{
backdrop: options.backdrop,
keyboard: false,
show: false
});
if (options.show)
{
dialog.modal("show");
}
// @TODO should we return the raw element here or should
// we wrap it in an object on which we can expose some neater
// methods, e.g. var d = bootbox.alert(); d.hide(); instead
// of d.modal("hide");
/*
function BBDialog(elem) {
this.elem = elem;
}
BBDialog.prototype = {
hide: function() {
return this.elem.modal("hide");
},
show: function() {
return this.elem.modal("show");
}
};
*/
return dialog;
};
exports.setDefaults = function()
{
var values = {};
if (arguments.length === 2)
{
// allow passing of single key/value...
values[arguments[0]] = arguments[1];
}
else
{
// ... and as an object too
values = arguments[0];
}
$.extend(defaults, values);
};
exports.hideAll = function()
{
$(".bootbox").modal("hide");
};
/**
* standard locales. Please add more according to ISO 639-1 standard. Multiple language variants are
* unlikely to be required. If this gets too large it can be split out into separate JS files.
*/
var locales = {
en:
{
OK: "OK",
CANCEL: "Cancel",
CONFIRM: "OK"
},
zh_cn:
{
OK: "确认",
CANCEL: "取消",
CONFIRM: "确认"
},
zh_tw:
{
OK: "確認",
CANCEL: "取消",
CONFIRM: "確認"
}
};
exports.init = function(_$)
{
return init(_$ || $);
};
return exports;
}));
/* ========================================================================
* ZUI: dashboard.js
* http://zui.sexy
* ========================================================================
* Copyright (c) 2014 cnezsoft.com; Licensed MIT
* ======================================================================== */
(function($, Math)
{
'use strict';
var Dashboard = function(element, options)
{
this.$ = $(element);
this.options = this.getOptions(options);
this.draggable = this.$.hasClass('dashboard-draggable') || this.options.draggable;
this.init();
};
Dashboard.DEFAULTS = {
height: 360,
shadowType: 'circle',
sensitive: false,
circleShadowSize: 100
};
Dashboard.prototype.getOptions = function(options)
{
options = $.extend(
{}, Dashboard.DEFAULTS, this.$.data(), options);
return options;
};
Dashboard.prototype.handleRemoveEvent = function()
{
var afterPanelRemoved = this.options.afterPanelRemoved;
var tip = this.options.panelRemovingTip;
this.$.on('click', '.remove-panel', function()
{
var panel = $(this).closest('.panel');
var name = panel.data('name') || panel.find('.panel-heading').text().replace('\n', '').replace(/(^\s*)|(\s*$)/g, '');
var index = panel.attr('data-id');
if (tip === undefined || confirm(tip.format(name)))
{
panel.parent().remove();
if (afterPanelRemoved && $.isFunction(afterPanelRemoved))
{
afterPanelRemoved(index);
}
}
});
};
Dashboard.prototype.handleRefreshEvent = function()
{
this.$.on('click', '.refresh-panel', function()
{
var panel = $(this).closest('.panel');
refreshPanel(panel);
});
};
Dashboard.prototype.handleDraggable = function()
{
var dashboard = this.$;
var options = this.options;
var circleShadow = options.shadowType === 'circle';
var circleSize = options.circleShadowSize;
var halfCircleSize = circleSize / 2;
var afterOrdered = options.afterOrdered;
this.$.addClass('dashboard-draggable');
this.$.find('.panel-actions').mousedown(function(event)
{
event.preventDefault();
event.stopPropagation();
});
var pColClass;
this.$.find('.panel-heading').mousedown(function(event)
{
// console.log('--------------------------------');
var panel = $(this).closest('.panel');
var pCol = panel.parent();
var row = panel.closest('.row');
var dPanel = panel.clone().addClass('panel-dragging-shadow');
var pos = panel.offset();
var dPos = dashboard.offset();
var dColShadow = row.find('.dragging-col-holder');
var sWidth = panel.width(),
sHeight = panel.height(),
sX1, sY1, sX2, sY2, moveFn, dropCol, dropBefore, nextDropCol;
if (!dColShadow.length)
{
dColShadow = $('
').removeClass('dragging-col').appendTo(row);
}
if (pColClass) dColShadow.removeClass(pColClass);
dColShadow.addClass(pColClass = pCol.attr('class'));
dColShadow.insertBefore(pCol).find('.panel').replaceWith(panel.clone().addClass('panel-dragging panel-dragging-holder'));
dashboard.addClass('dashboard-dragging');
panel.addClass('panel-dragging').parent().addClass('dragging-col');
dPanel.css(
{
left: pos.left - dPos.left,
top: pos.top - dPos.top,
width: sWidth,
height: sHeight
}).appendTo(dashboard).data('mouseOffset',
{
x: event.pageX - pos.left + dPos.left,
y: event.pageY - pos.top + dPos.top
});
if (circleShadow)
{
dPanel.addClass('circle');
setTimeout(function()
{
dPanel.css(
{
left: event.pageX - dPos.left - halfCircleSize,
top: event.pageY - dPos.top - halfCircleSize,
width: circleSize,
height: circleSize
}).data('mouseOffset',
{
x: dPos.left + halfCircleSize,
y: dPos.top + halfCircleSize
});
}, 100);
}
$(document).bind('mousemove', mouseMove).bind('mouseup', mouseUp);
event.preventDefault();
function mouseMove(event)
{
// console.log('......................');
var offset = dPanel.data('mouseOffset');
sX1 = event.pageX - offset.x;
sY1 = event.pageY - offset.y;
sX2 = sX1 + sWidth;
sY2 = sY1 + sHeight;
dPanel.css(
{
left: sX1,
top: sY1
});
row.find('.dragging-in').removeClass('dragging-in');
dropBefore = false;
dropCol = null;
var area = 0,
thisArea;
row.children(':not(.dragging-col)').each(function()
{
var col = $(this);
if (col.hasClass('dragging-col-holder'))
{
dropBefore = (!options.sensitive) || (area < 100);
return true;
}
var p = col.children('.panel');
var pP = p.offset(),
pW = p.width(),
pH = p.height();
var pX = pP.left,
pY = pP.top;
if (options.sensitive)
{
pX -= dPos.left;
pY -= dPos.top;
thisArea = getIntersectArea(sX1, sY1, sX2, sY2, pX, pY, pX + pW, pY + pH);
if (thisArea > 100 && thisArea > area && thisArea > Math.min(getRectArea(sX1, sY1, sX2, sY2), getRectArea(pX, pY, pX + pW, pY + pH)) / 3)
{
area = thisArea;
dropCol = col;
}
// if(thisArea)
// {
// console.log('panel ' + col.data('id'), '({0}, {1}, {2}, {3}), ({4}, {5}, {6}, {7})'.format(sX1, sY1, sX2, sY2, pX, pY, pX + pW, pY + pH));
// }
}
else
{
var mX = event.pageX,
mY = event.pageY;
if (mX > pX && mY > pY && mX < (pX + pW) && mY < (pY + pH))
{
// var dCol = row.find('.dragging-col');
dropCol = col;
return false;
}
}
});
if (dropCol)
{
if (moveFn) clearTimeout(moveFn);
nextDropCol = dropCol;
moveFn = setTimeout(movePanel, 50);
}
event.preventDefault();
}
function movePanel()
{
if (nextDropCol)
{
nextDropCol.addClass('dragging-in');
if (dropBefore) dColShadow.insertAfter(nextDropCol);
else dColShadow.insertBefore(nextDropCol);
dashboard.addClass('dashboard-holding');
moveFn = null;
nextDropCol = null;
}
}
function mouseUp(event)
{
if (moveFn) clearTimeout(moveFn);
var oldOrder = panel.data('order');
panel.parent().insertAfter(dColShadow);
var newOrder = 0;
var newOrders = {};
row.children(':not(.dragging-col-holder)').each(function()
{
var p = $(this).children('.panel');
p.data('order', ++newOrder);
newOrders[p.attr('id')] = newOrder;
p.parent().attr('data-order', newOrder);
});
if (oldOrder != newOrders[panel.attr('id')])
{
row.data('orders', newOrders);
if (afterOrdered && $.isFunction(afterOrdered))
{
afterOrdered(newOrders);
}
}
dPanel.remove();
dashboard.removeClass('dashboard-holding');
dashboard.find('.dragging-col').removeClass('dragging-col');
dashboard.find('.panel-dragging').removeClass('panel-dragging');
row.find('.dragging-in').removeClass('dragging-in');
dashboard.removeClass('dashboard-dragging');
$(document).unbind('mousemove', mouseMove).unbind('mouseup', mouseUp);
event.preventDefault();
}
});
};
Dashboard.prototype.handlePanelPadding = function()
{
this.$.find('.panel-body > table, .panel-body > .list-group').closest('.panel-body').addClass('no-padding');
};
Dashboard.prototype.handlePanelHeight = function()
{
var dHeight = this.options.height;
this.$.find('.row').each(function()
{
var row = $(this);
var panels = row.find('.panel');
var height = row.data('height') || dHeight;
if (typeof height != 'number')
{
height = 0;
panels.each(function()
{
height = Math.max(height, $(this).innerHeight());
});
}
panels.each(function()
{
var $this = $(this);
$this.find('.panel-body').css('height', height - $this.find('.panel-heading').outerHeight() - 2);
});
});
};
function refreshPanel(panel)
{
var url = panel.data('url');
if (!url) return;
panel.addClass('panel-loading').find('.panel-heading .icon-refresh,.panel-heading .icon-repeat').addClass('icon-spin');
$.ajax(
{
url: url,
dataType: 'html'
}).done(function(data)
{
panel.find('.panel-body').html(data);
}).fail(function()
{
panel.addClass('panel-error');
}).always(function()
{
panel.removeClass('panel-loading');
panel.find('.panel-heading .icon-refresh,.panel-heading .icon-repeat').removeClass('icon-spin');
});
}
function getRectArea(x1, y1, x2, y2)
{
return Math.abs((x2 - x1) * (y2 - y1));
}
function isPointInner(x, y, x1, y1, x2, y2)
{
return x >= x1 && x <= x2 && y >= y1 && y <= y2;
}
function getIntersectArea(ax1, ay1, ax2, ay2, bx1, by1, bx2, by2)
{
var x1 = Math.max(ax1, bx1),
y1 = Math.max(ay1, by1),
x2 = Math.min(ax2, bx2),
y2 = Math.min(ay2, by2);
if (isPointInner(x1, y1, ax1, ay1, ax2, ay2) && isPointInner(x2, y2, ax1, ay1, ax2, ay2) && isPointInner(x1, y1, bx1, by1, bx2, by2) && isPointInner(x2, y2, bx1, by1, bx2, by2))
{
return getRectArea(x1, y1, x2, y2);
}
return 0;
}
Dashboard.prototype.init = function()
{
this.handlePanelHeight();
this.handlePanelPadding();
this.handleRemoveEvent();
this.handleRefreshEvent();
if (this.draggable) this.handleDraggable();
var orderSeed = 0;
this.$.find('.panel').each(function()
{
var $this = $(this);
$this.data('order', ++orderSeed);
if (!$this.attr('id'))
{
$this.attr('id', 'panel' + orderSeed);
}
if (!$this.attr('data-id'))
{
$this.attr('data-id', orderSeed);
}
refreshPanel($this);
});
};
$.fn.dashboard = function(option)
{
return this.each(function()
{
var $this = $(this);
var data = $this.data('zui.dashboard');
var options = typeof option == 'object' && option;
if (!data) $this.data('zui.dashboard', (data = new Dashboard(this, options)));
if (typeof option == 'string') data[option]();
});
};
$.fn.dashboard.Constructor = Dashboard;
}(jQuery, Math));
/* ========================================================================
* ZUI: boards.js
* http://zui.sexy
* ========================================================================
* Copyright (c) 2014 cnezsoft.com; Licensed MIT
* ======================================================================== */
(function($){
'use strict';
if (!$.fn.droppable) throw new Error('droppable requires for boards');
var Boards = function(element, options)
{
this.$ = $(element);
this.options = this.getOptions(options);
this.getLang();
this.init();
};
Boards.DEFAULTS = {
lang: 'zh-cn',
langs:
{
'zh-cn':
{
append2end: '移动到末尾'
},
'zh-tw':
{
append2end: '移动到末尾'
},
'en':
{
append2end: 'Move to the end.'
}
}
}; // default options
Boards.prototype.getOptions = function(options)
{
options = $.extend(
{}, Boards.DEFAULTS, this.$.data(), options);
return options;
};
Boards.prototype.getLang = function()
{
var config = window.config;
if (!this.options.lang)
{
if (typeof(config) != 'undefined' && config.clientLang)
{
this.options.lang = config.clientLang;
}
else
{
var hl = $('html').attr('lang');
this.options.lang = hl ? hl : 'en';
}
this.options.lang = this.options.lang.replace(/-/, '_').toLowerCase();
}
this.lang = this.options.langs[this.options.lang] || this.options.langs[Boards.DEFAULTS.lang];
};
Boards.prototype.init = function()
{
var idSeed = 1;
var lang = this.lang;
this.$.find('.board-item:not(".disable-drop"), .board:not(".disable-drop")').each(function()
{
var $this = $(this);
if ($this.attr('id'))
{
$this.attr('data-id', $this.attr('id'));
}
else if (!$this.attr('data-id'))
{
$this.attr('data-id', 'board' + (idSeed++));
}
if ($this.hasClass('board'))
{
$this.find('.board-list').append('
'.format(lang));
}
});
this.bind();
};
Boards.prototype.bind = function(items)
{
var $boards = this.$,
setting = this.options;
if (typeof(items) == 'undefined')
{
items = $boards.find('.board-item:not(".disable-drop, .board-item-shadow")');
}
items.droppable(
{
target: '.board-item:not(".disable-drop, .board-item-shadow")',
flex: true,
start: function(e)
{
$boards.addClass('dragging').find('.board-item-shadow').height(e.element.outerHeight());
},
drag: function(e)
{
$boards.find('.board.drop-in-empty').removeClass('drop-in-empty');
if (e.isIn)
{
var board = e.target.closest('.board').addClass('drop-in');
var shadow = board.find('.board-item-shadow');
var target = e.target;
$boards.addClass('drop-in').find('.board.drop-in').not(board).removeClass('drop-in');
shadow.insertBefore(target);
board.toggleClass('drop-in-empty', target.hasClass('board-item-empty'));
}
},
drop: function(e)
{
if (e.isNew)
{
var DROP = 'drop';
var result;
if (setting.hasOwnProperty(DROP) && $.isFunction(setting[DROP]))
{
result = setting[DROP](e);
}
if (result !== false) e.element.insertBefore(e.target);
}
},
finish: function()
{
$boards.removeClass('dragging').removeClass('drop-in').find('.board.drop-in').removeClass('drop-in');
}
});
};
$.fn.boards = function(option)
{
return this.each(function()
{
var $this = $(this);
var data = $this.data('zui.boards');
var options = typeof option == 'object' && option;
if (!data) $this.data('zui.boards', (data = new Boards(this, options)));
if (typeof option == 'string') data[option]();
});
};
$.fn.boards.Constructor = Boards;
$(function()
{
$('[data-toggle="boards"]').boards();
});
}(jQuery));
/* ========================================================================
* ZUI: datatable.js
* http://zui.sexy
* ========================================================================
* Copyright (c) 2014 cnezsoft.com; Licensed MIT
* ======================================================================== */
(function($)
{
'use strict';
var name = 'zui.datatable';
var store = $.zui.store;
var DataTable = function(element, options)
{
this.name = name;
this.$ = $(element);
this.isTable = (this.$[0].tagName === 'TABLE');
this.firstShow = true;
if (this.isTable)
{
this.$table = this.$;
this.id = 'datatable-' + (this.$.attr('id') || $.zui.uuid());
}
else
{
this.$datatable = this.$.addClass('datatable');
if (this.$.attr('id'))
{
this.id = this.$.attr('id');
}
else
{
this.id = 'datatable-' + $.zui.uuid();
this.$.attr('id', this.id);
}
}
this.getOptions(options);
this.load();
this.callEvent('ready');
};
// default options
DataTable.DEFAULTS = {
// Check options
checkable: false, // added check icon to the head of rows
checkByClickRow: true, // change check status by click anywhere on a row
checkedClass: 'active', // apply CSS class to an checked row
checkboxName: null,
// Sort options
sortable: false, // enable sorter
// storage
storage: true, // enable storage
// fixed header of columns
fixedHeader: true, // fixed header
fixedHeaderOffset: 0, // set top offset of header when fixed
fixedLeftWidth: '30%', // set left width after first render
fixedRightWidth: '30%', // set right width after first render
flexHeadDrag: true, // scroll flexarea by drag header
scrollPos: 'in', // scroll bar position: 'out' | 'in'
// hover effection
rowHover: true, // apply hover effection to row
colHover: true, // apply hover effection to head
hoverClass: 'hover',
colHoverClass: 'col-hover',
// Merge rows
mergeRows: false, // Merge rows
// custom columns size
// customizable: false, // enable customizable
minColWidth: 20, // min width of columns
minFixedLeftWidth: 200, // min left width
minFixedRightWidth: 200, // min right width
minFlexAreaWidth: 200 // min flexarea width
};
// Get options
DataTable.prototype.getOptions = function(options)
{
var $e = this.$;
options = $.extend(
{}, DataTable.DEFAULTS, this.$.data(), options);
options.tableClass = options.tableClass || '';
options.tableClass = ' ' + options.tableClass + ' table-datatable';
$.each(['bordered', 'condensed', 'striped', 'condensed', 'fixed'], function(idx, cls)
{
cls = 'table-' + cls;
if($e.hasClass(cls)) options.tableClass += ' ' + cls;
});
if ($e.hasClass('table-hover') || options.rowHover)
{
options.tableClass += ' table-hover';
}
this.options = options;
};
// Load data form options or table dom
DataTable.prototype.load = function(data)
{
var options = this.options,
cols;
if($.isPlainObject(data))
{
this.data = data;
}
else if(typeof data === 'string')
{
var $table = $(data);
if($table.length)
{
this.$table = $table.first();
this.$table.data(name, this);
this.isTable = true;
}
data = null;
}
else
{
data = options.data;
}
if (!data)
{
if (this.isTable)
{
data = {
cols: [],
rows: []
};
cols = data.cols;
var rows = data.rows, i,
$th, $tr, $td, row, $t = this.$table, colSpan;
$t.find('thead > tr:first').children('th').each(function()
{
$th = $(this);
cols.push($.extend(
{
text: $th.html(),
flex: false || $th.hasClass('flex-col'),
width: 'auto',
cssClass: $th.attr('class'),
css: $th.attr('style'),
type: 'string',
ignore: $th.hasClass('ignore'),
sort: !$th.hasClass('sort-disabled'),
mergeRows: $th.attr('merge-rows')
}, $th.data()));
});
$t.find('tbody > tr').each(function()
{
$tr = $(this);
row = $.extend(
{
data: [],
checked: false,
cssClass: $tr.attr('class'),
css: $tr.attr('style'),
id: $tr.attr('id')
}, $tr.data());
$tr.children('td').each(function()
{
$td = $(this);
colSpan = $td.attr('colspan') || 1;
row.data.push($.extend(
{
cssClass: $td.attr('class'),
css: $td.attr('style'),
text: $td.html(),
colSpan: colSpan
}, $td.data()));
if(colSpan > 1)
{
for(i = 1; i < colSpan; i++)
{
row.data.push({empty: true});
}
}
});
rows.push(row);
});
var $tfoot = $t.find('tfoot');
if ($tfoot.length)
{
data.footer = $('
').append($tfoot);
}
}
else
{
throw new Error('No data avaliable!');
}
}
data.flexStart = -1;
data.flexEnd = -1;
cols = data.cols;
data.colsLength = cols.length;
for (var i = 0; i < data.colsLength; ++i)
{
var col = cols[i];
if (col.flex)
{
if (data.flexStart < 0)
{
data.flexStart = i;
}
data.flexEnd = i;
}
}
if (data.flexStart === 0 && data.flexEnd === data.colsLength)
{
data.flexStart = -1;
data.flexEnd = -1;
}
data.flexArea = data.flexStart >= 0;
data.fixedRight = data.flexEnd >= 0 && data.flexEnd < (data.colsLength - 1);
data.fixedLeft = data.flexStart > 0;
if (data.flexStart < 0 && data.flexEnd < 0)
{
data.fixedLeft = true;
data.flexStart = data.colsLength;
data.flexEnd = data.colsLength;
}
this.data = data;
this.callEvent('afterLoad',
{
data: data
});
this.render();
};
// Render datatable
DataTable.prototype.render = function()
{
var that = this;
var $datatable = that.$datatable || (that.isTable ? $('
') : that.$datatable),
options = that.options,
data = that.data,
cols = that.data.cols,
rows = that.data.rows;
var checkable = options.checkable,
$left,
i,
$right,
$flex,
dataRowSpan = '
';
$datatable.children('.datatable-head, .datatable-rows, .scroll-wrapper').remove();
// Set css class to datatable by options
$datatable.toggleClass('sortable', options.sortable);
// $datatable.toggleClass('customizable', options.customizable);
// Head
var $head = $('
');
for (i = 0; i < cols.length; i++)
{
col = cols[i];
$tr = i < data.flexStart ? $left : ((i >= data.flexStart && i <= data.flexEnd) ? $flex : $right);
if(i === 0 && checkable)
{
$tr.append('
');
// set sort class
$th.toggleClass('sort-down', col.sort === 'down')
.toggleClass('sort-up', col.sort === 'up')
.toggleClass('sort-disabled', col.sort === false);
$th.addClass(col.cssClass)
.addClass(col.colClass)
.html(col.text)
.attr(
{
'data-index' : i,
'data-type' : col.type,
style : col.css
});
$tr.append($th);
}
var $headSpan;
if(data.fixedLeft)
{
$headSpan = $(dataHeadSpan);
$headSpan.addClass('fixed-left')
// .find('.datatable-wrapper')
// .append('
')
.find('table')
.addClass(options.tableClass)
.find('thead').append($left);
$head.append($headSpan);
}
if (data.flexArea)
{
$headSpan = $(dataHeadSpan);
$headSpan.addClass('flexarea')
.find('.datatable-wrapper')
.append('
')
.find('table')
.addClass(options.tableClass)
.find('thead').append($flex);
$head.append($headSpan);
}
if (data.fixedRight)
{
$headSpan = $(dataHeadSpan);
$headSpan.addClass('fixed-right')
// .find('.datatable-wrapper')
// .append('
')
.find('table')
.addClass(options.tableClass)
.find('thead').append($right);
$head.append($headSpan);
}
$datatable.append($head);
// Rows
var $rows = $('
');
var $leftRow,
$flexRow,
$rightRow,
// $tr,
$td,
$cTd,
row,
rowLen = rows.length,
rowCol,
rowColLen;
$left = $('
');
$right = $('
');
$flex = $('
');
for (var r = 0; r < rowLen; ++r)
{
row = rows[r];
// format row
if(typeof row.id === 'undefined')
{
row.id = r;
}
row.index = r;
$leftRow = $('
');
$leftRow.addClass(row.cssClass)
.toggleClass(options.checkedClass, row.checked)
.attr(
{
'data-index' : r,
'data-id' : row.id
});
$flexRow = $leftRow.clone();
$rightRow = $leftRow.clone();
rowColLen = row.data.length;
for (i = 0; i < rowColLen; ++i)
{
rowCol = row.data[i];
if(i > 0 && rowCol.empty)
{
continue;
}
$tr = i < data.flexStart ? $leftRow : ((i >= data.flexStart && i <= data.flexEnd) ? $flexRow : $rightRow);
if(i === 0 && checkable)
{
$cTd = $('
');
if(options.checkboxName)
{
$cTd.append('
');
}
$tr.append($cTd);
}
if(cols[i].ignore) continue;
// format row column
if (!$.isPlainObject(rowCol))
{
rowCol =
{
text: rowCol,
row: r,
index: i
};
}
else
{
rowCol.row = r;
rowCol.index = i;
}
row.data[i] = rowCol;
$td = $('
');
$td.html(rowCol.text)
.addClass(rowCol.cssClass)
.addClass(cols[i].colClass)
.attr('colspan', rowCol.colSpan)
.attr(
{
'data-row' : r,
'data-index' : i,
'data-flex' : false,
'data-type' : cols[i].type,
style : rowCol.css
});
$tr.append($td);
}
$left.append($leftRow);
$flex.append($flexRow);
$right.append($rightRow);
}
var $rowSpan;
if (data.fixedLeft)
{
$rowSpan = $(dataRowSpan);
$rowSpan.addClass('fixed-left')
.find('table')
.addClass(options.tableClass)
.append($left);
$rows.append($rowSpan);
}
if (data.flexArea)
{
$rowSpan = $(dataRowSpan);
$rowSpan.addClass('flexarea')
.find('.datatable-wrapper')
.append('
')
.find('table')
.addClass(options.tableClass)
.append($flex);
$rows.append($rowSpan);
}
if (data.fixedRight)
{
$rowSpan = $(dataRowSpan);
$rowSpan.addClass('fixed-right')
.find('table')
.addClass(options.tableClass)
.append($right);
$rows.append($rowSpan);
}
$datatable.append($rows);
if(data.flexArea)
{
$datatable.append('
');
}
var $oldFooter = $datatable.children('.datatable-footer').detach();
if (data.footer)
{
$datatable.append($('').append(data.footer));
data.footer = null;
}
else if($oldFooter.length)
{
$datatable.append($oldFooter);
}
that.$datatable = $datatable.data(name, that);
if (that.isTable && that.firstShow)
{
that.$table.attr('data-datatable-id', this.id).hide().after($datatable);
that.firstShow = false;
}
that.bindEvents();
that.refreshSize();
that.callEvent('render');
};
// Bind global events
DataTable.prototype.bindEvents = function()
{
var that = this,
data = this.data,
options = this.options,
store = $.zui.store,
$datatable = this.$datatable;
var $dataSpans = that.$dataSpans = $datatable.children('.datatable-head, .datatable-rows').find('.datatable-span');
var $rowsSpans = that.$rowsSpans = $datatable.children('.datatable-rows').children('.datatable-rows-span');
var $headSpans = that.$headSpans = $datatable.children('.datatable-head').children('.datatable-head-span');
var $cells = that.$cells = $dataSpans.find('td, th');
var $dataCells = that.$dataCells = $cells.filter('td');
that.$headCells = $cells.filter('th');
var $rows = that.$rows = that.$rowsSpans.find('.table > tbody > tr');
// handle row hover events
if(options.rowHover)
{
var hoverClass = options.hoverClass;
$rowsSpans.on('mouseenter', 'td', function()
{
$dataCells.filter('.' + hoverClass).removeClass(hoverClass);
$rows.filter('.' + hoverClass).removeClass(hoverClass);
$rows.filter('[data-index="' + $(this).addClass(hoverClass).closest('tr').data('index') + '"]').addClass(hoverClass);
}).on('mouseleave', 'td', function()
{
$dataCells.filter('.' + hoverClass).removeClass(hoverClass);
$rows.filter('.' + hoverClass).removeClass(hoverClass);
});
}
// handle col hover events
if (options.colHover)
{
var colHoverClass = options.colHoverClass;
$headSpans.on('mouseenter', 'th', function()
{
$cells.filter('.' + colHoverClass).removeClass(colHoverClass);
$cells.filter('[data-index="' + $(this).data('index') + '"]').addClass(colHoverClass);
}).on('mouseleave', 'th', function()
{
$cells.filter('.' + colHoverClass).removeClass(colHoverClass);
});
}
// handle srcoll for flex area
if(data.flexArea)
{
var $scrollbar = $datatable.find('.scroll-slide'),
// $flexArea = $datatable.find('.datatable-span.flexarea .table'),
$flexArea = $datatable.find('.datatable-span.flexarea'),
$fixedLeft = $datatable.find('.datatable-span.fixed-left'),
// $flexTable = $datatable.find('.datatable-rows-span.flexarea .table');
$flexTable = $datatable.find('.datatable-span.flexarea .table');
var $bar = $scrollbar.children('.bar'),
flexWidth,
scrollWidth,
tableWidth,
lastBarLeft,
barLeft,
scrollOffsetStoreName = that.id + '_' + 'scrollOffset',
firtScroll,
left;
that.width = $datatable.width();
$datatable.resize(function()
{
that.width = $datatable.width();
});
var srollTable = function(offset, silence)
{
barLeft = Math.max(0, Math.min(flexWidth - scrollWidth, offset));
if (!silence)
{
$datatable.addClass('scrolling');
}
$bar.css('left', barLeft);
left = 0 - Math.floor((tableWidth - flexWidth) * barLeft / (flexWidth - scrollWidth));
$flexTable.css('left', left);
lastBarLeft = barLeft;
$datatable.toggleClass('scrolled-in', barLeft > 2)
.toggleClass('scrolled-out', barLeft < flexWidth - scrollWidth - 2);
if(options.storage) store.pageSet(scrollOffsetStoreName, barLeft);
};
var resizeScrollbar = function()
{
flexWidth = $flexArea.width();
$scrollbar.width(flexWidth).css('left', $fixedLeft.width());
tableWidth = $flexTable.width();
scrollWidth = Math.floor((flexWidth * flexWidth) / tableWidth);
$bar.css('width', scrollWidth);
$flexTable.css('min-width', flexWidth);
$datatable.toggleClass('show-scroll-slide', tableWidth > flexWidth);
if (!firtScroll && flexWidth !== scrollWidth)
{
firtScroll = true;
srollTable(store.pageGet(scrollOffsetStoreName, 0), true); // todo: unused?
}
if ($datatable.hasClass('size-changing'))
{
srollTable(barLeft, true);
}
};
// $scrollbar.resize(resizeScrollbar); // todo: unuseful?
$flexArea.resize(resizeScrollbar);
if(options.storage) resizeScrollbar();
var dragOptions = {
move: false,
stopPropagation: true,
drag: function(e)
{
srollTable($bar.position().left + e.smallOffset.x * (e.element.hasClass('bar') ? 1 : -1));
},
finish: function()
{
$datatable.removeClass('scrolling');
}
};
$bar.draggable(dragOptions);
if (options.flexHeadDrag)
{
$datatable.find('.datatable-head-span.flexarea').draggable(dragOptions);
}
$scrollbar.mousedown(function(event)
{
var x = event.pageX - $scrollbar.offset().left;
srollTable(x - (scrollWidth / 2));
});
}
// handle row check events
if (options.checkable)
{
var checkedStatusStoreName = that.id + '_checkedStatus',
checkedClass = options.checkedClass,
rowId;
var syncChecks = function()
{
var $checkRows = $rowsSpans.first().find('.table > tbody > tr');
var $checkedRows = $checkRows.filter('.' + checkedClass);
$checkRows.find('.check-row input:checkbox').prop('checked', false);
var checkedStatus = {
checkedAll: $checkRows.length === $checkedRows.length && $checkedRows.length > 0,
checks: $checkedRows.map(function()
{
rowId = $(this).data('id');
if(options.checkboxName)
{
$(this).find('.check-row input:checkbox').prop('checked', true);
}
return rowId;
}).toArray()
};
$.each(data.rows, function(index, value)
{
value.checked = ($.inArray(value.id, checkedStatus.checks) > -1);
});
$headSpans.find('.check-all').toggleClass('checked', checkedStatus.checkedAll);
if(options.storage) store.pageSet(checkedStatusStoreName, checkedStatus);
that.callEvent('checksChanged',
{
checks: checkedStatus
});
};
this.$rowsSpans.on('click', options.checkByClickRow ? 'tr' : '.check-row', function()
{
$rows.filter('[data-index="' + $(this).closest('tr').data('index') + '"]').toggleClass(checkedClass);
syncChecks();
});
var checkAllEventName = 'click.zui.datatable.check-all';
this.$datatable.off(checkAllEventName).on(checkAllEventName, '.check-all', function()
{
$rows.toggleClass(checkedClass, $(this).toggleClass('checked').hasClass('checked'));
syncChecks();
}).on('click', '.check-none', function()
{
$rows.toggleClass(checkedClass, false);
syncChecks();
}).on('click', '.check-inverse', function()
{
$rows.toggleClass(checkedClass);
syncChecks();
});
if(options.storage)
{
var checkedStatus = store.pageGet(checkedStatusStoreName);
if (checkedStatus)
{
$headSpans.find('.check-all').toggleClass('checked', checkedStatus.checkedAll);
if (checkedStatus.checkedAll)
{
$rows.addClass(checkedClass);
}
else
{
$rows.removeClass(checkedClass);
$.each(checkedStatus.checks, function(index, ele)
{
$rows.filter('[data-id="' + ele + '"]').addClass(checkedClass);
});
}
if (checkedStatus.checks.length)
{
syncChecks();
}
}
}
}
// fixed header
if(options.fixedHeader)
{
var offsetTop,
height,
scrollTop,
$dataTableHead = $datatable.children('.datatable-head'),
navbarHeight = options.fixedHeaderOffset || $('.navbar.navbar-fixed-top').height() || 0;
var handleScroll = function()
{
offsetTop = $datatable.offset().top;
scrollTop = $(window).scrollTop();
height = $datatable.height();
$datatable.toggleClass('head-fixed', (scrollTop + navbarHeight) > offsetTop && (scrollTop + navbarHeight) < (offsetTop + height));
if($datatable.hasClass('head-fixed'))
{
$dataTableHead.css(
{
width: $datatable.width(),
top: navbarHeight
});
}
else
{
$dataTableHead.attr('style', '');
}
};
$(window).scroll(handleScroll);
handleScroll();
}
// handle sort
if (options.sortable)
{
$headSpans.on('click', 'th:not(.sort-disabled, .check-btn)', function()
{
if ($datatable.hasClass('size-changing')) return;
that.sortTable($(this));
});
if(options.storage) that.sortTable();
}
else if (options.mergeRows)
{
this.mergeRows();
}
};
DataTable.prototype.mergeRows = function()
{
var $cells = this.$rowsSpans.find('.table > tbody > tr > td');
var cols = this.data.cols;
for(var i = 0; i < cols.length; i++)
{
var col = cols[i];
if(col.mergeRows)
{
var $cs = $cells.filter('[data-index="' + i + '"]');
if($cs.length > 1)
{
var $lastCell, rowspan;
$cs.each(function()
{
var $cell = $(this);
if($lastCell)
{
if($cell.html() === $lastCell.html())
{
rowspan = $lastCell.attr('rowspan') || 1;
if(typeof rowspan !== 'number')
{
rowspan = parseInt(rowspan);
if(isNaN(rowspan)) rowspan = 1;
}
$lastCell.attr('rowspan', rowspan + 1).css('vertical-align', 'middle');
$cell.remove();
}
else
{
$lastCell = $cell;
}
}
else
{
$lastCell = $cell;
}
});
}
}
}
};
// Sort table
DataTable.prototype.sortTable = function($th)
{
var store = $.zui.store,
options = this.options;
var sorterStoreName = this.id + '_datatableSorter';
var sorter = options.storage ? store.pageGet(sorterStoreName) : null;
if (!$th)
{
if (sorter)
{
$th = this.$headCells.filter('[data-index="' + sorter.index + '"]').addClass('sort-' + sorter.type);
}
else
{
$th = this.$headCells.filter('.sort-up, .sort-down').first();
}
}
if (!$th.length)
{
return;
}
var data = this.data;
var cols = data.cols,
rows = data.rows,
$headCells = this.$headCells,
sortUp,
type,
index;
sortUp = !$th.hasClass('sort-up');
$headCells.removeClass('sort-up sort-down');
$th.addClass(sortUp ? 'sort-up' : 'sort-down');
index = $th.data('index');
sortUp = $th.hasClass('sort-up');
$.each(cols, function(idx, col)
{
if (idx != index && (col.sort === 'up' || col.sort === 'down'))
{
col.sort = true;
}
else if (idx == index)
{
col.sort = sortUp ? 'up' : 'down';
type = col.type;
}
});
var valA, valB, result, $dataRows = this.$dataCells.filter('[data-index="' + index + '"]');
rows.sort(function(cellA, cellB)
{
cellA = cellA.data[index];
cellB = cellB.data[index];
valA = $dataRows.filter('[data-row="' + cellA.row + '"]').text();
valB = $dataRows.filter('[data-row="' + cellB.row + '"]').text();
if (type === 'number')
{
valA = parseFloat(valA);
valB = parseFloat(valB);
}
else if (type === 'date')
{
valA = Date.parse(valA);
valB = Date.parse(valB);
}
else
{
valA = valA.toLowerCase();
valB = valB.toLowerCase();
}
result = valA > valB ? 1 : (valA < valB ? -1 : 0);
if (sortUp)
{
result = result * (-1);
}
return result;
});
var $rows = this.$rows,
lastRows = [],
$row, $lastRow, $r;
$.each(rows, function(idx, row)
{
$row = $rows.filter('[data-index="' + row.index + '"]');
$row.each(function(rIdx)
{
$r = $(this);
$lastRow = lastRows[rIdx];
if ($lastRow)
{
$lastRow.after($r);
}
else
{
$r.parent().prepend($r);
}
lastRows[rIdx] = $r;
});
});
sorter = {
index: index,
type: sortUp ? 'up' : 'down'
};
// save sort with local storage
if(options.storage) store.pageSet(sorterStoreName, sorter);
this.callEvent('sort',
{
sorter: sorter
});
};
// Refresh size
DataTable.prototype.refreshSize = function()
{
var $datatable = this.$datatable,
options = this.options,
rows = this.data.rows,
cols = this.data.cols,
i;
$datatable.find('.datatable-span.fixed-left').css('width', options.fixedLeftWidth);
$datatable.find('.datatable-span.fixed-right').css('width', options.fixedRightWidth);
var findMaxHeight = function($cells)
{
var mx = 0, $cell, rowSpan;
$cells.css('height', 'auto');
$cells.each(function()
{
$cell = $(this);
rowSpan = $cell.attr('rowspan');
if(!rowSpan || rowSpan == 1) mx = Math.max(mx, $cell.outerHeight());
});
return mx;
},
$dataCells = this.$dataCells,
$cells = this.$cells,
$headCells = this.$headCells;
// set width of data cells
for (i = 0; i < cols.length; ++i)
{
$cells.filter('[data-index="' + i + '"]').css('width', cols[i].width);
}
// set height of head cells
var headMaxHeight = findMaxHeight($headCells);
$headCells.css('min-height', headMaxHeight).css('height', headMaxHeight);
// set height of data cells
var $rowCells;
for (i = 0; i < rows.length; ++i)
{
$rowCells = $dataCells.filter('[data-row="' + i + '"]');
var rowMaxHeight = findMaxHeight($rowCells);
$rowCells.css('min-height', rowMaxHeight).css('height', rowMaxHeight);
}
};
// Call event
DataTable.prototype.callEvent = function(name, params)
{
var result = this.$.callEvent(name + '.' + this.name, params, this).result;
return !(result !== undefined && (!result));
};
$.fn.datatable = function(option)
{
return this.each(function()
{
var $this = $(this);
var data = $this.data(name);
var options = typeof option == 'object' && option;
if (!data) $this.data(name, (data = new DataTable(this, options)));
if (typeof option == 'string') data[option]();
});
};
$.fn.datatable.Constructor = DataTable;
}(jQuery));
/* ========================================================================
* ZUI: color.js
* http://zui.sexy
* ========================================================================
* Copyright (c) 2014 cnezsoft.com; Licensed MIT
* ======================================================================== */
(function($, Math, window){
'use strict';
var hexReg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/;
var namedColors = {
aliceblue: '#f0f8ff',
antiquewhite: '#faebd7',
aqua: '#00ffff',
aquamarine: '#7fffd4',
azure: '#f0ffff',
beige: '#f5f5dc',
bisque: '#ffe4c4',
black: '#000000',
blanchedalmond: '#ffebcd',
blue: '#0000ff',
blueviolet: '#8a2be2',
brown: '#a52a2a',
burlywood: '#deb887',
cadetblue: '#5f9ea0',
chartreuse: '#7fff00',
chocolate: '#d2691e',
coral: '#ff7f50',
cornflowerblue: '#6495ed',
cornsilk: '#fff8dc',
crimson: '#dc143c',
cyan: '#00ffff',
darkblue: '#00008b',
darkcyan: '#008b8b',
darkgoldenrod: '#b8860b',
darkgray: '#a9a9a9',
darkgreen: '#006400',
darkkhaki: '#bdb76b',
darkmagenta: '#8b008b',
darkolivegreen: '#556b2f',
darkorange: '#ff8c00',
darkorchid: '#9932cc',
darkred: '#8b0000',
darksalmon: '#e9967a',
darkseagreen: '#8fbc8f',
darkslateblue: '#483d8b',
darkslategray: '#2f4f4f',
darkturquoise: '#00ced1',
darkviolet: '#9400d3',
deeppink: '#ff1493',
deepskyblue: '#00bfff',
dimgray: '#696969',
dodgerblue: '#1e90ff',
firebrick: '#b22222',
floralwhite: '#fffaf0',
forestgreen: '#228b22',
fuchsia: '#ff00ff',
gainsboro: '#dcdcdc',
ghostwhite: '#f8f8ff',
gold: '#ffd700',
goldenrod: '#daa520',
gray: '#808080',
green: '#008000',
greenyellow: '#adff2f',
honeydew: '#f0fff0',
hotpink: '#ff69b4',
indianred: '#cd5c5c',
indigo: '#4b0082',
ivory: '#fffff0',
khaki: '#f0e68c',
lavender: '#e6e6fa',
lavenderblush: '#fff0f5',
lawngreen: '#7cfc00',
lemonchiffon: '#fffacd',
lightblue: '#add8e6',
lightcoral: '#f08080',
lightcyan: '#e0ffff',
lightgoldenrodyellow: '#fafad2',
lightgray: '#d3d3d3',
lightgreen: '#90ee90',
lightpink: '#ffb6c1',
lightsalmon: '#ffa07a',
lightseagreen: '#20b2aa',
lightskyblue: '#87cefa',
lightslategray: '#778899',
lightsteelblue: '#b0c4de',
lightyellow: '#ffffe0',
lime: '#00ff00',
limegreen: '#32cd32',
linen: '#faf0e6',
magenta: '#ff00ff',
maroon: '#800000',
mediumaquamarine: '#66cdaa',
mediumblue: '#0000cd',
mediumorchid: '#ba55d3',
mediumpurple: '#9370db',
mediumseagreen: '#3cb371',
mediumslateblue: '#7b68ee',
mediumspringgreen: '#00fa9a',
mediumturquoise: '#48d1cc',
mediumvioletred: '#c71585',
midnightblue: '#191970',
mintcream: '#f5fffa',
mistyrose: '#ffe4e1',
moccasin: '#ffe4b5',
navajowhite: '#ffdead',
navy: '#000080',
oldlace: '#fdf5e6',
olive: '#808000',
olivedrab: '#6b8e23',
orange: '#ffa500',
orangered: '#ff4500',
orchid: '#da70d6',
palegoldenrod: '#eee8aa',
palegreen: '#98fb98',
paleturquoise: '#afeeee',
palevioletred: '#db7093',
papayawhip: '#ffefd5',
peachpuff: '#ffdab9',
peru: '#cd853f',
pink: '#ffc0cb',
plum: '#dda0dd',
powderblue: '#b0e0e6',
purple: '#800080',
red: '#ff0000',
rosybrown: '#bc8f8f',
royalblue: '#4169e1',
saddlebrown: '#8b4513',
salmon: '#fa8072',
sandybrown: '#f4a460',
seagreen: '#2e8b57',
seashell: '#fff5ee',
sienna: '#a0522d',
silver: '#c0c0c0',
skyblue: '#87ceeb',
slateblue: '#6a5acd',
slategray: '#708090',
snow: '#fffafa',
springgreen: '#00ff7f',
steelblue: '#4682b4',
tan: '#d2b48c',
teal: '#008080',
thistle: '#d8bfd8',
tomato: '#ff6347',
turquoise: '#40e0d0',
violet: '#ee82ee',
wheat: '#f5deb3',
white: '#ffffff',
whitesmoke: '#f5f5f5',
yellow: '#ffff00',
yellowgreen: '#9acd32'
};
/* color */
var Color = function(r, g, b, a)
{
this.r = 0;
this.g = 0;
this.b = 0;
this.a = 1;
if (a !== undefined) this.a = clamp(number(a), 1);
if (r !== undefined && g !== undefined && b !== undefined)
{
this.r = parseInt(clamp(number(r), 255));
this.g = parseInt(clamp(number(g), 255));
this.b = parseInt(clamp(number(b), 255));
}
else if (r !== undefined)
{
var type = typeof(r);
if (type == 'string')
{
r = r.toLowerCase();
if (r === 'transparent')
{
this.a = 0;
}
else if (namedColors[r])
{
this.rgb(hexToRgb(namedColors[r]));
}
else
{
this.rgb(hexToRgb(r));
}
}
else if (type == 'number' && g === undefined)
{
this.r = parseInt(clamp(r, 255));
this.g = this.r;
this.b = this.r;
}
else if (type == 'object' && r.hasOwnProperty('r'))
{
this.r = parseInt(clamp(number(r.r), 255));
if (r.hasOwnProperty('g')) this.g = parseInt(clamp(number(r.g), 255));
if (r.hasOwnProperty('b')) this.b = parseInt(clamp(number(r.b), 255));
if (r.hasOwnProperty('a')) this.a = clamp(number(r.a), 1);
}
else if (type == 'object' && r.hasOwnProperty('h'))
{
var hsl = {
h: clamp(number(r.h), 360),
s: 1,
l: 1,
a: 1
};
if (r.hasOwnProperty('s')) hsl.g = clamp(number(r.s), 255);
if (r.hasOwnProperty('l')) hsl.b = clamp(number(r.l), 255);
if (r.hasOwnProperty('a')) hsl.a = clamp(number(r.a), 1);
this.rgb(hslToRgb(hsl));
}
}
};
Color.prototype.rgb = function(rgb)
{
if (rgb !== undefined)
{
if (typeof(rgb) == 'object')
{
if (rgb.hasOwnProperty('r')) this.r = parseInt(clamp(number(rgb.r), 255));
if (rgb.hasOwnProperty('g')) this.g = parseInt(clamp(number(rgb.g), 255));
if (rgb.hasOwnProperty('b')) this.b = parseInt(clamp(number(rgb.b), 255));
if (rgb.hasOwnProperty('a')) this.a = clamp(number(rgb.a), 1);
}
else
{
var v = parseInt(number(rgb));
this.r = v;
this.g = v;
this.b = v;
}
return this;
}
else return {
r: this.r,
g: this.g,
b: this.b,
a: this.a
};
};
Color.prototype.hue = function(hue)
{
var hsl = this.toHsl();
if (hue === undefined) return hsl.h;
else
{
hsl.h = clamp(number(hue), 360);
this.rgb(hslToRgb(hsl));
return this;
}
};
Color.prototype.darken = function(amount)
{
var hsl = this.toHsl();
hsl.l -= amount / 100;
hsl.l = clamp(hsl.l, 1);
this.rgb(hslToRgb(hsl));
return this;
};
Color.prototype.clone = function()
{
return new Color(this.r, this.g, this.b, this.a);
};
Color.prototype.lighten = function(amount)
{
return this.darken(-amount);
};
Color.prototype.fade = function(amount)
{
this.a = clamp(amount / 100, 1);
return this;
};
Color.prototype.spin = function(amount)
{
var hsl = this.toHsl();
var hue = (hsl.h + amount) % 360;
hsl.h = hue < 0 ? 360 + hue : hue;
this.rgb(hslToRgb(hsl));
return this;
};
Color.prototype.toHsl = function()
{
var r = this.r / 255,
g = this.g / 255,
b = this.b / 255,
a = this.a;
var max = Math.max(r, g, b),
min = Math.min(r, g, b);
var h, s, l = (max + min) / 2,
d = max - min;
if (max === min)
{
h = s = 0;
}
else
{
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max)
{
case r:
h = (g - b) / d + (g < b ? 6 : 0);
break;
case g:
h = (b - r) / d + 2;
break;
case b:
h = (r - g) / d + 4;
break;
}
h /= 6;
}
return {
h: h * 360,
s: s,
l: l,
a: a
};
};
Color.prototype.luma = function()
{
var r = this.r / 255,
g = this.g / 255,
b = this.b / 255;
r = (r <= 0.03928) ? r / 12.92 : Math.pow(((r + 0.055) / 1.055), 2.4);
g = (g <= 0.03928) ? g / 12.92 : Math.pow(((g + 0.055) / 1.055), 2.4);
b = (b <= 0.03928) ? b / 12.92 : Math.pow(((b + 0.055) / 1.055), 2.4);
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
};
Color.prototype.saturate = function(amount)
{
var hsl = this.toHsl();
hsl.s += amount / 100;
hsl.s = clamp(hsl.s);
this.rgb(hslToRgb(hsl));
return this;
};
Color.prototype.desaturate = function(amount)
{
return this.saturate(-amount);
};
Color.prototype.contrast = function(dark, light, threshold)
{
if (typeof light === 'undefined') light = new Color(255, 255, 255, 1);
else light = new Color(light);
if (typeof dark === 'undefined') dark = new Color(0, 0, 0, 1);
else dark = new Color(dark);
if (this.a < 0.5) return dark;
if (threshold === undefined) threshold = 0.43;
else threshold = number(threshold);
if (dark.luma() > light.luma())
{
var t = light;
light = dark;
dark = t;
}
if (this.luma() < threshold)
{
return light;
}
else
{
return dark;
}
};
Color.prototype.hexStr = function()
{
var r = this.r.toString(16),
g = this.g.toString(16),
b = this.b.toString(16);
if (r.length == 1) r = '0' + r;
if (g.length == 1) g = '0' + g;
if (b.length == 1) b = '0' + b;
return '#' + r + g + b;
};
Color.prototype.toCssStr = function()
{
if (this.a > 0)
{
if (this.a < 1)
{
return 'rgba(' + this.r + ',' + this.g + ',' + this.b + ',' + this.a + ')';
}
else
{
return this.hexStr();
}
}
else
{
return 'transparent';
}
};
Color.prototype.isColor = isColor;
/* helpers */
function hexToRgb(hex)
{
hex = hex.toLowerCase();
if (hex && hexReg.test(hex))
{
var i;
if (hex.length === 4)
{
var hexNew = '#';
for (i = 1; i < 4; i += 1)
{
hexNew += hex.slice(i, i + 1).concat(hex.slice(i, i + 1));
}
hex = hexNew;
}
var hexChange = [];
for (i = 1; i < 7; i += 2)
{
hexChange.push(parseInt('0x' + hex.slice(i, i + 2)));
}
return {
r: hexChange[0],
g: hexChange[1],
b: hexChange[2],
a: 1
};
}
else
{
throw new Error('function hexToRgb: Wrong hex string! (hex: ' + hex + ')');
}
}
function isColor(hex)
{
return typeof(hex) === 'string' && (hex.toLowerCase() === 'transparent' || namedColors[hex.toLowerCase()] || hexReg.test($.trim(hex.toLowerCase())));
}
function hslToRgb(hsl)
{
var h = hsl.h,
s = hsl.s,
l = hsl.l,
a = hsl.a;
h = (number(h) % 360) / 360;
s = clamp(number(s));
l = clamp(number(l));
a = clamp(number(a));
var m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s;
var m1 = l * 2 - m2;
var r = {
r: hue(h + 1 / 3) * 255,
g: hue(h) * 255,
b: hue(h - 1 / 3) * 255,
a: 1
};
return r;
function hue(h)
{
h = h < 0 ? h + 1 : (h > 1 ? h - 1 : h);
if (h * 6 < 1)
{
return m1 + (m2 - m1) * h * 6;
}
else if (h * 2 < 1)
{
return m2;
}
else if (h * 3 < 2)
{
return m1 + (m2 - m1) * (2 / 3 - h) * 6;
}
else
{
return m1;
}
}
}
function fit(n, end, start)
{
if (start === undefined) start = 0;
if (end === undefined) end = 255;
return Math.min(Math.max(n, start), end);
}
function clamp(v, max)
{
return fit(v, max);
}
function number(n)
{
if (typeof(n) == 'number') return n;
return parseFloat(n);
}
$.zui({Color: Color});
}(jQuery, Math, window));
/* ========================================================================
* ZUI: calendar.js
* http://zui.sexy
* ========================================================================
* Copyright (c) 2014 cnezsoft.com; Licensed MIT
* ======================================================================== */
(function($, window)
{
'use strict';
var name = 'zui.calendar';
var NUMBER_TYPE_NAME = 'number';
var STRING_TYPE_NAME = 'string';
var UNDEFINED_TYPE_NAME = 'undefined';
var presetColors = {
"primary": 1, "green": 2, "red": 3, "blue": 4, "yellow": 5, "brown": 6, "purple": 7
};
var getNearbyLastWeekDay = function(date, lastWeek)
{
lastWeek = lastWeek || 1;
var d = date.clone();
while (d.getDay() != lastWeek)
{
d.addDays(-1);
}
d.clearTime();
return d;
},
getFirstDayOfMonth = function(date)
{
var d = date.clone();
d.setDate(1);
return d;
},
calculateDays = function(start, end) {
var s = start.clone().clearTime();
var e = end.clone().clearTime();
return Math.round((e.getTime() - s.getTime())/Date.ONEDAY_TICKS) + 1;
},
everyDayTo = function(start, end, callback) {
var a = start.clone();
var i = 0;
while(a <= end) {
callback(a.clone(), i++);
a.addDays(1);
}
};
// getLastDayOfMonth = function(date)
// {
// var d = date.clone();
// var month = d.getMonth();
// d.setDate(28);
// while (d.getMonth() == month)
// {
// d.addDays(1);
// }
// d.addDays(-1);
// return d;
// };
var Calendar = function(element, options)
{
this.name = name;
this.$ = $(element);
this.id = this.$.attr('id') || (name + $.zui.uuid());
this.$.attr('id', this.id);
this.storeName = name + '.' + this.id;
this.getOptions(options);
this.getLang();
this.data = this.options.data;
this.addCalendars(this.data.calendars);
this.addEvents(this.data.events);
this.sortEvents();
this.storeData = $.zui.store.pageGet(this.storeName,
{
date: 'today',
view: 'month'
});
this.date = this.options.startDate || 'today';
this.view = this.options.startView || 'month';
this.date = 'today';
this.$.toggleClass('limit-event-title', options.limitEventTitle);
if (this.options.withHeader)
{
var $header = this.$.children('.calender-header');
if (!$header.length)
{
$header = $('
'.format(this.lang));
this.$.append($header);
}
this.$caption = $header.find('.calendar-caption');
this.$todayBtn = $header.find('.btn-today');
this.$header = $header;
}
var $views = this.$.children('.calendar-views');
if (!$views.length)
{
$views = $('
');
this.$.append($views);
}
this.$views = $views;
this.$monthView = $views.children('.calendar-view.month');
this.display();
this.bindEvents();
};
// default options
Calendar.DEFAULTS = {
langs:
{
zh_cn:
{
weekNames: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
monthNames: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
today: '今天',
year: '{0}年',
month: '{0}月',
yearMonth: '{0}年{1}月'
},
zh_tw:
{
weekNames: ['週一', '週二', '週三', '週四', '週五', '週六', '週日'],
monthNames: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
today: '今天',
year: '{0}年',
month: '{0}月',
yearMonth: '{0}年{1}月'
},
en:
{
weekNames: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
monthNames: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'June', 'July', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
today: 'Today',
year: '{0}',
month: '{0}',
yearMonth: '{2}, {0}'
}
},
data:
{
calendars:
{
defaultCal:
{
color: '#229F24'
}
},
events: []
},
// startView: "month", // default view when load complete
// startDate: 'today', // default date when load complete
limitEventTitle: true,
storage: true,
withHeader: true,
dragThenDrop: true // drag an event and drop at another day
};
// Sort events by start datetime
Calendar.prototype.sortEvents = function()
{
var events = this.events;
if (!$.isArray(events))
{
events = [];
}
events.sort(function(a, b)
{
return a.start > b.start ? 1 : (a.start < b.start ? (-1) : 0);
});
};
Calendar.prototype.bindEvents = function()
{
var $e = this.$,
self = this;
$e.on('click', '.btn-today', function()
{
self.date = new Date();
self.display();
self.callEvent('clickTodayBtn');
}).on('click', '.btn-next', function()
{
if (self.view === 'month')
{
self.date.addMonths(1);
}
self.display();
self.callEvent('clickNextBtn');
}).on('click', '.btn-prev', function()
{
if (self.view === 'month')
{
self.date.addMonths(-1);
}
self.display();
self.callEvent('clickPrevBtn');
}).on('click', '.event', function(event)
{
self.callEvent('clickEvent',
{
element: this,
event: $(this).data('event'),
events: self.events
});
event.stopPropagation();
}).on('click', '.cell-day', function()
{
self.callEvent('clickCell',
{
element: this,
view: self.view,
date: new Date($(this).children('.day').attr('data-date')),
events: self.events
});
});
};
Calendar.prototype.addCalendars = function(calendars, silence)
{
var that = this;
if(!that.calendars) that.calendars = {};
if ($.isPlainObject(calendars))
{
calendars = [calendars];
}
$.each(calendars, function(index, cal)
{
if(!silence && !that.callEvent('beforeAddCalendars',
{
newCalendar: cal,
data: that.data
})) {
return;
}
if(!cal.color) cal.color = 'primary';
if(!presetColors[cal.color.toLowerCase()]) {
var c = new $.zui.Color(cal.color);
cal.textColor = c.contrast().hexStr();
} else {
cal.presetColor = true;
}
that.calendars[cal.name] = cal;
});
if(!silence) {
that.display();
that.callEvent('addCalendars',
{
newCalendars: calendars,
data: that.data
});
}
};
Calendar.prototype.addEvents = function(events, silence)
{
var that = this;
if(!that.events) that.events = [];
if ($.isPlainObject(events))
{
events = [events];
}
$.each(events, function(index, e)
{
if(!silence && !that.callEvent('beforeAddEvent',
{
newEvent: e,
data: that.data
})) {
return;
}
var startType = typeof e.start;
var endType = typeof e.end;
if (startType === NUMBER_TYPE_NAME || startType === STRING_TYPE_NAME)
{
e.start = new Date(e.start);
}
if (endType === NUMBER_TYPE_NAME || endType === STRING_TYPE_NAME)
{
e.end = new Date(e.end);
}
if (typeof e.id === UNDEFINED_TYPE_NAME)
{
e.id = $.zui.uuid();
}
if(e.allDay) {
e.start.clearTime();
e.end.clearTime().addDays(1).addMilliseconds(-1);
}
e.days = calculateDays(e.start, e.end);
that.events.push(e);
});
if(!silence) {
that.sortEvents();
that.display();
that.callEvent('addEvents',
{
newEvents: events,
data: that.data
});
}
};
Calendar.prototype.getEvent = function(id)
{
var events = this.events;
for (var i = 0; i < events.length; i++)
{
if (events[i].id == id)
{
return events[i];
}
}
return null;
};
Calendar.prototype.updateEvents = function(events)
{
var eventsParams = {
data: this.data,
changes: []
},
that = this;
if ($.isPlainObject(events))
{
events = [events];
}
var event, chgs, eventParam;
$.each(events, function(index, changes)
{
event = changes.event;
chgs = changes.changes;
eventParam = {
event: event,
changes: []
};
if (typeof event === STRING_TYPE_NAME)
{
event = that.getEvent(event);
}
if (event)
{
if ($.isPlainObject(chgs))
{
chgs = [chgs];
}
$.each(function(idx, chge)
{
if (that.callEvent('beforeChange',
{
event: event,
change: chge.change,
to: chge.to,
from: event[chge.change]
}))
{
eventParam.changes.push($.entend(true,
{}, chge,
{
from: event[chge.change]
}));
event[chge.change] = chge.to;
}
});
}
eventsParams.changes.push(eventParam);
});
that.sortEvents();
that.display();
that.callEvent('change', eventsParams);
};
Calendar.prototype.removeEvents = function(events)
{
if (!$.isArray(events))
{
events = [events];
}
var id, event, idx, evts = this.events,
that = this,
removedEvents = [];
$.each(events, function(index, value)
{
id = $.isPlainObject(value) ? value.id : value;
idx = -1;
for (var i = 0; i < evts.length; i++)
{
if (evts[i].id == id)
{
idx = i;
event = evts[i];
break;
}
}
if (idx >= 0 && that.callEvent('beforeRemoveEvent',
{
event: event,
eventId: id,
data: that.data
}))
{
evts.splice(idx, 1);
removedEvents.push(event);
}
});
that.sortEvents();
that.display();
that.callEvent('removeEvents',
{
removedEvents: removedEvents,
data: that.data
});
};
Calendar.prototype.getOptions = function(options)
{
this.options = $.extend(
{}, Calendar.DEFAULTS, this.$.data(), options);
};
Calendar.prototype.getLang = function()
{
this.lang = this.options.langs[this.options.lang || $.zui.clientLang()];
};
Calendar.prototype.display = function(view, date)
{
var that = this;
var viewType = typeof view;
var dateType = typeof date;
if (viewType === UNDEFINED_TYPE_NAME)
{
view = that.view;
}
else
{
that.view = view;
}
if (dateType === UNDEFINED_TYPE_NAME)
{
date = that.date;
}
else
{
that.date = date;
}
if (date === 'today')
{
date = new Date();
that.date = date;
}
if (typeof date === STRING_TYPE_NAME)
{
date = new Date(date);
that.date = date;
}
if (that.options.storage)
{
$.zui.store.pageSet(that.storeName,
{
date: date,
view: view
});
}
var eventPramas = {
view: view,
date: date
};
if (that.callEvent('beforeDisplay', eventPramas))
{
switch (view)
{
case 'month':
that.displayMonth(date);
break;
}
that.callEvent('display', eventPramas);
}
};
Calendar.prototype.displayMonth = function(date)
{
var that = this;
date = date || that.date;
var options = that.options,
self = that,
lang = that.lang,
i,
$views = that.$views,
$e = that.$;
var $view = self.$monthView;
if (!$view.length)
{
$view = $('
');
var $weekHead = $view.find('.week-head'),
$monthDays = $view.find('.month-days'),
$tr;
for (i = 0; i < 7; i++)
{
$weekHead.append('
' + lang.weekNames[i] + ' ');
}
for (i = 0; i < 6; i++)
{
$tr = $('
');
for (var j = 0; j < 7; j++)
{
$tr.append('
');
}
$monthDays.append($tr);
}
$views.append($view);
self.$monthView = $view;
}
var $weeks = $view.find('.week-days'),
$days = $view.find('.day'),
firstDayOfMonth = getFirstDayOfMonth(date),
// lastDayOfMonth = getLastDayOfMonth(date),
$week,
$day,
$cell,
year,
day,
month,
today = new Date();
var firstDay = getNearbyLastWeekDay(firstDayOfMonth),
thisYear = date.getFullYear(),
thisMonth = date.getMonth(),
todayMonth = today.getMonth(),
todayYear = today.getFullYear(),
todayDate = today.getDate();
var lastDay = firstDay.clone().addDays(6 * 7).addMilliseconds(-1),
printDate = firstDay.clone().addDays(1).addMilliseconds(-1);
var events = that.getEvents(firstDay, lastDay),
calendars = that.calendars, printDateId, isFirstDayOfWeek, $event, cal, $dayEvents;
$weeks.each(function(weekIdx)
{
$week = $(this);
$week.find('.day').each(function(dayIndex)
{
isFirstDayOfWeek = dayIndex === 0;
$day = $(this);
$cell = $day.closest('.cell-day');
year = printDate.getFullYear();
day = printDate.getDate();
month = printDate.getMonth();
printDateId = printDate.toDateString();
$day.attr('data-date', printDateId);
$day.find('.heading > .number').text(day);
$day.find('.heading > .month')
.toggle((weekIdx === 0 && dayIndex === 0) || day === 1)
.text(((month === 0 && day === 1) ? (lang.year.format(year) + ' ') : '') + lang.monthNames[month]);
$cell.toggleClass('current-month', month === thisMonth);
$cell.toggleClass('current', (day === todayDate && month === todayMonth && year === todayYear));
$cell.toggleClass('past', printDate < today);
$cell.toggleClass('future', printDate > today);
$dayEvents = $day.find('.events').empty();
var dayEvents = events[printDateId];
if(dayEvents)
{
var eventsMap = dayEvents.events,
stripCount = 0, e;
for(i = 0; i <= dayEvents.maxPos; ++i)
{
e = eventsMap[i];
if(!e || (e.placeholder && !isFirstDayOfWeek)) {
stripCount++;
continue;
}
$event = $('
' + e.start.format('hh:mm') + ' ' + e.title + '
');
$event.find('.time').toggle(!e.allDay);
$event.data('event', e);
$event.attr('data-days', e.days);
if (e.calendar)
{
cal = calendars[e.calendar];
if (cal)
{
if(cal.presetColor) {
$event.addClass('color-' + cal.color);
} else {
$event.css({'background-color': cal.color, color: cal.textColor});
}
}
}
if(e.days)
{
if(!e.placeholder)
{
$event.css('width', Math.min(7 - dayIndex, e.days) + '00%');
}
else if(isFirstDayOfWeek)
{
$event.css('width', Math.min(7, e.days - e.holderPos) + '00%');
}
}
if(stripCount > 0) {
$event.css('margin-top', stripCount * 22);
stripCount = 0;
}
$dayEvents.append($event);
}
}
printDate.addDays(1);
});
});
if (options.withHeader)
{
that.$caption.text(lang.yearMonth.format(thisYear, thisMonth + 1, lang.monthNames[thisMonth]));
that.$todayBtn.toggleClass('disabled', thisMonth === todayMonth);
}
// var $event,
// cal;
// $.each(that.events, function(index, e)
// {
// if (e.start >= firstDay && e.start <= lastDay)
// {
// $day = $days.filter('[data-date="' + e.start.toDateString() + '"]');
// if ($day.length)
// {
// $event = $('
' + e.start.format('hh:mm') + ' ' + e.title + '
');
// $event.find('.time').toggle(!e.allDay);
// $event.data('event', e);
// if (e.calendar)
// {
// cal = calendars[e.calendar];
// if (cal)
// {
// if(cal.presetColor) {
// $event.addClass('color-' + cal.color);
// } else {
// $event.css({'background-color': cal.color, color: cal.textColor});
// }
// }
// }
// $day.find('.events').append($event);
// }
// }
// });
if (options.dragThenDrop)
{
$view.find('.event').droppable(
{
target: $days,
container: $view,
flex: true,
start: function()
{
$e.addClass('event-dragging');
},
drop: function(e)
{
var et = e.element.data('event'),
newDate = e.target.attr('data-date');
var startDate = et.start.clone();
if (startDate.toDateString() != newDate)
{
newDate = new Date(newDate);
newDate.setHours(startDate.getHours());
newDate.setMinutes(startDate.getMinutes());
newDate.setSeconds(startDate.getSeconds());
if (self.callEvent('beforeChange',
{
event: et,
change: 'start',
to: newDate
}))
{
var oldEnd = et.end.clone();
et.end.addMilliseconds(et.end.getTime() - startDate.getTime());
et.start = newDate;
// e.target.find('.events').append(e.element);
that.display();
self.callEvent('change',
{
data: self.data,
changes: [
{
event: et,
changes: [
{
change: 'start',
from: startDate,
to: et.start
},
{
change: 'end',
from: oldEnd,
to: et.end
}]
}]
});
}
}
},
finish: function()
{
$e.removeClass('event-dragging');
}
});
}
};
Calendar.prototype.getEvents = function(start, end)
{
var events = {};
var calendars = this.calendars;
var addEventToDay = function(day, event, position)
{
var dayId = day.toDateString();
var dayEvents = events[dayId];
if(!dayEvents)
{
dayEvents = {maxPos: -1, events: {}};
}
if(typeof position === 'undefined')
{
for(var i = 0; i < 100; ++i)
{
if(!dayEvents.events[i])
{
position = i;
break;
}
}
}
dayEvents.maxPos = Math.max(position, dayEvents.maxPos);
dayEvents.events[position] = event;
events[dayId] = dayEvents;
return position;
};
$.each(this.events, function(index, e)
{
if(e.start >= start && e.start <= end)
{
var position = addEventToDay(e.start, e);
if(e.days > 1)
{
var placeholder = $.extend({placeholder: true}, e);
everyDayTo(e.start.clone().addDays(1), e.end, function(thisDay, i)
{
addEventToDay(thisDay.clone(), $.extend({holderPos: i}, placeholder), position);
});
}
}
});
return events;
};
Calendar.prototype.callEvent = function(name, params)
{
var result = this.$.callEvent(name + '.' + this.name, params, this);
return !(result.result !== undefined && (!result.result));
};
$.fn.calendar = function(option)
{
return this.each(function()
{
var $this = $(this);
var data = $this.data(name);
var options = typeof option == 'object' && option;
if (!data) $this.data(name, (data = new Calendar(this, options)));
if (typeof option == STRING_TYPE_NAME) data[option]();
});
};
$.fn.calendar.Constructor = Calendar;
}(jQuery, window));
/* ========================================================================
* jQuery Hotkeys Plugin
* Based upon the plugin by Tzury Bar Yochay:
* http://github.com/tzuryby/hotkeys
* ========================================================================
* Copyright 2010, John Resig
* Dual licensed under the MIT or GPL Version 2 licenses.
* Original idea by:
* Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/
* ======================================================================== */
(function(jQuery){
jQuery.hotkeys = {
version: "0.8",
specialKeys:
{
8: "backspace",
9: "tab",
13: "return",
16: "shift",
17: "ctrl",
18: "alt",
19: "pause",
20: "capslock",
27: "esc",
32: "space",
33: "pageup",
34: "pagedown",
35: "end",
36: "home",
37: "left",
38: "up",
39: "right",
40: "down",
45: "insert",
46: "del",
96: "0",
97: "1",
98: "2",
99: "3",
100: "4",
101: "5",
102: "6",
103: "7",
104: "8",
105: "9",
106: "*",
107: "+",
109: "-",
110: ".",
111: "/",
112: "f1",
113: "f2",
114: "f3",
115: "f4",
116: "f5",
117: "f6",
118: "f7",
119: "f8",
120: "f9",
121: "f10",
122: "f11",
123: "f12",
144: "numlock",
145: "scroll",
191: "/",
224: "meta"
},
shiftNums:
{
"`": "~",
"1": "!",
"2": "@",
"3": "#",
"4": "$",
"5": "%",
"6": "^",
"7": "&",
"8": "*",
"9": "(",
"0": ")",
"-": "_",
"=": "+",
";": ": ",
"'": "\"",
",": "<",
".": ">",
"/": "?",
"\\": "|"
}
};
function keyHandler(handleObj)
{
// Only care when a possible input has been specified
if (typeof handleObj.data !== "string")
{
return;
}
var origHandler = handleObj.handler,
keys = handleObj.data.toLowerCase().split(" ");
handleObj.handler = function(event)
{
// Don't fire in text-accepting inputs that we didn't directly bind to
if (this !== event.target && (/textarea|select/i.test(event.target.nodeName) ||
event.target.type === "text"))
{
return;
}
// Keypress represents characters, not special keys
var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[event.which],
character = String.fromCharCode(event.which).toLowerCase(),
key, modif = "",
possible = {};
// check combinations (alt|ctrl|shift+anything)
if (event.altKey && special !== "alt")
{
modif += "alt+";
}
if (event.ctrlKey && special !== "ctrl")
{
modif += "ctrl+";
}
// TODO: Need to make sure this works consistently across platforms
if (event.metaKey && !event.ctrlKey && special !== "meta")
{
modif += "meta+";
}
if (event.shiftKey && special !== "shift")
{
modif += "shift+";
}
if (special)
{
possible[modif + special] = true;
}
else
{
possible[modif + character] = true;
possible[modif + jQuery.hotkeys.shiftNums[character]] = true;
// "$" can be triggered as "Shift+4" or "Shift+$" or just "$"
if (modif === "shift+")
{
possible[jQuery.hotkeys.shiftNums[character]] = true;
}
}
for (var i = 0, l = keys.length; i < l; i++)
{
if (possible[keys[i]])
{
return origHandler.apply(this, arguments);
}
}
};
}
jQuery.each(["keydown", "keyup", "keypress"], function()
{
jQuery.event.special[this] = {
add: keyHandler
};
});
})(jQuery);
/* ========================================================================
* ZUI: auto-trigger.js
* http://zui.sexy
* ========================================================================
* Copyright (c) 2014 cnezsoft.com; Licensed MIT
* ======================================================================== */
(function($){
'use strict';
var AutoTrigger = function(element, options)
{
this.$ = $(element);
this.options = this.getOptions(options);
this.init();
};
AutoTrigger.DEFAULTS = {
trigger: 'toggle',
// selector: null,
animate: 'slide',
easing: 'linear',
animateSpeed: 'fast',
events: 'click',
// target: null,
preventDefault: true,
cancelBubble: true
//,before:
//,after:
}; // default options
AutoTrigger.prototype.getOptions = function(options)
{
options = $.extend(
{}, AutoTrigger.DEFAULTS, this.$.data(), options);
return options;
};
AutoTrigger.prototype.init = function()
{
this.bindEvents();
};
AutoTrigger.prototype.bindEvents = function()
{
var options = this.options,
i;
this.bindTrigger(options);
if ($.isArray(options.triggers))
{
for (i in options.triggers)
{
this.bindTrigger($.extend(
{}, options, options.triggers[i]));
}
}
else if (typeof options.triggers === 'string')
{
/* events,trigger,target,data */
var triggers = options.triggers.split('|');
for (i in triggers)
{
var ops = triggers[i].split(',', 4);
if (ops.length < 2) continue;
var option = {};
if (ops[0]) option.events = ops[0];
if (ops[1]) option.trigger = ops[1];
if (ops[2]) option.target = ops[2];
if (ops[3]) option.data = ops[3];
this.bindTrigger($.extend(
{}, options, option));
}
}
};
AutoTrigger.prototype.bindTrigger = function(options)
{
var that = this;
that.$.on(options.events, options.selector, function(event)
{
var target = (!options.target) || options.target == 'self' ? that.$ : $(options.target);
var data = {
event: event,
element: this,
target: target,
options: options
};
if (!$.zui.callEvent(options.before, data, that)) return;
if ($.isFunction(options.trigger))
{
$.zui.callEvent(options.trigger, data, that);
}
else
{
var type = options.trigger;
if (type === 'toggle')
{
type = target.hasClass('hide') ? 'show' : 'hide';
}
var params;
switch (type)
{
case 'toggle':
target.toggle();
break;
case 'show':
params = {
duration: options.animateSpeed,
easing: options.easing
};
target.removeClass('hide');
if (options.animate === 'slide')
{
target.slideDown(params);
}
else if (options.animate === 'fade')
{
target.fadeIn(params);
}
else
{
target.show(params);
}
break;
case 'hide':
params = {
duration: options.animateSpeed,
easing: options.easing,
complete: function()
{
target.addClass('hide');
}
};
if (options.animate === 'slide')
{
target.slideUp(params);
}
else if (options.animate === 'fade')
{
target.fadeOut(params);
}
else
{
target.hide(params);
}
break;
case 'addClass':
case 'removeClass':
case 'toggleClass':
target[type](options.data);
break;
}
}
$.zui.callEvent(options.after, data, that);
if (options.preventDefault) event.preventDefault();
if (options.cancelBubble) event.stopPropagation();
});
};
$.fn.autoTrigger = function(option)
{
return this.each(function()
{
var $this = $(this);
var data = $this.data('zui.autoTrigger');
var options = typeof option == 'object' && option;
if (!data) $this.data('zui.autoTrigger', (data = new AutoTrigger(this, options)));
if (typeof option == 'string') data[option]();
});
};
$.fn.autoTrigger.Constructor = AutoTrigger;
$(function()
{
$('[data-toggle="autoTrigger"]').autoTrigger();
$('[data-toggle="toggle"]').autoTrigger();
$('[data-toggle="show"]').autoTrigger(
{
trigger: 'show'
});
$('[data-toggle="hide"]').autoTrigger(
{
trigger: 'hide'
});
$('[data-toggle="addClass"]').autoTrigger(
{
trigger: 'addClass'
});
$('[data-toggle="removeClass"]').autoTrigger(
{
trigger: 'removeClass'
});
$('[data-toggle="toggleClass"]').autoTrigger(
{
trigger: 'toggleClass'
});
});
}(jQuery));
/*!
Chosen, a Select Box Enhancer for jQuery and Prototype
by Patrick Filler for Harvest, http://getharvest.com
Version 1.1.0
Full source at https://github.com/harvesthq/chosen
Copyright (c) 2011 Harvest http://getharvest.com
MIT License, https://github.com/harvesthq/chosen/blob/master/LICENSE.md
This file is generated by `grunt build`, do not edit it by hand.
*/
(function(){
var $, AbstractChosen, Chosen, SelectParser, _ref,
__hasProp = {}.hasOwnProperty,
__extends = function(child, parent)
{
for (var key in parent)
{
if (__hasProp.call(parent, key)) child[key] = parent[key];
}
function ctor()
{
this.constructor = child;
}
ctor.prototype = parent.prototype;
child.prototype = new ctor();
child.__super__ = parent.prototype;
return child;
};
SelectParser = (function()
{
function SelectParser()
{
this.options_index = 0;
this.parsed = [];
}
SelectParser.prototype.add_node = function(child)
{
if (child.nodeName.toUpperCase() === "OPTGROUP")
{
return this.add_group(child);
}
else
{
return this.add_option(child);
}
};
SelectParser.prototype.add_group = function(group)
{
var group_position, option, _i, _len, _ref, _results;
group_position = this.parsed.length;
this.parsed.push(
{
array_index: group_position,
group: true,
label: this.escapeExpression(group.label),
children: 0,
disabled: group.disabled,
title: group.title,
search_keys: ($.trim(group.getAttribute('data-keys') || '')).replace(/,/, ' ')
});
_ref = group.childNodes;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++)
{
option = _ref[_i];
_results.push(this.add_option(option, group_position, group.disabled));
}
return _results;
};
SelectParser.prototype.add_option = function(option, group_position, group_disabled)
{
if (option.nodeName.toUpperCase() === "OPTION")
{
if (option.text !== "")
{
if (group_position != null)
{
this.parsed[group_position].children += 1;
}
this.parsed.push(
{
array_index: this.parsed.length,
options_index: this.options_index,
value: option.value,
text: option.text,
title: option.title,
html: option.innerHTML,
selected: option.selected,
disabled: group_disabled === true ? group_disabled : option.disabled,
group_array_index: group_position,
classes: option.className,
style: option.style.cssText,
search_keys: ($.trim(option.getAttribute('data-keys') || '')).replace(/,/, ' ')
});
}
else
{
this.parsed.push(
{
array_index: this.parsed.length,
options_index: this.options_index,
empty: true
});
}
return this.options_index += 1;
}
};
SelectParser.prototype.escapeExpression = function(text)
{
var map, unsafe_chars;
if ((text == null) || text === false)
{
return "";
}
if (!/[\&\<\>\"\'\`]/.test(text))
{
return text;
}
map = {
"<": "<",
">": ">",
'"': """,
"'": "'",
"`": "`"
};
unsafe_chars = /&(?!\w+;)|[\<\>\"\'\`]/g;
return text.replace(unsafe_chars, function(chr)
{
return map[chr] || "&";
});
};
return SelectParser;
})();
SelectParser.select_to_array = function(select)
{
var child, parser, _i, _len, _ref;
parser = new SelectParser();
_ref = select.childNodes;
for (_i = 0, _len = _ref.length; _i < _len; _i++)
{
child = _ref[_i];
parser.add_node(child);
}
return parser.parsed;
};
AbstractChosen = (function()
{
function AbstractChosen(form_field, options)
{
this.form_field = form_field;
this.options = options != null ? options :
{};
if (!AbstractChosen.browser_is_supported())
{
return;
}
this.is_multiple = this.form_field.multiple;
this.set_default_text();
this.set_default_values();
this.setup();
this.set_up_html();
this.register_observers();
}
AbstractChosen.prototype.set_default_values = function()
{
var _this = this;
this.click_test_action = function(evt)
{
return _this.test_active_click(evt);
};
this.activate_action = function(evt)
{
return _this.activate_field(evt);
};
this.active_field = false;
this.mouse_on_container = false;
this.results_showing = false;
this.result_highlighted = null;
this.allow_single_deselect = (this.options.allow_single_deselect != null) && (this.form_field.options[0] != null) && this.form_field.options[0].text === "" ? this.options.allow_single_deselect : false;
this.disable_search_threshold = this.options.disable_search_threshold || 0;
this.disable_search = this.options.disable_search || false;
this.enable_split_word_search = this.options.enable_split_word_search != null ? this.options.enable_split_word_search : true;
this.group_search = this.options.group_search != null ? this.options.group_search : true;
this.search_contains = this.options.search_contains || false;
this.single_backstroke_delete = this.options.single_backstroke_delete != null ? this.options.single_backstroke_delete : true;
this.max_selected_options = this.options.max_selected_options || Infinity;
this.inherit_select_classes = this.options.inherit_select_classes || false;
this.display_selected_options = this.options.display_selected_options != null ? this.options.display_selected_options : true;
return this.display_disabled_options = this.options.display_disabled_options != null ? this.options.display_disabled_options : true;
};
AbstractChosen.prototype.set_default_text = function()
{
if (this.form_field.getAttribute("data-placeholder"))
{
this.default_text = this.form_field.getAttribute("data-placeholder");
}
else if (this.is_multiple)
{
this.default_text = this.options.placeholder_text_multiple || this.options.placeholder_text || AbstractChosen.default_multiple_text;
}
else
{
this.default_text = this.options.placeholder_text_single || this.options.placeholder_text || AbstractChosen.default_single_text;
}
return this.results_none_found = this.form_field.getAttribute("data-no_results_text") || this.options.no_results_text || AbstractChosen.default_no_result_text;
};
AbstractChosen.prototype.mouse_enter = function()
{
return this.mouse_on_container = true;
};
AbstractChosen.prototype.mouse_leave = function()
{
return this.mouse_on_container = false;
};
AbstractChosen.prototype.input_focus = function(evt)
{
var _this = this;
if (this.is_multiple)
{
if (!this.active_field)
{
return setTimeout((function()
{
return _this.container_mousedown();
}), 50);
}
}
else
{
if (!this.active_field)
{
return this.activate_field();
}
}
};
AbstractChosen.prototype.input_blur = function(evt)
{
var _this = this;
if (!this.mouse_on_container)
{
this.active_field = false;
return setTimeout((function()
{
return _this.blur_test();
}), 100);
}
};
AbstractChosen.prototype.results_option_build = function(options)
{
var content, data, _i, _len, _ref;
content = '';
_ref = this.results_data;
for (_i = 0, _len = _ref.length; _i < _len; _i++)
{
data = _ref[_i];
if (data.group)
{
content += this.result_add_group(data);
}
else
{
content += this.result_add_option(data);
}
if (options != null ? options.first : void 0)
{
if (data.selected && this.is_multiple)
{
this.choice_build(data);
}
else if (data.selected && !this.is_multiple)
{
this.single_set_selected_text(data.text);
}
}
}
return content;
};
AbstractChosen.prototype.result_add_option = function(option)
{
var classes, option_el;
if (!option.search_match)
{
return '';
}
if (!this.include_option_in_results(option))
{
return '';
}
classes = [];
if (!option.disabled && !(option.selected && this.is_multiple))
{
classes.push("active-result");
}
if (option.disabled && !(option.selected && this.is_multiple))
{
classes.push("disabled-result");
}
if (option.selected)
{
classes.push("result-selected");
}
if (option.group_array_index != null)
{
classes.push("group-option");
}
if (option.classes !== "")
{
classes.push(option.classes);
}
option_el = document.createElement("li");
option_el.className = classes.join(" ");
option_el.style.cssText = option.style;
option_el.title = option.title;
option_el.setAttribute("data-option-array-index", option.array_index);
option_el.innerHTML = option.search_text;
return this.outerHTML(option_el);
};
AbstractChosen.prototype.result_add_group = function(group)
{
var group_el;
if (!(group.search_match || group.group_match))
{
return '';
}
if (!(group.active_options > 0))
{
return '';
}
group_el = document.createElement("li");
group_el.className = "group-result";
group_el.title = group.title;
group_el.innerHTML = group.search_text;
return this.outerHTML(group_el);
};
AbstractChosen.prototype.results_update_field = function()
{
this.set_default_text();
if (!this.is_multiple)
{
this.results_reset_cleanup();
}
this.result_clear_highlight();
this.results_build();
if (this.results_showing)
{
return this.winnow_results();
}
};
AbstractChosen.prototype.reset_single_select_options = function()
{
var result, _i, _len, _ref, _results;
_ref = this.results_data;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++)
{
result = _ref[_i];
if (result.selected)
{
_results.push(result.selected = false);
}
else
{
_results.push(void 0);
}
}
return _results;
};
AbstractChosen.prototype.results_toggle = function()
{
if (this.results_showing)
{
return this.results_hide();
}
else
{
return this.results_show();
}
};
AbstractChosen.prototype.results_search = function(evt)
{
if (this.results_showing)
{
return this.winnow_results();
}
else
{
return this.results_show();
}
};
AbstractChosen.prototype.winnow_results = function()
{
var escapedSearchText, option, regex, regexAnchor, results, results_group, searchText, startpos, text, zregex, _i, _len, _ref;
this.no_results_clear();
results = 0;
searchText = this.get_search_text();
escapedSearchText = searchText.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
regexAnchor = this.search_contains ? "" : "^";
regex = new RegExp(regexAnchor + escapedSearchText, 'i');
zregex = new RegExp(escapedSearchText, 'i');
_ref = this.results_data;
for (_i = 0, _len = _ref.length; _i < _len; _i++)
{
option = _ref[_i];
option.search_match = false;
results_group = null;
if (this.include_option_in_results(option))
{
if (option.group)
{
option.group_match = false;
option.active_options = 0;
}
if ((option.group_array_index != null) && this.results_data[option.group_array_index])
{
results_group = this.results_data[option.group_array_index];
if (results_group.active_options === 0 && results_group.search_match)
{
results += 1;
}
results_group.active_options += 1;
}
if (!(option.group && !this.group_search))
{
option.search_text = option.group ? option.label : option.html;
option.search_keys_match = this.search_string_match(option.search_keys, regex);
option.search_text_match = this.search_string_match(option.search_text, regex);
option.search_match = option.search_text_match || option.search_keys_match;
if (option.search_match && !option.group)
{
results += 1;
}
if (option.search_match)
{
if (option.search_text_match && option.search_text.length)
{
startpos = option.search_text.search(zregex);
text = option.search_text.substr(0, startpos + searchText.length) + '' + option.search_text.substr(startpos + searchText.length);
option.search_text = text.substr(0, startpos) + '
' + text.substr(startpos);
}
else if (option.search_keys_match && option.search_keys.length)
{
startpos = option.search_keys.search(zregex);
text = option.search_keys.substr(0, startpos + searchText.length) + ' ' + option.search_keys.substr(startpos + searchText.length);
option.search_text += '
' + text.substr(0, startpos) + '' + text.substr(startpos) + ' ';
}
if (results_group != null)
{
results_group.group_match = true;
}
}
else if ((option.group_array_index != null) && this.results_data[option.group_array_index].search_match)
{
option.search_match = true;
}
}
}
}
this.result_clear_highlight();
if (results < 1 && searchText.length)
{
this.update_results_content("");
return this.no_results(searchText);
}
else
{
this.update_results_content(this.results_option_build());
return this.winnow_results_set_highlight();
}
};
AbstractChosen.prototype.search_string_match = function(search_string, regex)
{
var part, parts, _i, _len;
if (regex.test(search_string))
{
return true;
}
else if (this.enable_split_word_search && (search_string.indexOf(" ") >= 0 || search_string.indexOf("[") === 0))
{
parts = search_string.replace(/\[|\]/g, "").split(" ");
if (parts.length)
{
for (_i = 0, _len = parts.length; _i < _len; _i++)
{
part = parts[_i];
if (regex.test(part))
{
return true;
}
}
}
}
};
AbstractChosen.prototype.choices_count = function()
{
var option, _i, _len, _ref;
if (this.selected_option_count != null)
{
return this.selected_option_count;
}
this.selected_option_count = 0;
_ref = this.form_field.options;
for (_i = 0, _len = _ref.length; _i < _len; _i++)
{
option = _ref[_i];
if (option.selected && option.value != '')
{
this.selected_option_count += 1;
}
}
return this.selected_option_count;
};
AbstractChosen.prototype.choices_click = function(evt)
{
evt.preventDefault();
if (!(this.results_showing || this.is_disabled))
{
return this.results_show();
}
};
AbstractChosen.prototype.keyup_checker = function(evt)
{
var stroke, _ref;
stroke = (_ref = evt.which) != null ? _ref : evt.keyCode;
this.search_field_scale();
switch (stroke)
{
case 8:
if (this.is_multiple && this.backstroke_length < 1 && this.choices_count() > 0)
{
return this.keydown_backstroke();
}
else if (!this.pending_backstroke)
{
this.result_clear_highlight();
return this.results_search();
}
break;
case 13:
evt.preventDefault();
if (this.results_showing)
{
return this.result_select(evt);
}
break;
case 27:
if (this.results_showing)
{
this.results_hide();
}
return true;
case 9:
case 38:
case 40:
case 16:
case 91:
case 17:
break;
default:
return this.results_search();
}
};
AbstractChosen.prototype.clipboard_event_checker = function(evt)
{
var _this = this;
return setTimeout((function()
{
return _this.results_search();
}), 50);
};
AbstractChosen.prototype.container_width = function()
{
if (this.options.width != null)
{
return this.options.width;
}
else
{
return "" + this.form_field.offsetWidth + "px";
}
};
AbstractChosen.prototype.include_option_in_results = function(option)
{
if (this.is_multiple && (!this.display_selected_options && option.selected))
{
return false;
}
if (!this.display_disabled_options && option.disabled)
{
return false;
}
if (option.empty)
{
return false;
}
return true;
};
AbstractChosen.prototype.search_results_touchstart = function(evt)
{
this.touch_started = true;
return this.search_results_mouseover(evt);
};
AbstractChosen.prototype.search_results_touchmove = function(evt)
{
this.touch_started = false;
return this.search_results_mouseout(evt);
};
AbstractChosen.prototype.search_results_touchend = function(evt)
{
if (this.touch_started)
{
return this.search_results_mouseup(evt);
}
};
AbstractChosen.prototype.outerHTML = function(element)
{
var tmp;
if (element.outerHTML)
{
return element.outerHTML;
}
tmp = document.createElement("div");
tmp.appendChild(element);
return tmp.innerHTML;
};
AbstractChosen.browser_is_supported = function()
{
if (window.navigator.appName === "Microsoft Internet Explorer")
{
return document.documentMode >= 8;
}
if (/iP(od|hone)/i.test(window.navigator.userAgent))
{
return false;
}
if (/Android/i.test(window.navigator.userAgent))
{
if (/Mobile/i.test(window.navigator.userAgent))
{
return false;
}
}
return true;
};
AbstractChosen.default_multiple_text = "";
AbstractChosen.default_single_text = "";
AbstractChosen.default_no_result_text = "No results match";
return AbstractChosen;
})();
$ = jQuery;
$.fn.extend(
{
chosen: function(options)
{
if (!AbstractChosen.browser_is_supported())
{
return this;
}
return this.each(function(input_field)
{
var $this, chosen;
$this = $(this);
chosen = $this.data('chosen');
if (options === 'destroy' && chosen)
{
chosen.destroy();
}
else if (!chosen)
{
$this.data('chosen', new Chosen(this, options));
}
});
}
});
Chosen = (function(_super)
{
__extends(Chosen, _super);
function Chosen()
{
_ref = Chosen.__super__.constructor.apply(this, arguments);
return _ref;
}
Chosen.prototype.setup = function()
{
this.form_field_jq = $(this.form_field);
this.current_selectedIndex = this.form_field.selectedIndex;
return this.is_rtl = this.form_field_jq.hasClass("chosen-rtl");
};
Chosen.prototype.set_up_html = function()
{
var container_classes, container_props;
container_classes = ["chosen-container"];
container_classes.push("chosen-container-" + (this.is_multiple ? "multi" : "single"));
if (this.inherit_select_classes && this.form_field.className)
{
container_classes.push(this.form_field.className);
}
if (this.is_rtl)
{
container_classes.push("chosen-rtl");
}
var strClass = this.form_field.getAttribute('data-css-class');
if(strClass)
{
container_classes.push(strClass);
}
container_props = {
'class': container_classes.join(' '),
'style': "width: " + (this.container_width()) + ";",
'title': this.form_field.title
};
if (this.form_field.id.length)
{
container_props.id = this.form_field.id.replace(/[^\w]/g, '_') + "_chosen";
}
this.container = $("
", container_props);
if (this.is_multiple)
{
this.container.html('
');
}
else
{
this.container.html('
' + this.default_text + '
');
}
this.form_field_jq.hide().after(this.container);
this.dropdown = this.container.find('div.chosen-drop').first();
this.search_field = this.container.find('input').first();
this.search_results = this.container.find('ul.chosen-results').first();
this.search_field_scale();
this.search_no_results = this.container.find('li.no-results').first();
if (this.is_multiple)
{
this.search_choices = this.container.find('ul.chosen-choices').first();
this.search_container = this.container.find('li.search-field').first();
}
else
{
this.search_container = this.container.find('div.chosen-search').first();
this.selected_item = this.container.find('.chosen-single').first();
}
if(this.options.drop_width)
{
this.dropdown.css('width', this.options.drop_width).addClass('chosen-drop-size-limited');
}
this.results_build();
this.set_tab_index();
this.set_label_behavior();
return this.form_field_jq.trigger("chosen:ready",
{
chosen: this
});
};
Chosen.prototype.register_observers = function()
{
var _this = this;
this.container.bind('mousedown.chosen', function(evt)
{
_this.container_mousedown(evt);
});
this.container.bind('mouseup.chosen', function(evt)
{
_this.container_mouseup(evt);
});
this.container.bind('mouseenter.chosen', function(evt)
{
_this.mouse_enter(evt);
});
this.container.bind('mouseleave.chosen', function(evt)
{
_this.mouse_leave(evt);
});
this.search_results.bind('mouseup.chosen', function(evt)
{
_this.search_results_mouseup(evt);
});
this.search_results.bind('mouseover.chosen', function(evt)
{
_this.search_results_mouseover(evt);
});
this.search_results.bind('mouseout.chosen', function(evt)
{
_this.search_results_mouseout(evt);
});
this.search_results.bind('mousewheel.chosen DOMMouseScroll.chosen', function(evt)
{
_this.search_results_mousewheel(evt);
});
this.search_results.bind('touchstart.chosen', function(evt)
{
_this.search_results_touchstart(evt);
});
this.search_results.bind('touchmove.chosen', function(evt)
{
_this.search_results_touchmove(evt);
});
this.search_results.bind('touchend.chosen', function(evt)
{
_this.search_results_touchend(evt);
});
this.form_field_jq.bind("chosen:updated.chosen", function(evt)
{
_this.results_update_field(evt);
});
this.form_field_jq.bind("chosen:activate.chosen", function(evt)
{
_this.activate_field(evt);
});
this.form_field_jq.bind("chosen:open.chosen", function(evt)
{
_this.container_mousedown(evt);
});
this.form_field_jq.bind("chosen:close.chosen", function(evt)
{
_this.input_blur(evt);
});
this.search_field.bind('blur.chosen', function(evt)
{
_this.input_blur(evt);
});
this.search_field.bind('keyup.chosen', function(evt)
{
_this.keyup_checker(evt);
});
this.search_field.bind('keydown.chosen', function(evt)
{
_this.keydown_checker(evt);
});
this.search_field.bind('focus.chosen', function(evt)
{
_this.input_focus(evt);
});
this.search_field.bind('cut.chosen', function(evt)
{
_this.clipboard_event_checker(evt);
});
this.search_field.bind('paste.chosen', function(evt)
{
_this.clipboard_event_checker(evt);
});
if (this.is_multiple)
{
return this.search_choices.bind('click.chosen', function(evt)
{
_this.choices_click(evt);
});
}
else
{
return this.container.bind('click.chosen', function(evt)
{
evt.preventDefault();
});
}
};
Chosen.prototype.destroy = function()
{
$(this.container[0].ownerDocument).unbind("click.chosen", this.click_test_action);
if (this.search_field[0].tabIndex)
{
this.form_field_jq[0].tabIndex = this.search_field[0].tabIndex;
}
this.container.remove();
this.form_field_jq.removeData('chosen');
return this.form_field_jq.show();
};
Chosen.prototype.search_field_disabled = function()
{
this.is_disabled = this.form_field_jq[0].disabled;
if (this.is_disabled)
{
this.container.addClass('chosen-disabled');
this.search_field[0].disabled = true;
if (!this.is_multiple)
{
this.selected_item.unbind("focus.chosen", this.activate_action);
}
return this.close_field();
}
else
{
this.container.removeClass('chosen-disabled');
this.search_field[0].disabled = false;
if (!this.is_multiple)
{
return this.selected_item.bind("focus.chosen", this.activate_action);
}
}
};
Chosen.prototype.container_mousedown = function(evt)
{
if (!this.is_disabled)
{
if (evt && evt.type === "mousedown" && !this.results_showing)
{
evt.preventDefault();
}
if (!((evt != null) && ($(evt.target)).hasClass("search-choice-close")))
{
if (!this.active_field)
{
if (this.is_multiple)
{
this.search_field.val("");
}
$(this.container[0].ownerDocument).bind('click.chosen', this.click_test_action);
this.results_show();
}
else if (!this.is_multiple && evt && (($(evt.target)[0] === this.selected_item[0]) || $(evt.target).parents("a.chosen-single").length))
{
evt.preventDefault();
this.results_toggle();
}
return this.activate_field();
}
}
};
Chosen.prototype.container_mouseup = function(evt)
{
if (evt.target.nodeName === "ABBR" && !this.is_disabled)
{
return this.results_reset(evt);
}
};
Chosen.prototype.search_results_mousewheel = function(evt)
{
var delta;
if (evt.originalEvent)
{
delta = -evt.originalEvent.wheelDelta || evt.originalEvent.detail;
}
if (delta != null)
{
evt.preventDefault();
if (evt.type === 'DOMMouseScroll')
{
delta = delta * 40;
}
return this.search_results.scrollTop(delta + this.search_results.scrollTop());
}
};
Chosen.prototype.blur_test = function(evt)
{
if (!this.active_field && this.container.hasClass("chosen-container-active"))
{
return this.close_field();
}
};
Chosen.prototype.close_field = function()
{
$(this.container[0].ownerDocument).unbind("click.chosen", this.click_test_action);
this.active_field = false;
this.results_hide();
this.container.removeClass("chosen-container-active");
this.clear_backstroke();
this.show_search_field_default();
return this.search_field_scale();
};
Chosen.prototype.activate_field = function()
{
this.container.addClass("chosen-container-active");
this.active_field = true;
this.search_field.val(this.search_field.val());
return this.search_field.focus();
};
Chosen.prototype.test_active_click = function(evt)
{
var active_container;
active_container = $(evt.target).closest('.chosen-container');
if (active_container.length && this.container[0] === active_container[0])
{
return this.active_field = true;
}
else
{
return this.close_field();
}
};
Chosen.prototype.results_build = function()
{
this.parsing = true;
this.selected_option_count = null;
this.results_data = SelectParser.select_to_array(this.form_field);
if (this.is_multiple)
{
this.search_choices.find("li.search-choice").remove();
}
else if (!this.is_multiple)
{
this.single_set_selected_text();
if (this.disable_search || this.form_field.options.length <= this.disable_search_threshold)
{
this.search_field[0].readOnly = true;
this.container.addClass("chosen-container-single-nosearch");
}
else
{
this.search_field[0].readOnly = false;
this.container.removeClass("chosen-container-single-nosearch");
}
}
this.update_results_content(this.results_option_build(
{
first: true
}));
this.search_field_disabled();
this.show_search_field_default();
this.search_field_scale();
return this.parsing = false;
};
Chosen.prototype.result_do_highlight = function(el)
{
var high_bottom, high_top, maxHeight, visible_bottom, visible_top;
if (el.length)
{
this.result_clear_highlight();
this.result_highlight = el;
this.result_highlight.addClass("highlighted");
maxHeight = parseInt(this.search_results.css("maxHeight"), 10);
visible_top = this.search_results.scrollTop();
visible_bottom = maxHeight + visible_top;
high_top = this.result_highlight.position().top + this.search_results.scrollTop();
high_bottom = high_top + this.result_highlight.outerHeight();
if (high_bottom >= visible_bottom)
{
return this.search_results.scrollTop((high_bottom - maxHeight) > 0 ? high_bottom - maxHeight : 0);
}
else if (high_top < visible_top)
{
return this.search_results.scrollTop(high_top);
}
}
};
Chosen.prototype.result_clear_highlight = function()
{
if (this.result_highlight)
{
this.result_highlight.removeClass("highlighted");
}
return this.result_highlight = null;
};
Chosen.prototype.results_show = function()
{
if (this.is_multiple && this.max_selected_options <= this.choices_count())
{
this.form_field_jq.trigger("chosen:maxselected",
{
chosen: this
});
return false;
}
this.container.addClass("chosen-with-drop");
this.results_showing = true;
this.search_field.focus();
this.search_field.val(this.search_field.val());
this.winnow_results();
return this.form_field_jq.trigger("chosen:showing_dropdown",
{
chosen: this
});
};
Chosen.prototype.update_results_content = function(content)
{
return this.search_results.html(content);
};
Chosen.prototype.results_hide = function()
{
if (this.results_showing)
{
this.result_clear_highlight();
this.container.removeClass("chosen-with-drop");
this.form_field_jq.trigger("chosen:hiding_dropdown",
{
chosen: this
});
}
return this.results_showing = false;
};
Chosen.prototype.set_tab_index = function(el)
{
var ti;
if (this.form_field.tabIndex)
{
ti = this.form_field.tabIndex;
this.form_field.tabIndex = -1;
return this.search_field[0].tabIndex = ti;
}
};
Chosen.prototype.set_label_behavior = function()
{
var _this = this;
this.form_field_label = this.form_field_jq.parents("label");
if (!this.form_field_label.length && this.form_field.id.length)
{
this.form_field_label = $("label[for='" + this.form_field.id + "']");
}
if (this.form_field_label.length > 0)
{
return this.form_field_label.bind('click.chosen', function(evt)
{
if (_this.is_multiple)
{
return _this.container_mousedown(evt);
}
else
{
return _this.activate_field();
}
});
}
};
Chosen.prototype.show_search_field_default = function()
{
if (this.is_multiple && this.choices_count() < 1 && !this.active_field)
{
this.search_field.val(this.default_text);
return this.search_field.addClass("default");
}
else
{
this.search_field.val("");
return this.search_field.removeClass("default");
}
};
Chosen.prototype.search_results_mouseup = function(evt)
{
var target;
target = $(evt.target).hasClass("active-result") ? $(evt.target) : $(evt.target).parents(".active-result").first();
if (target.length)
{
this.result_highlight = target;
this.result_select(evt);
return this.search_field.focus();
}
};
Chosen.prototype.search_results_mouseover = function(evt)
{
var target;
target = $(evt.target).hasClass("active-result") ? $(evt.target) : $(evt.target).parents(".active-result").first();
if (target)
{
return this.result_do_highlight(target);
}
};
Chosen.prototype.search_results_mouseout = function(evt)
{
if ($(evt.target).hasClass("active-result" || $(evt.target).parents('.active-result').first()))
{
return this.result_clear_highlight();
}
};
Chosen.prototype.choice_build = function(item)
{
var choice, close_link,
_this = this;
choice = $('
',
{
"class": "search-choice"
}).html("
" + item.html + " ");
if (item.disabled)
{
choice.addClass('search-choice-disabled');
}
else
{
close_link = $('
',
{
"class": 'search-choice-close',
'data-option-array-index': item.array_index
});
close_link.bind('click.chosen', function(evt)
{
return _this.choice_destroy_link_click(evt);
});
choice.append(close_link);
}
return this.search_container.before(choice);
};
Chosen.prototype.choice_destroy_link_click = function(evt)
{
evt.preventDefault();
evt.stopPropagation();
if (!this.is_disabled)
{
return this.choice_destroy($(evt.target));
}
};
Chosen.prototype.choice_destroy = function(link)
{
if (this.result_deselect(link[0].getAttribute("data-option-array-index")))
{
this.show_search_field_default();
if (this.is_multiple && this.choices_count() > 0 && this.search_field.val().length < 1)
{
this.results_hide();
}
link.parents('li').first().remove();
return this.search_field_scale();
}
};
Chosen.prototype.results_reset = function()
{
this.reset_single_select_options();
this.form_field.options[0].selected = true;
this.single_set_selected_text();
this.show_search_field_default();
this.results_reset_cleanup();
this.form_field_jq.trigger("change");
if (this.active_field)
{
return this.results_hide();
}
};
Chosen.prototype.results_reset_cleanup = function()
{
this.current_selectedIndex = this.form_field.selectedIndex;
return this.selected_item.find("abbr").remove();
};
Chosen.prototype.result_select = function(evt)
{
var high, item;
if (this.result_highlight)
{
high = this.result_highlight;
this.result_clear_highlight();
if (this.is_multiple && this.max_selected_options <= this.choices_count())
{
this.form_field_jq.trigger("chosen:maxselected",
{
chosen: this
});
return false;
}
if (this.is_multiple)
{
high.removeClass("active-result");
}
else
{
this.reset_single_select_options();
}
item = this.results_data[high[0].getAttribute("data-option-array-index")];
item.selected = true;
this.form_field.options[item.options_index].selected = true;
this.selected_option_count = null;
if (this.is_multiple)
{
this.choice_build(item);
}
else
{
this.single_set_selected_text(item.text);
}
if (!((evt.metaKey || evt.ctrlKey) && this.is_multiple))
{
this.results_hide();
}
this.search_field.val("");
if (this.is_multiple || this.form_field.selectedIndex !== this.current_selectedIndex)
{
this.form_field_jq.trigger("change",
{
'selected': this.form_field.options[item.options_index].value
});
}
this.current_selectedIndex = this.form_field.selectedIndex;
return this.search_field_scale();
}
};
Chosen.prototype.single_set_selected_text = function(text)
{
if (text == null)
{
text = this.default_text;
}
if (text === this.default_text)
{
this.selected_item.addClass("chosen-default");
}
else
{
this.single_deselect_control_build();
this.selected_item.removeClass("chosen-default");
}
return this.selected_item.find("span").text(text);
};
Chosen.prototype.result_deselect = function(pos)
{
var result_data;
result_data = this.results_data[pos];
if (!this.form_field.options[result_data.options_index].disabled)
{
result_data.selected = false;
this.form_field.options[result_data.options_index].selected = false;
this.selected_option_count = null;
this.result_clear_highlight();
if (this.results_showing)
{
this.winnow_results();
}
this.form_field_jq.trigger("change",
{
deselected: this.form_field.options[result_data.options_index].value
});
this.search_field_scale();
return true;
}
else
{
return false;
}
};
Chosen.prototype.single_deselect_control_build = function()
{
if (!this.allow_single_deselect)
{
return;
}
if (!this.selected_item.find("abbr").length)
{
this.selected_item.find("span").first().after("
");
}
return this.selected_item.addClass("chosen-single-with-deselect");
};
Chosen.prototype.get_search_text = function()
{
if (this.search_field.val() === this.default_text)
{
return "";
}
else
{
return $('
').text($.trim(this.search_field.val())).html();
}
};
Chosen.prototype.winnow_results_set_highlight = function()
{
var do_high, selected_results;
selected_results = !this.is_multiple ? this.search_results.find(".result-selected.active-result") : [];
do_high = selected_results.length ? selected_results.first() : this.search_results.find(".active-result").first();
if (do_high != null)
{
return this.result_do_highlight(do_high);
}
};
Chosen.prototype.no_results = function(terms)
{
var no_results_html;
no_results_html = $('
' + this.results_none_found + ' " " ');
no_results_html.find("span").first().html(terms);
this.search_results.append(no_results_html);
return this.form_field_jq.trigger("chosen:no_results",
{
chosen: this
});
};
Chosen.prototype.no_results_clear = function()
{
return this.search_results.find(".no-results").remove();
};
Chosen.prototype.keydown_arrow = function()
{
var next_sib;
if (this.results_showing && this.result_highlight)
{
next_sib = this.result_highlight.nextAll("li.active-result").first();
if (next_sib)
{
return this.result_do_highlight(next_sib);
}
}
else
{
return this.results_show();
}
};
Chosen.prototype.keyup_arrow = function()
{
var prev_sibs;
if (!this.results_showing && !this.is_multiple)
{
return this.results_show();
}
else if (this.result_highlight)
{
prev_sibs = this.result_highlight.prevAll("li.active-result");
if (prev_sibs.length)
{
return this.result_do_highlight(prev_sibs.first());
}
else
{
if (this.choices_count() > 0)
{
this.results_hide();
}
return this.result_clear_highlight();
}
}
};
Chosen.prototype.keydown_backstroke = function()
{
var next_available_destroy;
if (this.pending_backstroke)
{
this.choice_destroy(this.pending_backstroke.find("a").first());
return this.clear_backstroke();
}
else
{
next_available_destroy = this.search_container.siblings("li.search-choice").last();
if (next_available_destroy.length && !next_available_destroy.hasClass("search-choice-disabled"))
{
this.pending_backstroke = next_available_destroy;
if (this.single_backstroke_delete)
{
return this.keydown_backstroke();
}
else
{
return this.pending_backstroke.addClass("search-choice-focus");
}
}
}
};
Chosen.prototype.clear_backstroke = function()
{
if (this.pending_backstroke)
{
this.pending_backstroke.removeClass("search-choice-focus");
}
return this.pending_backstroke = null;
};
Chosen.prototype.keydown_checker = function(evt)
{
var stroke, _ref1;
stroke = (_ref1 = evt.which) != null ? _ref1 : evt.keyCode;
this.search_field_scale();
if (stroke !== 8 && this.pending_backstroke)
{
this.clear_backstroke();
}
switch (stroke)
{
case 8:
this.backstroke_length = this.search_field.val().length;
break;
case 9:
if (this.results_showing && !this.is_multiple)
{
this.result_select(evt);
}
this.mouse_on_container = false;
break;
case 13:
evt.preventDefault();
break;
case 38:
evt.preventDefault();
this.keyup_arrow();
break;
case 40:
evt.preventDefault();
this.keydown_arrow();
break;
}
};
Chosen.prototype.search_field_scale = function()
{
var div, f_width, h, style, style_block, styles, w, _i, _len;
if (this.is_multiple)
{
h = 0;
w = 0;
style_block = "position:absolute; left: -1000px; top: -1000px; display:none;";
styles = ['font-size', 'font-style', 'font-weight', 'font-family', 'line-height', 'text-transform', 'letter-spacing'];
for (_i = 0, _len = styles.length; _i < _len; _i++)
{
style = styles[_i];
style_block += style + ":" + this.search_field.css(style) + ";";
}
div = $('
',
{
'style': style_block
});
div.text(this.search_field.val());
$('body').append(div);
w = div.width() + 25;
div.remove();
f_width = this.container.outerWidth();
if (w > f_width - 10)
{
w = f_width - 10;
}
return this.search_field.css(
{
'width': w + 'px'
});
}
};
return Chosen;
})(AbstractChosen);
}).call(this);
/* ========================================================================
* ZUI: chosen.icons.js
* http://zui.sexy
* ========================================================================
* Copyright (c) 2014 cnezsoft.com; Licensed MIT
* ======================================================================== */
+ function($)
{
'use strict';
var ChosenIcons = function(element, options)
{
this.$ = $(element);
this.options = this.getOptions(options);
this.lang = ChosenIcons.LANGS[this.options.lang];
this.id = 'chosen-icons-' + parseInt(Math.random() * 10000000000 + 1);
this.init();
};
ChosenIcons.DEFAULTS = {
canEmpty: true,
lang: 'zh-cn',
commonIcons: ['heart', 'user', 'group', 'list-ul', 'th', 'th-large', 'star', 'star-empty', 'search', 'envelope', 'dashboard', 'sitemap', 'umbrella', 'lightbulb', 'envelope-alt', 'cog', 'ok', 'remove', 'home', 'time', 'flag', 'flag-alt', 'flag-checkered', 'qrcode', 'tag', 'tags', 'book', 'bookmark', 'bookmark-empty', 'print', 'camera', 'picture', 'globe', 'map-marker', 'edit', 'edit-sign', 'play', 'stop', 'plus-sign', 'minus-sign', 'remove-sign', 'ok-sign', 'check-sign', 'question-sign', 'info-sign', 'exclamation-sign', 'plus', 'plus-sign', 'minus', 'minus-sign', 'asterisk', 'calendar', 'calendar-empty', 'comment', 'comment-alt', 'comments', 'comments-alt', 'folder-close', 'folder-open', 'folder-close-alt', 'folder-open-alt', 'thumbs-up', 'thumbs-down', 'pushpin', 'building', 'phone', 'rss', 'rss-sign', 'bullhorn', 'bell', 'bell-alt', 'certificate', 'wrench', 'tasks', 'cloud', 'beaker', 'magic', 'smile', 'frown', 'meh', 'code', 'location-arrow'],
webIcons: ['share', 'pencil', 'trash', 'file-alt', 'file', 'file-text', 'download-alt', 'upload-alt', 'inbox', 'repeat', 'refresh', 'lock', 'check', 'check-empty', 'eye-open', 'eye-close', 'key', 'signin', 'signout', 'external-link', 'external-link-sign', 'link', 'reorder', 'quote-left', 'quote-right', 'spinner', 'reply', 'question', 'info', 'archive', 'collapse', 'collapse-top'],
editorIcons: ['table', 'copy', 'save', 'list-ol', 'paste', 'keyboard', 'paper-clip', 'crop', 'unlink', 'sort-by-alphabet', 'sort-by-alphabet-alt', 'sort-by-attributes', 'sort-by-attributes-alt', 'sort-by-order', 'sort-by-order-alt'],
directionalIcons: ['chevron-left', 'chevron-right', 'chevron-down', 'chevron-up', 'arrow-left', 'arrow-right', 'arrow-down', 'arrow-up', 'hand-right', 'hand-left', 'hand-up', 'hand-down', 'circle-arrow-left', 'circle-arrow-right', 'circle-arrow-up', 'circle-arrow-down', 'double-angle-left', 'double-angle-right', 'double-angle-down', 'double-angle-up', 'angle-left', 'angle-right', 'angle-down', 'angle-up', 'long-arrow-left', 'long-arrow-right', 'long-arrow-down', 'long-arrow-up', 'caret-left', 'caret-right', 'caret-down', 'caret-up'],
otherIcons: ['desktop', 'laptop', 'tablet', 'mobile', 'building', 'firefox', 'ie', 'opera', 'qq', 'lemon', 'sign-blank', 'circle', 'circle-blank', 'terminal', 'html5', 'android', 'apple', 'windows', 'weibo', 'renren', 'bug', 'moon', 'sun']
};
ChosenIcons.LANGS = {};
ChosenIcons.LANGS['zh-cn'] = {
emptyIcon: '[没有图标]',
commonIcons: '常用图标',
webIcons: 'Web 图标',
editorIcons: '编辑器图标',
directionalIcons: '箭头总汇',
otherIcons: '其他图标',
};
ChosenIcons.LANGS['en'] = {
emptyIcon: '[No Icon]',
commonIcons: 'Common Icons',
webIcons: 'Web Icons',
editorIcons: 'Editor Icons',
directionalIcons: 'Directional Icons',
otherIcons: 'Other Icons'
};
ChosenIcons.LANGS['zh-tw'] = {
emptyIcon: '[沒有圖標]',
commonIcons: '常用圖標',
webIcons: 'Web 圖標',
editorIcons: '編輯器圖標',
directionalIcons: '箭頭總匯',
otherIcons: '其他圖標'
};
ChosenIcons.prototype.getOptions = function(options)
{
options = $.extend(
{}, ChosenIcons.DEFAULTS, this.$.data(), options);
return options;
};
ChosenIcons.prototype.init = function()
{
var $this = this.$.addClass('chosen-icons').addClass(this.id);
$this.empty();
if (this.options.canEmpty)
{
$this.append(this.getOptionHtml());
}
var lang = this.lang;
$this.append(this.getgroupHtml('commonIcons'));
$this.append(this.getgroupHtml('webIcons'));
$this.append(this.getgroupHtml('editorIcons'));
$this.append(this.getgroupHtml('directionalIcons'));
$this.append(this.getgroupHtml('otherIcons'));
$this.chosen(
{
placeholder_text: ' ',
disable_search: true,
width: '100%',
inherit_select_classes: true
});
var chosenSelector = '.chosen-container.' + this.id;
$this.on('chosen:showing_dropdown', function()
{
$(chosenSelector + ' .chosen-results .group-option').each(function()
{
var $this = $(this).addClass('icon');
var text = $(this).text();
$this.html('
');
});
}).change(function()
{
var span = $(chosenSelector + ' .chosen-single > span');
var text = $(this).val();
if (text && text.length > 0)
span.html('
' + text.substr(5).replace(/-/g, ' ') + ' ');
else span.html('
' + lang.emptyIcon + ' ')
});
var val = $this.data('value');
if (val)
{
$this.val(val).change();
}
}
ChosenIcons.prototype.getgroupHtml = function(name)
{
var icons = this.options[name];
var html = '
';
for (var i in icons)
{
html += this.getOptionHtml(icons[i]);
}
return html + ' ';
}
ChosenIcons.prototype.getOptionHtml = function(value)
{
name = value;
if (value && value.length > 0)
{
value = 'icon-' + value;
}
else
{
value = '';
name = this.lang.emptyIcon;
}
return '
' + name + ' ';
}
$.fn.chosenIcons = function(option)
{
return this.each(function()
{
var $this = $(this);
var data = $this.data('zui.chosenIcons');
var options = typeof option == 'object' && option;
if (!data) $this.data('zui.chosenIcons', (data = new ChosenIcons(this, options)));
if (typeof option == 'string') data[option]();
})
};
$.fn.chosenIcons.Constructor = ChosenIcons;
}(jQuery);
/*!
* ZUI - v1.3.0 - 2015-05-06
* http://zui.sexy
* GitHub: https://github.com/easysoft/zui.git
* Copyright (c) 2015 cnezsoft.com; Licensed MIT
*/
/* This file generated by grunt automatically. Do not edit it. */
(function($)
{
'use strict';
var nextColorIndex = 0;
var presetColors = ['primary', 'red', 'yellow', 'green', 'blue', 'purple', 'brown', 'dark'];
var colorset =
{
theme: 'light',
primary: '#3280fc',
secondary: '#145ccd',
pale: '#ebf2f9',
fore: '#353535',
back: '#ffffff',
grayDarker: '#222222',
grayDark: '#333333',
gray: '#808080',
grayLight: '#dddddd',
grayLighter: '#e5e5e5',
grayPale: '#f1f1f1',
white: '#ffffff',
black: '#000000',
red: '#ea644a',
yellow: '#f1a325',
green: '#38b03f',
blue: '#03b8cf',
purple: '#8666b8',
brown: '#bd7b46',
greenPale: '#ddf4df',
yellowPale: '#fff0d5',
redPale: '#ffe5e0',
bluePale: '#ddf3f5',
brownPale: '#f7ebe1',
purplePale: '#f5eeff',
light: '#ffffff',
dark: '#353535',
success: '#38b03f',
warning: '#f1a325',
danger: '#ea644a',
info: '#03b8cf',
important: '#bd7b46',
special: '#8666b8',
successPale: '#ddf4df',
warningPale: '#fff0d5',
dangerPale: '#ffe5e0',
infoPale: '#ddf3f5',
importantPale: '#f7ebe1',
specialPale: '#f5eeff'
};
colorset.get = function(colorName)
{
if(typeof colorName === 'undefined' || colorName === 'random') {
colorName = presetColors[(nextColorIndex++) % presetColors.length];
}
return new $.zui.Color(colorset[colorName] ? colorset[colorName] : colorName);
}
$.zui({colorset: colorset});
if($.zui.Color) $.extend($.zui.Color, colorset);
}(jQuery));
/*!
* Chart.js
* http://chartjs.org/
* Version: 1.0.2
*
* Copyright 2015 Nick Downie
* Released under the MIT license
* https://github.com/nnnick/Chart.js/blob/master/LICENSE.md
*/
/// ----- ZUI change begin -----
/// Add jquery object to namespace
/// (function(){ // Old code
(function($){
/// ----- ZUI change end -----
"use strict";
//Declare root variable - window in the browser, global on the server
/// ----- ZUI change begin -----
/// Change root to zui shared object
///
/// var root = this, // old code
var root = $ && $.zui ? $.zui : this,
/// ----- ZUI change end -----
previous = root.Chart;
//Occupy the global variable of Chart, and create a simple base class
var Chart = function(context)
{
var chart = this;
this.canvas = context.canvas;
this.ctx = context;
//Variables global to the chart
var computeDimension = function(element, dimension)
{
if (element['offset' + dimension])
{
return element['offset' + dimension];
}
else
{
return document.defaultView.getComputedStyle(element).getPropertyValue(dimension);
}
}
var width = this.width = computeDimension(context.canvas, 'Width');
var height = this.height = computeDimension(context.canvas, 'Height');
// Firefox requires this to work correctly
context.canvas.width = width;
context.canvas.height = height;
var width = this.width = context.canvas.width;
var height = this.height = context.canvas.height;
this.aspectRatio = this.width / this.height;
//High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale.
helpers.retinaScale(this);
return this;
};
//Globally expose the defaults to allow for user updating/changing
Chart.defaults = {
global:
{
// Boolean - Whether to animate the chart
animation: true,
// Number - Number of animation steps
animationSteps: 60,
// String - Animation easing effect
animationEasing: "easeOutQuart",
// Boolean - If we should show the scale at all
showScale: true,
// Boolean - If we want to override with a hard coded scale
scaleOverride: false,
// ** Required if scaleOverride is true **
// Number - The number of steps in a hard coded scale
scaleSteps: null,
// Number - The value jump in the hard coded scale
scaleStepWidth: null,
// Number - The scale starting value
scaleStartValue: null,
// String - Colour of the scale line
scaleLineColor: "rgba(0,0,0,.1)",
// Number - Pixel width of the scale line
scaleLineWidth: 1,
// Boolean - Whether to show labels on the scale
scaleShowLabels: true,
// Interpolated JS string - can access value
scaleLabel: "<%=value%>",
// Boolean - Whether the scale should stick to integers, and not show any floats even if drawing space is there
scaleIntegersOnly: true,
// Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value
scaleBeginAtZero: false,
// String - Scale label font declaration for the scale label
scaleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
// Number - Scale label font size in pixels
scaleFontSize: 12,
// String - Scale label font weight style
scaleFontStyle: "normal",
// String - Scale label font colour
scaleFontColor: "#666",
// Boolean - whether or not the chart should be responsive and resize when the browser does.
responsive: false,
// Boolean - whether to maintain the starting aspect ratio or not when responsive, if set to false, will take up entire container
maintainAspectRatio: true,
// Boolean - Determines whether to draw tooltips on the canvas or not - attaches events to touchmove & mousemove
showTooltips: true,
// Boolean - Determines whether to draw built-in tooltip or call custom tooltip function
customTooltips: false,
// Array - Array of string names to attach tooltip events
tooltipEvents: ["mousemove", "touchstart", "touchmove", "mouseout"],
// String - Tooltip background colour
tooltipFillColor: "rgba(0,0,0,0.8)",
// String - Tooltip label font declaration for the scale label
tooltipFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
// Number - Tooltip label font size in pixels
tooltipFontSize: 14,
// String - Tooltip font weight style
tooltipFontStyle: "normal",
// String - Tooltip label font colour
tooltipFontColor: "#fff",
// String - Tooltip title font declaration for the scale label
tooltipTitleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
// Number - Tooltip title font size in pixels
tooltipTitleFontSize: 14,
// String - Tooltip title font weight style
tooltipTitleFontStyle: "bold",
// String - Tooltip title font colour
tooltipTitleFontColor: "#fff",
// Number - pixel width of padding around tooltip text
tooltipYPadding: 6,
// Number - pixel width of padding around tooltip text
tooltipXPadding: 6,
// Number - Size of the caret on the tooltip
tooltipCaretSize: 8,
// Number - Pixel radius of the tooltip border
tooltipCornerRadius: 6,
// Number - Pixel offset from point x to tooltip edge
tooltipXOffset: 10,
// String - Template string for single tooltips
tooltipTemplate: "<%if (label){%><%=label%>: <%}%><%= value %>",
// String - Template string for single tooltips
multiTooltipTemplate: "<%= value %>",
// String - Colour behind the legend colour block
multiTooltipKeyBackground: '#fff',
// Function - Will fire on animation progression.
onAnimationProgress: function() {},
// Function - Will fire on animation completion.
onAnimationComplete: function() {}
}
};
//Create a dictionary of chart types, to allow for extension of existing types
Chart.types = {};
//Global Chart helpers object for utility methods and classes
var helpers = Chart.helpers = {};
//-- Basic js utility methods
var each = helpers.each = function(loopable, callback, self)
{
var additionalArgs = Array.prototype.slice.call(arguments, 3);
// Check to see if null or undefined firstly.
if (loopable)
{
if (loopable.length === +loopable.length)
{
var i;
for (i = 0; i < loopable.length; i++)
{
callback.apply(self, [loopable[i], i].concat(additionalArgs));
}
}
else
{
for (var item in loopable)
{
callback.apply(self, [loopable[item], item].concat(additionalArgs));
}
}
}
},
clone = helpers.clone = function(obj)
{
var objClone = {};
each(obj, function(value, key)
{
if (obj.hasOwnProperty(key)) objClone[key] = value;
});
return objClone;
},
extend = helpers.extend = function(base)
{
each(Array.prototype.slice.call(arguments, 1), function(extensionObject)
{
each(extensionObject, function(value, key)
{
if (extensionObject.hasOwnProperty(key)) base[key] = value;
});
});
return base;
},
merge = helpers.merge = function(base, master)
{
//Merge properties in left object over to a shallow clone of object right.
var args = Array.prototype.slice.call(arguments, 0);
args.unshift(
{});
return extend.apply(null, args);
},
indexOf = helpers.indexOf = function(arrayToSearch, item)
{
if (Array.prototype.indexOf)
{
return arrayToSearch.indexOf(item);
}
else
{
for (var i = 0; i < arrayToSearch.length; i++)
{
if (arrayToSearch[i] === item) return i;
}
return -1;
}
},
where = helpers.where = function(collection, filterCallback)
{
var filtered = [];
helpers.each(collection, function(item)
{
if (filterCallback(item))
{
filtered.push(item);
}
});
return filtered;
},
findNextWhere = helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex)
{
// Default to start of the array
if (!startIndex)
{
startIndex = -1;
}
for (var i = startIndex + 1; i < arrayToSearch.length; i++)
{
var currentItem = arrayToSearch[i];
if (filterCallback(currentItem))
{
return currentItem;
}
}
},
findPreviousWhere = helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex)
{
// Default to end of the array
if (!startIndex)
{
startIndex = arrayToSearch.length;
}
for (var i = startIndex - 1; i >= 0; i--)
{
var currentItem = arrayToSearch[i];
if (filterCallback(currentItem))
{
return currentItem;
}
}
},
inherits = helpers.inherits = function(extensions)
{
//Basic javascript inheritance based on the model created in Backbone.js
var parent = this;
var ChartElement = (extensions && extensions.hasOwnProperty("constructor")) ? extensions.constructor : function()
{
return parent.apply(this, arguments);
};
var Surrogate = function()
{
this.constructor = ChartElement;
};
Surrogate.prototype = parent.prototype;
ChartElement.prototype = new Surrogate();
ChartElement.extend = inherits;
if (extensions) extend(ChartElement.prototype, extensions);
ChartElement.__super__ = parent.prototype;
return ChartElement;
},
noop = helpers.noop = function() {},
uid = helpers.uid = (function()
{
var id = 0;
return function()
{
return "chart-" + id++;
};
})(),
warn = helpers.warn = function(str)
{
//Method for warning of errors
if (window.console && typeof window.console.warn == "function") console.warn(str);
},
amd = helpers.amd = (typeof define == 'function' && define.amd),
//-- Math methods
isNumber = helpers.isNumber = function(n)
{
return !isNaN(parseFloat(n)) && isFinite(n);
},
max = helpers.max = function(array)
{
return Math.max.apply(Math, array);
},
min = helpers.min = function(array)
{
return Math.min.apply(Math, array);
},
cap = helpers.cap = function(valueToCap, maxValue, minValue)
{
if (isNumber(maxValue))
{
if (valueToCap > maxValue)
{
return maxValue;
}
}
else if (isNumber(minValue))
{
if (valueToCap < minValue)
{
return minValue;
}
}
return valueToCap;
},
getDecimalPlaces = helpers.getDecimalPlaces = function(num)
{
if (num % 1 !== 0 && isNumber(num))
{
return num.toString().split(".")[1].length;
}
else
{
return 0;
}
},
toRadians = helpers.radians = function(degrees)
{
return degrees * (Math.PI / 180);
},
// Gets the angle from vertical upright to the point about a centre.
getAngleFromPoint = helpers.getAngleFromPoint = function(centrePoint, anglePoint)
{
var distanceFromXCenter = anglePoint.x - centrePoint.x,
distanceFromYCenter = anglePoint.y - centrePoint.y,
radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);
var angle = Math.PI * 2 + Math.atan2(distanceFromYCenter, distanceFromXCenter);
//If the segment is in the top left quadrant, we need to add another rotation to the angle
if (distanceFromXCenter < 0 && distanceFromYCenter < 0)
{
angle += Math.PI * 2;
}
return {
angle: angle,
distance: radialDistanceFromCenter
};
},
aliasPixel = helpers.aliasPixel = function(pixelWidth)
{
return (pixelWidth % 2 === 0) ? 0 : 0.5;
},
splineCurve = helpers.splineCurve = function(FirstPoint, MiddlePoint, AfterPoint, t)
{
//Props to Rob Spencer at scaled innovation for his post on splining between points
//http://scaledinnovation.com/analytics/splines/aboutSplines.html
var d01 = Math.sqrt(Math.pow(MiddlePoint.x - FirstPoint.x, 2) + Math.pow(MiddlePoint.y - FirstPoint.y, 2)),
d12 = Math.sqrt(Math.pow(AfterPoint.x - MiddlePoint.x, 2) + Math.pow(AfterPoint.y - MiddlePoint.y, 2)),
fa = t * d01 / (d01 + d12), // scaling factor for triangle Ta
fb = t * d12 / (d01 + d12);
return {
inner:
{
x: MiddlePoint.x - fa * (AfterPoint.x - FirstPoint.x),
y: MiddlePoint.y - fa * (AfterPoint.y - FirstPoint.y)
},
outer:
{
x: MiddlePoint.x + fb * (AfterPoint.x - FirstPoint.x),
y: MiddlePoint.y + fb * (AfterPoint.y - FirstPoint.y)
}
};
},
calculateOrderOfMagnitude = helpers.calculateOrderOfMagnitude = function(val)
{
return Math.floor(Math.log(val) / Math.LN10);
},
calculateScaleRange = helpers.calculateScaleRange = function(valuesArray, drawingSize, textSize, startFromZero, integersOnly)
{
//Set a minimum step of two - a point at the top of the graph, and a point at the base
var minSteps = 2,
maxSteps = Math.floor(drawingSize / (textSize * 1.5)),
skipFitting = (minSteps >= maxSteps);
var maxValue = max(valuesArray),
minValue = min(valuesArray);
// We need some degree of seperation here to calculate the scales if all the values are the same
// Adding/minusing 0.5 will give us a range of 1.
if (maxValue === minValue)
{
maxValue += 0.5;
// So we don't end up with a graph with a negative start value if we've said always start from zero
if (minValue >= 0.5 && !startFromZero)
{
minValue -= 0.5;
}
else
{
// Make up a whole number above the values
maxValue += 0.5;
}
}
var valueRange = Math.abs(maxValue - minValue),
rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange),
graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude),
graphMin = (startFromZero) ? 0 : Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude),
graphRange = graphMax - graphMin,
stepValue = Math.pow(10, rangeOrderOfMagnitude),
numberOfSteps = Math.round(graphRange / stepValue);
//If we have more space on the graph we'll use it to give more definition to the data
while ((numberOfSteps > maxSteps || (numberOfSteps * 2) < maxSteps) && !skipFitting)
{
if (numberOfSteps > maxSteps)
{
stepValue *= 2;
numberOfSteps = Math.round(graphRange / stepValue);
// Don't ever deal with a decimal number of steps - cancel fitting and just use the minimum number of steps.
if (numberOfSteps % 1 !== 0)
{
skipFitting = true;
}
}
//We can fit in double the amount of scale points on the scale
else
{
//If user has declared ints only, and the step value isn't a decimal
if (integersOnly && rangeOrderOfMagnitude >= 0)
{
//If the user has said integers only, we need to check that making the scale more granular wouldn't make it a float
if (stepValue / 2 % 1 === 0)
{
stepValue /= 2;
numberOfSteps = Math.round(graphRange / stepValue);
}
//If it would make it a float break out of the loop
else
{
break;
}
}
//If the scale doesn't have to be an int, make the scale more granular anyway.
else
{
stepValue /= 2;
numberOfSteps = Math.round(graphRange / stepValue);
}
}
}
if (skipFitting)
{
numberOfSteps = minSteps;
stepValue = graphRange / numberOfSteps;
}
return {
steps: numberOfSteps,
stepValue: stepValue,
min: graphMin,
max: graphMin + (numberOfSteps * stepValue)
};
},
/* jshint ignore:start */
// Blows up jshint errors based on the new Function constructor
//Templating methods
//Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/
template = helpers.template = function(templateString, valuesObject)
{
// If templateString is function rather than string-template - call the function for valuesObject
if (templateString instanceof Function)
{
return templateString(valuesObject);
}
var cache = {};
function tmpl(str, data)
{
// Figure out if we're getting a template, or if we need to
// load the template - and be sure to cache the result.
var fn = !/\W/.test(str) ?
cache[str] = cache[str] :
// Generate a reusable function that will serve as a template
// generator (and which will be cached).
new Function("obj",
"var p=[],print=function(){p.push.apply(p,arguments);};" +
// Introduce the data as local variables using with(){}
"with(obj){p.push('" +
// Convert the template into pure JavaScript
str
.replace(/[\r\t\n]/g, " ")
.split("<%").join("\t")
.replace(/((^|%>)[^\t]*)'/g, "$1\r")
.replace(/\t=(.*?)%>/g, "',$1,'")
.split("\t").join("');")
.split("%>").join("p.push('")
.split("\r").join("\\'") +
"');}return p.join('');"
);
// Provide some basic currying to the user
return data ? fn(data) : fn;
}
return tmpl(templateString, valuesObject);
},
/* jshint ignore:end */
generateLabels = helpers.generateLabels = function(templateString, numberOfSteps, graphMin, stepValue)
{
var labelsArray = new Array(numberOfSteps);
if (labelTemplateString)
{
each(labelsArray, function(val, index)
{
labelsArray[index] = template(templateString,
{
value: (graphMin + (stepValue * (index + 1)))
});
});
}
return labelsArray;
},
//--Animation methods
//Easing functions adapted from Robert Penner's easing equations
//http://www.robertpenner.com/easing/
easingEffects = helpers.easingEffects = {
linear: function(t)
{
return t;
},
easeInQuad: function(t)
{
return t * t;
},
easeOutQuad: function(t)
{
return -1 * t * (t - 2);
},
easeInOutQuad: function(t)
{
if ((t /= 1 / 2) < 1) return 1 / 2 * t * t;
return -1 / 2 * ((--t) * (t - 2) - 1);
},
easeInCubic: function(t)
{
return t * t * t;
},
easeOutCubic: function(t)
{
return 1 * ((t = t / 1 - 1) * t * t + 1);
},
easeInOutCubic: function(t)
{
if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t;
return 1 / 2 * ((t -= 2) * t * t + 2);
},
easeInQuart: function(t)
{
return t * t * t * t;
},
easeOutQuart: function(t)
{
return -1 * ((t = t / 1 - 1) * t * t * t - 1);
},
easeInOutQuart: function(t)
{
if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t;
return -1 / 2 * ((t -= 2) * t * t * t - 2);
},
easeInQuint: function(t)
{
return 1 * (t /= 1) * t * t * t * t;
},
easeOutQuint: function(t)
{
return 1 * ((t = t / 1 - 1) * t * t * t * t + 1);
},
easeInOutQuint: function(t)
{
if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t * t;
return 1 / 2 * ((t -= 2) * t * t * t * t + 2);
},
easeInSine: function(t)
{
return -1 * Math.cos(t / 1 * (Math.PI / 2)) + 1;
},
easeOutSine: function(t)
{
return 1 * Math.sin(t / 1 * (Math.PI / 2));
},
easeInOutSine: function(t)
{
return -1 / 2 * (Math.cos(Math.PI * t / 1) - 1);
},
easeInExpo: function(t)
{
return (t === 0) ? 1 : 1 * Math.pow(2, 10 * (t / 1 - 1));
},
easeOutExpo: function(t)
{
return (t === 1) ? 1 : 1 * (-Math.pow(2, -10 * t / 1) + 1);
},
easeInOutExpo: function(t)
{
if (t === 0) return 0;
if (t === 1) return 1;
if ((t /= 1 / 2) < 1) return 1 / 2 * Math.pow(2, 10 * (t - 1));
return 1 / 2 * (-Math.pow(2, -10 * --t) + 2);
},
easeInCirc: function(t)
{
if (t >= 1) return t;
return -1 * (Math.sqrt(1 - (t /= 1) * t) - 1);
},
easeOutCirc: function(t)
{
return 1 * Math.sqrt(1 - (t = t / 1 - 1) * t);
},
easeInOutCirc: function(t)
{
if ((t /= 1 / 2) < 1) return -1 / 2 * (Math.sqrt(1 - t * t) - 1);
return 1 / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1);
},
easeInElastic: function(t)
{
var s = 1.70158;
var p = 0;
var a = 1;
if (t === 0) return 0;
if ((t /= 1) == 1) return 1;
if (!p) p = 1 * 0.3;
if (a < Math.abs(1))
{
a = 1;
s = p / 4;
}
else s = p / (2 * Math.PI) * Math.asin(1 / a);
return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
},
easeOutElastic: function(t)
{
var s = 1.70158;
var p = 0;
var a = 1;
if (t === 0) return 0;
if ((t /= 1) == 1) return 1;
if (!p) p = 1 * 0.3;
if (a < Math.abs(1))
{
a = 1;
s = p / 4;
}
else s = p / (2 * Math.PI) * Math.asin(1 / a);
return a * Math.pow(2, -10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) + 1;
},
easeInOutElastic: function(t)
{
var s = 1.70158;
var p = 0;
var a = 1;
if (t === 0) return 0;
if ((t /= 1 / 2) == 2) return 1;
if (!p) p = 1 * (0.3 * 1.5);
if (a < Math.abs(1))
{
a = 1;
s = p / 4;
}
else s = p / (2 * Math.PI) * Math.asin(1 / a);
if (t < 1) return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) * 0.5 + 1;
},
easeInBack: function(t)
{
var s = 1.70158;
return 1 * (t /= 1) * t * ((s + 1) * t - s);
},
easeOutBack: function(t)
{
var s = 1.70158;
return 1 * ((t = t / 1 - 1) * t * ((s + 1) * t + s) + 1);
},
easeInOutBack: function(t)
{
var s = 1.70158;
if ((t /= 1 / 2) < 1) return 1 / 2 * (t * t * (((s *= (1.525)) + 1) * t - s));
return 1 / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);
},
easeInBounce: function(t)
{
return 1 - easingEffects.easeOutBounce(1 - t);
},
easeOutBounce: function(t)
{
if ((t /= 1) < (1 / 2.75))
{
return 1 * (7.5625 * t * t);
}
else if (t < (2 / 2.75))
{
return 1 * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75);
}
else if (t < (2.5 / 2.75))
{
return 1 * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375);
}
else
{
return 1 * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375);
}
},
easeInOutBounce: function(t)
{
if (t < 1 / 2) return easingEffects.easeInBounce(t * 2) * 0.5;
return easingEffects.easeOutBounce(t * 2 - 1) * 0.5 + 1 * 0.5;
}
},
//Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
requestAnimFrame = helpers.requestAnimFrame = (function()
{
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback)
{
return window.setTimeout(callback, 1000 / 60);
};
})(),
cancelAnimFrame = helpers.cancelAnimFrame = (function()
{
return window.cancelAnimationFrame ||
window.webkitCancelAnimationFrame ||
window.mozCancelAnimationFrame ||
window.oCancelAnimationFrame ||
window.msCancelAnimationFrame ||
function(callback)
{
return window.clearTimeout(callback, 1000 / 60);
};
})(),
animationLoop = helpers.animationLoop = function(callback, totalSteps, easingString, onProgress, onComplete, chartInstance)
{
var currentStep = 0,
easingFunction = easingEffects[easingString] || easingEffects.linear;
var animationFrame = function()
{
currentStep++;
var stepDecimal = currentStep / totalSteps;
var easeDecimal = easingFunction(stepDecimal);
callback.call(chartInstance, easeDecimal, stepDecimal, currentStep);
onProgress.call(chartInstance, easeDecimal, stepDecimal);
if (currentStep < totalSteps)
{
chartInstance.animationFrame = requestAnimFrame(animationFrame);
}
else
{
onComplete.apply(chartInstance);
}
};
requestAnimFrame(animationFrame);
},
//-- DOM methods
getRelativePosition = helpers.getRelativePosition = function(evt)
{
var mouseX, mouseY;
var e = evt.originalEvent || evt,
canvas = evt.currentTarget || evt.srcElement,
boundingRect = canvas.getBoundingClientRect();
if (e.touches)
{
mouseX = e.touches[0].clientX - boundingRect.left;
mouseY = e.touches[0].clientY - boundingRect.top;
}
else
{
mouseX = e.clientX - boundingRect.left;
mouseY = e.clientY - boundingRect.top;
}
return {
x: mouseX,
y: mouseY
};
},
addEvent = helpers.addEvent = function(node, eventType, method)
{
if (node.addEventListener)
{
node.addEventListener(eventType, method);
}
else if (node.attachEvent)
{
node.attachEvent("on" + eventType, method);
}
else
{
node["on" + eventType] = method;
}
},
removeEvent = helpers.removeEvent = function(node, eventType, handler)
{
if (node.removeEventListener)
{
node.removeEventListener(eventType, handler, false);
}
else if (node.detachEvent)
{
node.detachEvent("on" + eventType, handler);
}
else
{
node["on" + eventType] = noop;
}
},
bindEvents = helpers.bindEvents = function(chartInstance, arrayOfEvents, handler)
{
// Create the events object if it's not already present
if (!chartInstance.events) chartInstance.events = {};
each(arrayOfEvents, function(eventName)
{
chartInstance.events[eventName] = function()
{
handler.apply(chartInstance, arguments);
};
addEvent(chartInstance.chart.canvas, eventName, chartInstance.events[eventName]);
});
},
unbindEvents = helpers.unbindEvents = function(chartInstance, arrayOfEvents)
{
each(arrayOfEvents, function(handler, eventName)
{
removeEvent(chartInstance.chart.canvas, eventName, handler);
});
},
getMaximumWidth = helpers.getMaximumWidth = function(domNode)
{
var container = domNode.parentNode;
// TODO = check cross browser stuff with this.
return container.clientWidth;
},
getMaximumHeight = helpers.getMaximumHeight = function(domNode)
{
var container = domNode.parentNode;
// TODO = check cross browser stuff with this.
return container.clientHeight;
},
getMaximumSize = helpers.getMaximumSize = helpers.getMaximumWidth, // legacy support
retinaScale = helpers.retinaScale = function(chart)
{
var ctx = chart.ctx,
width = chart.canvas.width,
height = chart.canvas.height;
if (window.devicePixelRatio)
{
ctx.canvas.style.width = width + "px";
ctx.canvas.style.height = height + "px";
ctx.canvas.height = height * window.devicePixelRatio;
ctx.canvas.width = width * window.devicePixelRatio;
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
}
},
//-- Canvas methods
clear = helpers.clear = function(chart)
{
chart.ctx.clearRect(0, 0, chart.width, chart.height);
},
fontString = helpers.fontString = function(pixelSize, fontStyle, fontFamily)
{
return fontStyle + " " + pixelSize + "px " + fontFamily;
},
longestText = helpers.longestText = function(ctx, font, arrayOfStrings)
{
ctx.font = font;
var longest = 0;
each(arrayOfStrings, function(string)
{
var textWidth = ctx.measureText(string).width;
longest = (textWidth > longest) ? textWidth : longest;
});
return longest;
},
drawRoundedRectangle = helpers.drawRoundedRectangle = function(ctx, x, y, width, height, radius)
{
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.lineTo(x + width - radius, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
ctx.lineTo(x + width, y + height - radius);
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
ctx.lineTo(x + radius, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
ctx.lineTo(x, y + radius);
ctx.quadraticCurveTo(x, y, x + radius, y);
ctx.closePath();
};
//Store a reference to each instance - allowing us to globally resize chart instances on window resize.
//Destroy method on the chart will remove the instance of the chart from this reference.
Chart.instances = {};
Chart.Type = function(data, options, chart)
{
this.options = options;
this.chart = chart;
this.id = uid();
//Add the chart instance to the global namespace
Chart.instances[this.id] = this;
// Initialize is always called when a chart type is created
// By default it is a no op, but it should be extended
if (options.responsive)
{
this.resize();
}
this.initialize.call(this, data);
};
//Core methods that'll be a part of every chart type
extend(Chart.Type.prototype,
{
initialize: function()
{
return this;
},
clear: function()
{
clear(this.chart);
return this;
},
stop: function()
{
// Stops any current animation loop occuring
cancelAnimFrame(this.animationFrame);
return this;
},
resize: function(callback)
{
this.stop();
var canvas = this.chart.canvas,
newWidth = getMaximumWidth(this.chart.canvas),
newHeight = this.options.maintainAspectRatio ? newWidth / this.chart.aspectRatio : getMaximumHeight(this.chart.canvas);
canvas.width = this.chart.width = newWidth;
canvas.height = this.chart.height = newHeight;
retinaScale(this.chart);
if (typeof callback === "function")
{
callback.apply(this, Array.prototype.slice.call(arguments, 1));
}
return this;
},
reflow: noop,
render: function(reflow)
{
if (reflow)
{
this.reflow();
}
if (this.options.animation && !reflow)
{
helpers.animationLoop(
this.draw,
this.options.animationSteps,
this.options.animationEasing,
this.options.onAnimationProgress,
this.options.onAnimationComplete,
this
);
}
else
{
this.draw();
this.options.onAnimationComplete.call(this);
}
return this;
},
generateLegend: function()
{
return template(this.options.legendTemplate, this);
},
destroy: function()
{
this.clear();
unbindEvents(this, this.events);
var canvas = this.chart.canvas;
// Reset canvas height/width attributes starts a fresh with the canvas context
canvas.width = this.chart.width;
canvas.height = this.chart.height;
// < IE9 doesn't support removeProperty
if (canvas.style.removeProperty)
{
canvas.style.removeProperty('width');
canvas.style.removeProperty('height');
}
else
{
canvas.style.removeAttribute('width');
canvas.style.removeAttribute('height');
}
delete Chart.instances[this.id];
},
showTooltip: function(ChartElements, forceRedraw)
{
// Only redraw the chart if we've actually changed what we're hovering on.
if (typeof this.activeElements === 'undefined') this.activeElements = [];
var isChanged = (function(Elements)
{
var changed = false;
if (Elements.length !== this.activeElements.length)
{
changed = true;
return changed;
}
each(Elements, function(element, index)
{
if (element !== this.activeElements[index])
{
changed = true;
}
}, this);
return changed;
}).call(this, ChartElements);
if (!isChanged && !forceRedraw)
{
return;
}
else
{
this.activeElements = ChartElements;
}
this.draw();
if (this.options.customTooltips)
{
this.options.customTooltips(false);
}
if (ChartElements.length > 0)
{
// If we have multiple datasets, show a MultiTooltip for all of the data points at that index
if (this.datasets && this.datasets.length > 1)
{
var dataArray,
dataIndex;
for (var i = this.datasets.length - 1; i >= 0; i--)
{
dataArray = this.datasets[i].points || this.datasets[i].bars || this.datasets[i].segments;
dataIndex = indexOf(dataArray, ChartElements[0]);
if (dataIndex !== -1)
{
break;
}
}
var tooltipLabels = [],
tooltipColors = [],
medianPosition = (function(index)
{
// Get all the points at that particular index
var Elements = [],
dataCollection,
xPositions = [],
yPositions = [],
xMax,
yMax,
xMin,
yMin;
helpers.each(this.datasets, function(dataset)
{
dataCollection = dataset.points || dataset.bars || dataset.segments;
if (dataCollection[dataIndex] && dataCollection[dataIndex].hasValue())
{
Elements.push(dataCollection[dataIndex]);
}
});
helpers.each(Elements, function(element)
{
xPositions.push(element.x);
yPositions.push(element.y);
//Include any colour information about the element
tooltipLabels.push(helpers.template(this.options.multiTooltipTemplate, element));
tooltipColors.push(
{
fill: element._saved.fillColor || element.fillColor,
stroke: element._saved.strokeColor || element.strokeColor
});
}, this);
yMin = min(yPositions);
yMax = max(yPositions);
xMin = min(xPositions);
xMax = max(xPositions);
return {
x: (xMin > this.chart.width / 2) ? xMin : xMax,
y: (yMin + yMax) / 2
};
}).call(this, dataIndex);
new Chart.MultiTooltip(
{
x: medianPosition.x,
y: medianPosition.y,
xPadding: this.options.tooltipXPadding,
yPadding: this.options.tooltipYPadding,
xOffset: this.options.tooltipXOffset,
fillColor: this.options.tooltipFillColor,
textColor: this.options.tooltipFontColor,
fontFamily: this.options.tooltipFontFamily,
fontStyle: this.options.tooltipFontStyle,
fontSize: this.options.tooltipFontSize,
titleTextColor: this.options.tooltipTitleFontColor,
titleFontFamily: this.options.tooltipTitleFontFamily,
titleFontStyle: this.options.tooltipTitleFontStyle,
titleFontSize: this.options.tooltipTitleFontSize,
cornerRadius: this.options.tooltipCornerRadius,
labels: tooltipLabels,
legendColors: tooltipColors,
legendColorBackground: this.options.multiTooltipKeyBackground,
title: ChartElements[0].label,
chart: this.chart,
ctx: this.chart.ctx,
custom: this.options.customTooltips
}).draw();
}
else
{
each(ChartElements, function(Element)
{
var tooltipPosition = Element.tooltipPosition();
new Chart.Tooltip(
{
x: Math.round(tooltipPosition.x),
y: Math.round(tooltipPosition.y),
xPadding: this.options.tooltipXPadding,
yPadding: this.options.tooltipYPadding,
fillColor: this.options.tooltipFillColor,
textColor: this.options.tooltipFontColor,
fontFamily: this.options.tooltipFontFamily,
fontStyle: this.options.tooltipFontStyle,
fontSize: this.options.tooltipFontSize,
caretHeight: this.options.tooltipCaretSize,
cornerRadius: this.options.tooltipCornerRadius,
text: template(this.options.tooltipTemplate, Element),
chart: this.chart,
custom: this.options.customTooltips
}).draw();
}, this);
}
}
return this;
},
toBase64Image: function()
{
return this.chart.canvas.toDataURL.apply(this.chart.canvas, arguments);
}
});
Chart.Type.extend = function(extensions)
{
var parent = this;
var ChartType = function()
{
return parent.apply(this, arguments);
};
//Copy the prototype object of the this class
ChartType.prototype = clone(parent.prototype);
//Now overwrite some of the properties in the base class with the new extensions
extend(ChartType.prototype, extensions);
ChartType.extend = Chart.Type.extend;
if (extensions.name || parent.prototype.name)
{
var chartName = extensions.name || parent.prototype.name;
//Assign any potential default values of the new chart type
//If none are defined, we'll use a clone of the chart type this is being extended from.
//I.e. if we extend a line chart, we'll use the defaults from the line chart if our new chart
//doesn't define some defaults of their own.
var baseDefaults = (Chart.defaults[parent.prototype.name]) ? clone(Chart.defaults[parent.prototype.name]) :
{};
Chart.defaults[chartName] = extend(baseDefaults, extensions.defaults);
Chart.types[chartName] = ChartType;
//Register this new chart type in the Chart prototype
Chart.prototype[chartName] = function(data, options)
{
var config = merge(Chart.defaults.global, Chart.defaults[chartName], options ||
{});
return new ChartType(data, config, this);
};
}
else
{
warn("Name not provided for this chart, so it hasn't been registered");
}
return parent;
};
Chart.Element = function(configuration)
{
extend(this, configuration);
this.initialize.apply(this, arguments);
this.save();
};
extend(Chart.Element.prototype,
{
initialize: function() {},
restore: function(props)
{
if (!props)
{
extend(this, this._saved);
}
else
{
each(props, function(key)
{
this[key] = this._saved[key];
}, this);
}
return this;
},
save: function()
{
this._saved = clone(this);
delete this._saved._saved;
return this;
},
update: function(newProps)
{
each(newProps, function(value, key)
{
this._saved[key] = this[key];
this[key] = value;
}, this);
return this;
},
transition: function(props, ease)
{
each(props, function(value, key)
{
this[key] = ((value - this._saved[key]) * ease) + this._saved[key];
}, this);
return this;
},
tooltipPosition: function()
{
return {
x: this.x,
y: this.y
};
},
hasValue: function()
{
return isNumber(this.value);
}
});
Chart.Element.extend = inherits;
Chart.Point = Chart.Element.extend(
{
display: true,
inRange: function(chartX, chartY)
{
var hitDetectionRange = this.hitDetectionRadius + this.radius;
return ((Math.pow(chartX - this.x, 2) + Math.pow(chartY - this.y, 2)) < Math.pow(hitDetectionRange, 2));
},
draw: function()
{
if (this.display)
{
var ctx = this.ctx;
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.closePath();
ctx.strokeStyle = this.strokeColor;
ctx.lineWidth = this.strokeWidth;
ctx.fillStyle = this.fillColor;
ctx.fill();
ctx.stroke();
}
//Quick debug for bezier curve splining
//Highlights control points and the line between them.
//Handy for dev - stripped in the min version.
// ctx.save();
// ctx.fillStyle = "black";
// ctx.strokeStyle = "black"
// ctx.beginPath();
// ctx.arc(this.controlPoints.inner.x,this.controlPoints.inner.y, 2, 0, Math.PI*2);
// ctx.fill();
// ctx.beginPath();
// ctx.arc(this.controlPoints.outer.x,this.controlPoints.outer.y, 2, 0, Math.PI*2);
// ctx.fill();
// ctx.moveTo(this.controlPoints.inner.x,this.controlPoints.inner.y);
// ctx.lineTo(this.x, this.y);
// ctx.lineTo(this.controlPoints.outer.x,this.controlPoints.outer.y);
// ctx.stroke();
// ctx.restore();
}
});
Chart.Arc = Chart.Element.extend(
{
inRange: function(chartX, chartY)
{
var pointRelativePosition = helpers.getAngleFromPoint(this,
{
x: chartX,
y: chartY
});
//Check if within the range of the open/close angle
var betweenAngles = (pointRelativePosition.angle >= this.startAngle && pointRelativePosition.angle <= this.endAngle),
withinRadius = (pointRelativePosition.distance >= this.innerRadius && pointRelativePosition.distance <= this.outerRadius);
return (betweenAngles && withinRadius);
//Ensure within the outside of the arc centre, but inside arc outer
},
tooltipPosition: function()
{
var centreAngle = this.startAngle + ((this.endAngle - this.startAngle) / 2),
rangeFromCentre = (this.outerRadius - this.innerRadius) / 2 + this.innerRadius;
return {
x: this.x + (Math.cos(centreAngle) * rangeFromCentre),
y: this.y + (Math.sin(centreAngle) * rangeFromCentre)
};
},
draw: function(animationPercent)
{
var easingDecimal = animationPercent || 1;
var ctx = this.ctx;
ctx.beginPath();
ctx.arc(this.x, this.y, this.outerRadius, this.startAngle, this.endAngle);
ctx.arc(this.x, this.y, this.innerRadius, this.endAngle, this.startAngle, true);
ctx.closePath();
ctx.strokeStyle = this.strokeColor;
ctx.lineWidth = this.strokeWidth;
ctx.fillStyle = this.fillColor;
ctx.fill();
ctx.lineJoin = 'bevel';
if (this.showStroke)
{
ctx.stroke();
}
}
});
Chart.Rectangle = Chart.Element.extend(
{
draw: function()
{
var ctx = this.ctx,
halfWidth = this.width / 2,
leftX = this.x - halfWidth,
rightX = this.x + halfWidth,
top = this.base - (this.base - this.y),
halfStroke = this.strokeWidth / 2;
// Canvas doesn't allow us to stroke inside the width so we can
// adjust the sizes to fit if we're setting a stroke on the line
if (this.showStroke)
{
leftX += halfStroke;
rightX -= halfStroke;
top += halfStroke;
}
ctx.beginPath();
ctx.fillStyle = this.fillColor;
ctx.strokeStyle = this.strokeColor;
ctx.lineWidth = this.strokeWidth;
// It'd be nice to keep this class totally generic to any rectangle
// and simply specify which border to miss out.
ctx.moveTo(leftX, this.base);
ctx.lineTo(leftX, top);
ctx.lineTo(rightX, top);
ctx.lineTo(rightX, this.base);
ctx.fill();
if (this.showStroke)
{
ctx.stroke();
}
},
height: function()
{
return this.base - this.y;
},
inRange: function(chartX, chartY)
{
return (chartX >= this.x - this.width / 2 && chartX <= this.x + this.width / 2) && (chartY >= this.y && chartY <= this.base);
}
});
Chart.Tooltip = Chart.Element.extend(
{
draw: function()
{
var ctx = this.chart.ctx;
ctx.font = fontString(this.fontSize, this.fontStyle, this.fontFamily);
this.xAlign = "center";
this.yAlign = "above";
//Distance between the actual element.y position and the start of the tooltip caret
var caretPadding = this.caretPadding = 2;
var tooltipWidth = ctx.measureText(this.text).width + 2 * this.xPadding,
tooltipRectHeight = this.fontSize + 2 * this.yPadding,
tooltipHeight = tooltipRectHeight + this.caretHeight + caretPadding;
if (this.x + tooltipWidth / 2 > this.chart.width)
{
this.xAlign = "left";
}
else if (this.x - tooltipWidth / 2 < 0)
{
this.xAlign = "right";
}
if (this.y - tooltipHeight < 0)
{
this.yAlign = "below";
}
var tooltipX = this.x - tooltipWidth / 2,
tooltipY = this.y - tooltipHeight;
ctx.fillStyle = this.fillColor;
// Custom Tooltips
if (this.custom)
{
this.custom(this);
}
else
{
switch (this.yAlign)
{
case "above":
//Draw a caret above the x/y
ctx.beginPath();
ctx.moveTo(this.x, this.y - caretPadding);
ctx.lineTo(this.x + this.caretHeight, this.y - (caretPadding + this.caretHeight));
ctx.lineTo(this.x - this.caretHeight, this.y - (caretPadding + this.caretHeight));
ctx.closePath();
ctx.fill();
break;
case "below":
tooltipY = this.y + caretPadding + this.caretHeight;
//Draw a caret below the x/y
ctx.beginPath();
ctx.moveTo(this.x, this.y + caretPadding);
ctx.lineTo(this.x + this.caretHeight, this.y + caretPadding + this.caretHeight);
ctx.lineTo(this.x - this.caretHeight, this.y + caretPadding + this.caretHeight);
ctx.closePath();
ctx.fill();
break;
}
switch (this.xAlign)
{
case "left":
tooltipX = this.x - tooltipWidth + (this.cornerRadius + this.caretHeight);
break;
case "right":
tooltipX = this.x - (this.cornerRadius + this.caretHeight);
break;
}
drawRoundedRectangle(ctx, tooltipX, tooltipY, tooltipWidth, tooltipRectHeight, this.cornerRadius);
ctx.fill();
ctx.fillStyle = this.textColor;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(this.text, tooltipX + tooltipWidth / 2, tooltipY + tooltipRectHeight / 2);
}
}
});
Chart.MultiTooltip = Chart.Element.extend(
{
initialize: function()
{
this.font = fontString(this.fontSize, this.fontStyle, this.fontFamily);
this.titleFont = fontString(this.titleFontSize, this.titleFontStyle, this.titleFontFamily);
this.height = (this.labels.length * this.fontSize) + ((this.labels.length - 1) * (this.fontSize / 2)) + (this.yPadding * 2) + this.titleFontSize * 1.5;
this.ctx.font = this.titleFont;
var titleWidth = this.ctx.measureText(this.title).width,
//Label has a legend square as well so account for this.
labelWidth = longestText(this.ctx, this.font, this.labels) + this.fontSize + 3,
longestTextWidth = max([labelWidth, titleWidth]);
this.width = longestTextWidth + (this.xPadding * 2);
var halfHeight = this.height / 2;
//Check to ensure the height will fit on the canvas
if (this.y - halfHeight < 0)
{
this.y = halfHeight;
}
else if (this.y + halfHeight > this.chart.height)
{
this.y = this.chart.height - halfHeight;
}
//Decide whether to align left or right based on position on canvas
if (this.x > this.chart.width / 2)
{
this.x -= this.xOffset + this.width;
}
else
{
this.x += this.xOffset;
}
},
getLineHeight: function(index)
{
var baseLineHeight = this.y - (this.height / 2) + this.yPadding,
afterTitleIndex = index - 1;
//If the index is zero, we're getting the title
if (index === 0)
{
return baseLineHeight + this.titleFontSize / 2;
}
else
{
return baseLineHeight + ((this.fontSize * 1.5 * afterTitleIndex) + this.fontSize / 2) + this.titleFontSize * 1.5;
}
},
draw: function()
{
// Custom Tooltips
if (this.custom)
{
this.custom(this);
}
else
{
drawRoundedRectangle(this.ctx, this.x, this.y - this.height / 2, this.width, this.height, this.cornerRadius);
var ctx = this.ctx;
ctx.fillStyle = this.fillColor;
ctx.fill();
ctx.closePath();
ctx.textAlign = "left";
ctx.textBaseline = "middle";
ctx.fillStyle = this.titleTextColor;
ctx.font = this.titleFont;
ctx.fillText(this.title, this.x + this.xPadding, this.getLineHeight(0));
ctx.font = this.font;
helpers.each(this.labels, function(label, index)
{
ctx.fillStyle = this.textColor;
ctx.fillText(label, this.x + this.xPadding + this.fontSize + 3, this.getLineHeight(index + 1));
//A bit gnarly, but clearing this rectangle breaks when using explorercanvas (clears whole canvas)
//ctx.clearRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
//Instead we'll make a white filled block to put the legendColour palette over.
ctx.fillStyle = this.legendColorBackground;
ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize / 2, this.fontSize, this.fontSize);
ctx.fillStyle = this.legendColors[index].fill;
ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize / 2, this.fontSize, this.fontSize);
}, this);
}
}
});
Chart.Scale = Chart.Element.extend(
{
initialize: function()
{
this.fit();
},
buildYLabels: function()
{
this.yLabels = [];
var stepDecimalPlaces = getDecimalPlaces(this.stepValue);
for (var i = 0; i <= this.steps; i++)
{
this.yLabels.push(template(this.templateString,
{
value: (this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)
}));
}
this.yLabelWidth = (this.display && this.showLabels) ? longestText(this.ctx, this.font, this.yLabels) : 0;
},
addXLabel: function(label)
{
this.xLabels.push(label);
this.valuesCount++;
this.fit();
},
removeXLabel: function()
{
this.xLabels.shift();
this.valuesCount--;
this.fit();
},
// Fitting loop to rotate x Labels and figure out what fits there, and also calculate how many Y steps to use
fit: function()
{
// First we need the width of the yLabels, assuming the xLabels aren't rotated
// To do that we need the base line at the top and base of the chart, assuming there is no x label rotation
this.startPoint = (this.display) ? this.fontSize : 0;
this.endPoint = (this.display) ? this.height - (this.fontSize * 1.5) - 5 : this.height; // -5 to pad labels
// Apply padding settings to the start and end point.
this.startPoint += this.padding;
this.endPoint -= this.padding;
// Cache the starting height, so can determine if we need to recalculate the scale yAxis
var cachedHeight = this.endPoint - this.startPoint,
cachedYLabelWidth;
// Build the current yLabels so we have an idea of what size they'll be to start
/*
* This sets what is returned from calculateScaleRange as static properties of this class:
*
this.steps;
this.stepValue;
this.min;
this.max;
*
*/
this.calculateYRange(cachedHeight);
// With these properties set we can now build the array of yLabels
// and also the width of the largest yLabel
this.buildYLabels();
this.calculateXLabelRotation();
while ((cachedHeight > this.endPoint - this.startPoint))
{
cachedHeight = this.endPoint - this.startPoint;
cachedYLabelWidth = this.yLabelWidth;
this.calculateYRange(cachedHeight);
this.buildYLabels();
// Only go through the xLabel loop again if the yLabel width has changed
if (cachedYLabelWidth < this.yLabelWidth)
{
this.calculateXLabelRotation();
}
}
},
calculateXLabelRotation: function()
{
//Get the width of each grid by calculating the difference
//between x offsets between 0 and 1.
this.ctx.font = this.font;
var firstWidth = this.ctx.measureText(this.xLabels[0]).width,
lastWidth = this.ctx.measureText(this.xLabels[this.xLabels.length - 1]).width,
firstRotated,
lastRotated;
this.xScalePaddingRight = lastWidth / 2 + 3;
this.xScalePaddingLeft = (firstWidth / 2 > this.yLabelWidth + 10) ? firstWidth / 2 : this.yLabelWidth + 10;
this.xLabelRotation = 0;
if (this.display)
{
var originalLabelWidth = longestText(this.ctx, this.font, this.xLabels),
cosRotation,
firstRotatedWidth;
this.xLabelWidth = originalLabelWidth;
//Allow 3 pixels x2 padding either side for label readability
var xGridWidth = Math.floor(this.calculateX(1) - this.calculateX(0)) - 6;
//Max label rotate should be 90 - also act as a loop counter
while ((this.xLabelWidth > xGridWidth && this.xLabelRotation === 0) || (this.xLabelWidth > xGridWidth && this.xLabelRotation <= 90 && this.xLabelRotation > 0))
{
cosRotation = Math.cos(toRadians(this.xLabelRotation));
firstRotated = cosRotation * firstWidth;
lastRotated = cosRotation * lastWidth;
// We're right aligning the text now.
if (firstRotated + this.fontSize / 2 > this.yLabelWidth + 8)
{
this.xScalePaddingLeft = firstRotated + this.fontSize / 2;
}
this.xScalePaddingRight = this.fontSize / 2;
this.xLabelRotation++;
this.xLabelWidth = cosRotation * originalLabelWidth;
}
if (this.xLabelRotation > 0)
{
this.endPoint -= Math.sin(toRadians(this.xLabelRotation)) * originalLabelWidth + 3;
}
}
else
{
this.xLabelWidth = 0;
this.xScalePaddingRight = this.padding;
this.xScalePaddingLeft = this.padding;
}
},
// Needs to be overidden in each Chart type
// Otherwise we need to pass all the data into the scale class
calculateYRange: noop,
drawingArea: function()
{
return this.startPoint - this.endPoint;
},
calculateY: function(value)
{
var scalingFactor = this.drawingArea() / (this.min - this.max);
return this.endPoint - (scalingFactor * (value - this.min));
},
calculateX: function(index)
{
var isRotated = (this.xLabelRotation > 0),
// innerWidth = (this.offsetGridLines) ? this.width - offsetLeft - this.padding : this.width - (offsetLeft + halfLabelWidth * 2) - this.padding,
innerWidth = this.width - (this.xScalePaddingLeft + this.xScalePaddingRight),
valueWidth = innerWidth / Math.max((this.valuesCount - ((this.offsetGridLines) ? 0 : 1)), 1),
valueOffset = (valueWidth * index) + this.xScalePaddingLeft;
if (this.offsetGridLines)
{
valueOffset += (valueWidth / 2);
}
return Math.round(valueOffset);
},
update: function(newProps)
{
helpers.extend(this, newProps);
this.fit();
},
draw: function()
{
var ctx = this.ctx,
yLabelGap = (this.endPoint - this.startPoint) / this.steps,
xStart = Math.round(this.xScalePaddingLeft);
if (this.display)
{
ctx.fillStyle = this.textColor;
ctx.font = this.font;
each(this.yLabels, function(labelString, index)
{
var yLabelCenter = this.endPoint - (yLabelGap * index),
linePositionY = Math.round(yLabelCenter),
drawHorizontalLine = this.showHorizontalLines;
ctx.textAlign = "right";
ctx.textBaseline = "middle";
if (this.showLabels)
{
ctx.fillText(labelString, xStart - 10, yLabelCenter);
}
// This is X axis, so draw it
if (index === 0 && !drawHorizontalLine)
{
drawHorizontalLine = true;
}
if (drawHorizontalLine)
{
ctx.beginPath();
}
if (index > 0)
{
// This is a grid line in the centre, so drop that
ctx.lineWidth = this.gridLineWidth;
ctx.strokeStyle = this.gridLineColor;
}
else
{
// This is the first line on the scale
ctx.lineWidth = this.lineWidth;
ctx.strokeStyle = this.lineColor;
}
linePositionY += helpers.aliasPixel(ctx.lineWidth);
if (drawHorizontalLine)
{
ctx.moveTo(xStart, linePositionY);
ctx.lineTo(this.width, linePositionY);
ctx.stroke();
ctx.closePath();
}
ctx.lineWidth = this.lineWidth;
ctx.strokeStyle = this.lineColor;
ctx.beginPath();
ctx.moveTo(xStart - 5, linePositionY);
ctx.lineTo(xStart, linePositionY);
ctx.stroke();
ctx.closePath();
}, this);
each(this.xLabels, function(label, index)
{
var xPos = this.calculateX(index) + aliasPixel(this.lineWidth),
// Check to see if line/bar here and decide where to place the line
linePos = this.calculateX(index - (this.offsetGridLines ? 0.5 : 0)) + aliasPixel(this.lineWidth),
isRotated = (this.xLabelRotation > 0),
drawVerticalLine = this.showVerticalLines;
// This is Y axis, so draw it
if (index === 0 && !drawVerticalLine)
{
drawVerticalLine = true;
}
if (drawVerticalLine)
{
ctx.beginPath();
}
if (index > 0)
{
// This is a grid line in the centre, so drop that
ctx.lineWidth = this.gridLineWidth;
ctx.strokeStyle = this.gridLineColor;
}
else
{
// This is the first line on the scale
ctx.lineWidth = this.lineWidth;
ctx.strokeStyle = this.lineColor;
}
if (drawVerticalLine)
{
ctx.moveTo(linePos, this.endPoint);
ctx.lineTo(linePos, this.startPoint - 3);
ctx.stroke();
ctx.closePath();
}
ctx.lineWidth = this.lineWidth;
ctx.strokeStyle = this.lineColor;
// Small lines at the bottom of the base grid line
ctx.beginPath();
ctx.moveTo(linePos, this.endPoint);
ctx.lineTo(linePos, this.endPoint + 5);
ctx.stroke();
ctx.closePath();
ctx.save();
ctx.translate(xPos, (isRotated) ? this.endPoint + 12 : this.endPoint + 8);
ctx.rotate(toRadians(this.xLabelRotation) * -1);
ctx.font = this.font;
ctx.textAlign = (isRotated) ? "right" : "center";
ctx.textBaseline = (isRotated) ? "middle" : "top";
ctx.fillText(label, 0, 0);
ctx.restore();
}, this);
}
}
});
Chart.RadialScale = Chart.Element.extend(
{
initialize: function()
{
this.size = min([this.height, this.width]);
this.drawingArea = (this.display) ? (this.size / 2) - (this.fontSize / 2 + this.backdropPaddingY) : (this.size / 2);
},
calculateCenterOffset: function(value)
{
// Take into account half font size + the yPadding of the top value
var scalingFactor = this.drawingArea / (this.max - this.min);
return (value - this.min) * scalingFactor;
},
update: function()
{
if (!this.lineArc)
{
this.setScaleSize();
}
else
{
this.drawingArea = (this.display) ? (this.size / 2) - (this.fontSize / 2 + this.backdropPaddingY) : (this.size / 2);
}
this.buildYLabels();
},
buildYLabels: function()
{
this.yLabels = [];
var stepDecimalPlaces = getDecimalPlaces(this.stepValue);
for (var i = 0; i <= this.steps; i++)
{
this.yLabels.push(template(this.templateString,
{
value: (this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)
}));
}
},
getCircumference: function()
{
return ((Math.PI * 2) / this.valuesCount);
},
setScaleSize: function()
{
/*
* Right, this is really confusing and there is a lot of maths going on here
* The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9
*
* Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif
*
* Solution:
*
* We assume the radius of the polygon is half the size of the canvas at first
* at each index we check if the text overlaps.
*
* Where it does, we store that angle and that index.
*
* After finding the largest index and angle we calculate how much we need to remove
* from the shape radius to move the point inwards by that x.
*
* We average the left and right distances to get the maximum shape radius that can fit in the box
* along with labels.
*
* Once we have that, we can find the centre point for the chart, by taking the x text protrusion
* on each side, removing that from the size, halving it and adding the left x protrusion width.
*
* This will mean we have a shape fitted to the canvas, as large as it can be with the labels
* and position it in the most space efficient manner
*
* https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif
*/
// Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.
// Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points
var largestPossibleRadius = min([(this.height / 2 - this.pointLabelFontSize - 5), this.width / 2]),
pointPosition,
i,
textWidth,
halfTextWidth,
furthestRight = this.width,
furthestRightIndex,
furthestRightAngle,
furthestLeft = 0,
furthestLeftIndex,
furthestLeftAngle,
xProtrusionLeft,
xProtrusionRight,
radiusReductionRight,
radiusReductionLeft,
maxWidthRadius;
this.ctx.font = fontString(this.pointLabelFontSize, this.pointLabelFontStyle, this.pointLabelFontFamily);
for (i = 0; i < this.valuesCount; i++)
{
// 5px to space the text slightly out - similar to what we do in the draw function.
pointPosition = this.getPointPosition(i, largestPossibleRadius);
textWidth = this.ctx.measureText(template(this.templateString,
{
value: this.labels[i]
})).width + 5;
if (i === 0 || i === this.valuesCount / 2)
{
// If we're at index zero, or exactly the middle, we're at exactly the top/bottom
// of the radar chart, so text will be aligned centrally, so we'll half it and compare
// w/left and right text sizes
halfTextWidth = textWidth / 2;
if (pointPosition.x + halfTextWidth > furthestRight)
{
furthestRight = pointPosition.x + halfTextWidth;
furthestRightIndex = i;
}
if (pointPosition.x - halfTextWidth < furthestLeft)
{
furthestLeft = pointPosition.x - halfTextWidth;
furthestLeftIndex = i;
}
}
else if (i < this.valuesCount / 2)
{
// Less than half the values means we'll left align the text
if (pointPosition.x + textWidth > furthestRight)
{
furthestRight = pointPosition.x + textWidth;
furthestRightIndex = i;
}
}
else if (i > this.valuesCount / 2)
{
// More than half the values means we'll right align the text
if (pointPosition.x - textWidth < furthestLeft)
{
furthestLeft = pointPosition.x - textWidth;
furthestLeftIndex = i;
}
}
}
xProtrusionLeft = furthestLeft;
xProtrusionRight = Math.ceil(furthestRight - this.width);
furthestRightAngle = this.getIndexAngle(furthestRightIndex);
furthestLeftAngle = this.getIndexAngle(furthestLeftIndex);
radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI / 2);
radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI / 2);
// Ensure we actually need to reduce the size of the chart
radiusReductionRight = (isNumber(radiusReductionRight)) ? radiusReductionRight : 0;
radiusReductionLeft = (isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0;
this.drawingArea = largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2;
//this.drawingArea = min([maxWidthRadius, (this.height - (2 * (this.pointLabelFontSize + 5)))/2])
this.setCenterPoint(radiusReductionLeft, radiusReductionRight);
},
setCenterPoint: function(leftMovement, rightMovement)
{
var maxRight = this.width - rightMovement - this.drawingArea,
maxLeft = leftMovement + this.drawingArea;
this.xCenter = (maxLeft + maxRight) / 2;
// Always vertically in the centre as the text height doesn't change
this.yCenter = (this.height / 2);
},
getIndexAngle: function(index)
{
var angleMultiplier = (Math.PI * 2) / this.valuesCount;
// Start from the top instead of right, so remove a quarter of the circle
return index * angleMultiplier - (Math.PI / 2);
},
getPointPosition: function(index, distanceFromCenter)
{
var thisAngle = this.getIndexAngle(index);
return {
x: (Math.cos(thisAngle) * distanceFromCenter) + this.xCenter,
y: (Math.sin(thisAngle) * distanceFromCenter) + this.yCenter
};
},
draw: function()
{
if (this.display)
{
var ctx = this.ctx;
each(this.yLabels, function(label, index)
{
// Don't draw a centre value
if (index > 0)
{
var yCenterOffset = index * (this.drawingArea / this.steps),
yHeight = this.yCenter - yCenterOffset,
pointPosition;
// Draw circular lines around the scale
if (this.lineWidth > 0)
{
ctx.strokeStyle = this.lineColor;
ctx.lineWidth = this.lineWidth;
if (this.lineArc)
{
ctx.beginPath();
ctx.arc(this.xCenter, this.yCenter, yCenterOffset, 0, Math.PI * 2);
ctx.closePath();
ctx.stroke();
}
else
{
ctx.beginPath();
for (var i = 0; i < this.valuesCount; i++)
{
pointPosition = this.getPointPosition(i, this.calculateCenterOffset(this.min + (index * this.stepValue)));
if (i === 0)
{
ctx.moveTo(pointPosition.x, pointPosition.y);
}
else
{
ctx.lineTo(pointPosition.x, pointPosition.y);
}
}
ctx.closePath();
ctx.stroke();
}
}
if (this.showLabels)
{
ctx.font = fontString(this.fontSize, this.fontStyle, this.fontFamily);
if (this.showLabelBackdrop)
{
var labelWidth = ctx.measureText(label).width;
ctx.fillStyle = this.backdropColor;
ctx.fillRect(
this.xCenter - labelWidth / 2 - this.backdropPaddingX,
yHeight - this.fontSize / 2 - this.backdropPaddingY,
labelWidth + this.backdropPaddingX * 2,
this.fontSize + this.backdropPaddingY * 2
);
}
ctx.textAlign = 'center';
ctx.textBaseline = "middle";
ctx.fillStyle = this.fontColor;
ctx.fillText(label, this.xCenter, yHeight);
}
}
}, this);
if (!this.lineArc)
{
ctx.lineWidth = this.angleLineWidth;
ctx.strokeStyle = this.angleLineColor;
for (var i = this.valuesCount - 1; i >= 0; i--)
{
if (this.angleLineWidth > 0)
{
var outerPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max));
ctx.beginPath();
ctx.moveTo(this.xCenter, this.yCenter);
ctx.lineTo(outerPosition.x, outerPosition.y);
ctx.stroke();
ctx.closePath();
}
// Extra 3px out for some label spacing
var pointLabelPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max) + 5);
ctx.font = fontString(this.pointLabelFontSize, this.pointLabelFontStyle, this.pointLabelFontFamily);
ctx.fillStyle = this.pointLabelFontColor;
var labelsCount = this.labels.length,
halfLabelsCount = this.labels.length / 2,
quarterLabelsCount = halfLabelsCount / 2,
upperHalf = (i < quarterLabelsCount || i > labelsCount - quarterLabelsCount),
exactQuarter = (i === quarterLabelsCount || i === labelsCount - quarterLabelsCount);
if (i === 0)
{
ctx.textAlign = 'center';
}
else if (i === halfLabelsCount)
{
ctx.textAlign = 'center';
}
else if (i < halfLabelsCount)
{
ctx.textAlign = 'left';
}
else
{
ctx.textAlign = 'right';
}
// Set the correct text baseline based on outer positioning
if (exactQuarter)
{
ctx.textBaseline = 'middle';
}
else if (upperHalf)
{
ctx.textBaseline = 'bottom';
}
else
{
ctx.textBaseline = 'top';
}
ctx.fillText(this.labels[i], pointLabelPosition.x, pointLabelPosition.y);
}
}
}
}
});
// Attach global event to resize each chart instance when the browser resizes
helpers.addEvent(window, "resize", (function()
{
// Basic debounce of resize function so it doesn't hurt performance when resizing browser.
var timeout;
return function()
{
clearTimeout(timeout);
timeout = setTimeout(function()
{
each(Chart.instances, function(instance)
{
// If the responsive flag is set in the chart instance config
// Cascade the resize event down to the chart.
if (instance.options.responsive)
{
instance.resize(instance.render, true);
}
});
}, 50);
};
})());
if (amd)
{
define(function()
{
return Chart;
});
}
else if (typeof module === 'object' && module.exports)
{
module.exports = Chart;
}
root.Chart = Chart;
/// ----- ZUI change begin -----
/// Use jquery object to create Chart object
$.fn.chart = function()
{
var charts = [];
this.each(function(){
charts.push(new Chart(this.getContext("2d")));
});
return charts.length === 1 ? charts[0] : charts;
}
/// ----- ZUI change end -----
/// ----- ZUI change begin -----
/// Remove unused code
// Chart.noConflict = function() // old code begin
// {
// root.Chart = previous;
// return Chart;
// }; // old code end
/// ----- ZUI change end -----
/// ----- ZUI change begin -----
/// Add jquery object to namespace
/// }).call(this); // Old code
}).call(this, jQuery);
/// ----- ZUI change end -----
/// ----- ZUI change begin -----
/// Add jquery object to namespace
/// (function(){ // Old code
(function($){
/// ----- ZUI change end -----
"use strict";
/// ----- ZUI change begin -----
/// Change root to zui shared object
///
/// var root = this, // old code
var root = $ && $.zui ? $.zui : this,
/// ----- ZUI change end -----
Chart = root.Chart,
helpers = Chart.helpers;
var defaultConfig = {
///Boolean - Whether grid lines are shown across the chart
scaleShowGridLines: true,
//String - Colour of the grid lines
scaleGridLineColor: "rgba(0,0,0,.05)",
//Number - Width of the grid lines
scaleGridLineWidth: 1,
//Boolean - Whether to show horizontal lines (except X axis)
scaleShowHorizontalLines: true,
//Boolean - Whether to show vertical lines (except Y axis)
scaleShowVerticalLines: true,
//Boolean - Whether the line is curved between points
bezierCurve: true,
//Number - Tension of the bezier curve between points
bezierCurveTension: 0.4,
//Boolean - Whether to show a dot for each point
pointDot: true,
//Number - Radius of each point dot in pixels
pointDotRadius: 4,
//Number - Pixel width of point dot stroke
pointDotStrokeWidth: 1,
//Number - amount extra to add to the radius to cater for hit detection outside the drawn point
pointHitDetectionRadius: 20,
//Boolean - Whether to show a stroke for datasets
datasetStroke: true,
//Number - Pixel width of dataset stroke
datasetStrokeWidth: 2,
//Boolean - Whether to fill the dataset with a colour
datasetFill: true,
//String - A legend template
legendTemplate: "
-legend\"><% for (var i=0; i\"> <%if(datasets[i].label){%><%=datasets[i].label%><%}%> <%}%> "
};
Chart.Type.extend(
{
name: "Line",
defaults: defaultConfig,
initialize: function(data)
{
//Declare the extension of the default point, to cater for the options passed in to the constructor
this.PointClass = Chart.Point.extend(
{
strokeWidth: this.options.pointDotStrokeWidth,
radius: this.options.pointDotRadius,
display: this.options.pointDot,
hitDetectionRadius: this.options.pointHitDetectionRadius,
ctx: this.chart.ctx,
inRange: function(mouseX)
{
return (Math.pow(mouseX - this.x, 2) < Math.pow(this.radius + this.hitDetectionRadius, 2));
}
});
this.datasets = [];
//Set up tooltip events on the chart
if (this.options.showTooltips)
{
helpers.bindEvents(this, this.options.tooltipEvents, function(evt)
{
var activePoints = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : [];
this.eachPoints(function(point)
{
point.restore(['fillColor', 'strokeColor']);
});
helpers.each(activePoints, function(activePoint)
{
activePoint.fillColor = activePoint.highlightFill;
activePoint.strokeColor = activePoint.highlightStroke;
});
this.showTooltip(activePoints);
});
}
//Iterate through each of the datasets, and build this into a property of the chart
helpers.each(data.datasets, function(dataset)
{
/// ----- ZUI change begin -----
// add color theme
if($.zui && $.zui.Color && $.zui.Color.get)
{
var accentColor = $.zui.Color.get(dataset.color);
var accentColorValue = accentColor.toCssStr();
if(!dataset.fillColor) dataset.fillColor = accentColor.clone().fade(20).toCssStr();
if(!dataset.strokeColor) dataset.strokeColor = accentColorValue;
if(!dataset.pointColor) dataset.pointColor = accentColorValue;
if(!dataset.pointStrokeColor) dataset.pointStrokeColor = '#fff';
if(!dataset.pointHighlightFill) dataset.pointHighlightFill = '#fff';
if(!dataset.pointHighlightStroke) dataset.pointHighlightStroke = accentColorValue;
}
/// ----- ZUI change begin -----
var datasetObject = {
label: dataset.label || null,
fillColor: dataset.fillColor,
strokeColor: dataset.strokeColor,
pointColor: dataset.pointColor,
pointStrokeColor: dataset.pointStrokeColor,
points: []
};
this.datasets.push(datasetObject);
helpers.each(dataset.data, function(dataPoint, index)
{
//Add a new point for each piece of data, passing any required data to draw.
datasetObject.points.push(new this.PointClass(
{
value: dataPoint,
label: data.labels[index],
datasetLabel: dataset.label,
strokeColor: dataset.pointStrokeColor,
fillColor: dataset.pointColor,
highlightFill: dataset.pointHighlightFill || dataset.pointColor,
highlightStroke: dataset.pointHighlightStroke || dataset.pointStrokeColor
}));
}, this);
this.buildScale(data.labels);
this.eachPoints(function(point, index)
{
helpers.extend(point,
{
x: this.scale.calculateX(index),
y: this.scale.endPoint
});
point.save();
}, this);
}, this);
this.render();
},
update: function()
{
this.scale.update();
// Reset any highlight colours before updating.
helpers.each(this.activeElements, function(activeElement)
{
activeElement.restore(['fillColor', 'strokeColor']);
});
this.eachPoints(function(point)
{
point.save();
});
this.render();
},
eachPoints: function(callback)
{
helpers.each(this.datasets, function(dataset)
{
helpers.each(dataset.points, callback, this);
}, this);
},
getPointsAtEvent: function(e)
{
var pointsArray = [],
eventPosition = helpers.getRelativePosition(e);
helpers.each(this.datasets, function(dataset)
{
helpers.each(dataset.points, function(point)
{
if (point.inRange(eventPosition.x, eventPosition.y)) pointsArray.push(point);
});
}, this);
return pointsArray;
},
buildScale: function(labels)
{
var self = this;
var dataTotal = function()
{
var values = [];
self.eachPoints(function(point)
{
values.push(point.value);
});
return values;
};
var scaleOptions = {
templateString: this.options.scaleLabel,
height: this.chart.height,
width: this.chart.width,
ctx: this.chart.ctx,
textColor: this.options.scaleFontColor,
fontSize: this.options.scaleFontSize,
fontStyle: this.options.scaleFontStyle,
fontFamily: this.options.scaleFontFamily,
valuesCount: labels.length,
beginAtZero: this.options.scaleBeginAtZero,
integersOnly: this.options.scaleIntegersOnly,
calculateYRange: function(currentHeight)
{
var updatedRanges = helpers.calculateScaleRange(
dataTotal(),
currentHeight,
this.fontSize,
this.beginAtZero,
this.integersOnly
);
helpers.extend(this, updatedRanges);
},
xLabels: labels,
font: helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily),
lineWidth: this.options.scaleLineWidth,
lineColor: this.options.scaleLineColor,
showHorizontalLines: this.options.scaleShowHorizontalLines,
showVerticalLines: this.options.scaleShowVerticalLines,
gridLineWidth: (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0,
gridLineColor: (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)",
padding: (this.options.showScale) ? 0 : this.options.pointDotRadius + this.options.pointDotStrokeWidth,
showLabels: this.options.scaleShowLabels,
display: this.options.showScale
};
if (this.options.scaleOverride)
{
helpers.extend(scaleOptions,
{
calculateYRange: helpers.noop,
steps: this.options.scaleSteps,
stepValue: this.options.scaleStepWidth,
min: this.options.scaleStartValue,
max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
});
}
this.scale = new Chart.Scale(scaleOptions);
},
addData: function(valuesArray, label)
{
//Map the values array for each of the datasets
helpers.each(valuesArray, function(value, datasetIndex)
{
//Add a new point for each piece of data, passing any required data to draw.
this.datasets[datasetIndex].points.push(new this.PointClass(
{
value: value,
label: label,
x: this.scale.calculateX(this.scale.valuesCount + 1),
y: this.scale.endPoint,
strokeColor: this.datasets[datasetIndex].pointStrokeColor,
fillColor: this.datasets[datasetIndex].pointColor
}));
}, this);
this.scale.addXLabel(label);
//Then re-render the chart.
this.update();
},
removeData: function()
{
this.scale.removeXLabel();
//Then re-render the chart.
helpers.each(this.datasets, function(dataset)
{
dataset.points.shift();
}, this);
this.update();
},
reflow: function()
{
var newScaleProps = helpers.extend(
{
height: this.chart.height,
width: this.chart.width
});
this.scale.update(newScaleProps);
},
draw: function(ease)
{
var easingDecimal = ease || 1;
this.clear();
var ctx = this.chart.ctx;
// Some helper methods for getting the next/prev points
var hasValue = function(item)
{
return item.value !== null;
},
nextPoint = function(point, collection, index)
{
return helpers.findNextWhere(collection, hasValue, index) || point;
},
previousPoint = function(point, collection, index)
{
return helpers.findPreviousWhere(collection, hasValue, index) || point;
};
this.scale.draw(easingDecimal);
helpers.each(this.datasets, function(dataset)
{
var pointsWithValues = helpers.where(dataset.points, hasValue);
//Transition each point first so that the line and point drawing isn't out of sync
//We can use this extra loop to calculate the control points of this dataset also in this loop
helpers.each(dataset.points, function(point, index)
{
if (point.hasValue())
{
point.transition(
{
y: this.scale.calculateY(point.value),
x: this.scale.calculateX(index)
}, easingDecimal);
}
}, this);
// Control points need to be calculated in a seperate loop, because we need to know the current x/y of the point
// This would cause issues when there is no animation, because the y of the next point would be 0, so beziers would be skewed
if (this.options.bezierCurve)
{
helpers.each(pointsWithValues, function(point, index)
{
var tension = (index > 0 && index < pointsWithValues.length - 1) ? this.options.bezierCurveTension : 0;
point.controlPoints = helpers.splineCurve(
previousPoint(point, pointsWithValues, index),
point,
nextPoint(point, pointsWithValues, index),
tension
);
// Prevent the bezier going outside of the bounds of the graph
// Cap puter bezier handles to the upper/lower scale bounds
if (point.controlPoints.outer.y > this.scale.endPoint)
{
point.controlPoints.outer.y = this.scale.endPoint;
}
else if (point.controlPoints.outer.y < this.scale.startPoint)
{
point.controlPoints.outer.y = this.scale.startPoint;
}
// Cap inner bezier handles to the upper/lower scale bounds
if (point.controlPoints.inner.y > this.scale.endPoint)
{
point.controlPoints.inner.y = this.scale.endPoint;
}
else if (point.controlPoints.inner.y < this.scale.startPoint)
{
point.controlPoints.inner.y = this.scale.startPoint;
}
}, this);
}
//Draw the line between all the points
ctx.lineWidth = this.options.datasetStrokeWidth;
ctx.strokeStyle = dataset.strokeColor;
ctx.beginPath();
helpers.each(pointsWithValues, function(point, index)
{
if (index === 0)
{
ctx.moveTo(point.x, point.y);
}
else
{
if (this.options.bezierCurve)
{
var previous = previousPoint(point, pointsWithValues, index);
ctx.bezierCurveTo(
previous.controlPoints.outer.x,
previous.controlPoints.outer.y,
point.controlPoints.inner.x,
point.controlPoints.inner.y,
point.x,
point.y
);
}
else
{
ctx.lineTo(point.x, point.y);
}
}
}, this);
ctx.stroke();
if (this.options.datasetFill && pointsWithValues.length > 0)
{
//Round off the line by going to the base of the chart, back to the start, then fill.
ctx.lineTo(pointsWithValues[pointsWithValues.length - 1].x, this.scale.endPoint);
ctx.lineTo(pointsWithValues[0].x, this.scale.endPoint);
ctx.fillStyle = dataset.fillColor;
ctx.closePath();
ctx.fill();
}
//Now draw the points over the line
//A little inefficient double looping, but better than the line
//lagging behind the point positions
helpers.each(pointsWithValues, function(point)
{
point.draw();
});
}, this);
}
});
/// ----- ZUI change begin -----
/// Use jquery object to create Chart object
$.fn.lineChart = function(data, options)
{
var lineCharts = [];
this.each(function(){
var $this = $(this);
lineCharts.push(new Chart(this.getContext("2d")).Line(data, $.extend($this.data(), options)));
});
return lineCharts.length === 1 ? lineCharts[0] : lineCharts;
}
/// ----- ZUI change end -----
/// ----- ZUI change begin -----
/// Add jquery object to namespace
/// }).call(this); // Old code
}).call(this, jQuery);
/// ----- ZUI change end -----
/// ----- ZUI change begin -----
/// Add jquery object to namespace
/// (function(){ // Old code
(function($){
/// ----- ZUI change end -----
"use strict";
/// ----- ZUI change begin -----
/// Change root to zui shared object
///
/// var root = this, // old code
var root = $ && $.zui ? $.zui : this,
/// ----- ZUI change end -----
Chart = root.Chart,
//Cache a local reference to Chart.helpers
helpers = Chart.helpers;
var defaultConfig = {
//Boolean - Whether we should show a stroke on each segment
segmentShowStroke: true,
//String - The colour of each segment stroke
segmentStrokeColor: "#fff",
//Number - The width of each segment stroke
segmentStrokeWidth: 2,
//The percentage of the chart that we cut out of the middle.
percentageInnerCutout: 50,
/// ZUI change begin
// Boolean - Whether to show labels on the scale
scaleShowLabels: false,
// Interpolated JS string - can access value
scaleLabel: "<%=value%>",
// String - Scale label position
scaleLabelPlacement: 'auto',
/// Number - Amount of animation steps // old code
/// animationSteps: 100, // old code
animationSteps: 60,
/// ZUI change end
//String - Animation easing effect
animationEasing: "easeOutBounce",
//Boolean - Whether we animate the rotation of the Doughnut
animateRotate: true,
//Boolean - Whether we animate scaling the Doughnut from the centre
animateScale: false,
//String - A legend template
legendTemplate: "
-legend\"><% for (var i=0; i\"> <%if(segments[i].label){%><%=segments[i].label%><%}%> <%}%> "
};
Chart.Type.extend(
{
//Passing in a name registers this chart in the Chart namespace
name: "Doughnut",
//Providing a defaults will also register the deafults in the chart namespace
defaults: defaultConfig,
//Initialize is fired when the chart is initialized - Data is passed in as a parameter
//Config is automatically merged by the core of Chart.js, and is available at this.options
initialize: function(data)
{
//Declare segments as a static property to prevent inheriting across the Chart type prototype
this.segments = [];
this.outerRadius = (helpers.min([this.chart.width, this.chart.height]) - this.options.segmentStrokeWidth / 2) / 2;
this.SegmentArc = Chart.Arc.extend(
{
ctx: this.chart.ctx,
x: this.chart.width / 2,
y: this.chart.height / 2
});
//Set up tooltip events on the chart
if (this.options.showTooltips)
{
helpers.bindEvents(this, this.options.tooltipEvents, function(evt)
{
var activeSegments = (evt.type !== 'mouseout') ? this.getSegmentsAtEvent(evt) : [];
helpers.each(this.segments, function(segment)
{
segment.restore(["fillColor"]);
});
helpers.each(activeSegments, function(activeSegment)
{
activeSegment.fillColor = activeSegment.highlightColor;
});
this.showTooltip(activeSegments);
});
}
this.calculateTotal(data);
helpers.each(data, function(datapoint, index)
{
this.addData(datapoint, index, true);
}, this);
this.render();
},
getSegmentsAtEvent: function(e)
{
var segmentsArray = [];
var location = helpers.getRelativePosition(e);
helpers.each(this.segments, function(segment)
{
if (segment.inRange(location.x, location.y)) segmentsArray.push(segment);
}, this);
return segmentsArray;
},
addData: function(segment, atIndex, silent)
{
/// ----- ZUI change begin -----
/// Init segment color
if($.zui && $.zui.Color && $.zui.Color.get)
{
var color = new $.zui.Color.get(segment.color);
segment.color = color.toCssStr();
if(!segment.highlight) segment.highlight = color.lighten(5).toCssStr();
}
/// ----- ZUI change begin -----
var index = atIndex || this.segments.length;
this.segments.splice(index, 0, new this.SegmentArc(
{
value: segment.value,
outerRadius: (this.options.animateScale) ? 0 : this.outerRadius,
innerRadius: (this.options.animateScale) ? 0 : (this.outerRadius / 100) * this.options.percentageInnerCutout,
fillColor: segment.color,
highlightColor: segment.highlight || segment.color,
showStroke: this.options.segmentShowStroke,
strokeWidth: this.options.segmentStrokeWidth,
strokeColor: this.options.segmentStrokeColor,
startAngle: Math.PI * 1.5,
circumference: (this.options.animateRotate) ? 0 : this.calculateCircumference(segment.value),
label: segment.label
}));
if (!silent)
{
this.reflow();
this.update();
}
},
calculateCircumference: function(value)
{
return (Math.PI * 2) * (Math.abs(value) / this.total);
},
calculateTotal: function(data)
{
this.total = 0;
helpers.each(data, function(segment)
{
this.total += Math.abs(segment.value);
}, this);
},
update: function()
{
this.calculateTotal(this.segments);
// Reset any highlight colours before updating.
helpers.each(this.activeElements, function(activeElement)
{
activeElement.restore(['fillColor']);
});
helpers.each(this.segments, function(segment)
{
segment.save();
});
this.render();
},
removeData: function(atIndex)
{
var indexToDelete = (helpers.isNumber(atIndex)) ? atIndex : this.segments.length - 1;
this.segments.splice(indexToDelete, 1);
this.reflow();
this.update();
},
reflow: function()
{
helpers.extend(this.SegmentArc.prototype,
{
x: this.chart.width / 2,
y: this.chart.height / 2
});
this.outerRadius = (helpers.min([this.chart.width, this.chart.height]) - this.options.segmentStrokeWidth / 2) / 2;
helpers.each(this.segments, function(segment)
{
segment.update(
{
outerRadius: this.outerRadius,
innerRadius: (this.outerRadius / 100) * this.options.percentageInnerCutout
});
}, this);
},
/// ZUI change begin
drawLabel: function(segment, easeDecimal)
{
var options = this.options;
var middleAngle = (segment.endAngle + segment.startAngle) / 2;
var placement = options.scaleLabelPlacement;
if(placement !== 'inside' && placement !== 'outside') {
if((this.chart.width - this.chart.height) > 50) {
if(segment.circumference < (Math.PI / 36)) {
placement = 'outside';
}
}
}
var x = Math.cos(middleAngle) * segment.outerRadius,
y = Math.sin(middleAngle) * segment.outerRadius,
text = helpers.template(options.scaleLabel, {value: typeof easeDecimal === 'undefined' ? segment.value : Math.round(easeDecimal * segment.value)});
var ctx = this.chart.ctx;
ctx.font = helpers.fontString(options.scaleFontSize, options.scaleFontStyle, options.scaleFontFamily);
var textWidth = ctx.measureText(text).width;
var chartWidthHalf = this.chart.width / 2;
var chartHeightHalf = this.chart.height / 2;
if(placement === 'outside') {
var isRight = x >= 0;
var lineX = x + chartWidthHalf;
var lineY = y + chartHeightHalf;
if(isRight)
{
x = Math.max(chartWidthHalf + segment.outerRadius + 10, x + 30 + chartWidthHalf);
} else {
x = Math.min(chartWidthHalf - segment.outerRadius - 10 - textWidth, x - 30 + chartWidthHalf - textWidth);
}
y = Math.max(15, Math.min(this.chart.height - 15, y + chartHeightHalf - 20));
ctx.beginPath();
ctx.moveTo(lineX, lineY);
ctx.lineTo(isRight ? (x - 5) : (x + 5 + textWidth), y + 7);
ctx.lineTo(isRight ? (x + textWidth + 5) : (x - 5), y + 7)
ctx.strokeStyle = ($.zui && $.zui.Color) ? (new $.zui.Color(segment.fillColor).fade(20).toCssStr()) : segment.fillColor;
ctx.strokeWidth = options.scaleLineWidth;
ctx.stroke();
ctx.closePath();
ctx.fillStyle = segment.fillColor;
x += 5;
} else { // outside
x = x * 0.7 + chartWidthHalf;
y = y * 0.7 + chartHeightHalf;
ctx.fillStyle = ($.zui && $.zui.Color) ? (new $.zui.Color(segment.fillColor).contrast().toCssStr()) : '#fff';
x -= textWidth / 2;
y -= 6;
}
ctx.fillText(text, x, y);
},
// ZUI change end
draw: function(easeDecimal)
{
var animDecimal = (easeDecimal) ? easeDecimal : 1;
this.clear();
helpers.each(this.segments, function(segment, index)
{
segment.transition(
{
circumference: this.calculateCircumference(segment.value),
outerRadius: this.outerRadius,
innerRadius: (this.outerRadius / 100) * this.options.percentageInnerCutout
}, animDecimal);
segment.endAngle = segment.startAngle + segment.circumference;
segment.draw();
if (index === 0)
{
segment.startAngle = Math.PI * 1.5;
}
//Check to see if it's the last segment, if not get the next and update the start angle
if (index < this.segments.length - 1)
{
this.segments[index + 1].startAngle = segment.endAngle;
}
/// ZUI change begin
if(this.options.scaleShowLabels)
{
this.drawLabel(segment, easeDecimal);
}
/// ZUI change end
}, this);
}
});
Chart.types.Doughnut.extend(
{
name: "Pie",
defaults: helpers.merge(defaultConfig,
{
percentageInnerCutout: 0
})
});
/// ----- ZUI change begin -----
/// Use jquery object to create Chart object
$.fn.pieChart = function(data, options)
{
var pieCharts = [];
this.each(function(){
var $this = $(this);
pieCharts.push(new Chart(this.getContext("2d")).Pie(data, $.extend($this.data(), options)));
});
return pieCharts.length === 1 ? pieCharts[0] : pieCharts;
}
$.fn.doughnutChart = function(data, options)
{
var doughnutCharts = [];
this.each(function(){
var $this = $(this);
doughnutCharts.push(new Chart(this.getContext("2d")).Doughnut(data, $.extend($this.data(), options)));
});
return doughnutCharts.length === 1 ? doughnutCharts[0] : doughnutCharts;
}
/// ----- ZUI change end -----
/// ----- ZUI change begin -----
/// Add jquery object to namespace
/// }).call(this); // Old code
}).call(this, jQuery);
/// ----- ZUI change end -----