690 lines
21 KiB
JavaScript
690 lines
21 KiB
JavaScript
function ItemListHelper(listType, supportedSorting, supportedFilters, defaultSorting, defaultFilters, exclusiveFilters, filesPerPage) {
|
|
var self = this;
|
|
|
|
self.listType = listType;
|
|
self.supportedSorting = supportedSorting;
|
|
self.supportedFilters = supportedFilters;
|
|
self.defaultSorting = defaultSorting;
|
|
self.defaultFilters = defaultFilters;
|
|
self.exclusiveFilters = exclusiveFilters;
|
|
|
|
self.searchFunction = undefined;
|
|
|
|
self.allItems = [];
|
|
self.allSize = ko.observable(0);
|
|
|
|
self.items = ko.observableArray([]);
|
|
self.pageSize = ko.observable(filesPerPage);
|
|
self.currentPage = ko.observable(0);
|
|
self.currentSorting = ko.observable(self.defaultSorting);
|
|
self.currentFilters = ko.observableArray(self.defaultFilters);
|
|
self.selectedItem = ko.observable(undefined);
|
|
|
|
//~~ item handling
|
|
|
|
self.refresh = function() {
|
|
self._updateItems();
|
|
};
|
|
|
|
self.updateItems = function(items) {
|
|
self.allItems = items;
|
|
self.allSize(items.length);
|
|
self._updateItems();
|
|
};
|
|
|
|
self.selectItem = function(matcher) {
|
|
var itemList = self.items();
|
|
for (var i = 0; i < itemList.length; i++) {
|
|
if (matcher(itemList[i])) {
|
|
self.selectedItem(itemList[i]);
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
self.selectNone = function() {
|
|
self.selectedItem(undefined);
|
|
};
|
|
|
|
self.isSelected = function(data) {
|
|
return self.selectedItem() == data;
|
|
};
|
|
|
|
self.isSelectedByMatcher = function(matcher) {
|
|
return matcher(self.selectedItem());
|
|
};
|
|
|
|
self.removeItem = function(matcher) {
|
|
var item = self.getItem(matcher, true);
|
|
if (item === undefined) {
|
|
return;
|
|
}
|
|
|
|
var index = self.allItems.indexOf(item);
|
|
if (index > -1) {
|
|
self.allItems.splice(index, 1);
|
|
self._updateItems();
|
|
}
|
|
};
|
|
|
|
//~~ pagination
|
|
|
|
self.paginatedItems = ko.dependentObservable(function() {
|
|
if (self.items() == undefined) {
|
|
return [];
|
|
} else if (self.pageSize() == 0) {
|
|
return self.items();
|
|
} else {
|
|
var from = Math.max(self.currentPage() * self.pageSize(), 0);
|
|
var to = Math.min(from + self.pageSize(), self.items().length);
|
|
return self.items().slice(from, to);
|
|
}
|
|
});
|
|
self.lastPage = ko.dependentObservable(function() {
|
|
return (self.pageSize() == 0 ? 1 : Math.ceil(self.items().length / self.pageSize()) - 1);
|
|
});
|
|
self.pages = ko.dependentObservable(function() {
|
|
var pages = [];
|
|
if (self.pageSize() == 0) {
|
|
pages.push({ number: 0, text: 1 });
|
|
} else if (self.lastPage() < 7) {
|
|
for (var i = 0; i < self.lastPage() + 1; i++) {
|
|
pages.push({ number: i, text: i+1 });
|
|
}
|
|
} else {
|
|
pages.push({ number: 0, text: 1 });
|
|
if (self.currentPage() < 5) {
|
|
for (var i = 1; i < 5; i++) {
|
|
pages.push({ number: i, text: i+1 });
|
|
}
|
|
pages.push({ number: -1, text: "…"});
|
|
} else if (self.currentPage() > self.lastPage() - 5) {
|
|
pages.push({ number: -1, text: "…"});
|
|
for (var i = self.lastPage() - 4; i < self.lastPage(); i++) {
|
|
pages.push({ number: i, text: i+1 });
|
|
}
|
|
} else {
|
|
pages.push({ number: -1, text: "…"});
|
|
for (var i = self.currentPage() - 1; i <= self.currentPage() + 1; i++) {
|
|
pages.push({ number: i, text: i+1 });
|
|
}
|
|
pages.push({ number: -1, text: "…"});
|
|
}
|
|
pages.push({ number: self.lastPage(), text: self.lastPage() + 1})
|
|
}
|
|
return pages;
|
|
});
|
|
|
|
self.switchToItem = function(matcher) {
|
|
var pos = -1;
|
|
var itemList = self.items();
|
|
for (var i = 0; i < itemList.length; i++) {
|
|
if (matcher(itemList[i])) {
|
|
pos = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (pos > -1) {
|
|
var page = Math.floor(pos / self.pageSize());
|
|
self.changePage(page);
|
|
}
|
|
};
|
|
|
|
self.changePage = function(newPage) {
|
|
if (newPage < 0 || newPage > self.lastPage())
|
|
return;
|
|
self.currentPage(newPage);
|
|
}; self.prevPage = function() {
|
|
if (self.currentPage() > 0) {
|
|
self.currentPage(self.currentPage() - 1);
|
|
}
|
|
};
|
|
self.nextPage = function() {
|
|
if (self.currentPage() < self.lastPage()) {
|
|
self.currentPage(self.currentPage() + 1);
|
|
}
|
|
};
|
|
|
|
self.getItem = function(matcher, all) {
|
|
var itemList;
|
|
if (all !== undefined && all === true) {
|
|
itemList = self.allItems;
|
|
} else {
|
|
itemList = self.items();
|
|
}
|
|
for (var i = 0; i < itemList.length; i++) {
|
|
if (matcher(itemList[i])) {
|
|
return itemList[i];
|
|
}
|
|
}
|
|
|
|
return undefined;
|
|
};
|
|
|
|
//~~ searching
|
|
|
|
self.changeSearchFunction = function(searchFunction) {
|
|
self.searchFunction = searchFunction;
|
|
self.changePage(0);
|
|
self._updateItems();
|
|
};
|
|
|
|
self.resetSearch = function() {
|
|
self.changeSearchFunction(undefined);
|
|
};
|
|
|
|
//~~ sorting
|
|
|
|
self.changeSorting = function(sorting) {
|
|
if (!_.contains(_.keys(self.supportedSorting), sorting))
|
|
return;
|
|
|
|
self.currentSorting(sorting);
|
|
self._saveCurrentSortingToLocalStorage();
|
|
|
|
self.changePage(0);
|
|
self._updateItems();
|
|
};
|
|
|
|
//~~ filtering
|
|
|
|
self.toggleFilter = function(filter) {
|
|
if (!_.contains(_.keys(self.supportedFilters), filter))
|
|
return;
|
|
|
|
if (_.contains(self.currentFilters(), filter)) {
|
|
self.removeFilter(filter);
|
|
} else {
|
|
self.addFilter(filter);
|
|
}
|
|
};
|
|
|
|
self.addFilter = function(filter) {
|
|
if (!_.contains(_.keys(self.supportedFilters), filter))
|
|
return;
|
|
|
|
for (var i = 0; i < self.exclusiveFilters.length; i++) {
|
|
if (_.contains(self.exclusiveFilters[i], filter)) {
|
|
for (var j = 0; j < self.exclusiveFilters[i].length; j++) {
|
|
if (self.exclusiveFilters[i][j] == filter)
|
|
continue;
|
|
self.removeFilter(self.exclusiveFilters[i][j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
var filters = self.currentFilters();
|
|
filters.push(filter);
|
|
self.currentFilters(filters);
|
|
self._saveCurrentFiltersToLocalStorage();
|
|
|
|
self.changePage(0);
|
|
self._updateItems();
|
|
};
|
|
|
|
self.removeFilter = function(filter) {
|
|
if (!_.contains(_.keys(self.supportedFilters), filter))
|
|
return;
|
|
|
|
var filters = self.currentFilters();
|
|
filters.pop(filter);
|
|
self.currentFilters(filters);
|
|
self._saveCurrentFiltersToLocalStorage();
|
|
|
|
self.changePage(0);
|
|
self._updateItems();
|
|
};
|
|
|
|
//~~ update for sorted and filtered view
|
|
|
|
self._updateItems = function() {
|
|
// determine comparator
|
|
var comparator = undefined;
|
|
var currentSorting = self.currentSorting();
|
|
if (typeof currentSorting !== undefined && typeof self.supportedSorting[currentSorting] !== undefined) {
|
|
comparator = self.supportedSorting[currentSorting];
|
|
}
|
|
|
|
// work on all items
|
|
var result = self.allItems;
|
|
|
|
// filter if necessary
|
|
var filters = self.currentFilters();
|
|
_.each(filters, function(filter) {
|
|
if (typeof filter !== undefined && typeof supportedFilters[filter] !== undefined)
|
|
result = _.filter(result, supportedFilters[filter]);
|
|
});
|
|
|
|
// search if necessary
|
|
if (typeof self.searchFunction !== undefined && self.searchFunction) {
|
|
result = _.filter(result, self.searchFunction);
|
|
}
|
|
|
|
// sort if necessary
|
|
if (typeof comparator !== undefined)
|
|
result.sort(comparator);
|
|
|
|
// set result list
|
|
self.items(result);
|
|
};
|
|
|
|
//~~ local storage
|
|
|
|
self._saveCurrentSortingToLocalStorage = function() {
|
|
if ( self._initializeLocalStorage() ) {
|
|
var currentSorting = self.currentSorting();
|
|
if (currentSorting !== undefined)
|
|
localStorage[self.listType + "." + "currentSorting"] = currentSorting;
|
|
else
|
|
localStorage[self.listType + "." + "currentSorting"] = undefined;
|
|
}
|
|
};
|
|
|
|
self._loadCurrentSortingFromLocalStorage = function() {
|
|
if ( self._initializeLocalStorage() ) {
|
|
if (_.contains(_.keys(supportedSorting), localStorage[self.listType + "." + "currentSorting"]))
|
|
self.currentSorting(localStorage[self.listType + "." + "currentSorting"]);
|
|
else
|
|
self.currentSorting(defaultSorting);
|
|
}
|
|
};
|
|
|
|
self._saveCurrentFiltersToLocalStorage = function() {
|
|
if ( self._initializeLocalStorage() ) {
|
|
var filters = _.intersection(_.keys(self.supportedFilters), self.currentFilters());
|
|
localStorage[self.listType + "." + "currentFilters"] = JSON.stringify(filters);
|
|
}
|
|
};
|
|
|
|
self._loadCurrentFiltersFromLocalStorage = function() {
|
|
if ( self._initializeLocalStorage() ) {
|
|
self.currentFilters(_.intersection(_.keys(self.supportedFilters), JSON.parse(localStorage[self.listType + "." + "currentFilters"])));
|
|
}
|
|
};
|
|
|
|
self._initializeLocalStorage = function() {
|
|
if (!Modernizr.localstorage)
|
|
return false;
|
|
|
|
if (localStorage[self.listType + "." + "currentSorting"] !== undefined && localStorage[self.listType + "." + "currentFilters"] !== undefined && JSON.parse(localStorage[self.listType + "." + "currentFilters"]) instanceof Array)
|
|
return true;
|
|
|
|
localStorage[self.listType + "." + "currentSorting"] = self.defaultSorting;
|
|
localStorage[self.listType + "." + "currentFilters"] = JSON.stringify(self.defaultFilters);
|
|
|
|
return true;
|
|
};
|
|
|
|
self._loadCurrentFiltersFromLocalStorage();
|
|
self._loadCurrentSortingFromLocalStorage();
|
|
}
|
|
|
|
function formatSize(bytes) {
|
|
if (!bytes) return "-";
|
|
|
|
var units = ["bytes", "KB", "MB", "GB"];
|
|
for (var i = 0; i < units.length; i++) {
|
|
if (bytes < 1024) {
|
|
return _.sprintf("%3.1f%s", bytes, units[i]);
|
|
}
|
|
bytes /= 1024;
|
|
}
|
|
return _.sprintf("%.1f%s", bytes, "TB");
|
|
}
|
|
|
|
function formatDuration(seconds) {
|
|
if (!seconds) return "-";
|
|
if (seconds < 0) return "00:00:00";
|
|
|
|
var s = seconds % 60;
|
|
var m = (seconds % 3600) / 60;
|
|
var h = seconds / 3600;
|
|
|
|
return _.sprintf(gettext(/* L10N: duration format */ "%(hour)02d:%(minute)02d:%(second)02d"), {hour: h, minute: m, second: s});
|
|
}
|
|
|
|
function formatFuzzyEstimation(seconds, base) {
|
|
if (!seconds) return "-";
|
|
if (seconds < 0) return "-";
|
|
|
|
var m;
|
|
if (base != undefined) {
|
|
m = moment(base);
|
|
} else {
|
|
m = moment();
|
|
}
|
|
|
|
m.add(seconds, "s");
|
|
return m.fromNow(true);
|
|
}
|
|
|
|
function formatDate(unixTimestamp) {
|
|
if (!unixTimestamp) return "-";
|
|
return moment.unix(unixTimestamp).format(gettext(/* L10N: Date format */ "YYYY-MM-DD HH:mm"));
|
|
}
|
|
|
|
function formatTimeAgo(unixTimestamp) {
|
|
if (!unixTimestamp) return "-";
|
|
return moment.unix(unixTimestamp).fromNow();
|
|
}
|
|
|
|
function formatFilament(filament) {
|
|
if (!filament || !filament["length"]) return "-";
|
|
var result = "%(length).02fm";
|
|
if (filament.hasOwnProperty("volume") && filament.volume) {
|
|
result += " / " + "%(volume).02fcm³";
|
|
}
|
|
return _.sprintf(result, {length: filament["length"] / 1000, volume: filament["volume"]});
|
|
}
|
|
|
|
function cleanTemperature(temp) {
|
|
if (!temp || temp < 10) return gettext("off");
|
|
return temp;
|
|
}
|
|
|
|
function formatTemperature(temp) {
|
|
if (!temp || temp < 10) return gettext("off");
|
|
return _.sprintf("%.1f°C", temp);
|
|
}
|
|
|
|
function pnotifyAdditionalInfo(inner) {
|
|
return '<div class="pnotify_additional_info">'
|
|
+ '<div class="pnotify_more"><a href="#" onclick="$(this).children().toggleClass(\'icon-caret-right icon-caret-down\').parent().parent().next().slideToggle(\'fast\')">More <i class="icon-caret-right"></i></a></div>'
|
|
+ '<div class="pnotify_more_container hide">' + inner + '</div>'
|
|
+ '</div>';
|
|
}
|
|
|
|
function ping(url, callback) {
|
|
var img = new Image();
|
|
var calledBack = false;
|
|
|
|
img.onload = function() {
|
|
callback(true);
|
|
calledBack = true;
|
|
};
|
|
img.onerror = function() {
|
|
if (!calledBack) {
|
|
callback(true);
|
|
calledBack = true;
|
|
}
|
|
};
|
|
img.src = url;
|
|
setTimeout(function() {
|
|
if (!calledBack) {
|
|
callback(false);
|
|
calledBack = true;
|
|
}
|
|
}, 1500);
|
|
}
|
|
|
|
function showOfflineOverlay(title, message, reconnectCallback) {
|
|
if (title == undefined) {
|
|
title = gettext("Server is offline");
|
|
}
|
|
|
|
$("#offline_overlay_title").text(title);
|
|
$("#offline_overlay_message").html(message);
|
|
$("#offline_overlay_reconnect").click(reconnectCallback);
|
|
if (!$("#offline_overlay").is(":visible"))
|
|
$("#offline_overlay").show();
|
|
}
|
|
|
|
function hideOfflineOverlay() {
|
|
$("#offline_overlay").hide();
|
|
}
|
|
|
|
function showMessageDialog(msg, options) {
|
|
options = options || {};
|
|
if (_.isPlainObject(msg)) {
|
|
options = msg;
|
|
} else {
|
|
options.message = msg;
|
|
}
|
|
|
|
var title = options.title || "";
|
|
var message = options.message || "";
|
|
var close = options.close || gettext("Close");
|
|
var onclose = options.onclose || undefined;
|
|
|
|
if (_.isString(message)) {
|
|
message = $("<p>" + message + "</p>");
|
|
}
|
|
|
|
var modalHeader = $('<a href="javascript:void(0)" class="close" data-dismiss="modal" aria-hidden="true">×</a><h3>' + title + '</h3>');
|
|
var modalBody = $(message);
|
|
var modalFooter = $('<a href="javascript:void(0)" class="btn" data-dismiss="modal" aria-hidden="true">' + close + '</a>');
|
|
|
|
var modal = $('<div></div>')
|
|
.addClass('modal hide fade')
|
|
.append($('<div></div>').addClass('modal-header').append(modalHeader))
|
|
.append($('<div></div>').addClass('modal-body').append(modalBody))
|
|
.append($('<div></div>').addClass('modal-footer').append(modalFooter));
|
|
|
|
modal.on("hidden", function() {
|
|
if (onclose && _.isFunction(onclose)) {
|
|
onclose();
|
|
}
|
|
});
|
|
|
|
modal.modal("show");
|
|
return modal;
|
|
}
|
|
|
|
function showConfirmationDialog(msg, onacknowledge, options) {
|
|
options = options || {};
|
|
if (_.isPlainObject(msg)) {
|
|
options = msg;
|
|
} else {
|
|
options.message = msg;
|
|
options.onproceed = onacknowledge;
|
|
}
|
|
|
|
var title = options.title || gettext("Are you sure?");
|
|
var message = options.message || "";
|
|
var question = options.question || gettext("Are you sure you want to proceed?");
|
|
var cancel = options.cancel || gettext("Cancel");
|
|
var proceed = options.proceed || gettext("Proceed");
|
|
var proceedClass = options.proceedClass || "danger";
|
|
var onproceed = options.onproceed || undefined;
|
|
|
|
var modalHeader = $('<a href="javascript:void(0)" class="close" data-dismiss="modal" aria-hidden="true">×</a><h3>' + title + '</h3>');
|
|
var modalBody = $('<p>' + message + '</p><p>' + question + '</p>');
|
|
|
|
var cancelButton = $('<a href="javascript:void(0)" class="btn">' + cancel + '</a>')
|
|
.attr("data-dismiss", "modal")
|
|
.attr("aria-hidden", "true");
|
|
var proceedButton = $('<a href="javascript:void(0)" class="btn">' + proceed + '</a>')
|
|
.addClass("btn-" + proceedClass);
|
|
|
|
var modal = $('<div></div>')
|
|
.addClass('modal hide fade')
|
|
.append($('<div></div>').addClass('modal-header').append(modalHeader))
|
|
.append($('<div></div>').addClass('modal-body').append(modalBody))
|
|
.append($('<div></div>').addClass('modal-footer').append(cancelButton).append(proceedButton));
|
|
modal.modal("show");
|
|
|
|
proceedButton.click(function(e) {
|
|
e.preventDefault();
|
|
modal.modal("hide");
|
|
if (onproceed && _.isFunction(onproceed)) {
|
|
onproceed(e);
|
|
}
|
|
});
|
|
|
|
return modal;
|
|
}
|
|
|
|
function commentableLinesToArray(lines) {
|
|
return splitTextToArray(lines, "\n", true, function(item) {return !_.startsWith(item, "#")});
|
|
}
|
|
|
|
function splitTextToArray(text, sep, stripEmpty, filter) {
|
|
return _.filter(
|
|
_.map(
|
|
text.split(sep),
|
|
function(item) { return (item) ? item.trim() : ""; }
|
|
),
|
|
function(item) { return (stripEmpty ? item : true) && (filter ? filter(item) : true); }
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Returns true if comparing data and oldData yields changes, false otherwise.
|
|
*
|
|
* E.g.
|
|
*
|
|
* hasDataChanged(
|
|
* {foo: "bar", fnord: {one: "1", two: "2", three: "three", key: "value"}},
|
|
* {foo: "bar", fnord: {one: "1", two: "2", three: "3", four: "4"}}
|
|
* )
|
|
*
|
|
* will return
|
|
*
|
|
* true
|
|
*
|
|
* and
|
|
*
|
|
* hasDataChanged(
|
|
* {foo: "bar", fnord: {one: "1", two: "2", three: "3"}},
|
|
* {foo: "bar", fnord: {one: "1", two: "2", three: "3"}}
|
|
* )
|
|
*
|
|
* will return
|
|
*
|
|
* false
|
|
*
|
|
* Note that this will assume data and oldData to be structurally identical (same keys)
|
|
* and is optimized to check for value changes, not key updates.
|
|
*/
|
|
function hasDataChanged(data, oldData) {
|
|
if (data == undefined) {
|
|
return false;
|
|
}
|
|
|
|
if (oldData == undefined) {
|
|
return true;
|
|
}
|
|
|
|
if (_.isPlainObject(data)) {
|
|
return _.any(_.keys(data), function(key) {return hasDataChanged(data[key], oldData[key]);});
|
|
} else {
|
|
return !_.isEqual(data, oldData);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Compare provided data and oldData plain objects and only return those
|
|
* substructures of data that actually changed.
|
|
*
|
|
* E.g.
|
|
*
|
|
* getOnlyChangedData(
|
|
* {foo: "bar", fnord: {one: "1", two: "2", three: "three"}},
|
|
* {foo: "bar", fnord: {one: "1", two: "2", three: "3"}}
|
|
* )
|
|
*
|
|
* will return
|
|
*
|
|
* {fnord: {three: "three"}}
|
|
*
|
|
* and
|
|
*
|
|
* getOnlyChangedData(
|
|
* {foo: "bar", fnord: {one: "1", two: "2", three: "3"}},
|
|
* {foo: "bar", fnord: {one: "1", two: "2", three: "3"}}
|
|
* )
|
|
*
|
|
* will return
|
|
*
|
|
* {}
|
|
*
|
|
* Note that this will assume data and oldData to be structurally identical (same keys)
|
|
* and is optimized to check for value changes, not key updates.
|
|
*/
|
|
function getOnlyChangedData(data, oldData) {
|
|
if (data == undefined) {
|
|
return {};
|
|
}
|
|
|
|
if (oldData == undefined) {
|
|
return data;
|
|
}
|
|
|
|
var f = function(root, oldRoot) {
|
|
if (!_.isPlainObject(root)) {
|
|
return root;
|
|
}
|
|
|
|
var retval = {};
|
|
_.forOwn(root, function(value, key) {
|
|
var oldValue = oldRoot[key];
|
|
if (_.isPlainObject(value)) {
|
|
if (hasDataChanged(value, oldValue)) {
|
|
retval[key] = f(value, oldValue);
|
|
}
|
|
} else {
|
|
if (!_.isEqual(value, oldValue)) {
|
|
retval[key] = value;
|
|
}
|
|
}
|
|
});
|
|
return retval;
|
|
};
|
|
|
|
return f(data, oldData);
|
|
}
|
|
|
|
function callViewModels(allViewModels, method, callback) {
|
|
callViewModelsIf(allViewModels, method, undefined, callback);
|
|
}
|
|
|
|
function callViewModelsIf(allViewModels, method, condition, callback) {
|
|
if (condition == undefined || !_.isFunction(condition)) {
|
|
condition = function() { return true; };
|
|
}
|
|
|
|
var parameters = undefined;
|
|
if (!_.isFunction(callback)) {
|
|
// if callback is not a function that means we are supposed to directly
|
|
// call the view model method instead of providing it to the callback
|
|
// - let's figure out how
|
|
|
|
if (callback == undefined) {
|
|
// directly call view model method with no parameters
|
|
parameters = undefined;
|
|
log.trace("Calling method", method, "on view models");
|
|
} else if (_.isArray(callback)) {
|
|
// directly call view model method with these parameters
|
|
parameters = callback;
|
|
log.trace("Calling method", method, "on view models with specified parameters", parameters);
|
|
} else {
|
|
// ok, this doesn't make sense, callback is neither undefined nor
|
|
// an array, we'll return without doing anything
|
|
return;
|
|
}
|
|
|
|
// we reset this here so we now further down that we want to call
|
|
// the method directly
|
|
callback = undefined;
|
|
} else {
|
|
log.trace("Providing method", method, "on view models to specified callback", callback);
|
|
}
|
|
|
|
_.each(allViewModels, function(viewModel) {
|
|
if (viewModel.hasOwnProperty(method) && condition(viewModel, method)) {
|
|
if (callback == undefined) {
|
|
if (parameters != undefined) {
|
|
// call the method with the provided parameters
|
|
viewModel[method].apply(viewModel, parameters);
|
|
} else {
|
|
// call the method without parameters
|
|
viewModel[method]();
|
|
}
|
|
} else {
|
|
// provide the method to the callback
|
|
callback(viewModel[method]);
|
|
}
|
|
}
|
|
});
|
|
}
|