laravel-learn-bbs/public/packages/summerblue/administrator/js/admin.js

1690 lines
59 KiB
JavaScript
Raw Normal View History

2018-02-12 15:56:05 +00:00
(function ($) {
var admin = function () {
return this.init();
};
//setting up csrf token
$.ajaxSetup({
headers: {
'X-CSRF-TOKEN': window.csrf
}
});
admin.prototype = {
//properties
/*
* Main admin container
*
* @type jQuery object
*/
$container: null,
/*
* The container for the datatable
*
* @type jQuery object
*/
$tableContainer: null,
/*
* The data table
*
* @type jQuery object
*/
$dataTable: null,
/*
* If this is true, the dataTable is scrollable instead of
* skipping columns at the end
*
* @type bool
*/
dataTableScrollable: false,
/*
* The pixel points where the columns are hidden
*
* @type object
*/
columnHidePoints: {},
/*
* If this is true, history.js has started
*
* @type bool
*/
historyStarted: false,
/*
* Filters view model
*/
filtersViewModel: {
/* The filters for the current result set
* array
*/
filters: [],
/* The options lists for any fields
* object
*/
listOptions: {},
/**
* The options for booleans
* array
*/
boolOptions: [{id: 'true', text: 'true'}, {id: 'false', text: 'false'}]
},
/*
* KO viewModel
*/
viewModel: {
/*
* KO data model
*/
model: {},
/*
* If this is true, all the values have been initialized and we can
*
* bool
*/
initialized: ko.observable(false),
/* The model name for this data model
* string
*/
modelName: ko.observable(''),
/* The model title for this data model
* string
*/
modelTitle: ko.observable(''),
/* The sub title for this data model
* string
*/
subTitle: ko.observable(''),
/* The title for single items of this model
* string
*/
modelSingle: ko.observable(''),
/* The link (usually front-end) associated with this item
* string
*/
itemLink: ko.observable(null),
/* The expand width of the edit area
* int
*/
expandWidth: ko.observable(null),
/* The primary key value for this model
* string
*/
primaryKey: 'id',
/* The rows of the current result set
* array
*/
rows: ko.observableArray(),
/* The number of rows per page
* int
*/
rowsPerPage: ko.observable(20),
/* The options (1-100 ...set up in init method) for the rows per page
* array
*/
rowsPerPageOptions: [],
/* The columns for the current data model
* array
*/
columns: ko.observableArray(),
/* The options lists for any fields
* object
*/
listOptions: {},
/* The current sort options
* object
*/
sortOptions: {
field: ko.observable(),
direction: ko.observable()
},
/* The current pagination options
* object
*/
pagination: {
page: ko.observable(),
last: ko.observable(),
total: ko.observable(),
per_page: ko.observable(),
isFirst: true,
isLast: false,
},
/* The original edit fields array
* array
*/
originalEditFields: [],
/* The original data when fetched from the server initially
* object
*/
originalData: {},
/* The model edit fields
* array
*/
editFields: ko.observableArray(),
/* The id of the active item. If it's null, there is no active item. If it's 0, the active item is new
* mixed (null, int)
*/
activeItem: ko.observable(null),
/* The id of the last active item. This is set to null when an item is closed. 0 is new.
* mixed (null, int)
*/
lastItem: null,
/* If this is set to true, the loading screen will be visible
* bool
*/
loadingItem: ko.observable(false),
/* The id of the item currently being loaded
* int
*/
itemLoadingId: ko.observable(null),
/* If this is set to true, the row loading screen will be visible
* bool
*/
loadingRows: ko.observable(false),
/* The id of the rows currently being loaded
* int
*/
rowLoadingId: 0,
/* If this is set to true, the form becomes uneditable
* bool
*/
freezeForm: ko.observable(false),
/* If this is set to true, the action buttons on the form cannot be accessed
* bool
*/
freezeActions: ko.observable(false),
/* If this is set to true, the relationship constraints won't update
* bool
*/
freezeConstraints: false,
/* The current constraints queue
* object
*/
constraintsQueue: {},
/* If this is set to true, the relationship constraints queue won't process
* bool
*/
holdConstraintsQueue: true,
/* If custom actions are supplied, they are stored here
* array
*/
actions: ko.observableArray(),
/* If custom global actions are supplied, they are stored here
* array
*/
globalActions: ko.observableArray(),
/* Holds the per-action permissions
* object
*/
actionPermissions: {},
/* The languages array holds text for the current language
* object
*/
languages: {},
/* The status message and the type ('', 'success', 'error')
* strings
*/
statusMessage: ko.observable(''),
statusMessageType: ko.observable(''),
/* The global status message and the type ('', 'success', 'error')
* strings
*/
globalStatusMessage: ko.observable(''),
globalStatusMessageType: ko.observable(''),
/**
* Saves the item with the current settings. If id is 0, the server interprets it as a new item
*/
saveItem: function (norefresh) {
var self = this,
saveData = ko.mapping.toJS(self);
saveData._token = csrf;
//if this is a new item, delete the primary key from the data array
if (!saveData[self.primaryKey])
delete saveData[self.primaryKey];
//iterate over the edit fields and ensure that the belongs_to relationships are false if they are an empty string
$.each(self.editFields(), function (ind, field) {
if (field.relationship && !field.external && saveData[field.field_name] === '') {
saveData[field.field_name] = false;
}
});
self.statusMessage(self.languages['saving']).statusMessageType('');
self.freezeForm(true);
$.ajax({
url: base_url + self.modelName() + '/' + self[self.primaryKey]() + '/save',
data: saveData,
dataType: 'json',
type: 'POST',
complete: function () {
self.freezeForm(false);
window.admin.resizePage();
},
success: function (response) {
if (response.success) {
self.statusMessage(self.languages['saved']).statusMessageType('success');
self.updateRows();
self.updateSelfRelationships();
if (norefresh) return;
self.setData(response.data);
setTimeout(function () {
History.pushState({modelName: self.modelName()}, document.title, route + self.modelName());
}, 200);
}
else
self.statusMessage(response.errors).statusMessageType('error');
}
});
},
/**
* Deletes the active item
*/
deleteItem: function (root, event, key) {
var self = root;
swal({
title: '',
text: adminData.languages['delete_active_item'],
type: "warning",
showCancelButton: true,
confirmButtonColor: "#DD6B55",
cancelButtonText: adminData.languages['cancel'],
confirmButtonText: adminData.languages['delete'],
showLoaderOnConfirm: true,
closeOnConfirm: false
}, function () {
var mykey = key ? key : self[self.primaryKey]();
self.freezeForm(true);
$.ajax({
url: base_url + self.modelName() + '/' + mykey + '/delete',
data: {_token: csrf},
dataType: 'json',
type: 'POST',
complete: function () {
self.freezeForm(false);
window.admin.resizePage();
},
success: function (response) {
if (response.success) {
swal({
title: adminData.languages['deleted'],
text: "",
type: "success",
timer: 1000,
showConfirmButton: false
});
self.updateRows();
self.updateSelfRelationships();
if (mykey == self[self.primaryKey]()) {
setTimeout(function () {
History.pushState({modelName: self.modelName()}, document.title, route + self.modelName());
$('#sidebar').fadeIn();
}, 500);
}
}
else
swal(response.error, "", "error");
},
error: function (response) {
swal(adminData.languages['delete_failed'], "", "error");
}
});
});
},
/**
* Deletes selected items
*/
deleteItems: function () {
var self = this;
var selected = [];
$('.select-checkbox').each(function (i, el) {
if ($(el).is(':checked')) {
selected.push($(el).val());
}
});
if (!selected.length) {
swal('', adminData.languages['select_options'], "warning");
return;
}
swal({
title: '',
text: adminData.languages['delete_items'],
type: "warning",
showCancelButton: true,
confirmButtonColor: "#DD6B55",
cancelButtonText: adminData.languages['cancel'],
confirmButtonText: adminData.languages['delete'],
showLoaderOnConfirm: true,
closeOnConfirm: false
}, function () {
var mykey = selected.join(',');
self.freezeForm(true);
$.ajax({
url: base_url + self.modelName() + '/batch_delete',
data: {_token: csrf, ids: mykey},
dataType: 'json',
type: 'POST',
complete: function () {
self.freezeForm(false);
window.admin.resizePage();
},
success: function (response) {
if (response.success) {
swal({
title: adminData.languages['deleted'],
text: "",
type: "success",
timer: 1000,
showConfirmButton: false
});
self.updateRows();
self.updateSelfRelationships();
setTimeout(function () {
History.pushState({modelName: self.modelName()}, document.title, route + self.modelName());
$('#sidebar').fadeIn();
$('#select-all').prop('checked', false);
$('#delete-all').addClass('disabled');
}, 500);
}
else
swal(response.error, "", "error");
},
error: function (response) {
swal(adminData.languages['delete_failed'], "", "error");
}
});
});
},
/**
* Callback for clicking an item
*/
clickItem: function (id) {
if (!this.loadingItem() && this.activeItem() !== id && this.actionPermissions.view) {
History.pushState({
modelName: this.modelName(),
id: id
}, document.title, route + this.modelName() + '/' + id);
}
},
/**
* Gets the active item in the grid
*
* @param int id
*/
getItem: function (id) {
var self = this;
self.loadingItem(true);
//override the edit fields to the original non-existent model
adminData.edit_fields = self.originalEditFields;
self.editFields(window.admin.prepareEditFields());
//make sure constraints are only loaded once
self.holdConstraintsQueue = true;
//update all the info to the new item state
ko.mapping.updateData(self, self.model, self.model);
self.originalData = {};
//scroll to the top of the page
//$('html, body').animate({scrollTop: 0}, 'fast')
//if this is a new item (id is falsy), just overwrite the viewModel with the original data model
if (!id) {
self.setUpNewItem();
return;
}
//freeze the relationship constraint updates
self.freezeConstraints = true;
self.itemLoadingId(id);
$.ajax({
url: base_url + self.modelName() + '/' + id,
dataType: 'json',
success: function (data) {
//if there was an error, kick out
if (data.success === false && data.errors) {
alert(data.errors);
return;
}
if (self.itemLoadingId() !== id) {
//if there are no currently-loading items, clear the form
if (self.itemLoadingId() === null) {
self.loadingItem(false);
self.clearItem();
}
}
else
self.setData(data);
}
});
},
/**
* Sets the edit form up as a new item
*/
setUpNewItem: function () {
this.itemLoadingId(null);
this.activeItem(0);
//set the last item property which helps manage the animation states
this.lastItem = 0;
var data = {};
// 新建时加载 belongs_to_many 或 has_many 的默认值
$.each(adminData.edit_fields, function (ind, el) {
if (el.type == 'belongs_to_many' || el.type == 'has_many') {
if (el.value) {
data[ind] = el.value;
}
}
});
ko.mapping.updateData(this, this.model, data);
this.loadingItem(false);
//run the constraints queue
window.admin.runConstraintsQueue();
},
/**
* Overrides the data in the view model
*
* @param object data
* @param
*/
setData: function (data) {
var self = this;
//set the active item and update the model data
self.activeItem(data[self.primaryKey]);
self.loadingItem(false);
//update the edit fields
adminData.edit_fields = data.administrator_edit_fields;
self.editFields(window.admin.prepareEditFields());
//update the actions and the action permissions
self.actions(data.administrator_actions);
self.actionPermissions = data.administrator_action_permissions;
//set the original values
self.originalData = data;
//set the new options for relationships
$.each(adminData.edit_fields, function (ind, el) {
if (el.relationship && el.autocomplete) {
self[el.field_name + '_autocomplete'] = data[el.field_name + '_autocomplete'];
}
});
//set the item link if it exists
if (data.admin_item_link) {
self.itemLink(data.admin_item_link);
}
//set the last item property which helps manage the animation states
self.lastItem = data[self.primaryKey];
//fixes an error where the relationships wouldn't load
setTimeout(function () {
//first clear the data
ko.mapping.updateData(self, self.model, self.model);
//then update the data
ko.mapping.updateData(self, self.model, data);
//unfreeze the relationship constraint updates
self.freezeConstraints = false;
window.admin.resizePage();
//run the constraints queue
window.admin.runConstraintsQueue();
}, 50);
},
/**
* Closes the item edit/create window
*/
closeItem: function () {
History.pushState({modelName: this.modelName()}, document.title, route + this.modelName());
$('#sidebar').fadeIn();
},
/**
* Clears the current item
*/
clearItem: function () {
this.freezeForm(false);
this.statusMessage('');
this.statusMessageType('');
this.itemLink(null);
this.itemLoadingId(null);
this.activeItem(null);
this.lastItem = null;
},
/**
* Opens the create item form
*/
addNewItem: function () {
//$('#users_list').resetSelection();
this.getItem(0);
},
/**
* Performs a custom action on an item or the whole model
*
* @param bool isItem
* @param string action
* @param object messages
* @param string confirmation
*/
customAction: function (isItem, action, messages, confirmation) {
var self = this,
data = {_token: csrf, action_name: action},
url;
//if a confirmation string was supplied, flash it in a confirm()
if (confirmation) {
if (!confirm(confirmation))
return false;
}
//if this is an item action (compared to a global model action), set the proper url
if (isItem) {
url = base_url + self.modelName() + '/' + self[self.primaryKey]() + '/custom_action';
self.statusMessage(messages.active).statusMessageType('');
}
//otherwise set the url and add the filters
else {
url = base_url + self.modelName() + '/custom_action';
data.sortOptions = self.sortOptions;
data.filters = self.getFilters();
data.page = self.pagination.page();
self.globalStatusMessage(messages.active).globalStatusMessageType('');
}
self.freezeForm(true);
$.ajax({
url: url,
data: data,
dataType: 'json',
type: 'POST',
complete: function () {
self.freezeForm(false);
},
success: function (response) {
if (response.success) {
if (isItem) {
self.statusMessage(messages.success).statusMessageType('success');
self.setData(response.data);
}
else {
self.globalStatusMessage(messages.success).globalStatusMessageType('success');
}
// if this is a redirect, redirect the user to the supplied url
if (response.redirect)
window.location.href = response.redirect;
//if there was a file download initiated, redirect the user to the file download address
if (response.download)
self.downloadFile(response.download);
self.updateRows();
}
else {
if (isItem)
self.statusMessage(response.error).statusMessageType('error');
else
self.globalStatusMessage(response.error).globalStatusMessageType('error');
}
}
});
},
/**
* Initiates a file download
*
* @param string url
*/
downloadFile: function (url) {
var hiddenIFrameId = 'hiddenDownloader',
iframe = document.getElementById(hiddenIFrameId);
if (iframe === null) {
iframe = document.createElement('iframe');
iframe.id = hiddenIFrameId;
iframe.style.display = 'none';
document.body.appendChild(iframe);
}
iframe.src = url;
},
/**
* Updates the rows given the data model's current state. Set sort, filters, and anything else before you call this.
* Calling this locks the results table.
*
* @param object data
*/
updateRows: function () {
var self = this,
id = ++self.rowLoadingId,
data = {
_token: csrf,
sortOptions: self.sortOptions,
filters: self.getFilters(),
page: self.pagination.page(),
// hack by @Monkey: for paging logic
filter_by: self.filter_by,
filter_by_id: self.filter_by_id
};
//if the page hasn't been initialized yet, don't update the rows
if (!this.initialized())
return;
//if we're on page 0 (i.e. there is currently no result set, set the page to 1)
if (!data.page)
data.page = 1;
//set loadingRows to true so that the loading box comes up
self.loadingRows(true);
$.ajax({
url: base_url + self.modelName() + '/results',
type: 'POST',
dataType: 'json',
data: data,
success: function (response) {
//if the row loading id has changed, that means it's old...so don't use this data
if (self.rowLoadingId !== id) {
return;
}
//otherwise the rows aren't loading anymore and we can replace the data
self.pagination.page(response.last ? response.page : response.last);
self.pagination.last(response.last);
self.pagination.total(response.total);
self.rows(response.results);
self.loadingRows(false);
}
});
},
/**
* Updates the sort options when a column header is clicked
*
* @param string field
*/
setSortOptions: function (field) {
//check if the field is a valid column
var found = false;
//iterate over the columns to check if it's a valid sort_field or field
$.each(this.columns(), function (i, col) {
if (field === col.sort_field || field === col.column_name) {
found = true;
return false;
}
})
if (!found)
return false;
//the direction depends on the field
if (field == this.sortOptions.field())
//reverse the direction
this.sortOptions.direction((this.sortOptions.direction() == 'asc') ? 'desc' : 'asc');
else
//set the direction to asc
this.sortOptions.direction('asc');
//update the field
this.sortOptions.field(field);
//update the rows
this.updateRows();
},
/**
* Goes to the specified page
*
* @param string|int page
*/
page: function (page) {
var currPage = parseInt(this.pagination.page()),
newPage = 1,
lastPage = parseInt(this.pagination.last());
//if the value is 'prev' or 'next', increment or decrement
if (page === 'prev') {
if (currPage > 1) {
newPage = currPage - 1;
}
}
else if (page === 'next') {
if (currPage < lastPage) {
newPage = currPage + 1;
}
else {
newPage = lastPage;
}
}
else if (!isNaN(parseInt(page))) {
//set the page to the supplied value
if (page > lastPage) {
newPage = lastPage;
}
else {
newPage = page;
}
}
this.pagination.page(newPage);
//update the rows
this.updateRows();
},
/**
* Updates the rows per page for this model when the item is changed
*
* @param int
*/
updateRowsPerPage: function (rows) {
var self = this;
$.ajax({
url: rows_per_page_url,
data: {_token: csrf, rows: rows},
dataType: 'json',
type: 'POST',
complete: function () {
self.updateRows();
}
});
},
/**
* Gets a minimized filters array that can be sent to the server
*/
getFilters: function () {
var filters = [],
observables = ['value', 'min_value', 'max_value'];
$.each(window.admin.filtersViewModel.filters, function (ind, el) {
var filter = {
field_name: el.field_name,
type: el.type,
value: el.value() ? el.value() : null,
};
//iterate over the observables to see if we should include them
$(observables).each(function (i, obs) {
if (this in el) {
filter[this] = el[this]() ? el[this]() : null;
if (obs === 'value' && filter[this] && el.type === 'belongs_to_many' && typeof filter[this] === 'string') {
filter.value = filter.value.split(',');
}
}
});
//push this filter onto the filters array
filters.push(filter);
});
return filters;
},
/**
* Determines if the provided field is dirty
*
* @param string
*
* @return bool
*/
fieldIsDirty: function (field) {
return this.originalData[field] != this[field]();
},
/**
* Updates any self-relationships
*/
updateSelfRelationships: function () {
var self = this;
//first we will iterate over the filters and update them if any exist
$.each(window.admin.filtersViewModel.filters, function (ind, filter) {
var fieldIndex = ind,
fieldName = filter.field_name;
if ((!filter.constraints || !filter.constraints.length) && filter.self_relationship) {
window.admin.filtersViewModel.filters[fieldIndex].loadingOptions(true);
$.ajax({
url: base_url + self.modelName() + '/update_options',
type: 'POST',
dataType: 'json',
data: {
fields: [{
type: 'filter',
field: fieldName,
selectedItems: filter.value()
}]
},
complete: function () {
window.admin.filtersViewModel.filters[fieldIndex].loadingOptions(false);
},
success: function (response) {
//update the options
window.admin.filtersViewModel.listOptions[fieldName](response[fieldName]);
}
});
}
});
//then we'll update the edit fields
$.each(self.editFields(), function (ind, field) {
var fieldName = field.field_name;
//if there are no constraints for this field and if it is a self-relationship, update the options
if ((!field.constraints || !field.constraints.length) && field.self_relationship) {
field.loadingOptions(true);
$.ajax({
url: base_url + self.modelName() + '/update_options',
type: 'POST',
dataType: 'json',
data: {
fields: [{
type: 'edit',
field: fieldName,
selectedItems: self[fieldName]()
}]
},
complete: function () {
field.loadingOptions(false);
},
success: function (response) {
//update the options
self.listOptions[fieldName] = response[fieldName];
}
});
}
});
}
},
//methods
/**
* Init method
*/
init: function () {
var self = this;
//set up the basic pieces of data
this.viewModel.model = adminData.data_model;
this.$container = $('#admin_content');
var viewModel = ko.mapping.fromJS(this.viewModel.model);
$.extend(this.viewModel, viewModel);
this.viewModel.rows(adminData.rows.results);
this.viewModel.pagination.page(adminData.rows.page);
this.viewModel.pagination.last(adminData.rows.last);
this.viewModel.pagination.total(adminData.rows.total);
this.viewModel.sortOptions.field(adminData.sortOptions.field);
this.viewModel.sortOptions.direction(adminData.sortOptions.direction);
this.viewModel.columns(this.prepareColumns());
this.viewModel.modelName(adminData.model_name);
this.viewModel.modelTitle(adminData.model_title);
this.viewModel.subTitle(adminData.sub_title);
this.viewModel.modelSingle(adminData.model_single);
this.viewModel.expandWidth(adminData.expand_width);
this.viewModel.rowsPerPage(adminData.rows_per_page);
this.viewModel.primaryKey = adminData.primary_key;
this.viewModel.actions(adminData.actions);
this.viewModel.globalActions(adminData.global_actions);
this.viewModel.actionPermissions = adminData.action_permissions;
this.viewModel.languages = adminData.languages;
// hack by @Monkey: for paging logic
this.viewModel.filter_by = adminData.filter_by;
this.viewModel.filter_by_id = adminData.filter_by_id;
//set up the rowsPerPageOptions
var perPageArr = [20, 50, 100, 200, 500, 1000, 2000, 5000, 8000, 10000];
for (var i = 0; i < perPageArr.length; i++) {
this.viewModel.rowsPerPageOptions.push({id: perPageArr[i], text: perPageArr[i] + ''});
}
//now that we have most of our data, we can set up the computed values
this.initComputed();
//prepare the filters
this.filtersViewModel.filters = this.prepareFilters();
//prepare the edit fields
this.viewModel.originalEditFields = adminData.edit_fields;
this.viewModel.editFields(this.prepareEditFields());
//set up the relationships
this.initRelationships();
//set up the KO bindings
ko.applyBindings(this.viewModel, $('#content')[0]);
ko.applyBindings(this.filtersViewModel, $('#filters_sidebar_section')[0]);
//set up pushstate history
this.initHistory();
//set up the subscriptions
this.initSubscriptions();
//set up the events
this.initEvents();
//run an initial page resize
this.resizePage();
//finally run a timer to overcome bugs with select2
setTimeout(function () {
self.viewModel.initialized(true);
}, 1000);
return this;
},
/**
* Prepare the filters
*
* @return array with value observables
*/
prepareFilters: function () {
var filters = [];
$.each(adminData.filters, function (ind, filter) {
var observables = ['value', 'min_value', 'max_value'];
//iterate over the desired observables and check if they're there. if so, assign them an observable slot
$.each(observables, function (i, obs) {
if (obs in filter) {
filter[obs] = ko.observable(filter[obs]);
}
});
//if this is a relationship field, we want to set up the loading options observable
if (filter.relationship) {
filter.loadingOptions = ko.observable(false);
}
filter.field_id = 'filter_field_' + filter.field_name;
filters.push(filter);
});
return filters;
},
/**
* Prepare the edit fields
*
* @return object with loadingOptions observables
*/
prepareEditFields: function () {
var self = this,
fields = [];
$.each(adminData.edit_fields, function (ind, field) {
//if this is a relationship field, set up the loadingOptions observable
if (field.relationship) {
field.loadingOptions = ko.observable(false);
field.constraintLoading = ko.observable(false);
}
//if this is an image field, set the upload params
if (field.type === 'image' || field.type === 'file') {
field.uploading = ko.observable(false);
field.upload_percentage = ko.observable(0);
}
//add the id field
field.field_id = 'edit_field_' + ind;
fields.push(field);
});
return fields;
},
/**
* Sets up the column model with various observable values
*
* @return array
*/
prepareColumns: function () {
var self = this,
columns = [];
$.each(adminData.column_model, function (ind, column) {
column.visible = ko.observable(column.visible);
columns.push(column);
});
return columns;
},
/**
* Set up the relationship items
*/
initRelationships: function () {
var self = this;
//set up the filters
$.each(adminData.filters, function (ind, el) {
if (el.relationship)
self.filtersViewModel.listOptions[ind] = ko.observableArray(el.options);
});
//set up the edit fields
$.each(adminData.edit_fields, function (ind, el) {
if (el.relationship)
self.viewModel.listOptions[ind] = el.options;
// add any loaded option to the autocomplete array
if (el.autocomplete) {
if (!(el.field_name + '_autocomplete' in self.viewModel))
self.viewModel[el.field_name + '_autocomplete'] = [];
$.each(el.options, function (x, option) {
self.viewModel[el.field_name + '_autocomplete'][option.id] = option;
});
}
});
},
/**
* Inits the KO subscriptions
*/
initSubscriptions: function () {
var self = this,
runFilter = function (val) {
self.viewModel.updateRows();
};
//iterate over filters
$.each(self.filtersViewModel.filters, function (ind, filter) {
//subscribe to the value field
self.filtersViewModel.filters[ind].value.subscribe(function (val) {
//if this is an id field, make sure it's an integer
if (self.filtersViewModel.filters[ind].type === 'key') {
var intVal = isNaN(parseInt(val)) ? '' : parseInt(val);
self.filtersViewModel.filters[ind].value(intVal);
}
//update the rows now that we've got new filters
self.viewModel.updateRows();
});
//check if there's a min and max value. if so, subscribe to those as well
if ('min_value' in filter) {
self.filtersViewModel.filters[ind].min_value.subscribe(runFilter);
}
if ('max_value' in filter) {
self.filtersViewModel.filters[ind].max_value.subscribe(runFilter);
}
});
//iterate over the edit fields
$.each(self.viewModel.editFields(), function (ind, field) {
//if there are constraints to maintain, set up the subscriptions
if (field.constraints && self.getObjectSize(field.constraints)) {
self.establishFieldConstraints(field);
}
});
//subscribe to page change
self.viewModel.pagination.page.subscribe(function (val) {
self.viewModel.page(val);
});
//subscribe to rows per page change
self.viewModel.rowsPerPage.subscribe(function (val) {
self.viewModel.updateRowsPerPage(parseInt(val));
});
},
/**
* Establish constraints
*
* @param object field
*/
establishFieldConstraints: function (field) {
var self = this;
//we want to subscribe to changes on the OTHER fields since that's what defines changes to this one
$.each(field.constraints, function (key, relationshipName) {
var fieldName = field.field_name,
f = field,
constraintsLength = self.getFieldConstraintsLength(key);
self.viewModel[key].subscribe(function (val) {
if (self.viewModel.freezeConstraints || f.loadingOptions())
return;
//if this key hasn't been set up yet, set it
if (!self.viewModel.constraintsQueue[key])
self.viewModel.constraintsQueue[key] = {};
//add the constraint to the queue
self.viewModel.constraintsQueue[key][fieldName] = f;
var currentQueueLength = Object.keys(self.viewModel.constraintsQueue[key]).length;
if (!self.viewModel.holdConstraintsQueue && (currentQueueLength === constraintsLength))
self.runConstraintsQueue();
});
});
},
/**
* Sets the constrainer's constraintLoading field to true
*
* @param string key
*
* @return int
*/
getFieldConstraintsLength: function (key) {
var length = 0;
//iterate over the edit fields until we find our match
$.each(this.viewModel.editFields(), function (ind, field) {
if (field.constraints && field.constraints[key]) {
length++;
}
});
return length;
},
/**
* Sets the constrainer's constraintLoading field to true
*
* @param string key
* @param bool freeze
*/
setConstrainerFreeze: function (key, freeze) {
//iterate over the edit fields until we find our match
$.each(this.viewModel.editFields(), function (ind, field) {
if (field.field_name === key) {
field.constraintLoading(freeze);
return false;
}
});
},
/**
* Sets a field's loadingOptions
*
* @param string fieldName
* @param bool type
*/
setFieldLoadingOptions: function (fieldName, type) {
//iterate over the edit fields until we find our match
$.each(this.viewModel.editFields(), function (ind, field) {
if (field.field_name === fieldName) {
field.loadingOptions(type);
return false;
}
});
},
/**
* Runs the constraints queue
*/
runConstraintsQueue: function () {
var self = this,
fields = self.buildConstraintsFromQueue();
//if there are no fields, exit out
if (!fields.length)
return;
//freeze the actions
self.viewModel.freezeActions(true);
$.ajax({
url: base_url + self.viewModel.modelName() + '/update_options',
type: 'POST',
dataType: 'json',
data: {
fields: fields
},
complete: function () {
self.viewModel.freezeActions(false);
$.each(self.viewModel.constraintsQueue, function (key, fieldConstraints) {
$.each(fieldConstraints, function (fieldName, field) {
self.setFieldLoadingOptions(fieldName, false);
self.setConstrainerFreeze(key, false);
});
});
//clear the constraints queue
self.viewModel.constraintsQueue = {};
self.viewModel.holdConstraintsQueue = false;
},
success: function (response) {
//iterate over the results and put them in the autocomplete array
$.each(response, function (fieldName, el) {
var data = {};
$.each(el, function (i, e) {
data[e.id] = e;
});
self.viewModel[fieldName + '_autocomplete'] = data;
//update the options
self.viewModel.listOptions[fieldName] = el;
});
}
});
},
/**
* Prepares the constraints for the queue job
*/
buildConstraintsFromQueue: function () {
var self = this,
allConstraints = [];
$.each(self.viewModel.constraintsQueue, function (key, fieldConstraints) {
$.each(fieldConstraints, function (fieldName, field) {
var constraints = {};
//set the field to loading and freeze the constrainer
self.setFieldLoadingOptions(fieldName, true);
self.setConstrainerFreeze(key, true);
//iterate over this field's constraints
$.each(field.constraints, function (key, relationshipName) {
constraints[key] = self.viewModel[key]();
});
allConstraints.push({
constraints: constraints,
type: 'edit',
field: fieldName,
selectedItems: self.viewModel[fieldName]()
});
});
});
return allConstraints;
},
/**
* Inits the page events
*/
initEvents: function () {
var self = this;
//clicking the new item button
$('#content').on('click', 'div.results_header a.new_item', function (e) {
e.preventDefault();
History.pushState({
modelName: self.viewModel.modelName(),
id: 0
}, document.title, route + self.viewModel.modelName() + '/new');
});
//resizing the window
$(window).resize(self.resizePage);
//mousedowning or keypressing anywhere should resize the page as well
$('body').on('mouseup keypress', self.resizePage);
//set up the history event callback
History.Adapter.bind(window, 'statechange', function () {
var state = History.getState();
//if the ignore key is true, or if this is the inital state, exit out.
if (state.data.ignore || (state.data.init && !self.historyStarted))
return;
//if the model name is present
if ('modelName' in state.data)
//if that model name isn't the current model name, we are updating the model
if (state.data.modelName !== self.viewModel.modelName())
//get the new model
self.viewModel.getNewModel(state.data);
//if the state data has an id field and if it's not the active item
if ('id' in state.data) {
//get the new item (this includes when state.data.id === 0, which means it should be a new item)
if (state.data.id !== self.viewModel.activeItem())
self.viewModel.getItem(state.data.id);
}
else {
//otherwise, assume that the user wants to be taken back to the results page. close the form
self.viewModel.clearItem();
}
});
},
/**
* Sets up the push state's initial state
*/
initHistory: function () {
var historyData = {
modelName: this.viewModel.modelName(),
init: true
},
uri = route + this.viewModel.modelName();
//if the admin data had an id supplied, it means this is either the edit page or the new item page
if ('id' in adminData) {
//if the view model hasn't been set up yet, wait for it to be set up
var timer = setInterval(function () {
if (window.admin) {
window.admin.viewModel.getItem(adminData.id);
historyData.id = adminData.id;
uri += '/' + (historyData.id ? historyData.id : 'new');
//now call the same to trigger the statechange event
History.pushState(historyData, document.title, uri);
clearInterval(timer);
}
}, 100);
}
this.historyStarted = true;
},
/**
* Initializes the computed observables
*/
initComputed: function () {
//pagination information
this.viewModel.pagination.isFirst = ko.computed(function () {
return this.pagination.page() == 1;
}, this.viewModel);
this.viewModel.pagination.isLast = ko.computed(function () {
return this.pagination.page() == this.pagination.last();
}, this.viewModel);
},
/**
* Helper to get an object's size
*
* @param object
*
* @return int
*/
getObjectSize: function (obj) {
var size = 0, key;
for (key in obj) {
if (obj.hasOwnProperty(key)) size++;
}
return size;
},
/**
* Handles a window resize to make sure the admin area is always
*/
resizePage: function () {
setTimeout(function () {
var winHeight = $(window).height(),
itemEditHeight = $('div.item_edit').outerHeight() + 50,
usedHeight = winHeight > itemEditHeight ? winHeight - 45 : itemEditHeight,
size = window.getComputedStyle(document.body, ':after').getPropertyValue('content');
//resize the page height
$('#admin_page').css({minHeight: usedHeight + 45});
//resize or scroll the data table
if (window.admin) {
if (!window.admin.dataTableScrollable)
window.admin.resizeDataTable();
else
window.admin.scrollDataTable();
}
// Popover with html
$('.popover-with-html').popover({
html: true,
// trigger : 'click hover',
trigger: 'manual',
container: 'body',
placement: 'top',
delay: {show: 50, hide: 400},
content: function () {
return $(this).attr('hint');
}
}).on("mouseenter", function () {
var _this = this;
$(this).popover("show");
$(".popover").on("mouseleave", function () {
$(_this).popover('hide');
});
}).on("mouseleave", function () {
var _this = this;
setTimeout(function () {
if (!$(".popover:hover").length) {
$(_this).popover("hide");
}
}, 400);
});
}, 50);
},
/**
* Allows to scroll wide data tables (alternative to resizeDataTable)
*/
scrollDataTable: function () {
if (!self.$tableContainer) {
self.$tableContainer = $('div.table_container');
self.$dataTable = self.$tableContainer.find('table.results');
}
// exit if table is already wrapped
if (self.$dataTable.parent().hasClass('table_scrollable')) return true;
// wrap table within div.table_scrollable
self.$dataTable.wrap('<div class="table_scrollable"></div>')
},
/**
* Hides columns until the table container is at least as wide as the data table
*/
resizeDataTable: function () {
var self = window.admin,
winWidth = $(window).width();
if (!self.$tableContainer) {
self.$tableContainer = $('div.table_container');
self.$dataTable = self.$tableContainer.find('table.results');
}
//grab the columns
var columns = self.viewModel.columns();
//iterate over the column hide points to see if we should unhide any of them
$.each(self.columnHidePoints, function (i, el) {
if (el < winWidth)
columns[i].visible(true);
});
//walk backwards over the columns to determine which ones to hide
for (var i = columns.length - 1; i >= 2; i--) {
//if the datatable is visible and the table is large than its container
if (columns.length >= 2 && self.$dataTable.is(':visible') && (self.$tableContainer.width() < self.$dataTable.width())) {
//we don't want to hide all the columns
if (i <= 1)
return;
if (columns[i].visible()) {
columns[i].visible(false);
self.columnHidePoints[i] = winWidth;
break;
}
}
}
}
};
//set up the admin instance
$(function () {
if ($('#admin_page').length) {
window.admin = new admin();
}
// 二维码
var qrcode = new QRCode(document.getElementById('qrcode-img'), {
text: 'http://tianyinzaixian.com',
width: 320,
height: 320
});
// $('#qrcode-img').attr('title', '')
$(document).on('click', '.get-qrcode-btn', function (e) {
e.preventDefault();
// 重新生成二维码
qrcode.clear(); // clear the code.
qrcode.makeCode($(this).attr('data-link')); // make another code.
$('#getQrcode').modal('show');
});
// select all items
$('#select-all').on('click', function () {
var checked = false;
if ($(this).is(':checked')) {
$('.select-checkbox').prop('checked', true);
checked = true;
} else {
$('.select-checkbox').prop('checked', false);
}
if (checked && $('.select-checkbox').length) {
$('#delete-all').removeClass('disabled');
} else {
$('#delete-all').addClass('disabled');
}
});
// disable delete-all btn
$('.select-checkbox').on('click', function () {
var selected = 0;
$('.select-checkbox').each(function (i, el) {
if ($(el).is(':checked')) {
selected++;
}
});
if (selected > 0) {
$('#delete-all').removeClass('disabled');
} else {
$('#delete-all').addClass('disabled');
}
});
$('[data-toggle="tooltip"]').tooltip();
});
})(jQuery);