Static Monthly Calendar, Part III

“Mistakes are the portals of discovery.”
James Joyce

While experimenting with a number of various configurations for my Static Monthly Calendar, I ran into a number of issue that led me to make a few adjustments to the code, and eventually, to actually build a few new parts that I am hoping might come in handy in some future effort. The first problem that I ran into was when I tried to configure a content provider from a scoped app. The code that I was using to instantiate a content provider using the name was this:

var ClassFromString = this[options.content_provider];
contentProvider = new ClassFromString();

This works great for a global Script Include, but for a scoped component, you end up with this:

var ClassFromString = this['my_scope.MyScriptInclude'];

… when what you really need is this:

var ClassFromString = this['my_scope']['MyScriptInclude'];

I started to fix that by adding code to the widget, but then I decided that it was code that would probably be useful in other circumstances, so I ended up creating a separate global component to turn an object name into an instance of that object. That code turned out to look like this:

var Instantiator = Class.create();
Instantiator.prototype = {
	initialize: function() {
	},

	_root: null,

	setRoot: function(root) {
		this._root = root;
	},

	getInstance: function(name) {
		var instance;

		var scope;
		var parts = name.split('.');
		if (parts.length == 2) {
			scope = parts[0];
			name = parts[1];
		}
		var ClassFromString;
		try {
			if (scope) {
				ClassFromString = this._root[scope][name];
			} else {
				ClassFromString = this._root[name];
			}
			instance = new ClassFromString();
		} catch(e) {
			gs.error('Unable to instantiate instance named "' + name + '": ' + e);
		}

		return instance;
	},

	type: 'Instantiator'
};

This handles both global and scoped components, and also simplified the code in the widget, which turned out to be just this:

contentProvider = instantiator.getInstance(options.content_provider);

Another issue that I ran into was when I tried to inject content that allowed the user to click on an event to bring up some additional details about the event in a modal pop-up. I created a function called showDetails to handle the modal pop-up, and then added an ng-click to the enclosing DIV of the HTML provided by my example content provider call this new function. Unfortunately, the ng-click, which was added to the page with the rest of the provided content, was inserted using an ng-bind-html attribute, which simply copies in the raw HTML and doesn’t actually compile the AngularJS code. I tried various approaches to compiling the code myself, but I was never able to get any of those to work. Then I came across this, which seemed like just the thing that I needed. I thought about installing in in my instance, but then I thought that I had better check first, because it’s entirely possible that it is already in there. Sure enough, I came across the Angular Provider scBindHtmlCompile, which seemed like a version of the very same thing. So I attached it to my widget and replaced by ng-bind-html with sc-bind-html-compile.

Unfortunately, that just put the compiler into an endless loop, which ultimately resulted in filling up the Javascript console with quite a few of these error messages:

Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!

I searched around for a solution to that problem, but nothing that I tried would get around the problem. I ended up going in the opposite direction and swapping out the ng-click for an onclick, which doesn’t need to be compiled. Of course, the onclick can’t see any of the functions inside the scope of the app, so I had to write a stand-alone UI Script to include with a script tag in order to have a function to call. That function is outside of the scope of the app as well, so I ended up turning the script into yet another generic part that uses the element to get you back to the widget:

function functionBroker(id, func, arg1, arg2, arg3, arg4) {
	var scope = angular.element(document.getElementById(id)).scope();
	scope.$apply(function() {
		scope[func](arg1, arg2, arg3, arg4);
	});
}

You pass it the ID of your HTML element, the name of function that is in scope, and up to four independent arguments that you would like to pass to the function. It uses the element to locate the scope, and then uses the scope to find your desired function and passes in the arguments. After saving the new generic script, I went back into the widget and added a script tag to the widget’s HTML to pull the script onto the page.

<script type="text/javascript" src="/function_broker.jsdbx"></script>

Then I added a function to pop open a modal dialog based on a configuration object passed into the function.

$scope.showDetails = function(modalConfig) {
	spModal.open(modalConfig);
};

Now, I just needed something to pop up to see if it all worked. Not too long ago I made a simple widget to show off my rating type form field, and that looked like a good candidate to use just to see if everything was going to work out the way that it should. I pulled up the ExampleContentProvider that I created earlier, and added one more event in the middle of the month that would bring up this repurposed widget when clicked.

if (dd == 15) {
	response += '<div class="event" id="event15" onclick="functionBroker(\'event15\', \'showDetails\', {title: \'Fifteenth of the Month Celebration\', widget:\'feedback-example\', size: \'lg\'});" style="cursor: pointer;">\n';
	response += '  <div class="event-desc">\n';
	response += '    Fifteenth of the Month Celebration\n';
	response += '  </div>\n';
	response += '  <div class="event-time">\n';
	response += '    Party Time\n';
	response += '  </div>\n';
	response += '</div>\n';

The whole thing is kind of a Rube Goldberg operation, but it should work, so let’s light things up and give it a try.

Modal pop-up from clicking on an Event

After all of the failed attempts at making this happen, it’s nice to see the modal dialog actually appear on the screen! It still seems like there has got to be a simpler way to make this work, but until I figure that out, this will do. If you’d like to play around with it yourself, here’s an Update Set that I hope includes all of the right pieces. There are still a few little things that I would like to add one day, so this may not quite be the last you will see of this one.