laravel-learn-bbs/public/packages/summerblue/administrator/js/knockout/custom-bindings.js
2018-02-13 00:05:19 +08:00

714 lines
26 KiB
JavaScript
Vendored

(function ($) {
/**
* For the item form transition
*/
ko.bindingHandlers.itemTransition = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, context) {
var $element = $(element),
viewModel = context.$root,
$child = $element.find('.item_edit'),
$tableContainer = $('div.table_container'),
expandWidth = viewModel.expandWidth();
//the lastItem gets reset to null when the form is closed. This way we can draw the table properly initially
//so that it doesn't keep reopening.
if (viewModel.lastItem === null) {
$tableContainer.css('margin-right', 290);
$element.hide();
$child.css('marginLeft', expandWidth + 2);
}
else {
$tableContainer.css('margin-right', expandWidth + 5);
$child.css('marginLeft', 2);
}
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel, context) {
var $element = $(element),
viewModel = context.$root,
$child = $element.find('.item_edit'),
$tableContainer = $('div.table_container'),
expandWidth = viewModel.expandWidth();
//if the value is false, we want to hide the form, otherwise show it
if (!valueAccessor()) {
$child.stop().animate({marginLeft: expandWidth + 2}, 150, function () {
$element.hide();
});
$tableContainer.stop().animate({marginRight: 290}, 150, function () {
window.admin.resizePage();
});
}
else {
if (viewModel.lastItem === null) {
$element.show();
$child.stop().animate({marginLeft: 2}, 150);
$tableContainer.stop().animate({marginRight: expandWidth + 5}, 150, function () {
window.admin.resizePage();
});
}
}
}
};
var select2Defaults = {
placeholder: adminData.languages['select_options'],
formatNoMatches: function (term) {
return adminData.languages['no_results'];
},
width: 'resolve',
allowClear: true
};
//for select2
ko.bindingHandlers.select2 = {
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var options = valueAccessor(),
defaults = $.extend({}, select2Defaults),
data;
if (options && typeof options === 'object') {
$.extend(defaults, options);
}
//pull the latest from the list
if (defaults.data) {
if ($.isFunction(defaults.data.results)) {
defaults.data.results = options.data.results();
}
$(element).data('list_data', defaults.data.results);
defaults.data = function () {
return {results: $(element).data('list_data')};
}
}
//init select2 if it isn't already set up
if ($(element).data("select2") === undefined || $(element).data("select2") === null) {
//set the original list data in case we need it for sorting
$(element).data('original_list_data', [].concat($(element).data('list_data')));
$(element).select2(defaults);
//if the sort option is set, set up jquery ui sortable
if (options.sort) {
$(element).select2('container').find('ul.select2-choices').sortable({
containment: 'parent',
start: function () {
$(element).select2("onSortStart")
},
update: function () {
$(element).select2("onSortEnd")
}
});
}
}
//it's necessary to reorder the options array if the sort is set
if (options.sort) {
var listData = $(element).data('list_data'),
val = $(element).val();
//initially we want to reset the list data so we can work with a fresh, alphabetized sort
$(element).data('list_data', [].concat($(element).data('original_list_data')));
//if there is a value for this field, split it and find the relevant items in the array
if (val) {
var vals = val.split(','),
topItems = [],
allItems = $(element).data('list_data');
//iterate over the values
$.each(vals, function (ind, el) {
//iterate over all the items to find our value
$.each(allItems, function (i, e) {
if (e.id == el) {
topItems.push(e);
allItems.splice(i, 1);
return false;
}
});
});
$(element).data('list_data', topItems.concat(allItems));
}
}
//make sure we're monitoring the change event for page resizing
$(element).on('change', function () {
window.admin.resizePage();
});
setTimeout(function () {
$(element).trigger('change');
}, 50);
}
};
var select2RemoteHandler = function (element, valueAccessor, allBindingsAccessor, viewModel, context) {
var options = valueAccessor(),
defaults = $.extend({
minimumInputLength: 1,
allowClear: true,
ajax: {
url: base_url + adminData.model_name + '/update_options',
dataType: 'json',
quietMillis: 100,
type: 'POST',
data: function (term, page) {
var data = {
term: term,
page: page,
field: options.field,
type: options.type,
constraints: {}
};
if (data.type === 'edit') {
data.selectedItems = admin.viewModel[data.field]();
}
else if (data.type === 'filter') {
data.selectedItems = admin.filtersViewModel.filters[parseInt(options.filterIndex)].value();
}
//figure out if there are any constraints that we need to send over
if (options.constraints) {
$.each(options.constraints, function (ind, el) {
data.constraints[ind] = admin.viewModel[ind]();
});
}
return {fields: [data]};
},
results: function (returndata, page) {
var data = {},
val = $(element).val();
//we want to update the autocomplete index so we can show all possibly-selected items
if (val) {
$(val.split(',')).each(function (ind, el) {
data[this] = {
id: this,
text: admin.viewModel[options.field + '_autocomplete'][this].text
};
});
}
//iterate over the results and put them in the autocomplete array
$.each(returndata[options.field], function (ind, el) {
data[el.id] = el;
});
admin.viewModel[options.field + '_autocomplete'] = data;
return {
results: returndata[options.field]
}
}
},
initSelection: function (element, callback) {
var data = [],
val = $(element).val();
// If the select2 field has a default value,
// initSelection will be called before the admin object
// is correctly initialized.
if (!val || typeof admin === 'undefined')
return callback(null);
//if this is a multi-select, set up the data as an array
if (options.multiple) {
$(element.val().split(',')).each(function (ind, el) {
if (this in admin.viewModel[options.field + '_autocomplete'])
data.push({
id: this,
text: admin.viewModel[options.field + '_autocomplete'][this].text
});
});
}
//otherwise make the data a simple object
else {
if (val in admin.viewModel[options.field + '_autocomplete'])
data = {id: val, text: admin.viewModel[options.field + '_autocomplete'][val].text};
}
callback(data);
}
}, select2Defaults);
if (options && typeof options === 'object') {
$.extend(defaults, options);
}
//init select2 if it isn't already set up
if ($(element).data("select2") === undefined || $(element).data("select2") === null) {
$(element).select2(defaults);
//if the sort option is set, set up jquery ui sortable
if (options.sort) {
$(element).select2('container').find('ul.select2-choices').sortable({
containment: 'parent',
start: function () {
$(element).select2("onSortStart")
},
update: function () {
$(element).select2("onSortEnd")
}
});
}
}
setTimeout(function () {
$(element).trigger('change');
}, 50);
}
//for ajax/remote select2
ko.bindingHandlers.select2Remote = {
update: select2RemoteHandler
};
/**
* The number binding ensures that a value is decimal-like
*/
ko.bindingHandlers.number = {
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var options = valueAccessor(),
value = allBindingsAccessor().value(),
floatVal,
$element = $(element);
//if this is a null or false value, run a parseFloat on it so we can check for isNaN later
if (value === null || value === false) {
floatVal = parseFloat(value);
}
//else we will try to parse the number using the user-supplied thousands and decimal separators
else {
floatVal = parseFloat(value.toString().trim().split(options.thousandsSeparator).join('').split(options.decimalSeparator).join('.'));
}
//if the value is not a number, set the value equal to ''
if (isNaN(floatVal)) {
allBindingsAccessor().value(null);
//if this is an uneditable field, set the text
if ($element.hasClass('uneditable'))
$element.text('');
//otherwise we know it's an input
else
$element.val('');
}
//else set up the value up using the accounting library with the user-supplied separators
else {
//if this is an uneditable field, set the text
if ($element.hasClass('uneditable'))
$element.text(accounting.formatMoney(floatVal, "", options.decimals, options.thousandsSeparator, options.decimalSeparator));
//otherwise we know it's an input
else
$element.val(accounting.formatMoney(floatVal, "", options.decimals, options.thousandsSeparator, options.decimalSeparator));
}
}
};
/**
* The datepicker binding makes sure the jQuery UI datepicker is set for this item
*/
ko.bindingHandlers.datepicker = {
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var options = valueAccessor();
$(element).datepicker({
dateFormat: options.dateFormat
});
}
};
/**
* The formatDate binding transforms a date string into a formatted date
*/
ko.bindingHandlers.formatDate = {
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var options = valueAccessor(),
dateVal = options.value.length === 10 ? options.val + ' 00:00' : options.val;
$(element).text($.datepicker.formatDate(options.dateFormat, new Date(options.value)));
}
};
/**
* The timepicker binding makes sure the jQuery UI timepicker is set for this item
*/
ko.bindingHandlers.timepicker = {
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var options = valueAccessor(),
val = allBindingsAccessor().value(),
date = new Date('01/01/2013 ' + val),
timeObject = {
hour: date.getHours(),
minute: date.getMinutes()
};
if (val)
$(element).val($.datepicker.formatTime(options.timeFormat, timeObject));
$(element).timepicker({
timeFormat: options.timeFormat
});
}
};
/**
* The formatTime binding transforms a time string into a formatted time
*/
ko.bindingHandlers.formatTime = {
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var options = valueAccessor(),
date = new Date('01/01/2012 ' + options.value),
timeObject = {
hour: date.getHours(),
minute: date.getMinutes()
};
$(element).text($.datepicker.formatTime(options.timeFormat, timeObject));
}
};
/**
* The datetimepicker binding makes sure the jQuery UI datetimepicker is set for this item
*/
ko.bindingHandlers.datetimepicker = {
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var options = valueAccessor(),
val = allBindingsAccessor().value(),
date = new Date(val),
timeObject = {
hour: date.getHours(),
minute: date.getMinutes()
};
if (val && !isNaN(date.getHours())) {
var formattedDate = $.datepicker.formatDate(options.dateFormat, date),
formattedTime = $.datepicker.formatTime(options.timeFormat, timeObject);
$(element).val(formattedDate + ' ' + formattedTime);
}
$(element).datetimepicker({
dateFormat: options.dateFormat,
timeFormat: options.timeFormat
});
}
};
/**
* The formatTime binding transforms a datetime string into a formatted datetime
*/
ko.bindingHandlers.formatDateTime = {
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var options = valueAccessor(),
date = new Date(options.value),
timeObject = {
hour: date.getHours(),
minute: date.getMinutes()
};
if (!isNaN(date.getHours())) {
var formattedDate = $.datepicker.formatDate(options.dateFormat, date),
formattedTime = $.datepicker.formatTime(options.timeFormat, timeObject);
$(element).text(formattedDate + ' ' + formattedTime);
}
}
};
/**
* The characterLimit binding makes sure a text field only has so many characters
*/
ko.bindingHandlers.characterLimit = {
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var limit = valueAccessor(),
val = allBindingsAccessor().value();
val = val === null ? '' : val + '';
if (!limit || val === null || val.length < limit)
return;
val = val.substr(0, limit);
$(element).val(val);
allBindingsAccessor().value(val);
}
};
/**
* The charactersLeft binding fills the element with (#chars allowed - #chars typed)
*/
ko.bindingHandlers.charactersLeft = {
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var options = valueAccessor(),
limit = options.limit,
val = options.value();
val = val === null ? '' : val + '';
//if the limit is zero, there is no limit
if (!limit)
return;
//if the value is null, set it to an empty string
if (val === null)
val = '';
left = limit - val.length;
// text = ' character' + (left !== 1 ? 's' : '') + ' left';
text = (left !== 1 ? adminData.languages['characters_left'] : adminData.languages['character_left']);
$(element).text(left + text);
}
};
/**
* This ensures that a bool field is always a boolean value
*/
ko.bindingHandlers.bool = {
update: function (element, valueAccessor, allBindingsAccessor, viewModel, context) {
var viewModel = context.$root,
modelVal = viewModel[valueAccessor()]();
if (modelVal === '0')
viewModel[valueAccessor()](false);
else if (modelVal === '1')
viewModel[valueAccessor()](true);
}
};
var editors = {};
/**
* The wysiwyg binding makes the field a ckeditor wysiwyg
*/
ko.bindingHandlers.wysiwyg = {
init: function (element, valueAccessor, allBindingsAccessor, context) {
var options = valueAccessor(),
value = ko.utils.unwrapObservable(options.value),
$element = $(element),
editor;
value = value ? value : '';
$element.html(value);
if (options.id in editors)
editor = editors[options.id];
else {
$element.ckeditor({
language: language,
readOnly: !adminData.edit_fields[context.field_name].editable
});
editor = $element.ckeditorGet();
editors[options.id] = editor;
}
//when the editor is loaded, we want to resize our page
editor.on('loaded', function () {
setTimeout(function () {
window.admin.resizePage();
}, 50);
editor.on('resize', function () {
window.admin.resizePage();
});
});
//wire up the blur event to ensure our observable is properly updated
editor.focusManager.blur = function () {
var observable = valueAccessor().value,
$el = $('#' + options.id);
//set the blur attribute to true so we know now to set the editor data in the update method
$el.data('blur', true);
observable($el.val());
}
//handle destroying an editor (based on what jQuery plugin does)
ko.utils.domNodeDisposal.addDisposeCallback(element, function (test) {
var editor = editors[options.id];
if (editor) {
editor.destroy();
delete editors[options.id];
}
});
},
update: function (element, valueAccessor, allBindingsAccessor, context) {
//handle programmatic updates to the observable
var options = valueAccessor(),
value = ko.utils.unwrapObservable(options.value),
$element = $(element),
editor = editors[options.id];
value = value ? value : '';
//if there isn't a value, set the value immediately
if (!value) {
$element.html(value);
editor.setData(value);
}
//otherwise pause for a moment and then set it
else {
setTimeout(function () {
$element.html(value);
if ($element.data('blur'))
$element.removeData('blur');
else
editor.setData(value);
}, 50);
}
}
};
/**
* The markdown binding is attached to the field next a markdown textarea
*/
ko.bindingHandlers.markdown = {
update: function (element, valueAccessor, allBindingsAccessor, context) {
//handle programmatic updates to the observable
var value = ko.utils.unwrapObservable(valueAccessor());
if (!value) {
$(element).html(value);
}
else {
$(element).html(markdown.toHTML(value.toString()));
}
}
};
/**
* The enumText binding converts a value and an options array to a "Label (value)" readable format
*/
ko.bindingHandlers.enumText = {
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var options = valueAccessor(),
value = options.value,
enumOptions = options.enumOptions;
for (var i = 0; i < enumOptions.length; i++) {
if (enumOptions[i].id == value) {
$(element).html(enumOptions[i].text + " (" + value + ")");
return;
}
}
$(element).html(value);
}
};
/**
* File uploader using plupload
*/
ko.bindingHandlers.fileupload = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, context) {
var options = valueAccessor(),
cacheName = options.field + '_uploader',
viewModel = context.$root,
filters = options.image ? [{title: 'Image files', extensions: 'jpg,jpeg,gif,png'}] : [];
viewModel[cacheName] = new plupload.Uploader({
runtimes: 'html5,flash,silverlight,gears,browserplus',
browse_button: cacheName,
container: 'edit_field_' + options.field,
drop_element: cacheName,
multi_selection: false,
max_file_size: options.size_limit + 'mb',
url: options.upload_url,
flash_swf_url: asset_url + 'js/plupload/js/plupload.flash.swf',
silverlight_xap_url: asset_url + 'js/plupload/js/plupload.silverlight.xap',
filters: filters,
multipart_params: {"_token": window.csrf}
});
viewModel[cacheName].init();
viewModel[cacheName].bind('FilesAdded', function (up, files) {
viewModel.freezeActions(true);
$(files).each(function (i, file) {
//parent.uploader.removeFile(file);
});
options.upload_percentage(0);
options.uploading(true);
viewModel[cacheName].start();
});
viewModel[cacheName].bind('UploadProgress', function (up, file) {
options.upload_percentage(file.percent);
});
viewModel[cacheName].bind('Error', function (up, err) {
alert(err.message);
});
viewModel[cacheName].bind('FileUploaded', function (up, file, response) {
var data = JSON.parse(response.response);
options.uploading(false);
if (data.errors.length === 0) {
//success
//iterate over the files until we find it and then set the proper fields
viewModel[options.field](data.filename);
} else {
//error
alert(data.errors);
}
setTimeout(function () {
viewModel[cacheName].splice();
viewModel[cacheName].refresh();
$('div.plupload').css('z-index', 71);
viewModel.freezeActions(false);
admin.resizePage();
}, 200);
});
$('#' + cacheName).bind('dragenter', function (e) {
$(this).addClass('drag');
});
$('#' + cacheName).bind('dragleave drop', function (e) {
$(this).removeClass('drag');
});
//destroy the existing editor if the DOM node is removed
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
viewModel[cacheName].destroy();
});
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel, context) {
var options = valueAccessor(),
cacheName = options.field + '_uploader',
viewModel = context.$root;
//hack to get the z-index properly set up
setTimeout(function () {
viewModel[cacheName].refresh();
$('div.plupload').css('z-index', 71);
}, 200);
}
};
})(jQuery);