diff --git a/src/octoprint/static/js/app/main.js b/src/octoprint/static/js/app/main.js index 61d25b64..49e718ad 100644 --- a/src/octoprint/static/js/app/main.js +++ b/src/octoprint/static/js/app/main.js @@ -182,34 +182,33 @@ $(function() { } // helper to create a view model instance with injected constructor parameters from the view model map - var _createViewModelInstance = function(viewModel, viewModelMap){ - var viewModelClass = viewModel[0]; - var viewModelParameters = viewModel[1]; + var _createViewModelInstance = function(viewModel, viewModelMap) { - if (viewModelParameters != undefined) { - if (!_.isArray(viewModelParameters)) { - viewModelParameters = [viewModelParameters]; + // mirror the requested dependencies with an array of the viewModels + var viewModelParametersMap = function(parameter) { + // check if parameter is found within optional array and if all conditions are met return null instead of undefined + if (optionalDependencyPass && viewModel.optional.indexOf(parameter) !== -1 && !viewModelMap[parameter]) { + log.debug("Resolving optional parameter", [parameter], "without viewmodel"); + return null } - // now we'll try to resolve all of the view model's constructor parameters via our view model map - var constructorParameters = _.map(viewModelParameters, function(parameter){ - return viewModelMap[parameter] - }); - } else { - constructorParameters = []; - } + return viewModelMap[parameter] || undefined + }; - if (_.some(constructorParameters, function(parameter) { return parameter === undefined; })) { - var _extractName = function(entry) { return entry[0]; }; - var _onlyUnresolved = function(entry) { return entry[1] === undefined; }; - var missingParameters = _.map(_.filter(_.zip(viewModelParameters, constructorParameters), _onlyUnresolved), _extractName); - log.debug("Postponing", viewModel[0].name, "due to missing parameters:", missingParameters); + // try to resolve all of the view model's constructor parameters via our view model map + var constructorParameters = _.map(viewModel.dependencies, viewModelParametersMap) || []; + + if (constructorParameters.indexOf(undefined) !== -1) { + log.debug("Postponing", viewModel.name, "due to missing parameters:", _.keys(_.pick(_.object(viewModel.dependencies, constructorParameters), _.isUndefined))); return; } + // transform array into object if a plugin wants it as an object + constructorParameters = (viewModel.returnObject) ? _.object(viewModel.dependencies, constructorParameters) : constructorParameters; + // if we came this far then we could resolve all constructor parameters, so let's construct that view model - log.debug("Constructing", viewModel[0].name, "with parameters:", viewModelParameters); - return new viewModelClass(constructorParameters); + log.debug("Constructing", viewModel.name, "with parameters:", viewModel.dependencies); + return new viewModel.construct(constructorParameters); }; // map any additional view model bindings we might need to make @@ -229,8 +228,7 @@ $(function() { }); // helper for translating the name of a view model class into an identifier for the view model map - var _getViewModelId = function(viewModel){ - var name = viewModel[0].name; + var _getViewModelId = function(name){ return name.substr(0, 1).toLowerCase() + name.substr(1); // FooBarViewModel => fooBarViewModel }; @@ -243,6 +241,7 @@ $(function() { var allViewModels = []; var allViewModelData = []; var pass = 1; + var optionalDependencyPass = false; log.info("Starting dependency resolution..."); while (unprocessedViewModels.length > 0) { log.debug("Dependency resolution, pass #" + pass); @@ -252,11 +251,34 @@ $(function() { // now try to instantiate every one of our as of yet unprocessed view model descriptors while (unprocessedViewModels.length > 0){ var viewModel = unprocessedViewModels.shift(); - var viewModelId = _getViewModelId(viewModel); + + // wrap anything not object related into an object + if(!_.isPlainObject(viewModel)) { + viewModel = { + construct: (_.isArray(viewModel)) ? viewModel[0] : viewModel, + dependencies: viewModel[1] || [], + elements: viewModel[2] || [], + optional: viewModel[3] || [] + }; + } + + // make sure we have atleast a function + if (!_.isFunction(viewModel.construct)) { + log.error("No function to instantiate with", viewModel); + continue; + } + + // if name is not set, get name from constructor, if it's an anonymous function generate one + viewModel.name = viewModel.name || _getViewModelId(viewModel.construct.name) || _.uniqueId("unnamedViewModel"); + + // make sure all value's are in an array + viewModel.dependencies = (_.isArray(viewModel.dependencies)) ? viewModel.dependencies : [viewModel.dependencies]; + viewModel.elements = (_.isArray(viewModel.elements)) ? viewModel.elements : [viewModel.elements]; + viewModel.optional = (_.isArray(viewModel.optional)) ? viewModel.optional : [viewModel.optional]; // make sure that we don't have two view models going by the same name - if (_.has(viewModelMap, viewModelId)) { - log.error("Duplicate name while instantiating " + viewModelId); + if (_.has(viewModelMap, viewModel.name)) { + log.error("Duplicate name while instantiating " + viewModel.name); continue; } @@ -269,18 +291,15 @@ $(function() { } // we could resolve the depdendencies and the view model is not defined yet => add it, it's now fully processed - var viewModelBindTargets = viewModel[2]; - if (!_.isArray(viewModelBindTargets)) { - viewModelBindTargets = [viewModelBindTargets]; - } + var viewModelBindTargets = viewModel.elements; - if (additionalBindings.hasOwnProperty(viewModelId)) { - viewModelBindTargets = viewModelBindTargets.concat(additionalBindings[viewModelId]); + if (additionalBindings.hasOwnProperty(viewModel.name)) { + viewModelBindTargets = viewModelBindTargets.concat(additionalBindings[viewModel.name]); } allViewModelData.push([viewModelInstance, viewModelBindTargets]); allViewModels.push(viewModelInstance); - viewModelMap[viewModelId] = viewModelInstance; + viewModelMap[viewModel.name] = viewModelInstance; } // anything that's now in the postponed list has to be readded to the unprocessedViewModels @@ -289,12 +308,18 @@ $(function() { // if we still have the same amount of items in our list of unprocessed view models it means that we // couldn't instantiate any more view models over a whole iteration, which in turn mean we can't resolve the // dependencies of remaining ones, so log that as an error and then quit the loop - if (unprocessedViewModels.length == startLength) { - log.error("Could not instantiate the following view models due to unresolvable dependencies:"); - _.each(unprocessedViewModels, function(entry) { - log.error(entry[0].name + " (missing: " + _.filter(entry[1], function(id) { return !_.has(viewModelMap, id); }).join(", ") + " )"); - }); - break; + if (unprocessedViewModels.length === startLength) { + // I'm gonna let you finish but we will do another pass with the optional dependencies flag enabled + if(!optionalDependencyPass) { + log.debug("Resolving next pass with optional dependencies flag enabled"); + optionalDependencyPass = true; + } else { + log.error("Could not instantiate the following view models due to unresolvable dependencies:"); + _.each(unprocessedViewModels, function(entry) { + log.error(entry.name + " (missing: " + _.filter(entry.dependencies, function(id) { return !_.has(viewModelMap, id); }).join(", ") + " )"); + }); + break; + } } log.debug("Dependency resolution pass #" + pass + " finished, " + unprocessedViewModels.length + " view models left to process");