PageSourceSearch

https://www.stempeloase-frank.de/assets/lsjs/core/appBinder/binder…&debug=1&no-cache=1&no-minifier=1

js stempeloase-frank.de collected 2026-10-10 02:01:18 UTC 211,382 bytes, 6,989 lines download raw bytes

1(function() {
2"use strict";
3// ## CORE ## >
4/*  */
5(function() {
6	
7/*
8 * ==>
9 * Make sure that we can safely use all the console methods even if some of
10 * them might not be available in a certain client
11 */
12(function() {
13	var method;
14	var noop = function () {};
15	var methods = [
16		'assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error',
17		'exception', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log',
18		'markTimeline', 'profile', 'profileEnd', 'table', 'time', 'timeEnd',
19		'timeStamp', 'trace', 'warn', 'lsjsConsoleTest'
20	];
21	var length = methods.length;
22	console = (window.console = window.console || {});
23
24	while (length--) {
25		method = methods[length];
26
27		// Only stub undefined methods.
28		if (!console[method]) {
29			console[method] = noop;
30		}
31	}
32})();
33/*
34 * <==
35 */
36
37/*
38 * Request.cajax makes an ajax call and expects an html response with cajax elements
39 * whose contents replace the contents of elements currently in the dom with
40 * matching ids.
41 */
42Request.cajax = new Class({
43
44	Extends: Request,
45
46	options: {
47		cajaxMode: 'update', // can be 'update', 'updateCompletely' or 'append',
48		el_formToUseForFormData: false,
49		obj_additionalFormData: {},
50		headers: {
51			/*
52			 * We only expect html but we keep the Accept value as it is in
53			 * Request.HTML. Who knows what it might be good for...
54			 */
55			Accept: 'text/html, application/xml, text/xml, */*'
56		}
57	},
58
59	initialize: function(options){
60		this.parent(options);
61	},
62
63	success: function(str_text){
64		/*
65		 * Strip javascript to make sure that we only have html
66		 */
67		this.response.html = str_text.stripScripts(function(str_script){
68			this.response.javascript = str_script;
69		}.bind(this));
70		
71		/*
72		 * Create a temporary element with the html response because this parses
73		 * the html response so that we can actually work with the elements
74		 * returned by ls_cajax
75		 */
76		var el_temp = new Element('div').set('html', this.response.html);
77		
78		/*
79		 * The children of el_temp are the elements that were requested from
80		 * ls_cajax and that need to be updated in the dom.
81		 */
82		var els_updated = el_temp.getChildren();
83		
84		var els_toPassToOnSuccess = new Elements();
85		
86		Array.each(els_updated, function(el_updated) {
87			if ($(el_updated.getProperty('id')) === null) {
88				console.info('cajax response contains elements without IDs, which therefore can\'t be considered for content updates:')
89				console.log(el_updated);
90				return;
91			}
92			
93			els_toPassToOnSuccess.push(this.options.cajaxMode === 'updateCompletely' ? el_updated : $(el_updated.getProperty('id')));
94			
95			/*
96			 * We take the element whose id matches the id of the response element
97			 * from the dom and update its contents with those of the response element.
98			 */
99			switch(this.options.cajaxMode) {
100				case 'update':
101					$(el_updated.getProperty('id')).empty().adopt(el_updated.childNodes);
102					break;
103
104				case 'updateCompletely':
105					var float_scrollTop = $(el_updated.getProperty('id')).scrollTop;
106
107					el_updated.replaces($(el_updated.getProperty('id')));
108
109					el_updated.scrollTop = float_scrollTop;
110					break;
111
112				case 'append':
113					$(el_updated.getProperty('id')).adopt(el_updated.childNodes);
114					break;
115			}
116		}.bind(this));
117
118		this.onSuccess(els_toPassToOnSuccess, this.response.html, this.response.javascript);
119		
120		Array.each(els_toPassToOnSuccess, function(el_domReference) {
121			window.fireEvent('cajax_domUpdate', el_domReference);
122		});
123	},
124
125	send: function(options) {
126		/*
127		 * If we don't use FormData, we call the parent function...
128		 */
129		if (typeOf(this.options.el_formToUseForFormData) !== 'element') {
130			this.parent(options);
131			return;
132		}
133
134		/*
135		 * but if we use FormData, we have to use our own implementation of the send() function
136		 */
137
138		/*
139		 * first, we build the FormData object from the given form, adding the key/value pairs of the
140		 * additionalFormData object
141		 */
142		var obj_formData = new FormData(this.options.el_formToUseForFormData);
143		Object.each(this.options.obj_additionalFormData, function(value, key) {
144			obj_formData.append(key, value);
145		});
146
147		/*
148		console.log('sending FormData: ');
149		console.log(this.options.el_formToUseForFormData);
150		console.log(this.options.obj_additionalFormData);
151		for (var pair of obj_formData.entries()) {
152			console.log(pair[0]+ ', ' + pair[1]);
153		}
154		*/
155
156		this.sendFormData(options, obj_formData);
157	},
158
159	sendFormData: function(options, obj_formData){
160		if (!this.check(options)) return this;
161
162		this.options.isSuccess = this.options.isSuccess || this.isSuccess;
163		this.running = true;
164
165		var xhr = this.xhr;
166		var progressSupport = ('onprogress' in new Browser.Request);
167		if (progressSupport){
168			xhr.onloadstart = this.loadstart.bind(this);
169			xhr.onprogress = this.progress.bind(this);
170			xhr.upload.onprogress = this.progress.bind(this);
171		}
172
173		xhr.open('POST', this.options.url, true);
174		xhr.onreadystatechange = this.onStateChange.bind(this);
175
176		Object.each(this.headers, function(value, key){
177			try {
178				xhr.setRequestHeader(key, value);
179			} catch (e){
180				this.fireEvent('exception', [key, value]);
181			}
182		}, this);
183
184		this.fireEvent('request');
185		xhr.send(obj_formData);
186
187		if (!this.options.async) this.onStateChange();
188		if (this.options.timeout) this.timer = this.timeout.delay(this.options.timeout, this);
189		return this;
190	}
191
192});
193
194var classdef_lsjs_apiInterface = {
195	str_apiUrl: '',
196	
197	/*
198	 * This function executes an api request and returns the request object.
199	 * 
200	 * obj_options.str_resource needs to be provided in order to define the api
201	 * resource addressed in the request. If obj_options.obj_additionalRequestOptions
202	 * is given, this is not mandatory.
203	 * 
204	 * With obj_options.obj_params the data/parameters to be sent with the
205	 * request, can be defined.
206	 * 
207	 * With "obj_options.obj_additionalRequestOptions" it is possible to completely
208	 * override the predefined request settings.
209	 */
210	request: function(obj_options) {
211		var obj_params = {},
212			obj_requestOptions = {},
213			method = 'get',
214			self = this;
215		
216		obj_params = obj_options.obj_params !== undefined && obj_options.obj_params !== null ? obj_options.obj_params : obj_params;
217		
218		if (
219				(obj_options.str_resource === undefined || obj_options.str_resource === null)
220			&&	(obj_options.obj_additionalRequestOptions === undefined || obj_options.obj_additionalRequestOptions === null)
221		) {
222			console.error('no api resource specified for this request');
223			return;
224		}
225
226		if (
227				obj_options.obj_additionalRequestOptions !== undefined
228			&&	obj_options.obj_additionalRequestOptions.method !== undefined
229			&&	obj_options.obj_additionalRequestOptions.method === 'post'
230		) {
231			method = 'post';
232		}
233
234		if (method !== 'post') {
235			obj_params.resource = obj_options.str_resource;
236		}
237
238		obj_requestOptions = {
239			url: this.str_apiUrl + (method === 'post' ? (this.str_apiUrl.search(/\?/) === -1 ? '?' : '&') + 'resource=' + obj_options.str_resource : ''),
240			noCache: true,
241			method: method,
242			
243			data: Object.toQueryString(obj_params),
244			
245			onComplete: function(obj_response) {
246				if (obj_response === undefined || obj_response.status !== 'success') {
247					console.info('ajax/api request not successful');
248					return;
249				}
250				
251				if (typeOf(obj_options.func_onSuccess) === 'function') {
252					obj_options.func_onSuccess(obj_response.data, this);
253				}
254   			}
255		};
256		
257		Object.merge(obj_requestOptions, obj_options.obj_additionalRequestOptions);
258		
259		if (obj_requestOptions.url === undefined || !obj_requestOptions.url) {
260			console.error('no url specified for this request');
261			return;
262		}
263		
264		return new Request.JSON(obj_requestOptions).send();
265	}
266};
267var class_lsjs_apiInterface = new Class(classdef_lsjs_apiInterface);
268	
269var classdef_lsjs = {
270	obj_moduleClasses: {},
271	
272	/*
273	 * In the __moduleHelpers object, modules (not instances of a module but the
274	 * module itself) can store module related helper functions.
275	 * 
276	 * For example, the core module "messageBox" does this to provide a simplified
277	 * creator function (lsjs.__moduleHelpers.messageBox.open())
278	 */
279	__moduleHelpers: {},
280	
281	/*
282	 * Apps can store a reference to themselves in __appHelpers if they need 
283	 * to be accessible in the global scope (lsjs.__appHelpers.appXyz)
284	 */
285	__appHelpers: {},
286	
287	createModule: function(obj_args) {
288		if (obj_args.__name === undefined || obj_args.__name === null) {
289			console.error('module name missing');
290			return;
291		}
292		
293		return new class_lsjs_module(obj_args);
294	},
295	
296	addViewClass: function(str_moduleName, obj_classDefinition) {
297		if (str_moduleName === undefined || str_moduleName === null || !str_moduleName) {
298			console.error('module name missing');
299		}
300		
301		if (obj_classDefinition === undefined || obj_classDefinition === null || !obj_classDefinition) {
302			console.error('class definition missing');
303		}
304		
305		if (this.obj_moduleClasses[str_moduleName] === undefined) {
306			this.obj_moduleClasses[str_moduleName] = {};
307		}
308		
309		if (this.obj_moduleClasses[str_moduleName].view !== undefined && this.obj_moduleClasses[str_moduleName].view !== null) {
310			console.error('view class already created for module "' + str_moduleName + '"');
311		}
312		
313		this.obj_moduleClasses[str_moduleName].view = new Class(obj_classDefinition);
314	},
315	
316	addModelClass: function(str_moduleName, obj_classDefinition) {
317		if (str_moduleName === undefined || str_moduleName === null || !str_moduleName) {
318			console.error('module name missing');
319		}
320		
321		if (obj_classDefinition === undefined || obj_classDefinition === null || !obj_classDefinition) {
322			console.error('class definition missing');
323		}
324		
325		if (obj_classDefinition.name === undefined || obj_classDefinition.name === null || !obj_classDefinition.name) {
326			console.error('model name missing');
327		}
328		
329		if (this.obj_moduleClasses[str_moduleName] === undefined) {
330			this.obj_moduleClasses[str_moduleName] = {};
331		}
332		
333		if (this.obj_moduleClasses[str_moduleName].model === undefined) {
334			this.obj_moduleClasses[str_moduleName].model = {};
335		}
336		
337		if (this.obj_moduleClasses[str_moduleName].model[obj_classDefinition.name] !== undefined && this.obj_moduleClasses[str_moduleName].model[obj_classDefinition.name] !== null) {
338			console.error('model class "' + obj_classDefinition.name + '" already created in module "' + str_moduleName + '"');
339		}
340		
341		/*
342		 * We need to pass the data object along and then, after instantiating the
343		 * model class, we write a clone of this data object to the model instance's
344		 * data object. This way, we get an iterable data object and sidestep the
345		 * problem that occurs when converting the data object into a hash for this
346		 * purpose. When using the hash, we realized that the data objects of multiple
347		 * modules' models pointed to the same resource.
348		 */
349		this.obj_moduleClasses[str_moduleName].model[obj_classDefinition.name] = {
350			class_module: new Class(obj_classDefinition),
351			obj_data: obj_classDefinition.data
352		};
353	},
354	
355	addControllerClass: function(str_moduleName, obj_classDefinition) {
356		if (str_moduleName === undefined || str_moduleName === null || !str_moduleName) {
357			console.error('module name missing');
358		}
359		
360		if (obj_classDefinition === undefined || obj_classDefinition === null || !obj_classDefinition) {
361			console.error('class definition missing');
362		}
363		
364		if (this.obj_moduleClasses[str_moduleName] === undefined) {
365			this.obj_moduleClasses[str_moduleName] = {};
366		}
367		
368		if (this.obj_moduleClasses[str_moduleName].controller !== undefined && this.obj_moduleClasses[str_moduleName].controller !== null) {
369			console.error('controller class already created for module "' + str_moduleName + '"');
370		}
371		
372		this.obj_moduleClasses[str_moduleName].controller = new Class(obj_classDefinition);
373	},
374	
375	apiInterface: new class_lsjs_apiInterface()
376};
377var class_lsjs = new Class(classdef_lsjs);
378
379var classdef_lsjs_module = {
380	__name: '',
381	__parentModule: null,
382	
383	__el_container: null,
384	
385	__view: null, // will later hold the view
386	__models: null, // will later hold the object that provides access to all models
387	__controller: null, // will later hold the controller
388	
389	int_numModelsToLoad: 0,
390	
391	obj_args: {},
392	obj_registeredDataBindings: {},
393	
394	initialize: function(obj_args) {
395		this.obj_args = obj_args;
396		this.__name = this.obj_args.__name;
397		
398		if (this.obj_args.__parentModule !== undefined && this.obj_args.__parentModule !== null) {
399			this.__parentModule = this.obj_args.__parentModule;
400		}
401		
402		if (this.obj_args.__useLoadingIndicator === true && this.__name !== 'loadingIndicator' && lsjs.loadingIndicator !== undefined && lsjs.loadingIndicator !== null) {
403			lsjs.loadingIndicator.__controller.show();
404		}
405		
406		if (this.obj_args.__el_container !== undefined && typeOf(this.obj_args.__el_container) === 'element') {
407			this.__el_container = this.obj_args.__el_container;
408		} else {
409			if (this.obj_args.str_containerSelector === undefined || this.obj_args.str_containerSelector === null) {
410				this.__el_container = new Element('div.moduleContainer.autoModuleContainer.' + this.__name);
411			} else {
412				this.__el_container = $$(this.obj_args.str_containerSelector)[0];
413				if (this.__el_container === undefined || this.__el_container === null) {
414					console.error('no element found in DOM for selector "'+ this.obj_args.str_containerSelector + '" in module "' + this.__name + '"');
415					return;
416				}
417			}
418		}
419		
420		if (lsjs.obj_moduleClasses[this.__name] === undefined || lsjs.obj_moduleClasses[this.__name] === null) {
421			console.error('module classes not registered for module "' + this.__name + '". Did you include all of the script files?');
422			return;
423		}
424				
425		/*
426		 * Instantiate the controller
427		 */
428		this.__controller = new lsjs.obj_moduleClasses[this.__name].controller();
429		this.__controller.__module = this;
430		
431		/*
432		 * Instantiate the view and make some module functions available
433		 */
434		if (lsjs.obj_moduleClasses[this.__name].view !== undefined && lsjs.obj_moduleClasses[this.__name].view !== null) {
435			this.__view = new lsjs.obj_moduleClasses[this.__name].view();
436			this.__view.__module = this;
437			this.__view.__el_container = this.__el_container;
438			this.__view.tplPure = this.tplPure.bind(this);
439			this.__view.tplAdd = this.tplAdd.bind(this);
440			this.__view.tplReplace = this.tplReplace.bind(this);
441			this.__view.registerElements = this.registerElements.bind(this);
442			this.__view.removeDataBindings = this.removeDataBindings.bind(this);
443			this.__view.debugBindings = this.debugBindings.bind(this);
444		}
445		
446		/*
447		 * Make controller and view available for each other
448		 */
449		this.__controller.__view = this.__view;
450		if (this.__view !== undefined && this.__view !== null) {
451			this.__view.__controller = this.__controller;
452		}
453
454		
455		/*
456		 * Instantiate each model as a property of an object holding all models.
457		 * Make controller and view available for each model and call each model's
458		 * "start()" method.
459		 */
460		this.__models = {};
461		
462		/*
463		 * Make the object holding all models available for controller and view.
464		 * Important: This assignment needs to be created before actually instantiating
465		 * any models because otherwise a race condition exists which can cause
466		 * the controller's and the view's "start" method to be called before
467		 * __models is assigned to them.
468		 */
469		this.__controller.__models = this.__models;
470		if (this.__view !== undefined && this.__view !== null) {
471			this.__view.__models = this.__models;
472		}
473		
474		/*
475		 * First, we walk through all model classes and instantiate them but we
476		 * don't actually call the model's "start()" method because before the
477		 * first model can be started, we need to know how many models we have
478		 * to load in this module. This is important because only then we can
479		 * determine later if all modules have been loaded successfully.
480		 */
481		/*
482		 * [1.0.0_h_#3] make sure that we don't try to iterate over undefined or
483		 * null because as of mootools 1.5.2. this would cause an error.
484		 */
485		if (lsjs.obj_moduleClasses[this.__name].model !== undefined && lsjs.obj_moduleClasses[this.__name].model !== null) {
486			Object.each(lsjs.obj_moduleClasses[this.__name].model, function(obj_modelClassAndData, str_moduleName) {
487				this.int_numModelsToLoad++;
488				this.__models[str_moduleName] = new obj_modelClassAndData.class_module();
489				this.__models[str_moduleName].data = Object.clone(obj_modelClassAndData.obj_data);
490				this.__models[str_moduleName].__module = this;
491				this.__models[str_moduleName].__view = this.__view;
492				this.__models[str_moduleName].__controller = this.__controller;
493				this.__models[str_moduleName].__models = this.__models;
494				this.__models[str_moduleName].writeData = this.writeData;
495				this.__models[str_moduleName].readData = this.readData;
496				this.__models[str_moduleName].triggerDataBinding = this.triggerDataBinding;
497				this.__models[str_moduleName].handleDataBinding = this.handleDataBinding;
498				this.__models[str_moduleName].handleDataFunctionBinding = this.handleDataFunctionBinding;
499				this.__models[str_moduleName].lookForMatchingDataBindings = this.lookForMatchingDataBindings;
500			}.bind(this));
501		}
502		
503		/*
504		 * Walk through all models again and start them or, if there are no models
505		 * registered, call "onModelLoaded()" directly so that the module can work
506		 * without a model.
507		 */
508		if (Object.getLength(this.__models) > 0) {
509			Object.each(lsjs.obj_moduleClasses[this.__name].model, function(class_module, str_moduleName) {
510				this.__models[str_moduleName].start();
511			}.bind(this));
512		} else {
513			this.onModelLoaded();
514		}
515	},
516	
517	onModelLoaded: function() {
518		this.int_numModelsToLoad--;
519		
520		/*
521		 * If there are models that have yet to be loaded, we return
522		 * right now and do nothing
523		 */
524		if (this.int_numModelsToLoad > 0) {
525			return;
526		}
527		
528		/*
529		 * If all models are loaded we start view and controller
530		 */
531		/*
532		 * Start the view
533		 */
534		if (this.__view !== undefined && this.__view !== null) {
535			this.__view.start();
536		}
537		
538		/*
539		 * Start the controller
540		 */
541		this.__controller.start();
542		
543		if (this.obj_args.__useLoadingIndicator === true && this.__name !== 'loadingIndicator' && lsjs.loadingIndicator !== undefined && lsjs.loadingIndicator !== null) {
544			lsjs.loadingIndicator.__controller.hide();
545		}
546	},
547	
548	remove: function() {
549		this.__el_container.destroy();
550		delete this.__el_container;
551		delete this.__view;
552		delete this.__models;
553		delete this.__controller;
554	},
555	
556	tplPure: function(obj_usageOptions, str_moduleName, bln_registerSingleElementsInElementList) {
557		return this.tplUse('pure', obj_usageOptions, str_moduleName, bln_registerSingleElementsInElementList);
558	},
559	
560	tplAdd: function(obj_usageOptions, str_moduleName, bln_registerSingleElementsInElementList) {
561		return this.tplUse('add', obj_usageOptions, str_moduleName, bln_registerSingleElementsInElementList);
562	},
563	
564	tplReplace: function(obj_usageOptions, str_moduleName, bln_registerSingleElementsInElementList) {
565		return this.tplUse('replace', obj_usageOptions, str_moduleName, bln_registerSingleElementsInElementList);
566	},
567	
568	tplUse: function(str_mode, obj_usageOptions, str_moduleName, bln_registerSingleElementsInElementList) {
569		var el_renderedTemplate;
570		
571		if (str_moduleName === undefined || str_moduleName === null || !str_moduleName) {
572			str_moduleName = this.__name;
573		}
574		if (obj_usageOptions.arg === undefined || obj_usageOptions.arg === null) {
575			obj_usageOptions.arg = {};
576		}
577		
578		obj_usageOptions.arg.__models = this.__models;
579		obj_usageOptions.arg.__view = this.__view;
580		obj_usageOptions.arg.__controller = this.__controller;
581		obj_usageOptions.bind = obj_usageOptions.bind !== undefined && obj_usageOptions.bind !== null ? obj_usageOptions.bind : this;
582		obj_usageOptions.class = obj_usageOptions.class !== undefined && obj_usageOptions.class !== null ? obj_usageOptions.class : this.__name;
583		obj_usageOptions.parent = obj_usageOptions.parent !== undefined && obj_usageOptions.parent !== null ? obj_usageOptions.parent : this.__el_container;
584		obj_usageOptions.bln_discardContainerElement = obj_usageOptions.bln_discardContainerElement !== undefined && obj_usageOptions.bln_discardContainerElement ? true : false;
585
586		el_renderedTemplate = lsjs.tpl[str_mode](obj_usageOptions, str_moduleName);
587
588		if (el_renderedTemplate === null) {
589			return el_renderedTemplate;
590		}
591		
592		this.registerElements(el_renderedTemplate, obj_usageOptions.name, bln_registerSingleElementsInElementList);
593		this.registerDataBindings(el_renderedTemplate);
594		this.registerMultipleDataBindings(el_renderedTemplate);
595
596		this.registerEvents(el_renderedTemplate);
597
598		this.replaceElementPlaceholders(el_renderedTemplate);
599		this.replaceTemplatePlaceholders(el_renderedTemplate);
600
601		if (obj_usageOptions.bln_discardContainerElement) {
602			var el_renderedTemplateContent = el_renderedTemplate.getChildren();
603			el_renderedTemplate.getParent().adopt(el_renderedTemplateContent);
604			el_renderedTemplate.destroy();
605			return el_renderedTemplateContent;
606		} else {
607			return el_renderedTemplate;
608		}
609	},
610	
611	replaceTemplatePlaceholders: function(el_toSearchForElementsToReplace) {
612		Array.each(el_toSearchForElementsToReplace.getElements('[data-lsjs-replaceWithTemplate]'), function(el_toReplace) {
613			var el_toReplaceWith,
614				arg;
615			
616			/*
617			 * In the scope that this function is executed in, the "arg" parameter
618			 * that would be available in the template's scope does not exist.
619			 * Since the value for the 'data-lsjs-replaceWith' property is written
620			 * in the template we want the parameter "arg" to work, so we create
621			 * it here so that it works when the 'data-lsjs-replaceWith' value
622			 * is evaled here.
623			 */
624			arg = {
625				__view: this.__view,
626				__models: this.__models
627			};
628			
629			el_toReplaceWith = this.tplPure({name: el_toReplace.getProperty('data-lsjs-replaceWithTemplate')});
630			
631			if (typeOf(el_toReplaceWith) !== 'element') {
632				return;
633			}
634			
635			el_toReplaceWith.wraps(el_toReplace);
636			el_toReplace.destroy();
637		}.bind(this));
638	},
639	
640	replaceElementPlaceholders: function(el_toSearchForElementsToReplace) {
641		Array.each(el_toSearchForElementsToReplace.getElements('[data-lsjs-replaceWithElement]'), function(el_toReplace) {
642			var el_toReplaceWith,
643				arg;
644			
645			/*
646			 * In the scope that this funciton is executed in, the "arg" parameter
647			 * that would be available in the template's scope does not exist.
648			 * Since the value for the 'data-lsjs-replaceWith' property is written
649			 * in the template we want the parameter "arg" to work, so we create
650			 * it here so that it works when the 'data-lsjs-replaceWith' value
651			 * is evaled here.
652			 */
653			arg = {
654				__view: this.__view,
655				__models: this.__models
656			};
657			
658			eval('el_toReplaceWith = ' + el_toReplace.getProperty('data-lsjs-replaceWithElement'));
659			
660			if (typeOf(el_toReplaceWith) !== 'element') {
661				return;
662			}
663			
664			el_toReplaceWith.wraps(el_toReplace);
665			el_toReplace.destroy();
666		}.bind(this));
667	},
668
669	registerEvents: function(el_toSearchForEvents) {
670		Array.each(
671			el_toSearchForEvents.getElements('[data-lsjs-events]'),
672			function(el_toRegisterEventTo) {
673				var str_events = el_toRegisterEventTo.getProperty('data-lsjs-events');
674				var arr_events = null;
675
676				eval('arr_events = ' + str_events);
677				if (typeOf(arr_events) !== 'array') {
678					return;
679				}
680
681				Array.each(
682					arr_events,
683					function(obj_event) {
684						var func_toRegisterWithEvent = null;
685						eval('func_toRegisterWithEvent = this.' + obj_event.scope + '.' + obj_event.function + '.bind(this.' + obj_event.scope + ')');
686
687						el_toRegisterEventTo.addEvent(
688							obj_event.event,
689							function(event) {
690								var test = 'hallo';
691								func_toRegisterWithEvent(el_toRegisterEventTo, event);
692							}.bind(this)
693						);
694					}.bind(this)
695				);
696			}.bind(this)
697		);
698	},
699
700	removeDataBindings: function(el_toSearchForBindings) {
701		this.registerMultipleDataBindings(el_toSearchForBindings, true);
702		this.registerDataBindings(el_toSearchForBindings, true);
703	},
704	
705	registerMultipleDataBindings: function(el_toSearchForBindings, bln_performDeregistration) {
706		bln_performDeregistration = bln_performDeregistration !== undefined && bln_performDeregistration ? true : false;
707
708		Array.each(
709			el_toSearchForBindings.getElements('[data-lsjs-bindTo]'),
710			function(el_toBind) {
711				var str_bindTo = el_toBind.getProperty('data-lsjs-bindTo');
712				var arr_bindings = null;
713
714				eval('arr_bindings = ' + str_bindTo);
715				if (typeOf(arr_bindings) !== 'array') {
716					return;
717				}
718
719				Array.each(
720					arr_bindings,
721					function(obj_binding) {
722						var str_bindToModel = obj_binding.model;
723						var str_bindToPath = obj_binding.path !== undefined && obj_binding.path !== null ? obj_binding.path : '';
724						var str_bindToProperty = obj_binding.property !== undefined && obj_binding.property !== null ? obj_binding.property : '';
725
726						/*
727						 * use the new way to declare a binding translation but also accept the old way
728						 * if the new is not being used for this binding
729						 */
730						var str_bindToTranslation = obj_binding.translationModelToView !== undefined && obj_binding.translationModelToView !== null ? obj_binding.translationModelToView : '';
731
732						if (!str_bindToTranslation) {
733							str_bindToTranslation = obj_binding.translation !== undefined && obj_binding.translation !== null ? obj_binding.translation : '';
734						}
735
736						var str_bindToTranslationViewToModel = obj_binding.translationViewToModel !== undefined && obj_binding.translationViewToModel !== null ? obj_binding.translationViewToModel : '';
737
738						var str_bindToCallbackViewToModel = obj_binding.callbackViewToModel !== undefined && obj_binding.callbackViewToModel !== null ? obj_binding.callbackViewToModel : '';
739
740						if (bln_performDeregistration) {
741							this.deregisterSingleDataBinding(el_toBind, str_bindToModel, str_bindToPath, str_bindToProperty, str_bindToTranslation, str_bindToTranslationViewToModel, str_bindToCallbackViewToModel);
742						} else {
743							this.registerSingleDataBinding(el_toBind, str_bindToModel, str_bindToPath, str_bindToProperty, str_bindToTranslation, str_bindToTranslationViewToModel, str_bindToCallbackViewToModel);
744						}
745					}.bind(this)
746				);
747			}.bind(this)
748		);
749	},
750
751	registerDataBindings: function(el_toSearchForBindings, bln_performDeregistration) {
752		bln_performDeregistration = bln_performDeregistration !== undefined && bln_performDeregistration ? true : false;
753
754		Array.each(el_toSearchForBindings.getElements('[data-lsjs-bindToModel]'), function(el_toBind) {
755			var	str_bindToModel,
756				str_bindToPath,
757				str_bindToProperty,
758				str_bindToTranslation,
759				str_bindToTranslationViewToModel,
760				str_bindToCallbackViewToModel;
761
762			str_bindToModel = el_toBind.getProperty('data-lsjs-bindToModel');
763
764			str_bindToPath = el_toBind.getProperty('data-lsjs-bindToPath');
765			str_bindToPath = str_bindToPath ? str_bindToPath : '';
766
767			str_bindToProperty = el_toBind.getProperty('data-lsjs-bindToProperty');
768			str_bindToProperty = str_bindToProperty ? str_bindToProperty : '';
769
770			/*
771			 * use the new way to declare a binding translation but also accept the old way
772			 * if the new is not being used for this binding
773			 */
774			str_bindToTranslation = el_toBind.getProperty('data-lsjs-bindToTranslationModelToView');
775			str_bindToTranslation = str_bindToTranslation ? str_bindToTranslation : '';
776
777			if (!str_bindToTranslation) {
778				str_bindToTranslation = el_toBind.getProperty('data-lsjs-bindToTranslation');
779				str_bindToTranslation = str_bindToTranslation ? str_bindToTranslation : '';
780			}
781
782			str_bindToTranslationViewToModel = el_toBind.getProperty('data-lsjs-bindToTranslationViewToModel');
783			str_bindToTranslationViewToModel = str_bindToTranslationViewToModel ? str_bindToTranslationViewToModel : '';
784
785			str_bindToCallbackViewToModel = el_toBind.getProperty('data-lsjs-bindToCallbackViewToModel');
786			str_bindToCallbackViewToModel = str_bindToCallbackViewToModel ? str_bindToCallbackViewToModel : '';
787
788			if (bln_performDeregistration) {
789				this.deregisterSingleDataBinding(el_toBind, str_bindToModel, str_bindToPath, str_bindToProperty, str_bindToTranslation, str_bindToTranslationViewToModel, str_bindToCallbackViewToModel);
790			} else {
791				this.registerSingleDataBinding(el_toBind, str_bindToModel, str_bindToPath, str_bindToProperty, str_bindToTranslation, str_bindToTranslationViewToModel, str_bindToCallbackViewToModel);
792			}
793
794		}.bind(this));
795	},
796
797	debugBindings: function() {
798		console.log('DEBUG BINDINGS: this.obj_registeredDataBindings', Object.clone(this.obj_registeredDataBindings)
798);
799	},
800
801	deregisterSingleDataBinding: function(el_toBind, str_bindToModel, str_bindToPath, str_bindToProperty, str_bindToTranslation, str_bindToTranslationViewToModel, str_bindToCallbackViewToModel) {
802		if (this.obj_registeredDataBindings[str_bindToModel] === undefined || this.obj_registeredDataBindings[str_bindToModel] === null) {
803			return;
804		}
805
806		if (this.obj_registeredDataBindings[str_bindToModel][str_bindToPath] === undefined || this.obj_registeredDataBindings[str_bindToModel][str_bindToPath] === null) {
807			return;
808		}
809
810		this.obj_registeredDataBindings[str_bindToModel][str_bindToPath] = this.obj_registeredDataBindings[str_bindToModel][str_bindToPath].filter(
811			function(obj_binding) {
812				var bln_keepBinding =
813						obj_binding.str_bindToProperty !== str_bindToProperty
814					||	obj_binding.str_bindToTranslation !== str_bindToTranslation
815					||	obj_binding.str_bindToTranslationViewToModel !== str_bindToTranslationViewToModel
816					||	obj_binding.str_bindToCallbackViewToModel !== str_bindToCallbackViewToModel
817					||	obj_binding.el_bound !== el_toBind;
818
819				if (!bln_keepBinding) {
820					/*
821					 * If we remove the binding, we have to remove corresponding events as well
822					 */
823					if (obj_binding.func_bound !== null) {
824						el_toBind.removeEvent('change', obj_binding.func_bound);
825					}
826				}
827
828				return bln_keepBinding;
829			}
830		);
831
832		if (this.obj_registeredDataBindings[str_bindToModel][str_bindToPath].length === 0) {
833			delete this.obj_registeredDataBindings[str_bindToModel][str_bindToPath];
834		}
835	},
836
837	registerSingleDataBinding: function(el_toBind, str_bindToModel, str_bindToPath, str_bindToProperty, str_bindToTranslation, str_bindToTranslationViewToModel, str_bindToCallbackViewToModel) {
838		if (this.obj_registeredDataBindings[str_bindToModel] === undefined || this.obj_registeredDataBindings[str_bindToModel] === null) {
839			this.obj_registeredDataBindings[str_bindToModel] = {};
840		}
841
842		if (this.obj_registeredDataBindings[str_bindToModel][str_bindToPath] === undefined || this.obj_registeredDataBindings[str_bindToModel][str_bindToPath] === null) {
843			this.obj_registeredDataBindings[str_bindToModel][str_bindToPath] = [];
844		}
845
846		var func_bound =
847			str_bindToProperty === 'none'
848		?	null
849		:	function (eventOrElement) {
850				var el_target,
851					var_newValue,
852					var_oldValue = this.__models[str_bindToModel].readData(str_bindToPath);
853
854				if (eventOrElement === undefined || eventOrElement === null) {
855					/*
856					 * Before 2017-08-25 a missing eventOrElement caused an error
857					 * but this was a problem when an input field was fired by a datepicker
858					 * because in this case eventOrElement was missing and we couldn't do
859					 * anything about it. So, as of 2017-08-25, if eventOrElement is missing
860					 * we assume el_toBind is the element that fired the event.
861					 *
862					 * console.error('eventOrElement missing');
863					 * return;
864					 *
865					 */
866					eventOrElement = el_toBind;
867				}
868
869				el_target = typeOf(eventOrElement) === 'element' ? eventOrElement : eventOrElement.target;
870
871				/* ->
872				 * !!! Important: !!!
873				 * Don't react on change events that did not originate on the bound element itself but another
874				 * element inside from which the event bubbled up!
875				 */
876				if (el_target !== el_toBind) {
877					return;
878				}
879				/*
880				 * <-
881				 */
882
883				if (str_bindToProperty) {
884					var_newValue = el_target.getProperty(str_bindToProperty);
885				} else {
886					if (el_target.match('input[type="checkbox"')) {
887
888						/*
889						 * In case of a checkbox we might want to get a boolean value with either true set in case of
890						 * a checked checkbox and false set in case of an unchecked checkbo or the other way around.
891						 *
892						 * But we might also want the checkbox to deliver an explicitly set value if its checked and
893						 * an empty string its not.
894						 *
895						 * So the behavior implemented for handling checkbox values is as follows:
896						 *
897						 * - if a value attribute is set for the checkbox input element and this value is not the empty
898						 * string, this value is delivered when checked and an empty string when unchecked
899						 *
900						 * - if no value attribute is set, the string 'on' is delivered when checked and an empty
901						 * string when unchecked (this is to support the browser's default 'on' value)
902						 *
903						 * - if a value attribute is set and it is the empty string or the string 'true', a boolean
904						 * true is delivered when checked and a boolean false when unchecked
905						 *
906						 * - if a value attribute is set and it is the string 'false', a boolean false is delivered
907						 * when checked and a boolean true when unchecked
908						 */
909
910						var	var_valueChecked,
911							var_valueUnchecked;
912
913						if (
914							el_target.getProperty('value') === ''
915							||	el_target.getProperty('value') === 'true'
916						) {
917							var_valueChecked = true;
918							var_valueUnchecked = false;
919						} else if (
920							el_target.getProperty('value') === 'false'
921						) {
922							var_valueChecked = false;
923							var_valueUnchecked = true;
924						} else {
925							var_valueChecked = el_target.getProperty('value');
926							var_valueUnchecked = '';
927						}
928
929						var_newValue = el_target.checked ? var_valueChecked : var_valueUnchecked;
930
931					} else if (el_target.match('input') || el_target.match('select') || el_target.match('textarea')) {
932						var_newValue = el_target.getProperty('value');
933					} else {
934						var_newValue = el_target.getProperty('html');
935					}
936				}
937
938				if (str_bindToTranslationViewToModel) {
939					/*
940					 * a translation function can also be used for a validation. Therefore, the data path
941					 * as well as the old value and the bound element are passed as parameters.
942					 *
943					 * For example, the value could be validated with a validation pattern that depends on the
944					 * given data path and if the value is valid, it is returned. But if it is not valid,
945					 * the old value could be returned and the bound element could be the target for
946					 * some kind of error message output.
947					 */
948					var_newValue = this.__models[str_bindToModel][str_bindToTranslationViewToModel](
949						var_newValue, // new value
950						el_toBind, // the bound element
951
952						var_oldValue, // old value (useful if the old value needs to be reset for some reason)
953
954						/*
955						 * the data path, also useful if the old value needs to be restored for some reason
956						 * or if one callback function is registered for multiple bindings with different
957						 * data paths and should behave differently depending on the used data path
958						 */
959						str_bindToPath
960					);
961				}
962
963				this.__models[str_bindToModel].writeData(str_bindToPath, var_newValue);
964
965				if (str_bindToCallbackViewToModel) {
966					/*
967					 * A translation function can be used to trigger external data storage usind some kind
968					 * of api/ajax request. If external storage failed, it is possible to reset the old value
969					 * using the old value parameter and the data path parameter which both are passed to
970					 * the callback function.
971					 */
972					this.__models[str_bindToModel][str_bindToCallbackViewToModel](
973						var_newValue, // new value
974						el_toBind, // the bound element
975						var_oldValue, // old value (useful if the old value needs to be reset for some reason
976
977						/*
978						 * the data path, also useful if the old value needs to be restored for some reason
979						 * or if one callback function is registered for multiple bindings with different
980						 * data paths and should behave differently depending on the used data path
981						 */
982						str_bindToPath
983					);
984				}
985			}.bind(this);
986
987		this.obj_registeredDataBindings[str_bindToModel][str_bindToPath].push({
988			str_bindToProperty: str_bindToProperty,
989			str_bindToTranslation: str_bindToTranslation,
990			str_bindToTranslationViewToModel: str_bindToTranslationViewToModel,
991			str_bindToCallbackViewToModel: str_bindToCallbackViewToModel,
992			el_bound: el_toBind,
993			func_bound: func_bound
994		});
995
996		if (func_bound !== null) {
997			el_toBind.addEvent('change', func_bound);
998		}
999
1000		/*
1001		 * Initially set all bound elements to the current value
1002		 */
1003		this.__models[str_bindToModel].writeData(str_bindToPath, this.__models[str_bindToModel].readData(str_bindToPath), true, false);
1004
1005		// console.log('this.obj_registeredDataBindings', this.obj_registeredDataBindings);
1006	},
1007	
1008	registerElements: function(el_toSearchForElementsToRegister, str_registerGroup, bln_registerSingleElementsInElementList, str_elementNameOnly, str_subGroupNameOnly, bln_checkForParentSubGroups ) {
1009		str_registerGroup = str_registerGroup !== undefined && str_registerGroup ? str_registerGroup : 'defaultElementGroup';
1010		bln_registerSingleElementsInElementList = bln_registerSingleElementsInElementList !== undefined && bln_registerSingleElementsInElementList ? true : false;
1011		str_elementNameOnly = str_elementNameOnly !== undefined && str_elementNameOnly ? str_elementNameOnly : null;
1012		str_subGroupNameOnly = str_subGroupNameOnly !== undefined && str_subGroupNameOnly ? str_subGroupNameOnly : null;
1013		bln_checkForParentSubGroups = bln_checkForParentSubGroups !== undefined && bln_checkForParentSubGroups ? true : false;
1014
1015		if (this.__view.__autoElements === undefined || this.__view.__autoElements === null) {
1016			this.__view.__autoElements = {};
1017		}
1018		
1019		if (this.__view.__autoElements[str_registerGroup] === undefined || this.__view.__autoElements[str_registerGroup] === null) {
1020			this.__view.__autoElements[str_registerGroup] = {};
1021		}
1022
1023		Array.each(el_toSearchForElementsToRegister.getElements('[data-lsjs-element]'), function(el_toRegister) {
1024			var str_autoElementSubGroupKey = el_toRegister.getProperty('data-lsjs-autoElementSubGroup');
1025
1026			if (bln_checkForParentSubGroups) {
1027				if (str_autoElementSubGroupKey === null) {
1028					var el_subGroupParent = el_toRegister.getParent('[data-lsjs-autoElementSubGroup]');
1029					if (typeOf(el_subGroupParent) === 'element') {
1030						str_autoElementSubGroupKey = el_subGroupParent.getProperty('data-lsjs-autoElementSubGroup');
1031					}
1032				}
1033			}
1034
1035			if (str_subGroupNameOnly !== null && str_autoElementSubGroupKey !== str_subGroupNameOnly) {
1036				return;
1037			}
1038
1039			var str_autoElementName = el_toRegister.getProperty('data-lsjs-element');
1040			if (str_elementNameOnly !== null && str_autoElementName !== str_elementNameOnly) {
1041				return;
1042			}
1043
1044
1045			if (
1046					str_autoElementSubGroupKey !== null
1047				&&	(
1048							this.__view.__autoElements[str_registerGroup][str_autoElementSubGroupKey] === undefined
1049						||	this.__view.__autoElements[str_registerGroup][str_autoElementSubGroupKey] === null
1050					)
1051			) {
1052				this.__view.__autoElements[str_registerGroup][str_autoElementSubGroupKey] = {};
1053			}
1054
1055			var obj_autoElementsSubGroupPointer =
1056				str_autoElementSubGroupKey === null
1057					? this.__view.__autoElements[str_registerGroup]
1058					: this.__view.__autoElements[str_registerGroup][str_autoElementSubGroupKey];
1059
1060			var bln_isFirstElement =	typeOf(obj_autoElementsSubGroupPointer[str_autoElementName]) !== 'element'
1061									&&	typeOf(obj_autoElementsSubGroupPointer[str_autoElementName]) !== 'elements';
1062			if (
1063					!bln_registerSingleElementsInElementList
1064				&&	bln_isFirstElement
1065			) {
1066				/*
1067				 * If single elements should not be stored in element lists and
1068				 * there is no element registered under the name read out as the
1069				 * "data-lsjs-element" value, we store the element itself
1070				 */
1071				obj_autoElementsSubGroupPointer[str_autoElementName] = el_toRegister;
1072			} else {				
1073				/*
1074				 * If single elements should be stored in element lists or
1075				 * there's already an element registered, we create an element list,
1076				 * store the already registered element (if there is one) as the
1077				 * first array element and add the new element.
1078				 * 
1079				 * If we already have an element list, we simply add the new element.
1080				 */
1081				if (typeOf(obj_autoElementsSubGroupPointer[str_autoElementName]) !== 'elements') {
1082					obj_autoElementsSubGroupPointer[str_autoElementName] = bln_registerSingleElementsInElementList && bln_isFirstElement ? new Elements() : new Elements([obj_autoElementsSubGroupPointer[str_autoElementName]]);
1083				}
1084				obj_autoElementsSubGroupPointer[str_autoElementName].push(el_toRegister);
1085			}
1086		}.bind(this));
1087	},
1088	
1089	triggerDataBinding: function(str_dataPath, bln_triggerDataViewBindings, bln_triggerDataFunctionBindings) {
1090		this.writeData(str_dataPath, '___TRIGGER_ONLY___', bln_triggerDataViewBindings, bln_triggerDataFunctionBindings);
1091	},
1092	
1093	writeData: function(str_dataPath, var_value, bln_triggerDataViewBindings, bln_triggerDataFunctionBindings) {
1094		var obj_originalData;
1095		
1096		/*
1097		 * This weird assignment is only used to make sure that an IDE would
1098		 * not think 'var_value' was unused because that might lead a developer
1099		 * to the conclusion that the 'var_value' parameter could be removed,
1100		 * which it can't.
1101		 */
1102		var_value = var_value !== undefined ? var_value : undefined;
1103		
1104		bln_triggerDataViewBindings = bln_triggerDataViewBindings === undefined || bln_triggerDataViewBindings ? true : false;
1105		bln_triggerDataFunctionBindings = bln_triggerDataFunctionBindings === undefined || bln_triggerDataFunctionBindings ? true : false;
1106		
1107		/*
1108		 * Even the empty string is allowed as data path, pointing directly
1109		 * to 'this.data'
1110		 */
1111		if (str_dataPath === undefined || !str_dataPath) {
1112			str_dataPath = '';
1113		}
1114		
1115		/*
1116		 * If this function is called by a simple call to 'triggerDataBinding',
1117		 * the only purpose is to fire all data bindings registered for the
1118		 * data path and no data should actually be changed. Therefore we set the
1119		 * data to write to the data that is currently stored.
1120		 */
1121		if (var_value === '___TRIGGER_ONLY___') {
1122			var_value = this.readData(str_dataPath);
1123		}
1124		
1125		/*
1126		 * -> Create a copy of the original data before writing the new value.
1127		 * We need to pass the original data to the data function bindings so
1128		 * that resetting the original data is possible if the bound function
1129		 * needs to do so, e.g. if storing with an external storage engine or
1130		 * something similar fails.
1131		 * 
1132		 * We only create the copy if function bindings will be triggered later
1133		 * and if there are actually function bindings registered.
1134		 */
1135		if (
1136				bln_triggerDataFunctionBindings
1137			&&	this.obj_dataFunctionBindings !== undefined
1138			&&	this.obj_dataFunctionBindings !== null
1139		) {
1140			obj_originalData = Object.clone(this.data);
1141		}
1142		/*
1143		 * <-
1144		 */
1145
1146		if (var_value === '___DELETE___') {
1147			eval('delete this.data' + (str_dataPath ? '.' + str_dataPath : ''));
1148		} else {
1149			eval('this.data' + (str_dataPath ? '.' + str_dataPath : '') + ' = var_value');
1150		}
1151		
1152		if (bln_triggerDataViewBindings) {
1153			this.handleDataBinding(str_dataPath);
1154		}
1155		
1156		if (bln_triggerDataFunctionBindings) {
1157			this.handleDataFunctionBinding(str_dataPath, obj_originalData);
1158		}
1159	},
1160	
1161	handleDataFunctionBinding: function(str_dataPath, obj_originalData) {
1162		var arr_matchingDataPaths;
1163		
1164		/*
1165		 * Do nothing if no dataFunctionBindings are registered with the model
1166		 */
1167		if (this.obj_dataFunctionBindings === undefined || this.obj_dataFunctionBindings === null) {
1168			return;
1169		}
1170		
1171		arr_matchingDataPaths = this.lookForMatchingDataBindings(str_dataPath, this.obj_dataFunctionBindings, true);
1172		
1173		Array.each(arr_matchingDataPaths, function(str_registeredDataPath) {			
1174			this[this.obj_dataFunctionBindings[str_registeredDataPath]](
1175				this.readData(str_registeredDataPath), // the new data that is written to the actual data path
1176				str_registeredDataPath, // the registered data path, which might be some levels above the actual data path where the data is being written to
1177				this.readData(str_registeredDataPath, obj_originalData), // the original data that is stored in the actual data path before the new data is being written to it
1178				str_dataPath // the actual data path where the new data is being written to
1179			);
1180		}.bind(this));
1181	},
1182	
1183	handleDataBinding: function(str_dataPath) {
1184		var arr_matchingDataPaths;
1185		
1186		arr_matchingDataPaths = this.lookForMatchingDataBindings(str_dataPath, this.__module.obj_registeredDataBindings[this.name]);
1187
1188		Array.each(arr_matchingDataPaths, function(str_registeredDataPath) {
1189			if (
1190					this.__module.obj_registeredDataBindings[this.name][str_registeredDataPath] === undefined
1191				||	typeOf(this.__module.obj_registeredDataBindings[this.name][str_registeredDataPath]) !== 'array'
1192			) {
1193				/*
1194				 * Return if the value registered for the dataPath is not actually
1195				 * an element collection
1196				 */
1197				return;
1198			}
1199			
1200			Array.each(this.__module.obj_registeredDataBindings[this.name][str_registeredDataPath], function(obj_binding) {
1201				var var_valueToSet = this.readData(str_registeredDataPath);
1202				if (obj_binding.str_bindToTranslation) {
1203					var_valueToSet = this[obj_binding.str_bindToTranslation](var_valueToSet, obj_binding.el_bound);
1204				}
1205				
1206				/*
1207				 * Different element types need different ways to set their value
1208				 */
1209				if (obj_binding.str_bindToProperty) {
1210					if (obj_binding.str_bindToProperty !== 'none') {
1211						obj_binding.el_bound.setProperty(obj_binding.str_bindToProperty, var_valueToSet);
1212					}
1213				} else if (
1214						obj_binding.el_bound.match('select')
1215					||	obj_binding.el_bound.match('textarea')
1216					||	(obj_binding.el_bound.match('input') && obj_binding.el_bound.getProperty('type') === 'text')
1217				) {
1218					obj_binding.el_bound.setProperty('value', var_valueToSet);
1219				} else if (
1220					(obj_binding.el_bound.match('input') && obj_binding.el_bound.getProperty('type') === 'checkbox')
1221				) {
1222					if (typeOf(var_valueToSet) === 'boolean') {
1223						obj_binding.el_bound.setProperty('checked', obj_binding.el_bound.getProperty('value') === 'false' ? !var_valueToSet : var_valueToSet);
1224					} else {
1225						if (obj_binding.el_bound.getProperty('value') == var_valueToSet) {
1226							obj_binding.el_bound.setProperty('checked', true);
1227						} else {
1228							obj_binding.el_bound.setProperty('checked', false);
1229						}
1230					}
1231				} else if (
1232					(obj_binding.el_bound.match('input') && obj_binding.el_bound.getProperty('type') === 'radio')
1233				) {
1234					if (obj_binding.el_bound.getProperty('value') == var_valueToSet) {
1235						obj_binding.el_bound.setProperty('checked', true);
1236					} else {
1237						obj_binding.el_bound.setProperty('checked', false);
1238					}
1239				} else {
1240					obj_binding.el_bound.setProperty('html', var_valueToSet);
1241				}				
1242			}.bind(this));
1243		}.bind(this));
1244	},
1245	
1246	/*
1247	 * Multiple registered data bindings can match a dataPath used with writeData().
1248	 * In fact, all registered dataPaths that begin with the dataPath used with
1249	 * writeData() match, but there's an exception:
1250	 * 
1251	 * obj_test1.obj_test2.str_testValue
1252	 *		must match obj_test1.obj_test2
1253	 *		but must NOT match obj_test1.obj_test2.str_test
1254	 */
1255	lookForMatchingDataBindings: function(str_dataPath, obj_registeredDataBindings, bln_checkForFunctionBinding) {
1256		var arr_matchingDataPaths = [];
1257		
1258		bln_checkForFunctionBinding = bln_checkForFunctionBinding !== undefined && bln_checkForFunctionBinding !== null && bln_checkForFunctionBinding ? true : false;
1259		
1260		if (str_dataPath === undefined || str_dataPath === null) {
1261			console.error('str_dataPath missing');
1262		}
1263
1264		/*
1265		 * Nothing matches if there are no bindings defined for this model
1266		 */
1267		if (obj_registeredDataBindings === undefined || obj_registeredDataBindings === null) {
1268			return arr_matchingDataPaths;
1269		}
1270		
1271		/*
1272		 * Convert the data bindings object into a hash to make sure that
1273		 * iterating over its keys will work, because otherwise in some cases,
1274		 * it won't.
1275		 */
1276		obj_registeredDataBindings = new Hash(obj_registeredDataBindings);
1277		
1278		/*
1279		 * If str_dataPath is empty, all registered data paths match
1280		 */		
1281		if (!str_dataPath) {
1282			arr_matchingDataPaths = Object.keys(obj_registeredDataBindings);
1283			return arr_matchingDataPaths;
1284		}
1285		
1286		/*
1287		 * Check for registered data paths that begin with the written data path
1288		 * including the dot/square bracket or that are completely equal
1289		 */
1290		Array.each(Object.keys(obj_registeredDataBindings), function(str_registeredDataPath) {
1291			if (
1292					str_registeredDataPath === str_dataPath
1293				||	str_registeredDataPath.lastIndexOf(str_dataPath + '.', 0) === 0
1294				||	str_registeredDataPath.lastIndexOf(str_dataPath + '[', 0) === 0
1295
1296				/*
1297				 * If we are looking for "data function bindings" instead of
1298				 * "data view/template bindings", we have to match the data path
1299				 * whether the written data path is the first part of the registered
1300				 * data path or it is the other way round. In case of "data view/template
1301				 * bindings" we only consider it a match if the written data path
1302				 * represents the beginning of a registered data path.
1303				 */
1304				|| (bln_checkForFunctionBinding && str_dataPath.lastIndexOf(str_registeredDataPath + '.', 0) === 0)
1305				|| (bln_checkForFunctionBinding && str_dataPath.lastIndexOf(str_registeredDataPath + '[', 0) === 0)
1306			) {
1307				arr_matchingDataPaths.push(str_registeredDataPath);
1308			}
1309		});
1310		
1311		return arr_matchingDataPaths;
1312	},
1313	
1314	/*
1315	 * This function expects a data path and returns the corresonding value from
1316	 * the model's data. Additionally, it is possible to pass an object that is
1317	 * used instead of the model's data. This feature is used to access the data
1318	 * path from a copy of the model's data which is being used if written data
1319	 * needs to be reset.
1320	 * 
1321	 * If used without the optional "obj_dataToUseInstead" parameter, calling this
1322	 * function is basically equivalent to addressing a model's data-object
1323	 * directly:
1324	 * this.__models.abc.readData('obj_test.str_test') === this.__models.abc.data.obj_test.str_test
1325	 * 
1326	 */
1327	readData: function(str_dataPath, obj_dataToUseInstead) {
1328		var var_value;
1329		
1330		obj_dataToUseInstead = obj_dataToUseInstead !== undefined && obj_dataToUseInstead !== null ? obj_dataToUseInstead : null;
1331		
1332		/*
1333		 * Even the empty string is allowed as data path, pointing directly
1334		 * to 'this.data'
1335		 */
1336		if (str_dataPath === undefined || !str_dataPath) {
1337			str_dataPath = '';
1338		}
1339
1340		eval('var_value = ' + (obj_dataToUseInstead !== null ? 'obj_dataToUseInstead' : 'this.data') + (str_dataPath ? '.' + str_dataPath : ''));
1341		return var_value;
1342	}
1343};
1344var class_lsjs_module = new Class(classdef_lsjs_module);
1345
1346if (window.lsjs === undefined || window.lsjs === null) {
1347	window.lsjs = new class_lsjs();
1348
1349	window.addEvent('domready', function() {
1350		$$('body')[0].addClass('lsjs-active');
1351	});
1352} else {
1353	console.error('lsjs core already loaded!');
1354}
1355}
1355)();
1356(function() {
1357var classdef_lsjs_templateHandler = {
1358	obj_templateFunctions: {},
1359	
1360	initialize: function() {
1361	},
1362
1363	use: function(obj_usageOptions, str_moduleName) {
1364		var	obj_usageOptionsDefaults,
1365			func_templateBoundWithArguments,
1366			el_renderedTemplate;
1367		
1368		if (str_moduleName === undefined || str_moduleName === null || !str_moduleName) {
1369			str_moduleName = 'universal';
1370		}
1371
1372		obj_usageOptionsDefaults = {
1373			'name': '', // the name of the template function
1374			'arg': null, // the argument to pass to the template function
1375			'parent': null, // an element in which the parsed template should be inserted
1376			'mode': 'add', // the template usage mode (pure, add, replace)
1377			'bind': null, // an object to bind to the template function as "this" for the template function
1378			'class': null, // a class string to use with the template wrapping div
1379			'id': null // an id to use with the template wrapping div
1380		};
1381		
1382		Object.each(obj_usageOptionsDefaults, function(value, key) {
1383			if (obj_usageOptions[key] === undefined || obj_usageOptions[key] === null) {
1384				obj_usageOptions[key] = value;
1385			}
1386		});
1387		
1388		obj_usageOptions.mode = obj_usageOptions.mode === 'replace' || obj_usageOptions.mode === 'pure' ? obj_usageOptions.mode : 'add';
1389		
1390		if (
1391				this.obj_templateFunctions[str_moduleName] === undefined
1392			||	this.obj_templateFunctions[str_moduleName] === null
1393		) {
1394			console.error('module "' + str_moduleName + '" is not registered');
1395			return null;
1396		}
1397		
1398		if (
1399				obj_usageOptions.name === undefined
1400			||	!obj_usageOptions.name
1401		) {
1402			console.error('no template name given.');
1403			return null;
1404		}
1405		
1406		if (
1407				this.obj_templateFunctions[str_moduleName][obj_usageOptions.name] === undefined
1408			||	this.obj_templateFunctions[str_moduleName][obj_usageOptions.name] === null
1409		) {
1410			console.info('template "' + obj_usageOptions.name + '" is not registered');
1411			return null;
1412		}
1413		
1414		func_templateBoundWithArguments = this.obj_templateFunctions[str_moduleName][obj_usageOptions.name].pass(obj_usageOptions.arg, (obj_usageOptions.bind !== undefined && obj_usageOptions.bind !== null ? obj_usageOptions.bind : window));
1415				
1416		el_renderedTemplate = new Element('div').setProperty('html', func_templateBoundWithArguments());
1417		if (obj_usageOptions.id !== undefined && obj_usageOptions.id !== null) {
1418			el_renderedTemplate.setProperty('id', obj_usageOptions.id);
1419		}
1420		el_renderedTemplate.addClass(obj_usageOptions.name);
1421		if (obj_usageOptions.class !== undefined && obj_usageOptions.class !== null) {
1422			el_renderedTemplate.addClass(obj_usageOptions.class);
1423		}
1424
1425		if (typeOf(obj_usageOptions.parent) !== 'element') {
1426			return el_renderedTemplate;
1427		}
1428		
1429		if (obj_usageOptions.mode === 'pure') {
1430			return el_renderedTemplate;
1431		}
1432		
1433		if (obj_usageOptions.mode === 'replace') {
1434			/*
1435			 * First, we use destroy() on all the children, because we want to
1436			 * prepare them for garbage collection. This, however, does not eliminate
1437			 * text nodes, which is why, secondly, we use parent.empty() to get
1438			 * rid of them, too.
1439			 */
1440			obj_usageOptions.parent.getChildren().destroy();
1441			obj_usageOptions.parent.empty();
1442		}
1443		
1444		obj_usageOptions.parent.adopt(el_renderedTemplate);
1445		
1446		return el_renderedTemplate;
1447	},
1448	
1449	/*
1450	 * 'pure' simply returns the rendered template but does not add it to the DOM.
1451	 */
1452	pure: function(obj_usageOptions, str_moduleName) {
1453		obj_usageOptions.mode = 'pure';
1454		return this.use(obj_usageOptions, str_moduleName);
1455	},
1456	
1457	/*
1458	 * 'add' puts the rendered template in the DOM as a child of the given parent
1459	 */
1460	add: function(obj_usageOptions, str_moduleName) {
1461		obj_usageOptions.mode = 'add';
1462		return this.use(obj_usageOptions, str_moduleName);
1463	},
1464	
1465	/*
1466	 * 'replace' first removes all children from the given parent and then does
1467	 * what 'add' does.
1468	 */
1469	replace: function(obj_usageOptions, str_moduleName) {
1470		obj_usageOptions.mode = 'replace';
1471		return this.use(obj_usageOptions, str_moduleName);
1472	},
1473	
1474	register: function(obj_templateFunctions, str_moduleName) {
1475		if (str_moduleName === undefined || str_moduleName === null || !str_moduleName) {
1476			str_moduleName = 'universal';
1477		}
1478		Object.each(obj_templateFunctions, function(func_template, str_templateFunctionName) {
1479			this.registerSingleTemplate(str_templateFunctionName, func_template, str_moduleName);
1480		}.bind(this));
1481	},
1482	
1483	registerSingleTemplate: function(str_templateFunctionName, func_template, str_moduleName) {
1484		if (this.obj_templateFunctions[str_moduleName] === undefined || this.obj_templateFunctions[str_moduleName] === null) {
1485			this.obj_templateFunctions[str_moduleName] = {};
1486		}
1487		if (this.obj_templateFunctions[str_moduleName][str_templateFunctionName] !== undefined && typeOf(this.obj_templateFunctions[str_moduleName][str_templateFunctionName]) === 'function') {
1488			console.error('double assignment to template name "' + str_templateFunctionName + '". Is it possible that you included the same template file more than once?');
1489			return;
1490		}
1491		
1492		if (typeOf(func_template) !== 'function') {
1493			console.error('no template function given for template name "' + str_templateFunctionName + '"');
1494			return;
1495		}
1496		
1497		this.obj_templateFunctions[str_moduleName][str_templateFunctionName] = func_template;
1498	}
1499};
1500
1501var class_lsjs_templateHandler = new Class(classdef_lsjs_templateHandler);
1502
1503lsjs.tpl = new class_lsjs_templateHandler();
1504
1505})();
1506// < ## CORE ##
1507
1508// ## CORE MODULES ## >
1509
1510
1511
1512// #### MODULE ## >
1513// ###### TEMPLATES ## >
1514(function() {
1515
1516var obj_templates = {
1517	'main': function(arg) {
1518		var str_return = '<div class="selectAll">\
1519	<input id="' + arg.str_selectAllCheckboxId + '" type="checkbox">\
1520	<label for="' + arg.str_selectAllCheckboxId + '">' + arg.__models.lang.data.selectAllLabel + '</label>\
1521</div>';
1522
1523		// add template info in debug mode
1524		var str_return = '<!-- BEGIN LSJS TEMPLATE: modules/automaticSelectAllCheckbox/templates/main.html/main.html -->' + str_return + '<!-- END LSJS TEMPLATE: modules/automaticSelectAllCheckbox/templates/main.html/main.html -->';
1525
1526		return str_return;
1527	}
1528};
1529
1530lsjs.tpl.register(obj_templates, 'automaticSelectAllCheckbox');
1531
1532})();
1533// < ###### TEMPLATES ##
1534
1535// ###### MODELS ## >
1536(function() {
1537	
1538var obj_classdef_model = {
1539	name: 'lang',
1540	
1541	data: {
1542		selectAllLabel: 'Alle ausw&auml;hlen'
1543	},
1544
1545	start: function() {
1546		this.__module.onModelLoaded();
1547	}
1548};
1549
1550lsjs.addModelClass('automaticSelectAllCheckbox', obj_classdef_model);
1551
1552})();
1553
1554
1555
1556(function() {
1557	
1558var obj_classdef_model = {
1559	name: 'options',
1560	
1561	data: {},
1562	
1563	start: function() {
1564		/*
1565		 * Initializing the options in the data object with default values which
1566		 * can later be overwritten when the "set" method is called with other options
1567		 */
1568		this.data = {
1569			str_selector: '.useAutomaticSelectAllCheckbox',
1570			str_appliedClass: 'automaticSelectAllCheckboxApplied'
1571		};
1572	},
1573	
1574	set: function(obj_options) {
1575		Object.merge(this.data, obj_options);
1576		this.__module.onModelLoaded();
1577	}
1578};
1579
1580lsjs.addModelClass('automaticSelectAllCheckbox', obj_classdef_model);
1581
1582})();
1583
1584
1585
1586
1587// < ###### MODELS ##
1588
1589// ###### VIEW ## >
1590(function() {
1591	
1592// ### ENTER MODULE NAME HERE ######
1593var str_moduleName = 'automaticSelectAllCheckbox';
1594// #################################
1595
1596var obj_classdef = 	{
1597	start: function() {
1598		this.addSelectAllCheckboxes();
1599	},
1600	
1601	addSelectAllCheckboxes: function() {
1602		var	self = this,
1603			els_checkboxGroup,
1604			int_checkboxGroupCounter = 0;
1605		
1606		els_checkboxGroup = $$(this.__models.options.data.str_selector);
1607		
1608		Array.each(els_checkboxGroup, function(el_checkboxGroup) {
1609			if (el_checkboxGroup.hasClass(this.__models.options.data.str_appliedClass)) {
1610				return;
1611			}
1612			
1613			var el_selectAll = this.tplPure({
1614				name: 'main',
1615				parent: el_checkboxGroup,
1616				arg: {
1617					str_selectAllCheckboxId: 'selectAllCheckbox_' + int_checkboxGroupCounter
1618				}
1619			});
1620			
1621			el_checkboxGroup.addClass(this.__models.options.data.str_appliedClass);
1622			el_selectAll.inject(el_checkboxGroup, 'top');
1623			
1624			el_selectAll.getElement('input').addEvent('change', function() {
1625				Array.each(this.getParent(self.__models.options.data.str_selector).getElements('input[type="checkbox"]'), function(el_checkbox) {
1626					el_checkbox.setProperty('checked', this.getProperty('checked'));
1627				}.bind(this));
1628			});
1629			
1630			int_checkboxGroupCounter++;
1631		}.bind(this));		
1632	}
1633};
1634
1635lsjs.addViewClass(str_moduleName, obj_classdef);
1636
1637})();
1638// < ###### VIEW ##
1639// 
1640// ###### CONTROLLER ## >
1641/*
1642 * 
1643 * How to use this module:
1644 * 
1645 * If you have multiple checkboxes and want to automatically add an additional
1646 * checkbox with which all the others can be checked and unchecked at once,
1647 * simply wrap all your checkboxes in one container element (e.g. a div)
1648 * and give this container element the class "useAutomaticSelectAllCheckbox"
1649 * (or whatever is defined as the str_selector property in this module's options model).
1650 * 
1651 * Now, all you have to do is to run this module to automatically apply the
1652 * functionality:
1653 * 
1654 * lsjs.__moduleHelpers.automaticSelectAllCheckbox.start();
1655 * 
1656 * If other checkbox groups are added to the page dynamically at a later point,
1657 * you can use
1658 * 
1659 * lsjs.__moduleHelpers.automaticSelectAllCheckbox.refresh();
1660 * 
1661 * in order to rescan the page and apply the functionality wherever necessary.
1662 * Checkbox groups which already have the functionality are automatically skipped
1663 * in the refresh call.
1664 */
1665
1666(function() {
1667	
1668// ### ENTER MODULE NAME HERE ######
1669var str_moduleName = 'automaticSelectAllCheckbox';
1670// #################################
1671
1672var obj_classdef = {
1673	start: function() {
1674	}
1675};
1676
1677lsjs.addControllerClass(str_moduleName, obj_classdef);
1678
1679lsjs.__moduleHelpers[str_moduleName] = {
1680	self: null,
1681	
1682	start: function(obj_options) {
1683		/*
1684		 * Only allow one single instance of this module to be started!
1685		 */
1686		if (this.self !== null) {
1687			console.error('module ' + str_moduleName + ' has already been started');
1688			return;
1689		}
1690		
1691		this.self = lsjs.createModule({
1692			__name: str_moduleName
1693		});
1694		
1695		this.self.__models.options.set(obj_options);
1696	},
1697	
1698	refresh: function() {
1699		this.self.__view.addSelectAllCheckboxes();
1700	}
1701};
1702
1703}
1703)();
1704// < ###### CONTROLLER ##
1705// < #### MODULE ##
1706
1707
1708
1709
1710// #### MODULE ## >
1711// ###### TEMPLATES ## >
1712
1713// < ###### TEMPLATES ##
1714
1715// ###### MODELS ## >
1716(function() {
1717	
1718var obj_classdef_model = {
1719	name: 'options',
1720	
1721	data: {
1722		str_cajaxMode: 'updateCompletely',
1723
1724		obj_cajaxRequestData: {
1725			/*
1726			 * This object has to be filled using the data-lsjs-cajaxCallerOptions parameter in the DOM element to be
1727			 * enhanced by this module.
1728			 * IMPORTANT: Values must not be predefined here!
1729			 *
1730			requestedElementClass: 'requestedClass',
1731			requestedElementID: 'requestedId',
1732			custom: {
1733				testkey1: 'value1',
1734				testkey2: 'value2'
1735			}
1736			*/
1737		}
1738	},
1739	
1740	start: function() {
1741		this.__module.onModelLoaded();
1742	},
1743	
1744	set: function(obj_options) {
1745		Object.merge(this.data, obj_options);
1746	}
1747};
1748
1749lsjs.addModelClass('cajaxCallerInstance', obj_classdef_model);
1750
1751})();
1752
1753
1754
1755
1756// < ###### MODELS ##
1757
1758// ###### VIEW ## >
1759(function() {
1760	
1761// ### ENTER MODULE NAME HERE ######
1762var str_moduleName = 'cajaxCallerInstance';
1763// #################################
1764
1765var obj_classdef = 	{
1766	start: function() {
1767		var str_options,
1768			obj_options = {},
1769			str_eventType;
1770
1771		/*
1772		 * Only "form" elements and "a" elements are supported.
1773		 * If the container element itself is not a "form" or "a"
1774		 * element, we look for one of these inside the container.
1775		 */
1776		switch (this.__el_container.get('tag')) {
1777			case 'a':
1778				str_eventType = 'click';
1779				break;
1780
1781			case 'form':
1782				str_eventType = 'submit';
1783				break;
1784
1785			default:
1786				var el_alternativeInside = null;
1787
1788				el_alternativeInside = this.__el_container.getElement('form:not(.skipCajaxCaller)');
1789
1790				if (el_alternativeInside === null) {
1791					el_alternativeInside = this.__el_container.getElement('a:not(.skipCajaxCaller)');
1792				}
1793
1794				if (el_alternativeInside === null) {
1795					return;
1796				}
1797
1798				el_alternativeInside.setProperty('data-lsjs-cajaxCallerOptions', this.__el_container.getProperty('data-lsjs-cajaxCallerOptions'));
1799
1800				this.__el_container = el_alternativeInside;
1801
1802				this.start();
1803				return;
1804				break;
1805		}
1806
1807		str_options = this.__el_container.getProperty('data-lsjs-cajaxCallerOptions');
1808
1809		if (str_options) {
1810			obj_options = JSON.decode(str_options);
1811		}
1812
1813		this.__models.options.set(obj_options);
1814
1815		this.registerElements(this.__el_container, 'main', false);
1816
1817		/* -->
1818		 * Prepare bound functions that are needed for events
1819		 */
1820		this.bound_makeCajaxRequest = this.makeCajaxRequest.bind(this);
1821
1822		this.__el_container.addEvent(
1823			str_eventType,
1824			this.bound_makeCajaxRequest
1825		);
1826	},
1827
1828	bound_makeCajaxRequest: null,
1829	makeCajaxRequest: function(event) {
1830		event.stop();
1831
1832		switch (this.__el_container.get('tag')) {
1833			case 'a':
1834				lsjs.loadingIndicator.__controller.show();
1835
1836				new Request.cajax({
1837					url: this.__el_container.getProperty('href'),
1838					method: 'get',
1839					noCache: true,
1840					cajaxMode: this.__models.options.data.str_cajaxMode,
1841					data: 'cajaxRequestData=' + JSON.encode(this.__models.options.data.obj_cajaxRequestData),
1842					onComplete: function() {
1843						lsjs.loadingIndicator.__controller.hide();
1844					},
1845					onSuccess: function(els, str_html, str_script) {
1846						Browser.exec(str_script);
1847					}
1848				}).send();
1849				break;
1850
1851			case 'form':
1852				lsjs.loadingIndicator.__controller.show();
1853
1854				new Request.cajax({
1855					url: this.__el_container.getProperty('action'),
1856					method: 'post',
1857					noCache: true,
1858					cajaxMode: this.__models.options.data.str_cajaxMode,
1859					el_formToUseForFormData: this.__el_container,
1860					// obj_additionalFormData: this.__models.options.data.obj_additionalFormData,
1861					obj_additionalFormData: {
1862						cajaxRequestData: JSON.encode(this.__models.options.data.obj_cajaxRequestData)
1863					},
1864					onComplete: function() {
1865						lsjs.loadingIndicator.__controller.hide();
1866					},
1867					onSuccess: function(els, str_html, str_script) {
1868						Browser.exec(str_script);
1869					}
1870				}).send();
1871				break;
1872
1873			default:
1874				return;
1875				break;
1876		}
1877	}
1878};
1879
1880lsjs.addViewClass(str_moduleName, obj_classdef);
1881
1882})();
1883// < ###### VIEW ##
1884// 
1885// ###### CONTROLLER ## >
1886(function() {
1887	
1888// ### ENTER MODULE NAME HERE ######
1889var str_moduleName = 'cajaxCallerInstance';
1890// #################################
1891
1892var obj_classdef = {
1893	start: function() {
1894	}
1895};
1896
1897lsjs.addControllerClass(str_moduleName, obj_classdef);
1898
1899})();
1900// < ###### CONTROLLER ##
1901// < #### MODULE ##
1902
1903
1904
1905
1906// #### MODULE ## >
1907// ###### TEMPLATES ## >
1908
1909// < ###### TEMPLATES ##
1910
1911// ###### MODELS ## >
1912(function() {
1913	
1914var obj_classdef_model = {
1915	name: 'options',
1916	
1917	data: {},
1918	
1919	start: function() {
1920		/*
1921		 * Initializing the options in the data object with default values which
1922		 * can later be overwritten when the "set" method is called with other options
1923		 */
1924		this.data = {
1925			el_domReference: null,
1926			str_selector: '[data-lsjs-component~="cajaxCaller"]', // the selector for the dom element to enrich,
1927			str_classToSetWhenModuleApplied: 'cajaxCallerApplied'
1928		};
1929	},
1930	
1931	set: function(obj_options) {
1932		Object.merge(this.data, obj_options);
1933		this.__module.onModelLoaded();
1934	}
1935};
1936
1937lsjs.addModelClass('cajaxCallerManager', obj_classdef_model);
1938
1939})();
1940
1941
1942
1943
1944// < ###### MODELS ##
1945
1946// ###### VIEW ## >
1947
1948// < ###### VIEW ##
1949// 
1950// ###### CONTROLLER ## >
1951/*
1952 * -- ACTIVATION: --
1953 *
1954 * To activate this module, the following code has to be put in the app.js:
1955 *
1956	 lsjs.__moduleHelpers.cajaxCallerManager.start({
1957		 el_domReference: el_domReference
1958	 });
1959 *
1960 * The el_domReference parameter is only required if this module initialization code is called 
1960in a cajax_domUpdate event.
1961 *
1962 *
1963 *
1964 * -- FUNCTIONALITY AND USAGE: --
1965 *
1966 * Add the following attribute to a DOM element to apply this module:
1967 *
1968 * data-lsjs-component="cajaxCaller"
1969 *
1970 * TODO: Write a detailed functionality and usage description. Until then, the options models of the manager and
1971 * the instance submodule can be consulted for further information.
1972 */
1973
1974(function() {
1975	
1976// ### ENTER MODULE NAME HERE ######
1977var str_moduleName = 'cajaxCallerManager';
1978// #################################
1979
1980var obj_classdef = {
1981	start: function() {
1982		var els_toEnhance;
1983		/*
1984		 * Look for elements to enrich with the lsjs-module and then
1985		 * instantiate instances for each element found.
1986		 */
1987		if (this.__models.options.data.el_domReference !== undefined && typeOf(this.__models.options.data.el_domReference) === 'element') {
1988			els_toEnhance = this.__models.options.data.el_domReference.getElements(this.__models.options.data.str_selector);
1989		} else {
1990			els_toEnhance = $$(this.__models.options.data.str_selectors);
1991		}
1992
1993		Array.each(els_toEnhance, function(el_container) {
1994			/* ->
1995			 * Make sure not to handle an element more than once
1996			 */
1997			if (!el_container.retrieve('alreadyHandledBy_' + str_moduleName)) {
1998				el_container.store('alreadyHandledBy_' + str_moduleName, true);
1999			} else {
2000				return;
2001			}
2002			/*
2003			 * <-
2004			 */
2005
2006			el_container.addClass(this.__models.options.data.str_classToSetWhenModuleApplied);
2007
2008			lsjs.createModule({
2009				__name: 'cajaxCallerInstance',
2010				__parentModule: this.__module,
2011				__useLoadingIndicator: false,
2012				__el_container: el_container
2013			});
2014		}.bind(this));
2015	}
2016};
2017
2018lsjs.addControllerClass(str_moduleName, obj_classdef);
2019
2020lsjs.__moduleHelpers[str_moduleName] = {
2021	self: null,
2022	
2023	start: function(obj_options) {
2024		this.self = lsjs.createModule({
2025			__name: str_moduleName
2026		});
2027		this.self.__models.options.set(obj_options);
2028	}
2029};
2030
2031})();
2032// < ###### CONTROLLER ##
2033// < #### MODULE ##
2034
2035
2036
2037
2038// #### MODULE ## >
2039// ###### TEMPLATES ## >
2040
2041// < ###### TEMPLATES ##
2042
2043// ###### MODELS ## >
2044(function() {
2045	
2046var obj_classdef_model = {
2047	name: 'options',
2048	
2049	data: {
2050	},
2051	
2052	start: function() {
2053		this.__module.onModelLoaded();
2054	},
2055	
2056	set: function(obj_options) {
2057		Object.merge(this.data, obj_options);
2058	}
2059};
2060
2061lsjs.addModelClass('elementFolderInstance', obj_classdef_model);
2062
2063})();
2064
2065
2066
2067
2068// < ###### MODELS ##
2069
2070// ###### VIEW ## >
2071(function() {
2072
2073// ### ENTER MODULE NAME HERE ######
2074var str_moduleName = 'elementFolderInstance';
2075// #################################
2076
2077var obj_classdef = 	{
2078	str_containerID: null,
2079
2080	start: function() {
2081	    var str_options,
2082            obj_options,
2083            obj_unfoldOptions;
2084
2085		this.registerElements(this.__el_container, 'main', true);
2086
2087        str_options = this.__el_container.getProperty('data-lsjs-elementFolderOptions');
2088
2089        if (str_options) {
2090            obj_options = JSON.decode(str_options);
2091        }
2092
2093        this.__models.options.set(obj_options);
2094
2095        this.str_containerID = this.__el_container.getProperty('id');
2096
2097        obj_unfoldOptions = Object.merge(
2098            {
2099                str_initialToggleStatus: 'open',
2100                bln_toggleOnInitialization: false,
2101                bln_skipAnimationWhenTogglingOnInitialization: false,
2102                var_togglerSelector: this.__autoElements.main.elementFolderToggler,
2103                var_contentBoxSelector: this.__autoElements.main.elementFolderContent,
2104                var_wrapperSelector: this.__el_container,
2105                str_cookieIdentifierName: this.str_containerID ? this.str_containerID : '',
2106                str_initialCookieStatus: 'open',
2107                obj_morphOptions: {
2108                    'duration': 600
2109                }
2110            },
2111            this.__models.options.data
2112        );
2113
2114		lsjs.__moduleHelpers.unfold.start(obj_unfoldOptions);
2115
2116	}
2117};
2118
2119lsjs.addViewClass(str_moduleName, obj_classdef);
2120
2121})();
2122// < ###### VIEW ##
2123// 
2124// ###### CONTROLLER ## >
2125(function() {
2126	
2127// ### ENTER MODULE NAME HERE ######
2128var str_moduleName = 'elementFolderInstance';
2129// #################################
2130
2131var obj_classdef = {
2132	start: function() {
2133	}
2134};
2135
2136lsjs.addControllerClass(str_moduleName, obj_classdef);
2137
2138})();
2139// < ###### CONTROLLER ##
2140// < #### MODULE ##
2141
2142
2143
2144
2145// #### MODULE ## >
2146// ###### TEMPLATES ## >
2147
2148// < ###### TEMPLATES ##
2149
2150// ###### MODELS ## >
2151(function() {
2152	
2153var obj_classdef_model = {
2154	name: 'options',
2155	
2156	data: {},
2157	
2158	start: function() {
2159		/*
2160		 * Initializing the options in the data object with default values which
2161		 * can later be overwritten when the "set" method is called with other options
2162		 */
2163		this.data = {
2164			el_domReference: null,
2165			str_selector: '[data-lsjs-component~="elementFolder"]', // the selector for the dom element to enrich,
2166			str_classToSetWhenModuleApplied: 'elementFolderApplied'
2167		};
2168	},
2169	
2170	set: function(obj_options) {
2171		Object.merge(this.data, obj_options);
2172		this.__module.onModelLoaded();
2173	}
2174};
2175
2176lsjs.addModelClass('elementFolderManager', obj_classdef_model);
2177
2178})();
2179
2180
2181
2182
2183// < ###### MODELS ##
2184
2185// ###### VIEW ## >
2186
2187// < ###### VIEW ##
2188// 
2189// ###### CONTROLLER ## >
2190/*
2191 * -- ACTIVATION: --
2192 *
2193 * To activate this module, the following code has to be put in the app.js:
2194 *
2195	 lsjs.__moduleHelpers.elementFolderManager.start({
2196		 el_domReference: el_domReference
2197	 });
2198 *
2199 * The el_domReference parameter is only required if this module initialization code is called 
2199in a cajax_domUpdate event.
2200 *
2201 *
2202 *
2203 * -- FUNCTIONALITY AND USAGE: --
2204 *
2205 * Add the following attribute to a DOM element to apply this module:
2206 *
2207 * data-lsjs-component="elementFolder"
2208 *
2209 *
2210 * Add the following attribute to an another element inside the above mentioned
2211 * outer DOM element in order to use it as the toggler:
2212 *
2213 * data-lsjs-element="elementFolderToggler"
2214 *
2215 *
2216 * Add the following attribute to yet another element inside the above mentioned
2217 * outer DOM element in order to use it as the toggler's content:
2218 *
2219 * data-lsjs-element="elementFolderContent"
2220 *
2221 *
2222 *
2223 * Using the attribute "data-lsjs-elementFolderOptions" the default options for an
2224 * elementFolder instance can be overridden. Since "elementFolder" leveragesf the "unfold" module
2225 * all options supported by the "unfold" module can be used here.
2226 *
2227 * The following example shows how to create an elementFolder instance that starts closed
2228 * although by default it would start open.
2229 *
2230 * <section
2231 * 		data-lsjs-component="elementFolder"
2232 * 		data-lsjs-elementFolderOptions="
2233 * 			{
2234 * 				str_initialCookieStatus: 'closed'
2235 * 			}
2236 * 		"
2237 * 		id="someId"
2238 * >
2239 * 		<div data-lsjs-element="elementFolderToggler">TOGGLE</div>
2240 * 		<div data-lsjs-element="elementFolderContent">CONTENT</div>
2241 * </section>
2242 *
2243 *
2244 * TODO: Write a detailed functionality and usage description. Until then, the options models of the manager and
2245 * the instance submodule can be consulted for further information.
2246 *
2247 */
2248
2249(function() {
2250	
2251// ### ENTER MODULE NAME HERE ######
2252var str_moduleName = 'elementFolderManager';
2253// #################################
2254
2255var obj_classdef = {
2256	start: function() {
2257		var els_toEnhance;
2258		/*
2259		 * Look for elements to enrich with the lsjs-module and then
2260		 * instantiate instances for each element found.
2261		 */
2262		if (this.__models.options.data.el_domReference !== undefined && typeOf(this.__models.options.data.el_domReference) === 'element') {
2263			els_toEnhance = this.__models.options.data.el_domReference.getElements(this.__models.options.data.str_selector);
2264		} else {
2265			els_toEnhance = $$(this.__models.options.data.str_selectors);
2266		}
2267
2268		Array.each(els_toEnhance, function(el_container) {
2269			/* ->
2270			 * Make sure not to handle an element more than once
2271			 */
2272			if (!el_container.retrieve('alreadyHandledBy_' + str_moduleName)) {
2273				el_container.store('alreadyHandledBy_' + str_moduleName, true);
2274			} else {
2275				return;
2276			}
2277			/*
2278			 * <-
2279			 */
2280
2281			el_container.addClass(this.__models.options.data.str_classToSetWhenModuleApplied);
2282
2283			lsjs.createModule({
2284				__name: 'elementFolderInstance',
2285				__parentModule: this.__module,
2286				__useLoadingIndicator: false,
2287				__el_container: el_container
2288			});
2289		}.bind(this));
2290	}
2291};
2292
2293lsjs.addControllerClass(str_moduleName, obj_classdef);
2294
2295lsjs.__moduleHelpers[str_moduleName] = {
2296	self: null,
2297	
2298	start: function(obj_options) {
2299		this.self = lsjs.createModule({
2300			__name: str_moduleName
2301		});
2302		this.self.__models.options.set(obj_options);
2303	}
2304};
2305
2306})();
2307// < ###### CONTROLLER ##
2308// < #### MODULE ##
2309
2310
2311
2312
2313// #### MODULE ## >
2314// ###### TEMPLATES ## >
2315
2316// < ###### TEMPLATES ##
2317
2318// ###### MODELS ## >
2319(function() {
2320	
2321var obj_classdef_model = {
2322	name: 'options',
2323
2324	data: {},
2325
2326	start: function() {
2327		/*
2328		 * Initializing the options in the data object with default values which
2329		 * can later be overwritten when the "set" method is called with other options
2330		 */
2331		this.data = {
2332			var_naviSelector: null,
2333			bln_automaticallyToggleOnInitialization: true,
2334			bln_closeOnOutsideClick: false,
2335			bln_onlyFirstLevel: false
2336		};
2337	},
2338
2339	set: function(obj_options) {
2340		Object.merge(this.data, obj_options);
2341		this.__module.onModelLoaded();
2342	}
2343};
2344
2345lsjs.addModelClass('foldedNavi', obj_classdef_model);
2346
2347})();
2348
2349
2350
2351
2352// < ###### MODELS ##
2353
2354// ###### VIEW ## >
2355(function() {
2356
2357// ### ENTER MODULE NAME HERE ######
2358var str_moduleName = 'foldedNavi';
2359// #################################
2360
2361var obj_classdef = 	{
2362	start: function() {
2363		this.__el_container =
2364			(
2365				typeOf(this.__models.options.data.var_naviSelector) === 'element'
2366				||	typeOf(this.__models.options.data.var_naviSelector) === 'null'
2367			)
2368				?	this.__models.options.data.var_naviSelector
2369				:	$$(this.__models.options.data.var_naviSelector)[0];
2370
2371		if (typeOf(this.__el_container) !== 'element') {
2372			return;
2373		}
2374
2375		this.__el_container.addClass('useFoldedNavi');
2376
2377		Array.each(this.__el_container.getElements('li').filter(this.__models.options.data.bln_onlyFirstLevel ? '.level_1 > .submenu' : '.submenu'), function(el_li) {
2378			var el_toggler = new Element('span.foldedNaviToggler').inject(el_li, 'top');
2379
2380			lsjs.__moduleHelpers.unfold.start({
2381				bln_automaticallyCreateResizeBox: false,
2382				str_initialDisplayType: 'block',
2383				str_initialToggleStatus: 'closed',
2384				bln_toggleOnInitialization: this.__models.options.data.bln_automaticallyToggleOnInitialization ? (el_li.hasClass('trail') || el_li.hasClass('active')) : false,
2385				var_togglerSelector: el_toggler,
2386				var_contentBoxSelector: el_li.getElement('> ul'),
2387				var_wrapperSelector: el_li,
2388				str_animationMode: 'height',
2389				obj_morphOptions: {
2390					'duration': 400
2391				},
2392				bln_closeOnOutsideClick: this.__models.options.data.bln_closeOnOutsideClick
2393			});
2394		}.bind(this));
2395	}
2396};
2397
2398lsjs.addViewClass(str_moduleName, obj_classdef);
2399
2400})();
2401// < ###### VIEW ##
2402// 
2403// ###### CONTROLLER ## >
2404(function() {
2405	
2406// ### ENTER MODULE NAME HERE ######
2407var str_moduleName = 'foldedNavi';
2408// #################################
2409
2410var obj_classdef = {
2411	start: function() {
2412	}
2413};
2414
2415lsjs.addControllerClass(str_moduleName, obj_classdef);
2416
2417lsjs.__moduleHelpers[str_moduleName] = {
2418	self: null,
2419	
2420	start: function(obj_options) {
2421		this.self = lsjs.createModule({
2422			__name: str_moduleName
2423		});
2424		
2425		this.self.__models.options.set(obj_options);
2426	}
2427};
2428
2429}
2429)();
2430// < ###### CONTROLLER ##
2431// < #### MODULE ##
2432
2433
2434
2435
2436// #### MODULE ## >
2437// ###### TEMPLATES ## >
2438
2439// < ###### TEMPLATES ##
2440
2441// ###### MODELS ## >
2442
2443// < ###### MODELS ##
2444
2445// ###### VIEW ## >
2446
2447// < ###### VIEW ##
2448// 
2449// ###### CONTROLLER ## >
2450(function() {
2451	
2452// ### ENTER MODULE NAME HERE ######
2453var str_moduleName = 'formRedirect';
2454// #################################
2455
2456var obj_classdef = {
2457	start: function() {
2458		lsjs[str_moduleName] = this.__module;
2459	},
2460	
2461	/*
2462	 * This function redirects to a url sending data (normally as post data)
2463	 * with a dynamically created form.
2464	 * 
2465	 * As a first parameter it takes an object holding the data to send.
2466	 * The property names are used as field names in the form and the
2467	 * property values as field values.
2468	 * 
2469	 * As a second parameter it takes the url to redirect to or an empty string
2470	 * which would result in reloading the current page sending post data
2471	 * 
2472	 * As a third parameter it takes a string defining the method used when sending
2473	 * the form. It defaults to 'post'.
2474	 */
2475	send: function(obj_valuesToSend, str_targetUrl, str_method) {
2476		var	el_form,
2477			els_inputs;
2478		
2479		obj_valuesToSend = obj_valuesToSend !== undefined ? obj_valuesToSend : {};
2480		str_targetUrl = str_targetUrl !== undefined ? str_targetUrl : '';
2481		str_method = str_method !== undefined ? str_method : 'post';
2482		
2483		console.log('post redirect sending...');
2484		
2485		el_form = new Element('form')
2486		.setProperties({
2487			'method': str_method,
2488			'action': str_targetUrl
2489		})
2490		.setStyles({
2491			'position': 'absolute',
2492			'top': '-5000px',
2493			'left': '-5000px'
2494		});
2495		
2496		els_inputs = new Elements();
2497		
2498		Object.each(obj_valuesToSend, function(str_fieldValue, str_fieldName) {
2499			els_inputs.push(
2500				new Element('input').setProperties({
2501					'type': 'hidden',
2502					'name': str_fieldName,
2503					'value': str_fieldValue
2504				})
2505			);
2506		});
2507		
2508		$$('body')[0].adopt(
2509			el_form.adopt(els_inputs)
2510		);
2511		
2512		el_form.submit();
2513	}
2514};
2515
2516lsjs.addControllerClass(str_moduleName, obj_classdef);
2517
2518lsjs.createModule({
2519	__name: str_moduleName
2520});
2521
2522})();
2523// < ###### CONTROLLER ##
2524// < #### MODULE ##
2525
2526
2527
2528
2529// #### MODULE ## >
2530// ###### TEMPLATES ## >
2531(function() {
2532
2533var obj_templates = {
2534	'editToggler': function(arg) {
2535		var str_return = '<div class="pointer txs-xl-all aptr-0" data-lsjs-element="openFormToggler"><i class="fi fi-edit"></i></div>';
2536
2537		// add template info in debug mode
2538		var str_return = '<!-- BEGIN LSJS TEMPLATE: files/merconisfiles/themes/theme8/lsjsModsCoreApps/modules/formReviewer/formReviewerInstance/templates/editToggler.html/editToggler.html -->' + str_return + '<!-- END LSJS TEMPLATE: files/merconisfiles/themes/theme8/lsjsModsCoreApps/modules/formReviewer/formReviewerInstance/templates/editToggler.html/editToggler.html -->';
2539
2540		return str_return;
2541	}
2542};
2543
2544lsjs.tpl.register(obj_templates, 'formReviewerInstance');
2545
2546})();
2547// < ###### TEMPLATES ##
2548
2549// ###### MODELS ## >
2550(function() {
2551	
2552var obj_classdef_model = {
2553	name: 'options',
2554	
2555	data: {
2556	},
2557	
2558	start: function() {
2559		this.__module.onModelLoaded();
2560	},
2561	
2562	set: function(obj_options) {
2563		Object.merge(this.data, obj_options);
2564	}
2565};
2566
2567lsjs.addModelClass('formReviewerInstance', obj_classdef_model);
2568
2569})();
2570
2571
2572
2573
2574// < ###### MODELS ##
2575
2576// ###### VIEW ## >
2577(function() {
2578	
2579// ### ENTER MODULE NAME HERE ######
2580var str_moduleName = 'formReviewerInstance';
2581// #################################
2582
2583var obj_classdef = 	{
2584	els_allFormFields: null,
2585
2586	start: function() {
2587		this.getAllFormFields();
2588		if (this.els_allFormFields.length > 0) {
2589			this.initializeOpenCloseFunctionality();
2590		}
2591	},
2592
2593	getAllFormFields: function() {
2594		this.els_allFormFields = new Elements();
2595		Array.prototype.push.apply(this.els_allFormFields, this.__el_container.getElements('input'));
2596		Array.prototype.push.apply(this.els_allFormFields, this.__el_container.getElements('select'));
2597		Array.prototype.push.apply(this.els_allFormFields, this.__el_container.getElements('textarea'));
2598	},
2599
2600	initializeOpenCloseFunctionality: function() {
2601		var el_renderedTogglerTemplate = this.tplPure({
2602			name: 'editToggler'
2603		});
2604
2605		el_renderedTogglerTemplate.inject(this.__el_container, 'top');
2606
2607
2608
2609		/*
2610		 * Close or open the form initially, based on whether or not data is valid
2611		 */
2612		if (
2613			this.__el_container.getProperty('data-misc-close-form-initially') == '1'
2614			&&	this.__el_container.getElement('.error') === null
2615		) {
2616			this.closeForm();
2617		}
2618
2619		this.__autoElements.editToggler.openFormToggler.addEvent(
2620			'click',
2621			this.openForm.bind(this)
2622		)
2623	},
2624
2625	closeForm: function() {
2626		this.els_allFormFields.setProperty('disabled', true);
2627		this.__el_container.addClass('formClosed');
2628	},
2629
2630	openForm: function() {
2631		this.els_allFormFields.setProperty('disabled', false);
2632		this.__el_container.removeClass('formClosed');
2633	}
2634};
2635
2636lsjs.addViewClass(str_moduleName, obj_classdef);
2637
2638})();
2639// < ###### VIEW ##
2640// 
2641// ###### CONTROLLER ## >
2642(function() {
2643	
2644// ### ENTER MODULE NAME HERE ######
2645var str_moduleName = 'formReviewerInstance';
2646// #################################
2647
2648var obj_classdef = {
2649	start: function() {
2650	}
2651};
2652
2653lsjs.addControllerClass(str_moduleName, obj_classdef);
2654
2655})();
2656// < ###### CONTROLLER ##
2657// < #### MODULE ##
2658
2659
2660
2661
2662// #### MODULE ## >
2663// ###### TEMPLATES ## >
2664
2665// < ###### TEMPLATES ##
2666
2667// ###### MODELS ## >
2668(function() {
2669	
2670var obj_classdef_model = {
2671	name: 'options',
2672	
2673	data: {},
2674	
2675	start: function() {
2676		/*
2677		 * Initializing the options in the data object with default values which
2678		 * can later be overwritten when the "set" method is called with other options
2679		 */
2680		this.data = {
2681			el_domReference: null,
2682			str_selector: '[data-lsjs-component~="formReviewer"]', // the selector for the dom element to enrich,
2683			str_classToSetWhenModuleApplied: 'formReviewerApplied'
2684		};
2685	},
2686	
2687	set: function(obj_options) {
2688		Object.merge(this.data, obj_options);
2689		this.__module.onModelLoaded();
2690	}
2691};
2692
2693lsjs.addModelClass('formReviewerManager', obj_classdef_model);
2694
2695})();
2696
2697
2698
2699
2700// < ###### MODELS ##
2701
2702// ###### VIEW ## >
2703
2704// < ###### VIEW ##
2705// 
2706// ###### CONTROLLER ## >
2707/*
2708 * -- ACTIVATION: --
2709 *
2710 * To activate this module, the following code has to be put in the app.js:
2711 *
2712	 lsjs.__moduleHelpers.formReviewerManager.start({
2713		 el_domReference: el_domReference
2714	 });
2715 *
2716 * The el_domReference parameter is only required if this module initialization code is called 
2716in a cajax_domUpdate event.
2717 *
2718 *
2719 *
2720 * -- FUNCTIONALITY AND USAGE: --
2721 *
2722 * Add the following attribute to a DOM element to apply this module:
2723 *
2724 * data-lsjs-component="formReviewer"
2725 *
2726 * Add the following attribute to the DOM element to indicate that the form reviewer should initially close the form
2727 * unless there's a child DOM element with an ".error" class.
2728 *
2729 * data-misc-close-form-initially="1"
2730 *
2731 * TODO: Write a detailed functionality and usage description. Until then, the options models of the manager and
2732 * the instance submodule can be consulted for further information.
2733 *
2734 */
2735
2736(function() {
2737	
2738// ### ENTER MODULE NAME HERE ######
2739var str_moduleName = 'formReviewerManager';
2740// #################################
2741
2742var obj_classdef = {
2743	start: function() {
2744		var els_toEnhance;
2745		/*
2746		 * Look for elements to enrich with the lsjs-module and then
2747		 * instantiate instances for each element found.
2748		 */
2749		if (this.__models.options.data.el_domReference !== undefined && typeOf(this.__models.options.data.el_domReference) === 'element') {
2750			els_toEnhance = this.__models.options.data.el_domReference.getElements(this.__models.options.data.str_selector);
2751		} else {
2752			els_toEnhance = $$(this.__models.options.data.str_selectors);
2753		}
2754
2755		Array.each(els_toEnhance, function(el_container) {
2756			/* ->
2757			 * Make sure not to handle an element more than once
2758			 */
2759			if (!el_container.retrieve('alreadyHandledBy_' + str_moduleName)) {
2760				el_container.store('alreadyHandledBy_' + str_moduleName, true);
2761			} else {
2762				return;
2763			}
2764			/*
2765			 * <-
2766			 */
2767
2768			el_container.addClass(this.__models.options.data.str_classToSetWhenModuleApplied);
2769
2770			lsjs.createModule({
2771				__name: 'formReviewerInstance',
2772				__parentModule: this.__module,
2773				__useLoadingIndicator: false,
2774				__el_container: el_container
2775			});
2776		}.bind(this));
2777	}
2778};
2779
2780lsjs.addControllerClass(str_moduleName, obj_classdef);
2781
2782lsjs.__moduleHelpers[str_moduleName] = {
2783	self: null,
2784	
2785	start: function(obj_options) {
2786		this.self = lsjs.createModule({
2787			__name: str_moduleName
2788		});
2789		this.self.__models.options.set(obj_options);
2790	}
2791};
2792
2793})();
2794// < ###### CONTROLLER ##
2795// < #### MODULE ##
2796
2797
2798
2799
2800// #### MODULE ## >
2801// ###### TEMPLATES ## >
2802(function() {
2803
2804var obj_templates = {
2805	'main': function(arg) {
2806		var str_return = '<div data-lsjs-element="loadingText">' + arg.__models.lang.data.standardText + '</div>';
2807
2808		// add template info in debug mode
2809		var str_return = '<!-- BEGIN LSJS TEMPLATE: modules/loadingIndicator/templates/main.html/main.html -->' + str_return + '<!-- END LSJS TEMPLATE: modules/loadingIndicator/templates/main.html/main.html -->';
2810
2811		return str_return;
2812	}
2813};
2814
2815lsjs.tpl.register(obj_templates, 'loadingIndicator');
2816
2817})();
2818// < ###### TEMPLATES ##
2819
2820// ###### MODELS ## >
2821(function() {
2822	
2823var obj_classdef_model = {
2824	name: 'lang',
2825	
2826	data: {
2827		standardText: 'Loading ...'
2828	},
2829
2830	start: function() {
2831		this.__module.onModelLoaded();
2832	}
2833};
2834
2835lsjs.addModelClass('loadingIndicator', obj_classdef_model);
2836
2837})();
2838
2839
2840
2841(function() {
2842	
2843var obj_classdef_model = {
2844	name: 'loadingCounter',
2845	
2846	data: {
2847		numLoading: 0
2848	},
2849
2850	start: function() {
2851		this.__module.onModelLoaded();
2852	},
2853	
2854	increase: function() {
2855		this.data.numLoading++;
2856	},
2857	
2858	decrease: function() {
2859		if (this.data.numLoading > 0) {
2860			this.data.numLoading--;
2861		}
2862	},
2863	
2864	getNumLoading: function() {
2865		return this.data.numLoading;
2866	}
2867};
2868
2869lsjs.addModelClass('loadingIndicator', obj_classdef_model);
2870
2871})();
2872
2873
2874
2875
2876// < ###### MODELS ##
2877
2878// ###### VIEW ## >
2879(function() {
2880	
2881// ### ENTER MODULE NAME HERE ######
2882var str_moduleName = 'loadingIndicator';
2883// #################################
2884
2885var obj_classdef = 	{
2886	
2887	start: function() {
2888		this.tplReplace({
2889			name: 'main'
2890		});
2891	},
2892	
2893	show: function() {
2894		this.__el_container.addClass('show');
2895		new Fx.Morph(this.__el_container, {
2896			duration: 100
2897		})
2898		.start({
2899			'opacity': 1
2900		});
2901	},
2902	
2903	hide: function(bln_resetText) {
2904		new Fx.Morph(this.__el_container, {
2905			duration: 300,
2906			onComplete: function() {
2907				this.__el_container.removeClass('show');
2908				if (bln_resetText) {
2909					this.resetText();
2910				}
2911			}.bind(this)
2912		})
2913		.start({
2914			'opacity': 0
2915		});
2916	},
2917	
2918	overwriteText: function(str_deviantText) {
2919		this.__autoElements.main.loadingText.setProperty('html', str_deviantText);
2920	},
2921	
2922	resetText: function() {
2923		this.__autoElements.main.loadingText.setProperty('html', this.__models.lang.data.standardText);
2924	}
2925};
2926
2927lsjs.addViewClass(str_moduleName, obj_classdef);
2928
2929})();
2930// < ###### VIEW ##
2931// 
2932// ###### CONTROLLER ## >
2933(function() {
2934	
2935// ### ENTER MODULE NAME HERE ######
2936var str_moduleName = 'loadingIndicator';
2937// #################################
2938
2939var obj_classdef = {
2940	start: function() {
2941		if (this.__view === undefined || this.__view === null) {
2942			console.error('The loading indicator\'s view is not available in its controller.js! Please make sure that controller.js is loaded after all the other module scripts (templates.js, view.js, model.js)');
2943		}
2944		
2945		lsjs[str_moduleName] = this.__module;
2946		window.addEvent('domready', function() {
2947			$$('body')[0].adopt(this.__module.__el_container);
2948		}.bind(this));
2949	},
2950	
2951	show: function(str_deviantText) {
2952		if (str_deviantText !== undefined && str_deviantText !== null) {
2953			this.overwriteText(str_deviantText);
2954		}
2955		
2956		this.__models.loadingCounter.increase();
2957		if (this.__models.loadingCounter.getNumLoading() === 1) {
2958			this.__view.show();
2959		}
2960	},
2961	
2962	hide: function(bln_resetText) {
2963		bln_resetText = bln_resetText !== undefined && bln_resetText;
2964		
2965		this.__models.loadingCounter.decrease();
2966		if (this.__models.loadingCounter.getNumLoading() === 0) {
2967			this.__view.hide(bln_resetText);
2968		} else if (bln_resetText) {
2969			this.resetText();
2970		}
2971	},
2972	
2973	overwriteText: function(str_deviantText) {
2974		if (str_deviantText === undefined || str_deviantText === null) {
2975			console.info('no deviant text given as argument');
2976			return;
2977		}
2978		
2979		this.__view.overwriteText(str_deviantText);
2980	},
2981	
2982	resetText: function() {
2983		this.__view.resetText();
2984	}
2985};
2986
2987lsjs.addControllerClass(str_moduleName, obj_classdef);
2988
2989lsjs.createModule({
2990	__name: str_moduleName
2991});
2992
2993})();
2994// < ###### CONTROLLER ##
2995// < #### MODULE ##
2996
2997
2998
2999
3000// #### MODULE ## >
3001// ###### TEMPLATES ## >
3002
3003// < ###### TEMPLATES ##
3004
3005// ###### MODELS ## >
3006(function() {
3007	
3008var obj_classdef_model = {
3009	name: 'options',
3010
3011	data: {},
3012
3013	start: function() {
3014		/*
3015		 * Initializing the options in the data object with default values which
3016		 * can later be overwritten when the "set" method is called with other options
3017		 */
3018		this.data = {
3019			var_naviSelector: null,
3020			int_offsetLeft: 0,
3021			int_offsetRight: 0,
3022			bln_onlyFirstLevel: false
3023		};
3024	},
3025
3026	set: function(obj_options) {
3027		Object.merge(this.data, obj_options);
3028		this.__module.onModelLoaded();
3029	}
3030};
3031
3032lsjs.addModelClass('maxWidthNavi', obj_classdef_model);
3033
3034})();
3035
3036
3037
3038
3039// < ###### MODELS ##
3040
3041// ###### VIEW ## >
3042(function() {
3043
3044// ### ENTER MODULE NAME HERE ######
3045	var str_moduleName = 'maxWidthNavi';
3046// #################################
3047
3048	var obj_classdef = 	{
3049		start: function() {
3050			this.__el_container =
3051				(
3052					typeOf(this.__models.options.data.var_naviSelector) === 'element'
3053					||	typeOf(this.__models.options.data.var_naviSelector) === 'null'
3054				)
3055					?	this.__models.options.data.var_naviSelector
3056					:	$$(this.__models.options.data.var_naviSelector)[0];
3057
3058			if (typeOf(this.__el_container) !== 'element') {
3059				return;
3060			}
3061
3062			this.__el_container.addClass('useMaxWidthNavi');
3063			this.calculateAndSetSize();
3064			window.addEvent('resize', this.calculateAndSetSize.bind(this));
3065		},
3066
3067		calculateAndSetSize: function() {
3068			Array.each(this.__el_container.getElements('li').filter(this.__models.options.data.bln_onlyFirstLevel ? '.level_1 > .submenu' : '.submenu'), function(el_li) {
3069				var obj_li = el_li.getCoordinates();
3070				var obj_nav = el_li.getParent('nav').getCoordinates();
3071				var float_offsetLeft = obj_li.left - obj_nav.left;
3072				var float_offsetRight = obj_nav.right - obj_li.right;
3073
3074				el_li.getElement('> ul').setStyles({
3075					'left': (Math.round(float_offsetLeft) + this.__models.options.data.int_offsetLeft) * -1,
3076					'right': (Math.round(float_offsetRight) + this.__models.options.data.int_offsetRight) * -1
3077				});
3078			}.bind(this));
3079		}
3080	};
3081
3082	lsjs.addViewClass(str_moduleName, obj_classdef);
3083
3084})();
3085// < ###### VIEW ##
3086// 
3087// ###### CONTROLLER ## >
3088(function() {
3089	
3090// ### ENTER MODULE NAME HERE ######
3091var str_moduleName = 'maxWidthNavi';
3092// #################################
3093
3094var obj_classdef = {
3095	start: function() {
3096	}
3097};
3098
3099lsjs.addControllerClass(str_moduleName, obj_classdef);
3100
3101lsjs.__moduleHelpers[str_moduleName] = {
3102	self: null,
3103	
3104	start: function(obj_options) {
3105		this.self = lsjs.createModule({
3106			__name: str_moduleName
3107		});
3108		
3109		this.self.__models.options.set(obj_options);
3110	}
3111};
3112
3113}
3113)();
3114// < ###### CONTROLLER ##
3115// < #### MODULE ##
3116
3117
3118
3119
3120// #### MODULE ## >
3121// ###### TEMPLATES ## >
3122(function() {
3123
3124var obj_templates = {
3125	'main': function(arg) {
3126		var str_return = '<div class="msgBoxInside">\
3127	<div class="msg">' + arg.__models.options.data.str_msg + '</div>\
3128	';
3129		Object.each(arg.__models.options.data.obj_buttons, function(obj_buttonSettings, str_buttonLabel) {
3130			str_return += '\
3131			<button data-lsjs-element="btn_' + str_buttonLabel + '" class="btn_' + str_buttonLabel + '">' + obj_buttonSettings.str_label + '</button>\
3132			';
3133		});
3134	str_return += '\
3135</div>';
3136
3137		// add template info in debug mode
3138		var str_return = '<!-- BEGIN LSJS TEMPLATE: modules/messageBox/templates/main.html/main.html -->' + str_return + '<!-- END LSJS TEMPLATE: modules/messageBox/templates/main.html/main.html -->';
3139
3140		return str_return;
3141	}
3142};
3143
3144lsjs.tpl.register(obj_templates, 'messageBox');
3145
3146})();
3147// < ###### TEMPLATES ##
3148
3149// ###### MODELS ## >
3150(function() {
3151	
3152var obj_classdef_model = {
3153	name: 'lang',
3154	
3155	data: {
3156		str_standardMessage: 'Hi, I am the message box. Please give me a message to display!',
3157		str_defaultButtonText: 'OK'
3158	},
3159
3160	start: function() {
3161		/*
3162		 * Every model needs to call the "this.__module.onModelLoaded()" method
3163		 * when its data is completely loaded and available or, since in some
3164		 * cases data is loaded later, when the model is ready for the view
3165		 * to be rendered.
3166		 */
3167		this.__module.onModelLoaded();
3168	}
3169};
3170
3171lsjs.addModelClass('messageBox', obj_classdef_model);
3172
3173})();
3174
3175
3176
3177(function() {
3178	
3179var obj_classdef_model = {
3180	name: 'options',
3181	
3182	data: {},
3183	
3184	start: function() {
3185		/*
3186		 * Initializing the options in the data object with default values which
3187		 * can later be overwritten when the "set" method is called with other options
3188		 */
3189		this.data = {
3190			str_msg: this.__models.lang.data.str_standardMessage,
3191			obj_buttons: {
3192				default: {
3193					str_label: this.__models.lang.data.str_defaultButtonText,
3194					bln_closeOnClick: true
3195				}
3196			}
3197		};
3198	},
3199	
3200	set: function(obj_options) {
3201		Object.merge(this.data, obj_options);
3202		this.__module.onModelLoaded();
3203	}
3204};
3205
3206lsjs.addModelClass('messageBox', obj_classdef_model);
3207
3208})();
3209
3210
3211
3212
3213// < ###### MODELS ##
3214
3215// ###### VIEW ## >
3216(function() {
3217	
3218// ### ENTER MODULE NAME HERE ######
3219var str_moduleName = 'messageBox';
3220// #################################
3221
3222var obj_classdef = 	{
3223	start: function() {
3224		this.tplAdd({
3225			name: 'main'
3226		});
3227
3228		Object.each(this.__models.options.data.obj_buttons, function (obj_buttonSettings, str_buttonLabel) {
3229			this.__autoElements.main['btn_' + str_buttonLabel].addEvent('click', this.callCallbackFunction.bind(this, obj_buttonSettings));
3230		}.bind(this));
3231
3232		$$('body')[0].adopt(this.__el_container);
3233	},
3234
3235	callCallbackFunction: function(obj_buttonSettings) {
3236		if (obj_buttonSettings.bln_closeOnClick === undefined || obj_buttonSettings.bln_closeOnClick) {
3237			var myFx = new Fx.Tween(
3238				this.__el_container,
3239					{
3240					duration: 200,
3241					link: 'cancel',
3242					property: 'opacity',
3243					onComplete: this.__module.remove.bind(this.__module)
3244				}
3245			);
3246
3247			myFx.start(1,0);
3248		}
3249
3250		if (typeOf(obj_buttonSettings.func_callback) === 'function') {
3251			obj_buttonSettings.func_callback();
3252		}
3253	}
3254};
3255
3256lsjs.addViewClass(str_moduleName, obj_classdef);
3257
3258})();
3259// < ###### VIEW ##
3260// 
3261// ###### CONTROLLER ## >
3262(function() {
3263	
3264// ### ENTER MODULE NAME HERE ######
3265var str_moduleName = 'messageBox';
3266// #################################
3267
3268var obj_classdef = {
3269	start: function() {
3270	},
3271	
3272	setOptions: function(obj_options) {
3273		this.__models.options.set(obj_options);
3274	}
3275};
3276
3277lsjs.addControllerClass(str_moduleName, obj_classdef);
3278
3279/*
3280 * Registering a specific instantiation method with lsjs that can (or rather should)
3281 * be used instead of manually calling "lsjs.createModule()" in order to open
3282 * a messageBox. Not every module needs to use this option but in case of standard
3283 * core modules it often is a good idea.
3284 */
3285lsjs.__moduleHelpers[str_moduleName] = {
3286	open: function(obj_options) {
3287		lsjs.createModule({
3288			__name: str_moduleName
3289		}).__controller.setOptions(obj_options);
3290	}
3291};
3292
3293/*
3294 * When the message box is opened, people might try to close it by pressing the
3295 * enter or escape key. We don't provide that functionality yet but at least we
3296 * want to make sure that pressing these keys while the message box is opened
3297 * will not do anything else, like, for example, sending a form.
3298 */
3299window.addEvent('keydown', function(event) {
3300	/*
3301	 * Don't stop anything with the event if no message box is opened
3302	 */
3303	if (typeOf($$('.autoModuleContainer.messageBox')[0]) !== 'element') {
3304		return;
3305	}
3306
3307	switch (event.key) {
3308		case 'enter': // Enter key pressed
3309		case 'esc': // Escape key pressed
3310			event.stop();
3311			break;
3312	}
3313}.bind(this));
3314
3315})();
3316// < ###### CONTROLLER ##
3317// < #### MODULE ##
3318
3319
3320
3321
3322// #### MODULE ## >
3323// ###### TEMPLATES ## >
3324
3325// < ###### TEMPLATES ##
3326
3327// ###### MODELS ## >
3328(function() {
3329	
3330var obj_classdef_model = {
3331	name: 'status',
3332	
3333	data: {
3334		autoplayStatus: false,
3335		stoppedOnMouseenter: false,
3336		int_currentTimeout: null,
3337		currentTabKey: 0,
3338		bln_firstElementHasBeenActivated: false
3339	},
3340	
3341	start: function() {
3342		this.__module.onModelLoaded();
3343	},
3344	
3345	set_autoplayStatus: function(bln_status) {
3346		bln_status = bln_status !== undefined ? bln_status : false;
3347		this.writeData('autoplayStatus', bln_status);
3348	},
3349	
3350	set_stoppedOnMouseenter: function(bln_status) {
3351		bln_status = bln_status !== undefined ? bln_status : false;
3352		this.writeData('stoppedOnMouseenter', bln_status);
3353	},
3354	
3355	set_bln_firstElementHasBeenActivated: function(bln_firstElementHasBeenActivated) {
3356		bln_firstElementHasBeenActivated = bln_firstElementHasBeenActivated !== undefined ? bln_firstElementHasBeenActivated : false;
3357		this.writeData('bln_firstElementHasBeenActivated', bln_firstElementHasBeenActivated);
3358	},
3359	
3360	set_currentTabKey: function(currentTabKey) {
3361		this.writeData('currentTabKey', currentTabKey);
3362	},
3363	
3364	set_currentTimeout: function(int_timeout) {
3365		int_timeout = int_timeout ? int_timeout : null;
3366		this.writeData('int_currentTimeout', int_timeout);
3367	}
3368};
3369
3370lsjs.addModelClass('navtabInstance', obj_classdef_model);
3371
3372})();
3373
3374
3375
3376
3377// < ###### MODELS ##
3378
3379// ###### VIEW ## >
3380/**
3381 * This class creates a tab navigation/slideshow from a simple html construction.
3382 * 
3383 * Example:
3384		<div data-lsjs-component="navtab">
3385			<div data-lsjs-element="navtabLabelContainer">
3386				<div data-lsjs-element="navtabLabel" class="start">TAB 01</div>
3387				<div data-lsjs-element="navtabLabel" >TAB 02</div>
3388				<div data-lsjs-element="navtabLabel" >TAB 03</div>
3389			</div>
3390			<div data-lsjs-element="navtabContentContainer">
3391				<div data-lsjs-element="navtabContent" class="noJsDefault">CONTENT TAB 01</div>
3392				<div data-lsjs-element="navtabContent" >CONTENT TAB 02</div>
3393				<div data-lsjs-element="navtabContent" >CONTENT TAB 03</div>
3394			</div>
3395			<div data-lsjs-element="navtabNavigation" class="navigation">
3396				<div data-lsjs-element="btn_prev" class="prev">PREV</div>
3397				<div data-lsjs-element="btn_next" class="next">NEXT</div>
3398				<div data-lsjs-element="btn_play" class="play">PLAY</div>
3399				<div data-lsjs-element="btn_stop" class="stop">STOP</div>
3400			</div>
3401		</div>
3402
3403 * FIXME: Add more detailed description
3404 */
3405
3406(function() {
3407	
3408// ### ENTER MODULE NAME HERE ######
3409var str_moduleName = 'navtabInstance';
3410// #################################
3411
3412var obj_classdef = 	{
3413	start: function() {
3414		this.registerElements(this.__el_container, 'main', true);
3415		
3416//		console.log(this.__module.__parentModule.__models.options.data);
3417// 		console.log(this.__autoElements);
3418		
3419		if (this.__autoElements.main.navtabContent.length !== this.__autoElements.main.navtabLabel.length) {
3420			console.error('number of label elements does not match the number of content elements');
3421			return;
3422		}
3423		
3424		this.__models.status.set_autoplayStatus(this.__module.__parentModule.__models.options.data.defaultAutoplayStatus);
3425		
3426		this.showHiddenElements();
3427		this.addNavigationEvents();
3428		this.prepareContentElements();
3429		this.prepareLabelElements();
3430		this.__controller.activateStartLabel();
3431	},
3432	
3433	prepareLabelElements: function() {
3434		/*
3435		 * Get the corresponding content div for each label div
3436		 * and add the click event to each label div
3437		 */
3438		Array.each(this.__autoElements.main.navtabLabel, function(el_label, key) {
3439			el_label.store('tabKey', key);
3440			
3441			var el_correspondingContent = this.__autoElements.main.navtabContent[key];
3442			
3443			el_label.activate = function() {
3444				window.clearTimeout(this.__models.status.data.int_currentTimeout);
3445				
3446				if (this.__models.status.data.autoplayStatus) {
3447					this.__models.status.set_currentTimeout(
3448						window.setTimeout(
3449							function() {
3450								this.__controller.gotoNext();
3451							}.bind(this),
3452							this.__module.__parentModule.__models.options.data.autoplayDelay
3453						)
3454					);
3455				}
3456
3457				// do nothing more if the label is already active
3458				if (el_label.hasClass('active')) {
3459					return;
3460				}
3461				
3462				/*
3463				 * Remove 'active' classes from all labels and set it for the
3464				 * currently clicked label
3465				 */
3466				this.__autoElements.main.navtabLabel.removeClass('active');
3467				el_label.addClass('active');
3468				
3469				// store the tabKey of the currently clicked label
3470				this.__models.status.set_currentTabKey(el_label.retrieve('tabKey'));
3471				
3472				/*
3473				 * Walk through all contents and remove the 'active' class wherever it is present and
3474				 * call the content's tweenOut function
3475				 */
3476				Array.each(this.__autoElements.main.navtabContent, function(el_content) {
3477					if (el_content.hasClass('active')) {
3478						el_content.removeClass('active');
3479						el_content.tweenOut();
3480					}
3481				});
3482				
3483				/*
3484				 * add the 'active' class for the content corresponding with the currently clicked label
3485				 * and call the content's tweenIn function
3486				 */
3487				el_correspondingContent.addClass('active');
3488				
3489				if (!this.__models.status.data.bln_firstElementHasBeenActivated) {
3490					this.__models.status.set_bln_firstElementHasBeenActivated(true);
3491					el_correspondingContent.removeClass(this.__module.__parentModule.__models.options.data.cssClassForHide);
3492					el_correspondingContent.addClass(this.__module.__parentModule.__models.options.data.cssClassForShow);
3493				} else {
3494					el_correspondingContent.tweenIn();
3495				}
3496			}.bind(this);
3497			
3498			el_label.addEvent('click', function() {
3499				this.__controller.stopAutoplay();
3500				el_label.activate();
3501			}.bind(this));
3502		}.bind(this));	
3503	},
3504	
3505	prepareContentElements: function() {
3506		this.__autoElements.main.navtabContent.addClass(this.__module.__parentModule.__models.options.data.cssClassForHide);
3507		
3508		/*
3509		 * Store the tween effect for each content div and create the tweenIn and tweenOut functions
3510		 */
3511		Array.each(this.__autoElements.main.navtabContent, function(el_content) {
3512			var obj_tweenInOptions,
3513				obj_tweenOutOptions;
3514				
3515			el_content.store('obj_originalPositionValues', {
3516				'position': el_content.getStyle('position'),
3517				'top': el_content.getStyle('top'),
3518				'z-index': el_content.getStyle('z-index')
3519			});
3520			
3521			el_content.store('cssClassForShow', this.__module.__parentModule.__models.options.data.cssClassForShow);
3522			el_content.store('cssClassForHide', this.__module.__parentModule.__models.options.data.cssClassForHide);
3523			
3524			/*
3525			 * add the onComplete function for the tweenIn because if the tweenIn transition
3526			 * is finished, the content div's position properties need to be set to the
3527			 * original values
3528			 */
3529			obj_tweenInOptions = this.__module.__parentModule.__models.options.data.tweenInOptions;
3530
3531			obj_tweenInOptions.onComplete = function() {
3532				el_content.setStyles(el_content.retrieve('obj_originalPositionValues'));
3533			};
3534
3535			el_content.store('fxTweenIn', new Fx.Morph(
3536				el_content,
3537				obj_tweenInOptions
3538			));
3539			
3540			
3541			/*
3542			 * add the onComplete function for the tweenOut because if the tweenOut transition
3543			 * is finished, the content div's display property needs to be set to 'none'
3544			 */
3545			obj_tweenOutOptions = this.__module.__parentModule.__models.options.data.tweenOutOptions;
3546			
3547			obj_tweenOutOptions.onComplete = function() {
3548				el_content.setStyle('display', 'none');
3549			};
3550			
3551			el_content.store('fxTweenOut', new Fx.Morph(
3552				el_content,
3553				obj_tweenOutOptions
3554			));
3555			
3556			el_content.tweenIn = this.tweenIn;
3557			el_content.tweenOut = this.tweenOut;
3558			
3559			el_content.addEvent('mouseenter', function() {
3560				if (this.__module.__parentModule.__models.options.data.stopAutoplayOnMouseenter) {
3561					if (this.__models.status.data.autoplayStatus) {
3562						this.__models.status.set_stoppedOnMouseenter(true);
3563						this.__controller.stopAutoplay();
3564					}
3565				}
3566			}.bind(this));
3567			
3568			el_content.addEvent('mouseleave', function() {
3569				if (this.__module.__parentModule.__models.options.data.startAutoplayOnMouseleave) {
3570					if (this.__models.status.data.stoppedOnMouseenter) {
3571						this.__models.status.set_stoppedOnMouseenter(false);
3572						this.__controller.startAutoplay();
3573					}
3574				}
3575			}.bind(this));
3576		}.bind(this));
3577	},
3578	
3579	addNavigationEvents: function() {
3580		if (this.__autoElements.main.btn_prev) {
3581			this.__autoElements.main.btn_prev.addEvent('click', this.__controller.gotoPrev.bind(this.__controller));
3582		}
3583		if (this.__autoElements.main.btn_next) {
3584			this.__autoElements.main.btn_next.addEvent('click', this.__controller.gotoNext.bind(this.__controller, true));
3585		}
3586		if (this.__autoElements.main.btn_play) {
3587			this.__autoElements.main.btn_play.addEvent('click', this.__controller.startAutoplay.bind(this.__controller));
3588			if (this.__module.__parentModule.__models.options.data.defaultAutoplayStatus) {
3589				this.__autoElements.main.btn_play.addClass('active');
3590			}
3591		}
3592		if (this.__autoElements.main.btn_stop) {
3593			this.__autoElements.main.btn_stop.addEvent('click', this.__controller.stopAutoplay.bind(this.__controller));
3594			if (!this.__module.__parentModule.__models.options.data.defaultAutoplayStatus) {
3595				this.__autoElements.main.btn_stop.addClass('active');
3596			}
3597		}
3598	},
3599	
3600	/*
3601	 * Show elements that are hidden by default because they must not be
3602	 * displayed without js.
3603	 */
3604	showHiddenElements: function() {
3605		if (this.__autoElements.main.navtabNavigation) {
3606			this.__autoElements.main.navtabNavigation.addClass('visible');
3607		}
3608		
3609		if (this.__autoElements.main.navtabLabelContainer) {
3610			this.__autoElements.main.navtabLabelContainer.addClass('visible');
3611		}
3612	},
3613	
3614	tweenIn: function() {
3615		// first, make the content visible
3616		this.setStyles({
3617			'display': 'block'
3618		});
3619		
3620		/*
3621		 * Set the absolute position if the element originally wasn't positioned
3622		 * absolutely. This is necessary because the elements morphing in and out
3623		 * should overlay each other.
3624		 */
3625		if (this.retrieve('obj_originalPositionValues').position !== 'absolute') {
3626			this.setStyles({
3627				'position': 'absolute',
3628				'top': '0'
3629			});			
3630		}
3631		
3632		this.setStyles({
3633			'z-index': '1000'
3634		});
3635		
3636		/*
3637		 * If a tweenOut transition is already/still running, cancel it because
3638		 * otherwise its onComplete function will be called in the end and hide
3639		 * the content that should be visible
3640		 */
3641		if (this.retrieve('fxTweenOut').isRunning()) {
3642			this.retrieve('fxTweenOut').cancel();
3643		}
3644		
3645		/*
3646		 * Start the transition that has been stored in the content element
3647		 */
3648		this.retrieve('fxTweenIn').start('.' + this.retrieve('cssClassForShow'));
3649	},
3650	
3651	tweenOut: function() {
3652		/*
3653		 * Start the transition that has been stored in the content element
3654		 */
3655		this.retrieve('fxTweenOut').start('.' + this.retrieve('cssClassForHide'));
3656	}
3657};
3658
3659lsjs.addViewClass(str_moduleName, obj_classdef);
3660
3661})();
3662// < ###### VIEW ##
3663// 
3664// ###### CONTROLLER ## >
3665(function() {
3666	
3667// ### ENTER MODULE NAME HERE ######
3668var str_moduleName = 'navtabInstance';
3669// #################################
3670
3671var obj_classdef = {
3672	start: function() {
3673	},
3674	
3675	activateStartLabel: function() {
3676		Array.each(this.__view.__autoElements.main.navtabLabel.filter('.start'), function(el_startLabel) {
3677			el_startLabel.activate();
3678		});
3679	},
3680	
3681	gotoPrev: function() {
3682		var numTabs = this.__view.__autoElements.main.navtabLabel.length;
3683		var currentTabKey = this.__models.status.data.currentTabKey;
3684		var newTabKey = currentTabKey - 1;
3685		
3686		if (newTabKey < 0) {
3687			newTabKey = numTabs - 1;
3688		}
3689
3690		this.__view.__autoElements.main.navtabLabel[newTabKey].fireEvent('click');
3691	},
3692	
3693	gotoNext: function(bln_useClick) {
3694		bln_useClick = bln_useClick != undefined ? bln_useClick : false;
3695		
3696		var numTabs,
3697			currentTabKey,
3698			newTabKey;
3699		
3700		numTabs = this.__view.__autoElements.main.navtabLabel.length;
3701		
3702		if (!this.__module.__parentModule.__models.options.data.autoplayRandomOrder || bln_useClick) {
3703			currentTabKey = this.__models.status.data.currentTabKey;
3704			newTabKey = currentTabKey + 1;
3705			if (newTabKey > numTabs - 1) {
3706				newTabKey = 0;
3707			}
3708		} else {
3709			newTabKey = Number.random(0, numTabs - 1);
3710		}
3711		
3712		/*
3713		 * If the gotoNext function is called by clicking on the next button, the autoplay
3714		 * has to stop, i.e. a real click on the next label has to be performed because
3715		 * this stops the autoplay, whereas just activating the next tab doesn't.
3716		 */
3717		if (bln_useClick) {
3718			this.__view.__autoElements.main.navtabLabel[newTabKey].fireEvent('click');
3719		} else {
3720			this.__view.__autoElements.main.navtabLabel[newTabKey].activate();
3721		}
3722	},
3723	
3724	startAutoplay: function() {
3725		this.__models.status.set_autoplayStatus(true);
3726		
3727		this.__view.__autoElements.main.navtabLabel[this.__models.status.data.currentTabKey].activate();
3728		
3729		if (this.__view.__autoElements.main.btn_play) {
3730			this.__view.__autoElements.main.btn_play.addClass('active');
3731		}
3732		if (this.__view.__autoElements.main.btn_stop) {
3733			this.__view.__autoElements.main.btn_stop.removeClass('active');
3734		}
3735	},
3736	
3737	stopAutoplay: function() {
3738		window.clearTimeout(this.__models.status.data.int_currentTimeout);
3739		
3740		this.__models.status.set_autoplayStatus(false);
3741		
3742		if (this.__view.__autoElements.main.btn_play) {
3743			this.__view.__autoElements.main.btn_play.removeClass('active');
3744		}
3745		if (this.__view.__autoElements.main.btn_stop) {
3746			this.__view.__autoElements.main.btn_stop.addClass('active');
3747		}
3748	}
3749};
3750
3751lsjs.addControllerClass(str_moduleName, obj_classdef);
3752
3753})();
3754// < ###### CONTROLLER ##
3755// < #### MODULE ##
3756
3757
3758
3759
3760// #### MODULE ## >
3761// ###### TEMPLATES ## >
3762
3763// < ###### TEMPLATES ##
3764
3765// ###### MODELS ## >
3766(function() {
3767	
3768var obj_classdef_model = {
3769	name: 'options',
3770	
3771	data: {},
3772	
3773	start: function() {
3774		/*
3775		 * Initializing the options in the data object with default values which
3776		 * can later be overwritten when the "set" method is called with other options
3777		 */
3778		this.data = {
3779			el_domReference: null,
3780			navtabContainerSelector: '[data-lsjs-component~="navtab"]', // the selector for the dom element containing the navtab
3781			defaultAutoplayStatus: false, // true if the autoplay should start automatically
3782			autoplayDelay: 2000, // the time between to tabs in autoplay mode (milliseconds)
3783			autoplayRandomOrder: false, // use a random tab order in autoplay mode
3784
3785			stopAutoplayOnMouseenter: false, // does just what the option name suggests
3786			startAutoplayOnMouseleave: false, // does just what the option name suggests
3787
3788			tweenOutOptions: { // the mootools Fx options (see http://mootools.net/docs/core/Fx/Fx)
3789				transition: Fx.Transitions.Quad.easeIn,
3790				duration: 1000
3791			},
3792
3793			tweenInOptions: { // the mootools Fx options (see http://mootools.net/docs/core/Fx/Fx)
3794				transition: Fx.Transitions.Quad.easeIn,
3795				duration: 1000
3796			},
3797			
3798			cssClassForShow: 'navtabContentShow',
3799			cssClassForHide: 'navtabContentHide'
3800		};
3801	},
3802	
3803	set: function(obj_options) {
3804		Object.merge(this.data, obj_options);
3805		this.__module.onModelLoaded();
3806	}
3807};
3808
3809lsjs.addModelClass('navtabManager', obj_classdef_model);
3810
3811})();
3812
3813
3814
3815
3816// < ###### MODELS ##
3817
3818// ###### VIEW ## >
3819
3820// < ###### VIEW ##
3821// 
3822// ###### CONTROLLER ## >
3823(function() {
3824	
3825// ### ENTER MODULE NAME HERE ######
3826var str_moduleName = 'navtabManager';
3827// #################################
3828
3829var obj_classdef = {
3830	start: function() {
3831		var els_toEnhance;
3832		/*
3833		 * Look for navtabs to enrich with the lsjs-module and then
3834		 * instantiate variantSelectorInstance for each variant-selector found.
3835		 */
3836		if (this.__models.options.data.el_domReference !== undefined && typeOf(this.__models.options.data.el_domReference) === 'element') {
3837			els_toEnhance = this.__models.options.data.el_domReference.getElements(this.__models.options.data.navtabContainerSelector);
3838		} else {
3839			els_toEnhance = $$(this.__models.options.data.navtabContainerSelector);
3840		}
3841		
3842		Array.each(els_toEnhance, function(el_container) {
3843			/* ->
3844			 * Make sure not to handle an element more than once
3845			 */
3846			if (!el_container.retrieve('alreadyHandledBy_' + str_moduleName)) {
3847				el_container.store('alreadyHandledBy_' + str_moduleName, true);
3848			} else {
3849				return;
3850			}
3851			/*
3852			 * <-
3853			 */
3854
3855			lsjs.createModule({
3856				__name: 'navtabInstance',
3857				__parentModule: this.__module,
3858				__useLoadingIndicator: false,
3859				__el_container: el_container
3860			});
3861		}.bind(this));
3862	}
3863};
3864
3865lsjs.addControllerClass(str_moduleName, obj_classdef);
3866
3867lsjs.__moduleHelpers[str_moduleName] = {
3868	self: null,
3869	
3870	start: function(obj_options) {
3871		this.self = lsjs.createModule({
3872			__name: str_moduleName
3873		});
3874		this.self.__models.options.set(obj_options);
3875	}
3876};
3877
3878}
3878)();
3879// < ###### CONTROLLER ##
3880// < #### MODULE ##
3881
3882
3883
3884
3885// #### MODULE ## >
3886// ###### TEMPLATES ## >
3887
3888// < ###### TEMPLATES ##
3889
3890// ###### MODELS ## >
3891(function() {
3892	
3893var obj_classdef_model = {
3894	name: 'options',
3895	
3896	data: {},
3897	
3898	start: function() {
3899		/*
3900		 * Initializing the options in the data object with default values which
3901		 * can later be overwritten when the "set" method is called with other options
3902		 */
3903		this.data = {
3904			str_togglerSelector: '#lsOcNaviToggler',
3905			str_naviSelector: '#lsOcNavi',
3906			str_contentSelector: ''
3907		};
3908	},
3909	
3910	set: function(obj_options) {
3911		Object.merge(this.data, obj_options);
3912		this.__module.onModelLoaded();
3913	}
3914};
3915
3916lsjs.addModelClass('ocNavi', obj_classdef_model);
3917
3918})();
3919
3920
3921
3922
3923// < ###### MODELS ##
3924
3925// ###### VIEW ## >
3926(function() {
3927	
3928// ### ENTER MODULE NAME HERE ######
3929var str_moduleName = 'ocNavi';
3930// #################################
3931
3932var obj_classdef = 	{
3933	el_toggler: null,
3934	el_navi: null,
3935	el_body: null,
3936	el_content: null,
3937	
3938	start: function() {
3939		this.el_body = $$('body')[0];
3940
3941		this.el_navi = $$(this.__models.options.data.str_naviSelector)[0];
3942		if (typeOf(this.el_navi) !== 'element') {
3943			this.el_navi = new Element('div' + this.__models.options.data.str_naviSelector);
3944			this.el_navi.inject(this.el_body, 'top');
3945		}
3946
3947		this.el_toggler = $$(this.__models.options.data.str_togglerSelector)[0];
3948		if (typeOf(this.el_toggler) !== 'element') {
3949			this.el_toggler = new Element('div' + this.__models.options.data.str_togglerSelector);
3950			this.el_toggler.inject(this.el_body, 'top');
3951		}
3952
3953		if (this.__models.options.data.str_contentSelector) {
3954			this.el_content = $$(this.__models.options.data.str_contentSelector)[0];
3955			if (typeOf(this.el_content) === 'element') {
3956				this.el_navi.adopt(this.el_content);
3957				this.el_content.setStyle('display', 'block');
3958			}
3959		}
3960		
3961		if (
3962				typeOf(this.el_toggler) !== 'element'
3963			||	typeOf(this.el_navi) !== 'element'
3964			||	typeOf(this.el_body) !== 'element'
3965		) {
3966			return;
3967		}
3968		
3969		this.el_body.addClass('useOcNavi');
3970		
3971		lsjs.__moduleHelpers.unfold.start({
3972			bln_automaticallyCreateResizeBox: false,
3973			str_initialDisplayType: 'block',
3974			str_initialToggleStatus: 'closed',
3975			var_togglerSelector: this.el_toggler,
3976			var_contentBoxSelector: this.el_navi,
3977			var_wrapperSelector: this.el_body,
3978			str_animationMode: 'margin-top',
3979			str_classOpen: 'lsOcNaviOpen',
3980			str_classClosed: 'lsOcNaviClosed',
3981			str_classRunning: 'lsOcNaviRunning',
3982			str_classUseLsUnfold: '',
3983			
3984			obj_morphOptions: {
3985				'duration': 600
3986			}
3987		});
3988
3989		lsjs.__moduleHelpers.foldedNavi.start({
3990			var_naviSelector: this.el_navi
3991		});
3992	}
3993};
3994
3995lsjs.addViewClass(str_moduleName, obj_classdef);
3996
3997})();
3998// < ###### VIEW ##
3999// 
4000// ###### CONTROLLER ## >
4001(function() {
4002	
4003// ### ENTER MODULE NAME HERE ######
4004var str_moduleName = 'ocNavi';
4005// #################################
4006
4007var obj_classdef = {
4008	start: function() {
4009	}
4010};
4011
4012lsjs.addControllerClass(str_moduleName, obj_classdef);
4013
4014lsjs.__moduleHelpers[str_moduleName] = {
4015	self: null,
4016	
4017	start: function(obj_options) {
4018		this.self = lsjs.createModule({
4019			__name: str_moduleName
4020		});
4021		
4022		this.self.__models.options.set(obj_options);
4023	}
4024};
4025
4026})();
4027// < ###### CONTROLLER ##
4028// < #### MODULE ##
4029
4030
4031
4032
4033// #### MODULE ## >
4034// ###### TEMPLATES ## >
4035
4036// < ###### TEMPLATES ##
4037
4038// ###### MODELS ## >
4039
4040// < ###### MODELS ##
4041
4042// ###### VIEW ## >
4043
4044// < ###### VIEW ##
4045// 
4046// ###### CONTROLLER ## >
4047(function() {
4048	
4049// ### ENTER MODULE NAME HERE ######
4050var str_moduleName = 'scrollAssistant';
4051// #################################
4052
4053var obj_classdef = {
4054	start: function() {
4055		lsjs[str_moduleName] = this.__module;
4056		
4057		window.addEvent('domready', function() {
4058			Array.each($$('.onClickToScrollPosition'), function(el) {
4059				el.addEvent('click', function() {
4060					this.getLSFEScrollOffset();
4061				}.bind(this));
4062			}.bind(this));
4063		}.bind(this));
4064
4065		window.addEvent('load', function() {
4066			this.scrollToLSFEOffset();
4067		}.bind(this));
4068	},
4069	
4070	/**
4071	 * Saves the current vertical page offset in a cookie which causes
4072	 * the page to scroll to that vertical position on the next page load.
4073	 */
4074	getLSFEScrollOffset: function() {
4075		Cookie.write('LSFE_PAGE_OFFSET', window.getScroll().y);
4076	},
4077	
4078	/**
4079	 * This function scrolls to a vertical page offset if it has been
4080	 * saved previously in a cookie
4081	 */
4082	scrollToLSFEOffset: function() {
4083		var LSFE_PAGE_OFFSET = Cookie.read('LSFE_PAGE_OFFSET');
4084		if (LSFE_PAGE_OFFSET === undefined || LSFE_PAGE_OFFSET === null) {
4085			return;
4086		}
4087		window.scrollTo(null, parseInt(LSFE_PAGE_OFFSET));
4088		Cookie.dispose('LSFE_PAGE_OFFSET');
4089	}
4090};
4091
4092lsjs.addControllerClass(str_moduleName, obj_classdef);
4093
4094lsjs.createModule({
4095	__name: str_moduleName
4096});
4097
4098})();
4099// < ###### CONTROLLER ##
4100// < #### MODULE ##
4101
4102
4103
4104
4105// #### MODULE ## >
4106// ###### TEMPLATES ## >
4107
4108// < ###### TEMPLATES ##
4109
4110// ###### MODELS ## >
4111(function() {
4112	
4113var obj_classdef_model = {
4114	name: 'options',
4115	
4116	data: {
4117		arr_reactions: []
4118	},
4119	
4120	start: function() {
4121	},
4122	
4123	set: function(obj_options) {
4124		Object.merge(this.data, obj_options);
4125		this.__module.onModelLoaded();
4126	}
4127};
4128
4129lsjs.addModelClass('scrollReactor', obj_classdef_model);
4130
4131})();
4132
4133
4134
4135
4136// < ###### MODELS ##
4137
4138// ###### VIEW ## >
4139(function() {
4140	
4141// ### ENTER MODULE NAME HERE ######
4142var str_moduleName = 'scrollReactor';
4143// #################################
4144
4145var obj_classdef = 	{
4146	arr_preparedReactions: [],
4147
4148	str_currentDirection: 'down',
4149
4150	int_currentScrollY: 0,
4151	int_lastScrollY: 0,
4152
4153	obj_currentReaction: null,
4154
4155	obj_defaultReaction: {
4156		func_reactionCrossingTop: function() {
4157			console.log('default reaction crossing top ' + this.str_currentDirection);
4158		},
4159		func_reactionCrossingBottom: function() {
4160			console.log('default reaction crossing bottom ' + this.str_currentDirection);
4161		},
4162		func_reactionBetween: function() {
4163			console.log('default reaction ' + this.str_currentDirection + ' between ' + this.obj_currentReaction.int_scrollPositionTop + ' and ' + this.obj_currentReaction.int_scrollPositionBottom);
4164		},
4165		int_scrollPositionTop: 100,
4166		int_scrollPositionBottom: 300,
4167		obj_reactOn: {
4168			str_crossingTop: 'both', // up, down, both, none
4169			str_crossingBottom: 'both', // up, down, both, none
4170			str_between: 'both' // up, down, both, none
4171		},
4172
4173		/*
4174		 * Defines how many times the reactio for a specific scroll event will be performed.
4175		 * 0 means no limit at all; any integer value will be counted down to zero and then the
4176		 * corresponding obj_reactOn value will be set to 'none'
4177		 */
4178		obj_reactTimes: {
4179			int_crossingTop: 0,
4180			int_crossingBottom: 0,
4181			int_between: 0
4182		},
4183
4184		/*
4185		 * If the elements involved in the reaction are stated here,
4186		 * the reaction will be dismissed if none of the elements are
4187		 * present during initialization
4188		 */
4189		arr_elementSelectorsInvolved: [],
4190
4191		bln_aboveScrollPositionTop: true,
4192		bln_aboveScrollPositionBottom: true,
4193		bln_crossedScrollPositionTop: false,
4194		bln_crossedScrollPositionBottom: false
4195	},
4196	
4197	start: function() {
4198		this.initializeReactor();
4199		window.addEvent('resize', this.initializeReactor.bind(this));
4200		window.addEvent('scroll', this.react.bind(this));
4201	},
4202
4203	initializeReactor: function() {
4204		this.prepareReactions();
4205
4206		// console.log('this.arr_preparedReactions', this.arr_preparedReactions);
4207		// console.log('this.__models.options.data.arr_reactions', this.__models.options.data.arr_reactions);
4208
4209		/*
4210		 * Make sure to call the react function once in the beginning even if no scroll event has been
4211		 * triggered because if a page is being opened by an external link using an anchor, the page is being
4212		 * loaded in an already scrolled position but no scroll event is being triggered.
4213		 */
4214		this.react();
4215	},
4216
4217	prepareReactions: function() {
4218		this.arr_preparedReactions = [];
4219
4220		/*
4221		 * ->
4222		 * Only clone and further prepare reactions whose involved elements exist
4223		 */
4224		Array.each(this.__models.options.data.arr_reactions, function(obj_reaction) {
4225			var obj_defaultReaction = Object.clone(this.obj_defaultReaction);
4226			obj_reaction = Object.merge(obj_defaultReaction, obj_reaction);
4227
4228			if (!this.check_involvedElementsExist(obj_reaction)) {
4229				return;
4230			}
4231
4232			this.arr_preparedReactions.push(obj_reaction);
4233		}.bind(this));
4234		/*
4235		 * <-
4236		 */
4237
4238		Array.each(this.arr_preparedReactions, function(obj_reaction) {
4239			obj_reaction.func_reactionCrossingTop = obj_reaction.func_reactionCrossingTop.bind(this);
4240			obj_reaction.func_reactionCrossingBottom = obj_reaction.func_reactionCrossingBottom.bind(this);
4241			obj_reaction.func_reactionBetween = obj_reaction.func_reactionBetween.bind(this);
4242		}.bind(this));
4243	},
4244
4245	check_involvedElementsExist: function(obj_reaction) {
4246		/*
4247		 * If no selectors for involved elements are given, we assume that everything that's required for the
4248		 * reaction exists and therefore return true.
4249		 */
4250		if (obj_reaction.arr_elementSelectorsInvolved.length == 0) {
4251			return true;
4252		}
4253
4254		var bln_elementsExist = false;
4255
4256		Array.some(obj_reaction.arr_elementSelectorsInvolved, function(str_selector) {
4257			var els_elementsInvolved = $$(str_selector);
4258
4259			if (typeOf(els_elementsInvolved) === 'elements' && els_elementsInvolved.length > 0) {
4260				bln_elementsExist = true;
4261				return true;
4262			}
4263		});
4264
4265		return bln_elementsExist;
4266	},
4267
4268	react: function() {
4269		this.determineScrollPositionAndDirection();
4270
4271		// console.log('this.str_currentDirection', this.str_currentDirection);
4272
4273		Array.each(this.arr_preparedReactions, function (obj_reaction) {
4274			this.determineReactionRelatedScrollPosition(obj_reaction);
4275			this.processReaction(obj_reaction);
4276		}.bind(this));
4277	},
4278
4279	determineReactionRelatedScrollPosition: function(obj_reaction) {
4280		if (obj_reaction.obj_reactOn.str_crossingTop !== 'none' || obj_reaction.obj_reactOn.str_between !== 'none') {
4281			var bln_aboveScrollPositionTop = this.int_currentScrollY < obj_reaction.int_scrollPositionTop;
4282			if (obj_reaction.bln_aboveScrollPositionTop !== bln_aboveScrollPositionTop) {
4283				obj_reaction.bln_crossedScrollPositionTop = true;
4284				obj_reaction.bln_aboveScrollPositionTop = bln_aboveScrollPositionTop;
4285			}
4286		}
4287
4288		if (obj_reaction.obj_reactOn.str_crossingBottom !== 'none' || obj_reaction.obj_reactOn.str_between !== 'none') {
4289			var bln_aboveScrollPositionBottom = this.int_currentScrollY < obj_reaction.int_scrollPositionBottom;
4290			if (obj_reaction.bln_aboveScrollPositionBottom !== bln_aboveScrollPositionBottom) {
4291				obj_reaction.bln_crossedScrollPositionBottom = true;
4292				obj_reaction.bln_aboveScrollPositionBottom = bln_aboveScrollPositionBottom;
4293			}
4294		}
4295	},
4296
4297	determineScrollPositionAndDirection: function() {
4298		this.int_currentScrollY = window.getScroll().y;
4299		if (this.int_currentScrollY > this.int_lastScrollY) {
4300			this.str_currentDirection = 'down';
4301		} else if (this.int_currentScrollY < this.int_lastScrollY) {
4302			this.str_currentDirection = 'up';
4303		}
4304		this.int_lastScrollY = this.int_currentScrollY;
4305	},
4306
4307	processReaction: function(obj_reaction) {
4308		this.obj_currentReaction = obj_reaction;
4309
4310		// console.log(obj_reaction);
4311
4312		if (obj_reaction.bln_crossedScrollPositionTop) {
4313			if (obj_reaction.obj_reactOn.str_crossingTop === 'both' || obj_reaction.obj_reactOn.str_crossingTop === this.str_currentDirection) {
4314				if (obj_reaction.obj_reactTimes.int_crossingTop > 0) {
4315					obj_reaction.obj_reactTimes.int_crossingTop--;
4316					if (obj_reaction.obj_reactTimes.int_crossingTop === 0) {
4317						obj_reaction.obj_reactOn.str_crossingTop = 'none';
4318					}
4319				}
4320				obj_reaction.func_reactionCrossingTop();
4321			}
4322		}
4323
4324		if (obj_reaction.bln_crossedScrollPositionBottom) {
4325			if (obj_reaction.obj_reactOn.str_crossingBottom === 'both' || obj_reaction.obj_reactOn.str_crossingBottom === this.str_currentDirection) {
4326				if (obj_reaction.obj_reactTimes.int_crossingBottom > 0) {
4327					obj_reaction.obj_reactTimes.int_crossingBottom--;
4328					if (obj_reaction.obj_reactTimes.int_crossingBottom === 0) {
4329						obj_reaction.obj_reactOn.str_crossingBottom = 'none';
4330					}
4331				}
4332				obj_reaction.func_reactionCrossingBottom();
4333			}
4334		}
4335
4336		if (!obj_reaction.bln_aboveScrollPositionTop && obj_reaction.bln_aboveScrollPositionBottom) {
4337			if (obj_reaction.obj_reactOn.str_between === 'both' || obj_reaction.obj_reactOn.str_between === this.str_currentDirection) {
4338				if (obj_reaction.obj_reactTimes.int_between> 0) {
4339					obj_reaction.obj_reactTimes.int_between--;
4340					if (obj_reaction.obj_reactTimes.int_between === 0) {
4341						obj_reaction.obj_reactOn.str_between = 'none';
4342					}
4343				}
4344				obj_reaction.func_reactionBetween();
4345			}
4346		}
4347
4348		obj_reaction.bln_crossedScrollPositionTop = false;
4349		obj_reaction.bln_crossedScrollPositionBottom = false;
4350
4351		this.obj_currentReaction = null;
4352	}
4353
4354};
4355
4356lsjs.addViewClass(str_moduleName, obj_classdef);
4357
4358})();
4359// < ###### VIEW ##
4360// 
4361// ###### CONTROLLER ## >
4362(function() {
4363	
4364// ### ENTER MODULE NAME HERE ######
4365var str_moduleName = 'scrollReactor';
4366// #################################
4367
4368var obj_classdef = {
4369	start: function() {
4370	}
4371};
4372
4373lsjs.addControllerClass(str_moduleName, obj_classdef);
4374
4375lsjs.__moduleHelpers[str_moduleName] = {
4376	self: null,
4377	
4378	start: function(obj_options) {
4379		this.self = lsjs.createModule({
4380			__name: str_moduleName
4381		});
4382		
4383		this.self.__models.options.set(obj_options);
4384	}
4385};
4386
4387}
4387)();
4388// < ###### CONTROLLER ##
4389// < #### MODULE ##
4390
4391
4392
4393
4394// #### MODULE ## >
4395// ###### TEMPLATES ## >
4396
4397// < ###### TEMPLATES ##
4398
4399// ###### MODELS ## >
4400
4401// < ###### MODELS ##
4402
4403// ###### VIEW ## >
4404(function() {
4405	
4406// ### ENTER MODULE NAME HERE ######
4407var str_moduleName = 'slidesInstance';
4408// #################################
4409
4410var obj_classdef = 	{
4411	el_slideboxContent: null,
4412	obj_contentSize: null,
4413	
4414	el_slideboxContentSizeProtector: null,
4415	
4416	el_slidebox: null,
4417	
4418	el_buttonUp: null,
4419	el_buttonRight: null,
4420	el_buttonDown: null,
4421	el_buttonLeft: null,
4422
4423	obj_currentPosition: {
4424		x: 0,
4425		y: 0
4426	},
4427	
4428	start: function() {
4429		this.el_slideboxContent = this.__el_container;
4430		
4431		this.performAutoSize();
4432		
4433		this.obj_contentSize = this.el_slideboxContent.measure(function() {
4434			return this.getSize();
4435		});
4436
4437		
4438		// Create the slidebox element and set the given slideboxSize
4439		this.el_slidebox = new Element('div.lsSlideboxContainer' + (this.__module.__parentModule.__models.options.data.containerClass != '' ? '.' + this.__module.__parentModule.__models.options.data.containerClass : ''));
4440		this.el_slidebox.setStyles({
4441			width: this.__module.__parentModule.__models.options.data.slideboxSize.x,
4442			height: this.__module.__parentModule.__models.options.data.slideboxSize.y,
4443			overflow: 'hidden',
4444			position: 'relative'
4445		});
4446		
4447		// Inject the slidebox so that it gets it's real dimensions (e.g. if the size is given percentaged)
4448		this.el_slidebox.inject(this.el_slideboxContent, 'before');
4449		
4450		// overwrite the given slideboxSize with the actual size
4451		this.updateSlideboxSize();
4452		
4453		/*
4454		 * el_slideboxContentSizeProtector is needed because a content with a variable width would adjust to the
4455		 * smaller slidebox if injected into it and that must not happen. Therefore el_slideboxContentSizeProtector
4456		 * gets exactly the size that the content had before injecting it into the slidebox and thus preserves the
4457		 * contents size.
4458		 * 
4459		 * el_slideboxContentSizeProtector is also the container that actually gets slided.
4460		 */
4461		this.el_slideboxContentSizeProtector = new Element('div').setStyles({
4462			width: this.obj_contentSize.x,
4463			height: this.obj_contentSize.y
4464		});
4465		
4466		this.el_slideboxContentSizeProtector.setStyles({
4467			position: 'absolute',
4468			top: this.obj_currentPosition.y,
4469			left: this.obj_currentPosition.x
4470		});
4471		
4472		this.el_slideboxContentSizeProtector.set('morph', {duration: this.__module.__parentModule.__models.options.data.duration, transition: this.__module.__parentModule.__models.options.data.transition});
4473		
4474		this.el_buttonUp = new Element('div.button.buttonUp').addEvents({click: this.moveUp.bind(this), dblclick: this.preventSelection});
4475		this.el_buttonRight = new Element('div.button.buttonRight').addEvents({click: this.moveRight.bind(this), dblclick: this.preventSelection});
4476		this.el_buttonDown = new Element('div.button.buttonDown').addEvents({click: this.moveDown.bind(this), dblclick: this.preventSelection});
4477		this.el_buttonLeft = new Element('div.button.buttonLeft').addEvents({click: this.moveLeft.bind(this), dblclick: this.preventSelection});
4478		
4479		this.el_slidebox.adopt(
4480			this.el_slideboxContentSizeProtector,
4481			this.el_buttonUp,
4482			this.el_buttonRight,
4483			this.el_buttonDown,
4484			this.el_buttonLeft
4485		);
4486		
4487		this.el_buttonUp.setStyle('left', this.__module.__parentModule.__models.options.data.slideboxSize.x / 2 - this.el_buttonUp.measure(function() { return this.getSize();}).x / 2);
4488		this.el_buttonRight.setStyle('top', this.__module.__parentModule.__models.options.data.slideboxSize.y / 2 - this.el_buttonRight.measure(function() { return this.getSize();}).y / 2);
4489		this.el_buttonDown.setStyle('left', this.__module.__parentModule.__models.options.data.slideboxSize.x / 2 - this.el_buttonDown.measure(function() { return this.getSize();}).x / 2);
4490		this.el_buttonLeft.setStyle('top', this.__module.__parentModule.__models.options.data.slideboxSize.y / 2 - this.el_buttonLeft.measure(function() { return this.getSize();}).y / 2);
4491
4492		this.el_slideboxContent.inject(this.el_slideboxContentSizeProtector);
4493		
4494		this.setMovingPossibilitiyClasses();
4495		
4496		if (this.__module.__parentModule.__models.options.data.blnUpdateMovingPossibilityClassesOnWindowResize) {
4497			window.addEvent('resize', this.setMovingPossibilitiyClasses.bind(this));
4498		}
4499	},
4500	
4501	updateSlideboxSize: function() {
4502		this.__module.__parentModule.__models.options.data.slideboxSize = this.el_slidebox.measure(function() {
4503			return this.getSize();
4504		});
4505	},
4506	
4507	performAutoSize: function() {
4508		var el_target;
4509		if (
4510				this.__module.__parentModule.__models.options.data.autoSize == undefined
4511			||	this.__module.__parentModule.__models.options.data.autoSize == null
4512			||	this.__module.__parentModule.__models.options.data.autoSize.targetSelector == undefined
4513			||	this.__module.__parentModule.__models.options.data.autoSize.targetSelector == null
4514			||	this.__module.__parentModule.__models.options.data.autoSize.countingElementSelector == undefined
4515			||	this.__module.__parentModule.__models.options.data.autoSize.countingElementSelector == null
4516			||	this.__module.__parentModule.__models.options.data.autoSize.sizePerElement == undefined
4517			||	this.__module.__parentModule.__models.options.data.autoSize.sizePerElement == null
4518			||	(
4519						(this.__module.__parentModule.__models.options.data.autoSize.sizePerElement.x == undefined || this.__module.__parentModule.__models.options.data.autoSize.sizePerElement.x == null || this.__module.__parentModule.__models.options.data.autoSize.sizePerElement.x == 0 || this.__module.__parentModule.__models.options.data.autoSize.sizePerElement.x == '')
4520					&&	(this.__module.__parentModule.__models.options.data.autoSize.sizePerElement.y == undefined || this.__module.__parentModule.__models.options.data.autoSize.sizePerElement.y == null || this.__module.__parentModule.__models.options.data.autoSize.sizePerElement.y == 0 || this.__module.__parentModule.__models.options.data.autoSize.sizePerElement.y == '')
4521				)
4522		) {
4523			return;
4524		}
4525		
4526		el_target = this.__module.__parentModule.__models.options.data.autoSize.targetSelector == '' ? this.el_slideboxContent : this.el_slideboxContent.getElement(this.__module.__parentModule.__models.options.data.autoSize.targetSelector);
4527		if (el_target == undefined || el_target == null) {
4528			return;
4529		}
4530		
4531		var offsetX = 0;
4532		var offsetY = 0;
4533		if (this.__module.__parentModule.__models.options.data.autoSize.sizeOffset != undefined && this.__module.__parentModule.__models.options.data.autoSize.sizeOffset != null) {
4534			if (this.__module.__parentModule.__models.options.data.autoSize.sizeOffset.x != undefined && this.__module.__parentModule.__models.options.data.autoSize.sizeOffset.x != null) {
4535				offsetX = this.__module.__parentModule.__models.options.data.autoSize.sizeOffset.x;
4536			}
4537			if (this.__module.__parentModule.__models.options.data.autoSize.sizeOffset.y != undefined && this.__module.__parentModule.__models.options.data.autoSize.sizeOffset.y != null) {
4538				offsetY = this.__module.__parentModule.__models.options.data.autoSize.sizeOffset.y;
4539			}
4540		}
4541		
4542		var numCountingElements = this.el_slideboxContent.getElements(this.__module.__parentModule.__models.options.data.autoSize.countingElementSelector).length;
4543		if (this.__module.__parentModule.__models.options.data.autoSize.sizePerElement.x != undefined && this.__module.__parentModule.__models.options.data.autoSize.sizePerElement.x != null && this.__module.__parentModule.__models.options.data.autoSize.sizePerElement.x != 0 && this.__module.__parentModule.__models.options.data.autoSize.sizePerElement.x != '') {
4544			el_target.setStyle('width', this.__module.__parentModule.__models.options.data.autoSize.sizePerElement.x * numCountingElements + offsetX);
4545		}
4546		if (this.__module.__parentModule.__models.options.data.autoSize.sizePerElement.y != undefined && this.__module.__parentModule.__models.options.data.autoSize.sizePerElement.y != null && this.__module.__parentModule.__models.options.data.autoSize.sizePerElement.y != 0 && this.__module.__parentModule.__models.options.data.autoSize.sizePerElement.y != '') {
4547			el_target.setStyle('height', this.__module.__parentModule.__models.options.data.autoSize.sizePerElement.y * numCountingElements + offsetY);
4548		}
4549	},
4550	
4551	moveUp: function() {
4552		this.move('up');
4553	},
4554	
4555	moveRight: function() {
4556		this.move('right');
4557	},
4558	
4559	moveDown: function() {
4560		this.move('down');
4561	},
4562	
4563	moveLeft: function() {
4564		this.move('left');
4565	},
4566	
4567	preventSelection: function(event) {
4568		event.stop();
4569		if (window.getSelection) {
4570			if (window.getSelection().empty) {
4571				window.getSelection().empty();
4572			} else if (window.getSelection().removeAllRanges) {
4573				window.getSelection().removeAllRanges();
4574			}
4575		} else if (document.selection) {
4576			document.selection.empty();
4577		}
4578	},
4579	
4580	move: function(direction) {
4581		var objNewPosition = this.getNewPosition(direction);
4582		this.el_slideboxContentSizeProtector.morph({
4583			top: objNewPosition.y,
4584			left: objNewPosition.x
4585		});
4586		this.obj_currentPosition = objNewPosition;
4587		
4588		this.setMovingPossibilitiyClasses();
4589	},
4590	
4591	getMoveDistance: function(direction) {
4592		if (this.__module.__parentModule.__models.options.data.blnUseVariableSlideDistance) {
4593			this.updateSlideboxSize();
4594		}
4595		
4596		var distance = 0;
4597		
4598		switch(direction) {
4599			case 'up':
4600			case 'down':
4601				if (!this.__module.__parentModule.__models.options.data.blnUseVariableSlideDistance || this.__module.__parentModule.__models.options.data.autoSize.sizePerElement.y == undefined || this.__module.__parentModule.__models.options.data.autoSize.sizePerElement.y == null) {
4602					distance = this.__module.__parentModule.__models.options.data.slideboxSize.y;
4603				} else {
4604					distance = Math.floor(this.__module.__parentModule.__models.options.data.slideboxSize.y / this.__module.__parentModule.__models.options.data.autoSize.sizePerElement.y) * this.__module.__parentModule.__models.options.data.autoSize.sizePerElement.y;
4605				}
4606				distance = distance + this.__module.__parentModule.__models.options.data.slideDistanceOffset.y;
4607				break;
4608
4609			case 'right':
4610			case 'left':
4611				if (!this.__module.__parentModule.__models.options.data.blnUseVariableSlideDistance || this.__module.__parentModule.__models.options.data.autoSize.sizePerElement.x == undefined || this.__module.__parentModule.__models.options.data.autoSize.sizePerElement.x == null) {
4612					distance = this.__module.__parentModule.__models.options.data.slideboxSize.x;
4613				} else {
4614					distance = Math.floor(this.__module.__parentModule.__models.options.data.slideboxSize.x / this.__module.__parentModule.__models.options.data.autoSize.sizePerElement.x) * this.__module.__parentModule.__models.options.data.autoSize.sizePerElement.x;
4615				}
4616				distance = distance + this.__module.__parentModule.__models.options.data.slideDistanceOffset.x;
4617				break;
4618		}
4619		
4620		return distance;
4621	},
4622	
4623	getNewPosition: function(direction) {
4624		var objNewPosition = {x: this.obj_currentPosition.x, y: this.obj_currentPosition.y};
4625		switch(direction) {
4626			case 'up':
4627				objNewPosition.y = this.obj_currentPosition.y + this.getMoveDistance(direction);
4628				break;
4629
4630			case 'right':
4631				objNewPosition.x = this.obj_currentPosition.x - this.getMoveDistance(direction);
4632				break;
4633
4634			case 'down':
4635				objNewPosition.y = this.obj_currentPosition.y - this.getMoveDistance(direction);
4636				break;
4637
4638			case 'left':
4639				objNewPosition.x = this.obj_currentPosition.x + this.getMoveDistance(direction);
4640				break;
4641		}
4642		
4643		/*
4644		 * check if the new position is within the limits. If a value
4645		 * is outside the limits, set it to the old position value (do
4646		 * not move)
4647		 */
4648		if (objNewPosition.x > 0) {
4649			// objNewPosition.x = this.obj_currentPosition.x;
4650			objNewPosition.x = 0;
4651		}
4652		if (objNewPosition.y > 0) {
4653			// objNewPosition.y = this.obj_currentPosition.y;
4654			objNewPosition.y = 0;
4655		}
4656		if (objNewPosition.x <= this.obj_contentSize.x * -1) {
4657			objNewPosition.x = this.obj_currentPosition.x;
4658		}
4659		if (objNewPosition.y <= this.obj_contentSize.y * -1) {
4660			objNewPosition.y = this.obj_currentPosition.y;
4661		}
4662		return objNewPosition;
4663	},
4664	
4665	setMovingPossibilitiyClasses: function() {
4666		this.setMovingPossibilitiyClass('up');
4667		this.setMovingPossibilitiyClass('right');
4668		this.setMovingPossibilitiyClass('down');
4669		this.setMovingPossibilitiyClass('left');
4670	},
4671	
4672	setMovingPossibilitiyClass: function(direction) {
4673		var objNewPosition = this.getNewPosition(direction);
4674		if (objNewPosition.y != this.obj_currentPosition.y || objNewPosition.x != this.obj_currentPosition.x) {
4675			this.el_slidebox.addClass(direction + 'Possible');
4676			this.el_slidebox.removeClass(direction + 'Impossible');
4677		} else {
4678			this.el_slidebox.removeClass(direction + 'Possible');
4679			this.el_slidebox.addClass(direction + 'Impossible');
4680		}
4681	}
4682};
4683
4684lsjs.addViewClass(str_moduleName, obj_classdef);
4685
4686})();
4687// < ###### VIEW ##
4688// 
4689// ###### CONTROLLER ## >
4690(function() {
4691	
4692// ### ENTER MODULE NAME HERE ######
4693var str_moduleName = 'slidesInstance';
4694// #################################
4695
4696var obj_classdef = {
4697	start: function() {
4698	}
4699};
4700
4701lsjs.addControllerClass(str_moduleName, obj_classdef);
4702
4703})();
4704// < ###### CONTROLLER ##
4705// < #### MODULE ##
4706
4707
4708
4709
4710// #### MODULE ## >
4711// ###### TEMPLATES ## >
4712
4713// < ###### TEMPLATES ##
4714
4715// ###### MODELS ## >
4716(function() {
4717	
4718var obj_classdef_model = {
4719	name: 'options',
4720	
4721	data: {},
4722	
4723	start: function() {
4724		/*
4725		 * Initializing the options in the data object with default values which
4726		 * can later be overwritten when the "set" method is called with other options
4727		 */
4728		this.data = {
4729			el_domReference: null,
4730			contentSelector: '.lsSlides',
4731			containerClass: '',
4732			slideboxSize: {
4733				x: 200, // if you want to use a percentage value, please declare it as a string (e.g. '100%')
4734				y: 200
4735			},
4736			duration: 800, // duration of the sliding effect in ms
4737			transition: 'sine:in:out', // any string that defines a transition type in the mootools way (see http://mootools.net/docs/core/Fx/Fx.Transitions#Fx-Transitions:sine)
4738
4739			/*
4740			 * If multiple elements should be positioned horizontally next to each other (horizontal slides) but the number of elements is variable,
4741			 * the surrounding element can be set to a width calculated based on the number of elements counted on the basis of a given selector.
4742			 * Without this autoSize functionality the only way to perform a horizontal slide with an unknown number of contained elements would be
4743			 * to set the surrounding container to a much too big width but this would cause the lsSlides to slide into a whole lot of empty space.
4744			 * 
4745			 * In the vertical direction it is easy to work with an unknown number of elements because they will automatically be positioned right
4746			 * below each other using a simple css floating technique or even without any special css because of the standard text flow. However,
4747			 * in order to create a flexible and consistent autoSize functionality it is even possible to autoSize the height of the surrounding element.
4748			 */
4749			autoSize: {
4750				targetSelector: '', // The selector for the element which should be autosized. The selector will only be searched inside the element found using the contentSelector. If set to an empty string, the element found using the contentSelector will be resized itself.
4751				countingElementSelector: '', // The selector for the elements which should be counted (only counted inside the element found using the contentSelector)
4752				sizePerElement: {
4753					x: 0, // The width that should be added for each counted element. If set to 0, the width will not be calculated and not set.
4754					y: 0 // The height that should be added for each counted element. If set to 0, the height will not be calculated and not set.
4755				},
4756				sizeOffset: {
4757					x: 0, // A horizontal offset. This can be useful for example if all counted elements have a right margin except for the last one. In this case the width including the right margin will be added for each element but the missing right margin of the last element needs to be subtracted. A negative offset can provide this.
4758					y: 0 // A vertical offset. See description of the horizontal offset
4759				}
4760			},
4761			blnUseVariableSlideDistance: false, // Set this parameter to true if your slideboxSize is set as a percentage and you want the slide distance to be adjusted dynamically if the actual slidebox size changes
4762			slideDistanceOffset: {
4763				x: 0, // A horizontal offset. By default lsSlides slides the width of the slidebox but this distance can be adjusted with this offset.
4764				y: 0 // A vertical offset. See description of the horizontal offset			
4765			},
4766			blnUpdateMovingPossibilityClassesOnWindowResize: false // Set this parameter to true if you want the moving possibility classes of the buttons to be updated if the window resizes. This way, if the slidebox size changes because the user resized the window, the sliding buttons will appear as needed. 
4767		};
4768	},
4769	
4770	set: function(obj_options) {
4771		Object.merge(this.data, obj_options);
4772		this.__module.onModelLoaded();
4773	}
4774};
4775
4776lsjs.addModelClass('slidesManager', obj_classdef_model);
4777
4778})();
4779
4780
4781
4782
4783// < ###### MODELS ##
4784
4785// ###### VIEW ## >
4786
4787// < ###### VIEW ##
4788// 
4789// ###### CONTROLLER ## >
4790(function() {
4791	
4792// ### ENTER MODULE NAME HERE ######
4793var str_moduleName = 'slidesManager';
4794// #################################
4795
4796var obj_classdef = {
4797	start: function() {
4798		var els_toEnhance;
4799		/*
4800		 * Look for elements to enrich with the lsjs-module and then
4801		 * create an instance for each element found.
4802		 */
4803		if (this.__models.options.data.el_domReference !== undefined && typeOf(this.__models.options.data.el_domReference) === 'element') {
4804			els_toEnhance = this.__models.options.data.el_domReference.getElements(this.__models.options.data.contentSelector);
4805		} else {
4806			els_toEnhance = $$(this.__models.options.data.contentSelector);
4807		}
4808		
4809		Array.each(els_toEnhance, function(el_container) {
4810			/* ->
4811			 * Make sure not to handle an element more than once
4812			 */
4813			if (!el_container.retrieve('alreadyHandledBy_' + str_moduleName)) {
4814				el_container.store('alreadyHandledBy_' + str_moduleName, true);
4815			} else {
4816				return;
4817			}
4818			/*
4819			 * <-
4820			 */
4821
4822			lsjs.createModule({
4823				__name: 'slidesInstance',
4824				__parentModule: this.__module,
4825				__useLoadingIndicator: false,
4826				__el_container: el_container
4827			});
4828		}.bind(this));
4829	}
4830};
4831
4832lsjs.addControllerClass(str_moduleName, obj_classdef);
4833
4834lsjs.__moduleHelpers[str_moduleName] = {
4835	self: null,
4836	
4837	start: function(obj_options) {
4838		this.self = lsjs.createModule({
4839			__name: str_moduleName
4840		});
4841		this.self.__models.options.set(obj_options);
4842	}
4843};
4844
4845}
4845)();
4846// < ###### CONTROLLER ##
4847// < #### MODULE ##
4848
4849
4850
4851
4852// #### MODULE ## >
4853// ###### TEMPLATES ## >
4854
4855// < ###### TEMPLATES ##
4856
4857// ###### MODELS ## >
4858(function() {
4859	
4860var obj_classdef_model = {
4861	name: 'options',
4862	
4863	data: {
4864		str_eventType: 'click',
4865		str_propertyToToggle: 'data-lsjs-statusTogglerStatus',
4866
4867		/*
4868		 * Important: Only string values are allowed as status values. If a status should be 8, it has to be defined as '8'.
4869		 * The first value in this array is the value that will be set initially.
4870		 * It is possible to define more than two status values in which case they will be looped over.
4871		 */
4872		arr_statusValue: ['off', 'on'],
4873
4874		str_sessionStorageKey: null
4875	},
4876	
4877	start: function() {
4878		this.__module.onModelLoaded();
4879	},
4880	
4881	set: function(obj_options) {
4882		Object.merge(this.data, obj_options);
4883	}
4884};
4885
4886lsjs.addModelClass('statusTogglerInstance', obj_classdef_model);
4887
4888})();
4889
4890
4891
4892
4893// < ###### MODELS ##
4894
4895// ###### VIEW ## >
4896(function() {
4897	
4898// ### ENTER MODULE NAME HERE ######
4899var str_moduleName = 'statusTogglerInstance';
4900// #################################
4901
4902var obj_classdef = 	{
4903	start: function() {
4904		var str_options,
4905			obj_options = {},
4906			str_startStatusValueFromSessionStorage,
4907			str_startStatusValue;
4908
4909		str_options = this.__el_container.getProperty('data-lsjs-statusTogglerOptions');
4910
4911		if (str_options) {
4912			obj_options = JSON.decode(str_options);
4913		}
4914
4915		this.__models.options.set(obj_options);
4916
4917		this.registerElements(this.__el_container, 'main', false);
4918
4919		/* -->
4920		 * Prepare bound functions that are needed for events
4921		 */
4922		this.bound_changeStatus = this.changeStatus.bind(this);
4923
4924		str_startStatusValue = this.__models.options.data.arr_statusValue[0];
4925
4926		if (this.__models.options.data.str_sessionStorageKey) {
4927			try {
4928				str_startStatusValueFromSessionStorage = sessionStorage.getItem('lsjs_statusToggler_' + this.__models.options.data.str_sessionStorageKey);
4929				if (str_startStatusValueFromSessionStorage !== null) {
4930					str_startStatusValue = str_startStatusValueFromSessionStorage;
4931				}
4932			} catch(e) {}
4933		}
4934
4935		this.__el_container.setProperty(this.__models.options.data.str_propertyToToggle, str_startStatusValue);
4936
4937		if (typeOf(this.__autoElements.main.toggler) === 'element' || typeOf(this.__autoElements.main.toggler) === 'elements') {
4938			this.__autoElements.main.toggler.addEvent(this.__models.options.data.str_eventType, this.bound_changeStatus);
4939		} else {
4940			this.__el_container.addEvent(this.__models.options.data.str_eventType, this.bound_changeStatus);
4941		}
4942	},
4943
4944	bound_changeStatus: null,
4945	changeStatus: function() {
4946		var str_currentStatusValue = this.__el_container.getProperty(this.__models.options.data.str_propertyToToggle);
4947		var int_currentStatusIndex = this.__models.options.data.arr_statusValue.indexOf(str_currentStatusValue);
4948		if (int_currentStatusIndex < 0) {
4949			int_currentStatusIndex = 0;
4950		}
4951
4952		var int_nextStatusIndex = int_currentStatusIndex === this.__models.options.data.arr_statusValue.length - 1 ? 0 : int_currentStatusIndex + 1;
4953
4954		this.__el_container.setProperty(this.__models.options.data.str_propertyToToggle, this.__models.options.data.arr_statusValue[int_nextStatusIndex]);
4955
4956		if (this.__models.options.data.str_sessionStorageKey) {
4957			try {
4958				sessionStorage.setItem('lsjs_statusToggler_' + this.__models.options.data.str_sessionStorageKey, this.__models.options.data.arr_statusValue[int_nextStatusIndex]);
4959			} catch(e) {}
4960		}
4961	}
4962};
4963
4964lsjs.addViewClass(str_moduleName, obj_classdef);
4965
4966})();
4967// < ###### VIEW ##
4968// 
4969// ###### CONTROLLER ## >
4970(function() {
4971	
4972// ### ENTER MODULE NAME HERE ######
4973var str_moduleName = 'statusTogglerInstance';
4974// #################################
4975
4976var obj_classdef = {
4977	start: function() {
4978	}
4979};
4980
4981lsjs.addControllerClass(str_moduleName, obj_classdef);
4982
4983})();
4984// < ###### CONTROLLER ##
4985// < #### MODULE ##
4986
4987
4988
4989
4990// #### MODULE ## >
4991// ###### TEMPLATES ## >
4992
4993// < ###### TEMPLATES ##
4994
4995// ###### MODELS ## >
4996(function() {
4997	
4998var obj_classdef_model = {
4999	name: 'options',
5000	
5001	data: {},
5002	
5003	start: function() {
5004		/*
5005		 * Initializing the options in the data object with default values which
5006		 * can later be overwritten when the "set" method is called with other options
5007		 */
5008		this.data = {
5009			el_domReference: null,
5010			str_selector: '[data-lsjs-component~="statusToggler"]', // the selector for the dom element to enrich,
5011			str_classToSetWhenModuleApplied: 'statusTogglerApplied'
5012		};
5013	},
5014	
5015	set: function(obj_options) {
5016		Object.merge(this.data, obj_options);
5017		this.__module.onModelLoaded();
5018	}
5019};
5020
5021lsjs.addModelClass('statusTogglerManager', obj_classdef_model);
5022
5023})();
5024
5025
5026
5027
5028// < ###### MODELS ##
5029
5030// ###### VIEW ## >
5031
5032// < ###### VIEW ##
5033// 
5034// ###### CONTROLLER ## >
5035/*
5036 * -- ACTIVATION: --
5037 *
5038 * To activate this module, the following code has to be put in the app.js:
5039 *
5040	 lsjs.__moduleHelpers.statusTogglerManager.start({
5041		 el_domReference: el_domReference
5042	 });
5043 *
5044 * The el_domReference parameter is only required if this module initialization code is called 
5044in a cajax_domUpdate event.
5045 *
5046 *
5047 *
5048 * -- FUNCTIONALITY AND USAGE: --
5049 *
5050 * This module enriches DOM elements by giving them a status that can be toggled and then be used for CSS styling
5051 * based on an element attribute that holds the status value.
5052 *
5053 * Example:
5054 *
5055 * We have an element with the following DOM structure:
5056 *
5057	<div>
5058		<h1>Click me!</h1>
5059		<div class="hidden">
5060			Show me!
5061		</div>
5062	</div>
5063 *
5064 * The div with the class hidden originally has a "display: none" style set. If the user clicks on "Click me!" we
5065 * want the hidden div to be displayed if it is currently hidden or to hide again if it is currently displayed.
5066 *
5067 * We can achieve this by making the following changes to the markup
5068 *
5069	 <div data-lsjs-component="statusToggler">
5070		 <h1 data-lsjs-element="toggler">Click me!</h1>
5071		 <div class="hidden">
5072			 Show me!
5073		 </div>
5074	 </div>
5075 *
5076 * By default, the element that has the statusToggler applied (data-lsjs-component="statusToggler") gets an attribute
5077 * "data-lsjs-statusTogglerStatus" initially set to the value "off" and by clicking on the element marked as the
5078 * toggler (data-lsjs-element="toggler") the value toggles between "off" and "on".
5079 *
5080 * This attribute can then be used to create a CSS selector that applies any kind of styling to the element itself
5081 * or any of its children.
5082 *
5083 * Therefore, this styling would achieve the desired show/hide effect:
5084 *
5085	 #div[data-lsjs-statusTogglerStatus="off"] .hidden {
5086	 	display: none;
5087	}
5088
5089	 #div[data-lsjs-statusTogglerStatus="on"] .hidden {
5090	 	display: block;
5091	}
5092 *
5093 * Defining an explicit toggler element is optional. Without it, the whole element that has the statusToggler module
5094 * applied, reacts on the click event.
5095 *
5096 * The element marked with data-lsjs-component="statusToggler" can also send custom configurator options to the module:
5097 *
5098	 <div
5099		 data-lsjs-component="statusToggler"
5100		 data-lsjs-statusTogglerOptions="
5101		 {
5102			 str_eventType: 'mouseover',
5103			 str_propertyToToggle: 'class',
5104			 arr_statusValue: ['status1', 'status2', 'status3'],
5105			 str_sessionStorageKey: 'test123'
5106		 }
5107		 "
5108	 >
5109		 <h1 data-lsjs-element="toggler">Click me!</h1>
5110		 <div class="hidden">
5111			 Show me!
5112		 </div>
5113	 </div>
5114 *
5115 * In this example, every time a mouseover event on the toggler element occurs, the class value will be changed.
5116 * Since we have more than two status values defined, they will be looped over.
5117 *
5118 * The status values could be numeric values but they have to be defined as strings ("1" instead of 1) because applying
5119 * these values to an html attribute would cast them as strings anyway and detection of the currently set status value
5120 * would fail if they had been defined as integers in the module configuration.
5121 *
5122 * If the configuration parameter "str_sessionStorageKey" is used, the status value will be stored in the sessionStorage
5123 * so that the status can be set to its last value if the page gets reloaded.
5124 */
5125
5126(function() {
5127	
5128// ### ENTER MODULE NAME HERE ######
5129var str_moduleName = 'statusTogglerManager';
5130// #################################
5131
5132var obj_classdef = {
5133	start: function() {
5134		var els_toEnhance;
5135		/*
5136		 * Look for elements to enrich with the lsjs-module and then
5137		 * instantiate instances for each element found.
5138		 */
5139		if (this.__models.options.data.el_domReference !== undefined && typeOf(this.__models.options.data.el_domReference) === 'element') {
5140			els_toEnhance = this.__models.options.data.el_domReference.getElements(this.__models.options.data.str_selector);
5141		} else {
5142			els_toEnhance = $$(this.__models.options.data.str_selectors);
5143		}
5144
5145		Array.each(els_toEnhance, function(el_container) {
5146			/* ->
5147			 * Make sure not to handle an element more than once
5148			 */
5149			if (!el_container.retrieve('alreadyHandledBy_' + str_moduleName)) {
5150				el_container.store('alreadyHandledBy_' + str_moduleName, true);
5151			} else {
5152				return;
5153			}
5154			/*
5155			 * <-
5156			 */
5157
5158			el_container.addClass(this.__models.options.data.str_classToSetWhenModuleApplied);
5159
5160			lsjs.createModule({
5161				__name: 'statusTogglerInstance',
5162				__parentModule: this.__module,
5163				__useLoadingIndicator: false,
5164				__el_container: el_container
5165			});
5166		}.bind(this));
5167	}
5168};
5169
5170lsjs.addControllerClass(str_moduleName, obj_classdef);
5171
5172lsjs.__moduleHelpers[str_moduleName] = {
5173	self: null,
5174	
5175	start: function(obj_options) {
5176		this.self = lsjs.createModule({
5177			__name: str_moduleName
5178		});
5179		this.self.__models.options.set(obj_options);
5180	}
5181};
5182
5183}
5183)();
5184// < ###### CONTROLLER ##
5185// < #### MODULE ##
5186
5187
5188
5189
5190// #### MODULE ## >
5191// ###### TEMPLATES ## >
5192
5193// < ###### TEMPLATES ##
5194
5195// ###### MODELS ## >
5196(function() {
5197	
5198var obj_classdef_model = {
5199	name: 'options',
5200
5201	data: {
5202		str_headerSelector: '#header',
5203		str_stickyClass: 'sticky',
5204		int_offset: 0
5205	},
5206
5207	start: function() {
5208	},
5209
5210	set: function(obj_options) {
5211		Object.merge(this.data, obj_options);
5212		this.__module.onModelLoaded();
5213	}
5214};
5215
5216lsjs.addModelClass('stickyHeader', obj_classdef_model);
5217
5218})();
5219
5220
5221
5222
5223// < ###### MODELS ##
5224
5225// ###### VIEW ## >
5226(function() {
5227
5228// ### ENTER MODULE NAME HERE ######
5229var str_moduleName = 'stickyHeader';
5230// #################################
5231
5232var obj_classdef = 	{
5233	start: function() {
5234		var els_header,
5235			el_header,
5236			self = this;
5237
5238		els_header = $$(this.__models.options.data.str_headerSelector);
5239
5240		if (typeOf(els_header) !== 'elements' || els_header.length < 1) {
5241			console.log('no elements found for selector "' + this.__models.options.data.str_headerSelector + '"');
5242			return;
5243		}
5244
5245		if (els_header.length > 1) {
5246			console.log('more than one element found for selector "' + this.__models.options.data.str_headerSelector + '"');
5247			return;
5248		}
5249
5250		el_header = els_header[0];
5251
5252		lsjs.__moduleHelpers.scrollReactor.start({
5253			arr_reactions: [
5254				{
5255					func_reactionCrossingTop: function () {
5256						if (this.str_currentDirection === 'down') {
5257							el_header.addClass(self.__models.options.data.str_stickyClass);
5258						} else {
5259							el_header.removeClass(self.__models.options.data.str_stickyClass);
5260						}
5261					},
5262					int_scrollPositionTop: el_header.getPosition().y + self.__models.options.data.int_offset,
5263					obj_reactOn: {
5264						str_crossingTop: 'both',
5265						str_crossingBottom: 'none',
5266						str_between: 'none'
5267					}
5268				}
5269			]
5270		});
5271	}
5272};
5273
5274lsjs.addViewClass(str_moduleName, obj_classdef);
5275
5276})();
5277// < ###### VIEW ##
5278// 
5279// ###### CONTROLLER ## >
5280(function() {
5281	
5282// ### ENTER MODULE NAME HERE ######
5283var str_moduleName = 'stickyHeader';
5284// #################################
5285
5286/*
5287 * Use like this:
5288 *
5289 * lsjs.__moduleHelpers.stickyHeader.start({str_headerSelector: '#header', str_stickyClass: 'sticky'});
5290 *
5291 * The object given as a parameter in this example can be omitted since the values in the example are used as default.
5292 */
5293var obj_classdef = {
5294	start: function() {
5295	}
5296};
5297
5298lsjs.addControllerClass(str_moduleName, obj_classdef);
5299
5300lsjs.__moduleHelpers[str_moduleName] = {
5301	self: null,
5302	
5303	start: function(obj_options) {
5304		this.self = lsjs.createModule({
5305			__name: str_moduleName
5306		});
5307		
5308		this.self.__models.options.set(obj_options);
5309	}
5310};
5311
5312})();
5313// < ###### CONTROLLER ##
5314// < #### MODULE ##
5315
5316
5317
5318
5319// #### MODULE ## >
5320// ###### TEMPLATES ## >
5321
5322// < ###### TEMPLATES ##
5323
5324// ###### MODELS ## >
5325(function() {
5326	
5327var obj_classdef_model = {
5328	name: 'options',
5329	
5330	data: {},
5331	
5332	start: function() {
5333		/*
5334		 * Initializing the options in the data object with default values which
5335		 * can later be overwritten when the "set" method is called with other options
5336		 */
5337		this.data = {
5338			/*
5339			 * Mandatory.
5340			 * 
5341			 * The selector for the toggler. If the selector matches multiple elements,
5342			 * they all will be used. Can also be a DOM element or a collection of DOM elements.
5343			 */
5344			var_togglerSelector: '#whateverToggler',
5345
5346			/*
5347			 * Mandatory.
5348			 * 
5349			 * The selector for the resize box. Only the first matched element will
5350			 * be used. Can also be a DOM element.
5351			 */
5352			var_contentBoxSelector: '#whateverContentBox',
5353
5354			/*
5355			 * Optional.
5356			 * 
5357			 * The selector for the wrapper. Only the first matched element will
5358			 * be used. Can also be a DOM element.
5359			 */
5360			var_wrapperSelector: null,
5361
5362			/*
5363			 * Optional.
5364			 * 
5365			 * The selector for a specific close button. If the selector
5366			 * matches multiple elements, they all will be used. Can also be a
5367			 * DOM element or a collection of DOM elements.
5368			 */
5369			var_closeButtonSelector: null,
5370
5371			/*
5372			 * Optional.
5373			 * 
5374			 * True, if the content box should be automatically wrapped in another
5375			 * element, that's being resized instead of the given content box itself.
5376			 * 
5377			 * False, if no new element should be created and if the content box itself
5378			 * should be resized when folding/unfolding
5379			 */
5380			bln_automaticallyCreateResizeBox: true,
5381
5382			/*
5383			 * Optional.
5384			 * 
5385			 * Defines, which events are used to toggle the unfold effect.
5386			 * All event type values for the Mootools addEvent function are possible.
5387			 * Also, an array holding two event types can be given. In this case,
5388			 * the two event types will be switched, which makes mouseenter/mouseleave
5389			 * or mouseover/mouseout possible.
5390			 * 
5391			 * If the only way to close the element should be the click on a special
5392			 * close button or an outside click, the second event in the array can be
5393			 * 'closeButton'. Actually, every non-existing can be used, because the
5394			 * only way that this event can be triggered is by explicitly calling it,
5395			 * which happens in case of a close button click or outside click.
5396			 * 
5397			 * Examples: ['mouseenter', 'mouseleave'] or ['mouseenter', 'closeButton'] etc.
5398			 */
5399			str_togglerEventType: 'click',
5400
5401			/*
5402			 * Optional.
5403			 * Possible values: 'open' or 'closed'.
5404			 * 
5405			 * Defines, whether the element is already opened when instantiating the
5406			 * effect. Please note that this value refers to the element's status
5407			 * before the lsUnfold effect is initialized. This means that an element
5408			 * that, without active javascript, would be displayed as open because
5409			 * of its css styles needs this value set to 'open' even if (with active
5410			 * javascript and lsUnfold applied) it will be closed instantly using
5411			 * the options 'bln_toggleOnInitialization' and 'bln_skipAnimationWhenTogglingOnInitialization'
5412			 * or the option 'str_initialCookieStatus'.
5413			 */
5414			str_initialToggleStatus: 'open',
5415
5416			/*
5417			 * Optional.
5418			 * Boolean.
5419			 * 
5420			 * True if the element should be morphed right after initialization
5421			 * (simulating an automatic toggle).
5422			 */
5423			bln_toggleOnInitialization: false,
5424
5425			/*
5426			 * Optional.
5427			 * Boolean.
5428			 * 
5429			 * True if the animation should be skipped when the element is being
5430			 * toggled on initialization
5431			 */
5432			bln_skipAnimationWhenTogglingOnInitialization: false,
5433
5434			/*
5435			 * Optional.
5436			 * 
5437			 * If set, this name will be used to store the toggle status in a cookie
5438			 * and then later restore it on page reload
5439			 */
5440			str_cookieIdentifierName: '',
5441
5442			/*
5443			 * Optional.
5444			 * 
5445			 * If set to either closed or open, this value is used as the initial
5446			 * cookie status if the cookie doesn't exist yet.
5447			 */
5448			str_initialCookieStatus: '',
5449
5450			/*
5451			 * Optional.
5452			 * Boolean.
5453			 * 
5454			 * True if a click outside the unfolded element should close it
5455			 */
5456			bln_closeOnOutsideClick: false,
5457
5458			/*
5459			 * Optional.
5460			 * Possible values: 'height', 'margin-top'
5461			 * 
5462			 * The animation mode defines which css property is used to unfold
5463			 * the resizeBox.
5464			 */
5465			str_animationMode: 'height',
5466
5467			/*
5468			 * Optional.
5469			 * Boolean.
5470			 * 
5471			 * True, if the paddings of the resizeBox should also be morphed to
5472			 * and from 0. Please keep in mind, that it is much better not to have
5473			 * a padding on the resizeBox at all but instead set the padding to an
5474			 * element inside the resizeBox.
5475			 */
5476			bln_morphPaddings: false,
5477
5478			/*
5479			 * Optional.
5480			 * Boolean.
5481			 * 
5482			 * When using the animation mode "margin-top" the resizeBox is probably
5483			 * hidden outside the window using a negative margin. Unfolding the element
5484			 * means morphing from the negative margin to zero margin.
5485			 * 
5486			 * Using this flag, the current window scroll position can be considered,
5487			 * so that the element will not morph from its negative margin to zero margin
5488			 * but to the scroll position as a positive margin, which means that the 
5489			 * element still moves inside the screen from the top, no matter what
5490			 * the current scroll position is.
5491			 */
5492			bln_considerWindowScrollInMarginAnimationMode: false,
5493
5494			/*
5495			 * Optional.
5496			 * Boolean.
5497			 * 
5498			 * If the animation mode "margin-top" has been usde to slide the element
5499			 * in from the top, this flag can be used to make sure, that the element
5500			 * stays visible even if the user scrolls the window.
5501			 */
5502			bln_moveWithWindowScrollInMarginAnimationMode: false,
5503
5504			/*
5505			 * Optional.
5506			 * Object
5507			 * 
5508			 * If bln_moveWithWindowScrollInMarginAnimationMode is true, then the
5509			 * element stays in its position when the window is being scrolled. However,
5510			 * if the element is higher then the visible screen/window, it will be scrolled
5511			 * up and down until its top or bottom edge is inside the visible area.
5512			 * The offsets that can be defined in this object will be considered when
5513			 * determining whether the top or bottom edge is in the visible area.
5514			 * Example: A bottom value of 10 means that when scrolling down, the element
5515			 * will move up until its bottom edge is 10 px inside the visible area.
5516			 */
5517			obj_moveWithWindowScrollInMarginAnimationModeOffsets: {
5518				/*
5519				 * Integer
5520				 * 
5521				 * The offset in px without unit.
5522				 */
5523				top: 0,
5524
5525				/*
5526				 * Integer
5527				 * 
5528				 * The offset in px without unit.
5529				 */
5530				bottom: 0
5531			},
5532
5533			/*
5534			 * Optional, if the resizeBox starts opened.
5535			 * Mandatory, if the resizeBox starts closed.
5536			 * 
5537			 * Can be any valid value for the css property "display" (e.g. "block",
5538			 * "inline-block" etc.). This option needs to be set, if the element
5539			 * to resize starts with "display: none;" in which case lsUnfold would
5540			 * not be able to determine the correct display type to use for the
5541			 * visible/unfolded state of the element.
5542			 */
5543			str_initialDisplayType: null,
5544
5545			/*
5546			 * Optional.
5547			 * Possible values: 'measure', 'getStyle', 'auto' or the height in
5548			 * pixels without unit, e.g. 150
5549			 * 
5550			 * The initial height value can either be given directly (var_initalHeight: 'auto',
5551			 * var_initalHeight: 150) or it can be determined using the getStyle method
5552			 * (var_initalHeight: 'getStyle') or it can be determined using the
5553			 * getDimensions method (var_initalHeight: 'measure').
5554			 * 
5555			 * In most cases, it's best not to use 'measure' or 'getStyle' because
5556			 * it's not always easy to understand what the expected result should be.
5557			 * 
5558			 * If the resizeBox should be able to adapt its size dynamically (e.g.
5559			 * because its content can grow), the value 'auto' should be set and it
5560			 * is also important do define 'height: auto;' in the stylesheet for this
5561			 * element as well, if the resizeBox starts opened.
5562			 */
5563			var_initialHeight: 'auto',
5564
5565			/*
5566			 * Optional.
5567			 * Integer.
5568			 * 
5569			 * When lsUnfold determines the size to which the element should be unfolded,
5570			 * it is sometimes necessary to manipulate that value by adding a positive
5571			 * or negative offset.
5572			 */
5573			int_heightOffset: 0,
5574
5575			/*
5576			 * Optional.
5577			 * 
5578			 * The class to add to the wrapper, the toggler and the resizeBox when
5579			 * the status is open.
5580			 */
5581			str_classOpen: 'lsUnfoldOpen',
5582
5583			/*
5584			 * Optional.
5585			 * 
5586			 * The class to add to the wrapper, the toggler and the resizeBox when
5587			 * the status is closed.
5588			 */
5589			str_classClosed: 'lsUnfoldClosed',
5590
5591			/*
5592			 * Optional.
5593			 * 
5594			 * The class to add to the wrapper, the toggler and the resizeBox when
5595			 * the status is running.
5596			 */
5597			str_classRunning: 'lsUnfoldRunning',
5598
5599			/*
5600			 * For development purposes you can activate a logging in the console
5601			 */
5602			bln_useLogging: false,
5603
5604			/*
5605			 * Optional.
5606			 * 
5607			 * The class to add to the wrapper and close button to indicate that lsUnfold is
5608			 * used with this element. This can be used in a css selector in order to only
5609			 * display a toggler button if the element can actually be toggled.
5610			 */
5611			str_classUseLsUnfold: 'useLsUnfold',
5612
5613			obj_morphOptions: {
5614				'duration': 250,
5615				'link': 'ignore'
5616			}
5617		};
5618	},
5619	
5620	set: function(obj_options) {
5621		/*
5622		 * The user calls this function in order to override our default values.
5623		 * 
5624		 * After that we add our onComplete function (not before, because it must
5625		 * not be overridden) and in the onComplete function we make a call to
5626		 * the onComplete function possibly given as a user argument.
5627		 */
5628		Object.merge(this.data, obj_options);
5629		
5630		this.data.obj_morphOptions.onComplete = function() {
5631			this.__view.setEndStyles();
5632			this.__view.toggleStatus();
5633			this.__view.onMorphComplete();
5634			if (
5635					obj_options.obj_morphOptions !== undefined
5636				&&	obj_options.obj_morphOptions.onComplete !== undefined
5637				&&	typeOf(obj_options.obj_morphOptions.onComplete) === 'function'
5638			) {
5639				obj_options.obj_morphOptions.onComplete();
5640			}
5641		}.bind(this);
5642		
5643		this.__module.onModelLoaded();
5644	}
5645};
5646
5647lsjs.addModelClass('unfold', obj_classdef_model);
5648
5649})();
5650
5651
5652
5653
5654// < ###### MODELS ##
5655
5656// ###### VIEW ## >
5657(function() {
5658	
5659// ### ENTER MODULE NAME HERE ######
5660var str_moduleName = 'unfold';
5661// #################################
5662
5663var obj_classdef = 	{
5664	bound_onToggleEvent: null,
5665	bound_closeOnOutsideClick: null,
5666	bound_closeOnCloseButtonClick: null,
5667	
5668	interval_scrollPositionChecker: null,
5669	
5670	els_toggler: null,
5671	el_resizeBox: null,
5672	el_contentBox: null,
5673	el_wrapper: null,
5674	els_closeButton: null,
5675	
5676	var_initialHeight: null,
5677	obj_initialPaddings: {
5678		'top': 0,
5679		'right': 0,
5680		'bottom': 0,
5681		'left': 0
5682	},
5683	str_initialDisplayType: null,
5684	int_windowScrollY: 0,
5685		
5686	obj_morph: null,
5687	obj_morphSkipAnimation: null,
5688	obj_morph_slideToScrollPosition: null,
5689	
5690	obj_startStyles: {},
5691	obj_endStyles: {},
5692	obj_morphParams: {},
5693	
5694	str_toggleStatus: 'open',
5695	str_togglerEventTypeToUse: 'click',
5696	
5697	bln_skipAnimation: false,
5698	
5699	start: function() {
5700		/* -->
5701		 * Prepare bound functions that are needed for events
5702		 */
5703		this.bound_onToggleEvent = this.onToggleEvent.bind(this);
5704		this.bound_closeOnOutsideClick = this.closeOnOutsideClick.bind(this);
5705		this.bound_closeOnCloseButtonClick = this.closeOnCloseButtonClick.bind(this);
5706		/*
5707		 * <--
5708		 */
5709		
5710		/*
5711		 * Determine the event type to start with. If the given option value is
5712		 * a string, this event will be used. If it's an array, which means that
5713		 * two values should be switched, we start using the first array value.
5714		 */
5715		this.str_togglerEventTypeToUse =	typeOf(this.__models.options.data.str_togglerEventType) === 'array'
5716										?	this.__models.options.data.str_togglerEventType[0]
5717										:	this.__models.options.data.str_togglerEventType;
5718		
5719		/*
5720		 * Preparing the DOM elements to use. toggler and closeButton can also be
5721		 * a collection of elements.
5722		 */
5723		this.els_toggler = typeOf(this.__models.options.data.var_togglerSelector) === 'element' || typeOf(this.__models.options.data.var_togglerSelector) === 'null' ? this.__models.options.data.var_togglerSelector : $$(this.__models.options.data.var_togglerSelector);
5724		this.el_wrapper = typeOf(this.__models.options.data.var_wrapperSelector) === 'element' || typeOf(this.__models.options.data.var_wrapperSelector) === 'null' ? this.__models.options.data.var_wrapperSelector : $$(this.__models.options.data.var_wrapperSelector)[0];
5725		this.els_closeButton = typeOf(this.__models.options.data.var_closeButtonSelector) === 'element' || typeOf(this.__models.options.data.var_closeButtonSelector) === 'null' ? this.__models.options.data.var_closeButtonSelector : $$(this.__models.options.data.var_closeButtonSelector);
5726		this.el_contentBox = typeOf(this.__models.options.data.var_contentBoxSelector) === 'element' || typeOf(this.__models.options.data.var_contentBoxSelector) === 'null' ? this.__models.options.data.var_contentBoxSelector : $$(this.__models.options.data.var_contentBoxSelector)[0];
5727		
5728		/*
5729		 * Don't do anything without a toggler element and a resizeBox element.
5730		 */
5731		if (
5732				this.els_toggler === undefined || this.els_toggler === null
5733			||	this.el_contentBox === undefined || this.el_contentBox === null
5734		) {
5735			if (this.__models.options.data.bln_useLogging) {
5736				console.error('required elements missing:');
5737				console.log('this.els_toggler', this.els_toggler);
5738				console.log('this.el_contentBox', this.el_contentBox);
5739				console.log('this.__models.options.data', this.__models.options.data);
5740			}
5741			return;
5742		}
5743		
5744		this.str_toggleStatus = this.__models.options.data.str_initialToggleStatus;
5745		
5746		if (this.__models.options.data.bln_automaticallyCreateResizeBox) {
5747			/*
5748			 * Create a resizeBox and wrap the given content box in it. The
5749			 * newly created resizeBox is then used for the morph.
5750			 */
5751			this.el_resizeBox = new Element('div.lsUnfoldResizeBox').wraps(this.el_contentBox);
5752			this.el_resizeBox.setStyles({
5753				height: 'auto',
5754				overflow: 'hidden',
5755				display: this.__models.options.data.str_initialToggleStatus === 'open' ? 'block' : 'none'
5756			});
5757		} else {
5758			/*
5759			 * Don't create a new element but insead use the given contentBox
5760			 * directly for the morph.
5761			 */
5762			this.el_resizeBox = this.el_contentBox;
5763		}
5764		
5765		/*
5766		 * Determining the intial height
5767		 */
5768		if (this.__models.options.data.var_initialHeight === 'getStyle') {
5769			this.var_initialHeight = this.el_resizeBox.getStyle('height');
5770		} else if (this.__models.options.data.var_initialHeight === 'measure') {
5771			this.var_initialHeight = this.getResizeBoxHeight();
5772		} else {
5773			this.var_initialHeight = this.__models.options.data.var_initialHeight;
5774		}
5775		
5776		/*
5777		 * Determining the initial paddings
5778		 */
5779		this.obj_initialPaddings = this.el_resizeBox.measure(function() {
5780			var obj_paddings,
5781				obj_dimensions;
5782				
5783				obj_dimensions = this.getDimensions({computeSize: true});
5784				obj_paddings = {
5785					'top': obj_dimensions['padding-top'],
5786					'right': obj_dimensions['padding-right'],
5787					'bottom': obj_dimensions['padding-bottom'],
5788					'left': obj_dimensions['padding-left']
5789				};
5790				
5791				return obj_paddings;
5792		});
5793		
5794		/*
5795		 * Determining the initial display type ('block', 'inline-block' etc.)
5796		 */
5797		this.str_initialDisplayType =
5798			(
5799					this.__models.options.data.str_initialDisplayType !== undefined
5800				&&	this.__models.options.data.str_initialDisplayType !== null
5801			)
5802			?	this.__models.options.data.str_initialDisplayType
5803			:	this.el_resizeBox.getStyle('display');
5804		
5805		/*
5806		 * Preparing the morph effect for folding/unfolding the resizeBox
5807		 */
5808		this.obj_morph = new Fx.Morph(this.el_resizeBox, this.__models.options.data.obj_morphOptions);
5809		var obj_morphOptionsSkipAnimation = this.__models.options.data.obj_morphOptions;
5810		obj_morphOptionsSkipAnimation.duration = 0;
5811		this.obj_morphSkipAnimation =  new Fx.Morph(this.el_resizeBox, obj_morphOptionsSkipAnimation);
5812		
5813		/*
5814		 * Preparing the morph effect for sliding to the scroll position
5815		 */
5816		this.obj_morph_slideToScrollPosition = new Fx.Morph(this.el_resizeBox, {
5817			'duration': 1000,
5818			'link': 'cancel'
5819		});
5820		
5821		/* -->
5822		 * Adding a class to the wrapper and the closeButton (if existing),
5823		 * indicating that lsUnfold is being used.
5824		 */
5825		if (this.el_wrapper !== undefined && this.el_wrapper !== null) {
5826			this.el_wrapper.addClass(this.__models.options.data.str_classUseLsUnfold);
5827		}
5828		
5829		if (this.els_closeButton !== undefined && this.els_closeButton !== null) {
5830			this.els_closeButton.addClass(this.__models.options.data.str_classUseLsUnfold);
5831		}
5832		/*
5833		 * <--
5834		 */
5835		
5836		/*
5837		 * Add the event to the toggler
5838		 */
5839		this.els_toggler.addEvent(this.str_togglerEventTypeToUse, this.bound_onToggleEvent);
5840		
5841		/*
5842		 * Make sure classes are set correctly
5843		 */
5844		this.adjustClasses();
5845		
5846		/*
5847		 * Toggle directly on initialization, if the corresponding flag is set
5848		 */
5849		if (this.__models.options.data.bln_toggleOnInitialization) {
5850			if (this.__models.options.data.bln_skipAnimationWhenTogglingOnInitialization) {
5851				this.bln_skipAnimation = true;
5852			}
5853			this.els_toggler.fireEvent(this.str_togglerEventTypeToUse);
5854			if (this.__models.options.data.bln_skipAnimationWhenTogglingOnInitialization) {
5855				this.bln_skipAnimation = false;
5856			}
5857		}
5858		
5859		this.considerStatusFromCookie();
5860	},
5861	
5862	considerStatusFromCookie: function() {
5863		if (!this.__models.options.data.str_cookieIdentifierName) {
5864			return;
5865		}
5866		
5867		if (
5868				Cookie.read(this.__models.options.data.str_cookieIdentifierName) !== 'open'
5869			&&	Cookie.read(this.__models.options.data.str_cookieIdentifierName) !== 'closed'
5870		) {
5871			Cookie.write(this.__models.options.data.str_cookieIdentifierName, this.__models.options.data.str_initialCookieStatus ? this.__models.options.data.str_initialCookieStatus : this.str_toggleStatus);
5872		}
5873		if (this.str_toggleStatus != Cookie.read(this.__models.options.data.str_cookieIdentifierName)) {
5874			this.bln_skipAnimation = true;
5875			this.els_toggler.fireEvent(this.str_togglerEventTypeToUse);
5876			this.bln_skipAnimation = false;
5877		}		
5878	},
5879	
5880	onToggleEvent: function(event) {
5881		/*
5882		 * If an event is given, we want to prevent the default action,
5883		 * i.e. to prevent a hyperlink to be followed because if a hyperlinked
5884		 * anchor is used as the toggler, its link is considered the "fallback
5885		 * action" if javascript is not active.
5886		 */
5887		if (event !== undefined) {
5888			event.preventDefault();
5889		}
5890		
5891		/*
5892		 * If the element is currently being unfolded/morphed, we don't
5893		 * accept another toggle.
5894		 */
5895		if (this.obj_morph.isRunning()) {
5896			return;
5897		}
5898		
5899		/*
5900		 * The function called here makes sure that the correct toggle event
5901		 * is active. This is important because if two different event types
5902		 * are given, they need to be switched (mouseenter -> mouseout -> mouseenter ...)
5903		 */
5904		this.updateTogglerEventTypeToUse();
5905		
5906		/*
5907		 * Adding the element classes indicating that the morph is currently running
5908		 */
5909		if (this.el_wrapper !== undefined && this.el_wrapper !== null) {
5910			this.el_wrapper.addClass(this.__models.options.data.str_classRunning);
5911		}
5912		this.el_resizeBox.addClass(this.__models.options.data.str_classRunning);
5913		this.els_toggler.addClass(this.__models.options.data.str_classRunning);
5914		
5915		/*
5916		 * Call the function to either open or close the resizeBox,
5917		 * depending on the current toggle status.
5918		 */
5919		if (this.str_toggleStatus === 'closed') {
5920			this.openResizeBox();
5921		} else {
5922			this.closeResizeBox();
5923		}
5924	},
5925	
5926	/*
5927	 * This function makes sure that the correct toggle event is active.
5928	 * This is important because if two different event types are given,
5929	 * they need to be switched (mouseenter -> mouseout -> mouseenter ...)
5930	 */
5931	updateTogglerEventTypeToUse: function() {
5932		/*
5933		 * If the given togglerEventType is not an array, we don't have to
5934		 * do anything, because only one single event type is given and
5935		 * there's nothing to switch.
5936		 */
5937		if (typeOf(this.__models.options.data.str_togglerEventType) !== 'array') {
5938			return;
5939		}
5940		
5941		/*
5942		 * If the event type needs to be switched, we first have to remove the
5943		 * currently existing event listener, because it listens for the previous
5944		 * event type.
5945		 */
5946		this.els_toggler.removeEvent(this.str_togglerEventTypeToUse, this.bound_onToggleEvent);
5947		
5948		/*
5949		 * Determine which event type to use now.
5950		 */
5951		this.str_togglerEventTypeToUse = this.__models.options.data.str_togglerEventType[0] === this.str_togglerEventTypeToUse ? this.__models.options.data.str_togglerEventType[1] : this.__models.options.data.str_togglerEventType[0];
5952		
5953		/*
5954		 * Adding the new event listener for the determined event type.
5955		 */
5956		this.els_toggler.addEvent(this.str_togglerEventTypeToUse, this.bound_onToggleEvent);
5957	},
5958	
5959	/*
5960	 * Opening the resizeBox
5961	 */
5962	openResizeBox: function() {
5963		/*
5964		 * Determine the height that the opened resizeBox needs to have
5965		 */
5966		var int_endHeight = this.getResizeBoxHeight();
5967		
5968		/*
5969		 * Adding the event to listen for the outside click if necessary
5970		 */
5971		if (this.__models.options.data.bln_closeOnOutsideClick) {
5972			window.addEvent('click', this.bound_closeOnOutsideClick);
5973		}
5974		
5975		/*
5976		 * Adding the event to listen for the close button click if necessary
5977		 */
5978		if (this.els_closeButton) {
5979			this.els_closeButton.addEvent('click', this.bound_closeOnCloseButtonClick);
5980		}
5981		
5982		/*
5983		 * If the window scroll position needs to be considered, it is stored
5984		 * here for future reference.
5985		 */
5986		if (this.__models.options.data.bln_considerWindowScrollInMarginAnimationMode) {
5987			this.int_windowScrollY = window.getScroll().y;
5988		}
5989		
5990		/*
5991		 * Adding the offset to the element's target height
5992		 */
5993		int_endHeight += this.__models.options.data.int_heightOffset;
5994		
5995		/*
5996		 * Resetting the styles which have to be applied before starting the morph
5997		 * and the parameters for the morph effect.
5998		 */
5999		this.obj_startStyles = {};
6000		this.obj_morphParams = {};
6001		
6002		/*
6003		 * Different styles and morph parameters have to be used depending on
6004		 * the animation mode
6005		 */
6006		switch (this.__models.options.data.str_animationMode) {
6007			case 'height':
6008				/*
6009				 * When opening, we start with 0 height and we set the initial
6010				 * display type to make the element visible because before, it
6011				 * was set to display: none.
6012				 */
6013				this.obj_startStyles = {
6014					'height': 0,
6015					'display': this.str_initialDisplayType
6016				};
6017				
6018				/*
6019				 * We morph the element's height from 0 to the end height
6020				 */
6021				this.obj_morphParams = {
6022					'height': [0, int_endHeight]
6023				};
6024				
6025				/*
6026				 * After the morph effect, we set the initial height value, which
6027				 * is necessary to make sure that the resizeBox can be set to
6028				 * auto to allow it to grow flexibly with its content. Otherwise
6029				 * even if the resizeBox started with height: auto, it would have
6030				 * a fixed height value after the morph.
6031				 */
6032				this.obj_endStyles = {
6033					'height': this.var_initialHeight
6034				};
6035				break;
6036			
6037			case 'margin-top':
6038				/*
6039				 * When opening, we start with a negative margin that is as big
6040				 * as the determined element's height. We add the window scroll
6041				 * position, which might be 0 if the flag indicating that the
6042				 * scroll position should be considered is not set. If the scroll
6043				 * position should be considered, the window scroll position added
6044				 * here makes sure, that the margin where the morph begins positions
6045				 * the element right outside the window.
6046				 * 
6047				 * We also set the initial display type to make the element
6048				 * visible because before, it was set to display: none.
6049				 */
6050				this.obj_startStyles = {
6051					'margin-top': this.int_windowScrollY + (int_endHeight * -1),
6052					'display': this.str_initialDisplayType
6053				};
6054				
6055				/*
6056				 * We morph the element's margin-top from the negative margin
6057				 * (as big as the element's height) to margin-top 0. To both values
6058				 * we add the window scroll position which is either a positive
6059				 * value or 0 if the window scroll position should not be considered.
6060				 */
6061				this.obj_morphParams = {
6062					'margin-top': [this.int_windowScrollY + (int_endHeight * -1), this.int_windowScrollY + 0]
6063				};				
6064				break;
6065		}
6066		
6067		/*
6068		 * If the paddings should also be morphed, we have to adjust the morphParams
6069		 * as well as the startStyles and endStyles accordingly.
6070		 */
6071		if (this.__models.options.data.bln_morphPaddings) {
6072			this.obj_morphParams['padding-top'] = [0, this.obj_initialPaddings.top];
6073			this.obj_morphParams['padding-bottom'] = [0, this.obj_initialPaddings.bottom];
6074
6075			this.obj_startStyles['padding-top'] = 0;
6076			this.obj_startStyles['padding-bottom'] = 0;
6077
6078			this.obj_endStyles['padding-top'] = this.obj_initialPaddings.top;
6079			this.obj_endStyles['padding-bottom'] = this.obj_initialPaddings.bottom;
6080		}
6081		
6082		/*
6083		 * We apply the startStyles ...
6084		 */
6085		this.el_resizeBox.setStyles(this.obj_startStyles);
6086		
6087		/*
6088		 * ... and then we start the morph effect.
6089		 */
6090		if (this.bln_skipAnimation) {
6091			this.obj_morphSkipAnimation.start(this.obj_morphParams);
6092		} else {
6093			this.obj_morph.start(this.obj_morphParams);
6094		}
6095	},
6096	
6097	/*
6098	 * Closing the resize box
6099	 */
6100	closeResizeBox: function() {
6101		/*
6102		 * We have to determine the element's current height before starting
6103		 * the closing morph because with an auto height the element's height
6104		 * might have changed.
6105		 */
6106		var int_beginHeight = this.getResizeBoxHeight();
6107		
6108		/*
6109		 * We don't want any unnecessary event listeners, so we remove the listener
6110		 * for the click event on the outside click
6111		 */
6112		window.removeEvent('click', this.bound_closeOnOutsideClick);
6113		
6114		/*
6115		 * Adding the offset to the element's determined height
6116		 */
6117		int_beginHeight += this.__models.options.data.int_heightOffset;
6118		
6119		/*
6120		 * Resetting the startSTyles and morphParams
6121		 */
6122		this.obj_startStyles = {};
6123		this.obj_morphParams = {};
6124		
6125		/*
6126		 * Different styles and morph parameters have to be used depending on
6127		 * the animation mode
6128		 */
6129		switch (this.__models.options.data.str_animationMode) {
6130			case 'height':
6131				/*
6132				 * When closing, we morph from the element's current height to 0
6133				 */
6134				this.obj_morphParams = {
6135					'height': [int_beginHeight, 0]
6136				};
6137				
6138				/*
6139				 * After the morph, we set the inital height value to make sure
6140				 * that a subsequent call to openResizeBox() can determine the
6141				 * element's correct height. We also set display: none to make
6142				 * sure that the element is hidden.
6143				 */
6144				this.obj_endStyles = {
6145					'height': this.var_initialHeight,
6146					'display': 'none'
6147				};
6148				break;
6149			
6150			case 'margin-top':
6151				/*
6152				 * When opening, we morph from a zero margin to a negative margin
6153				 * as big as the element's height. To both values
6154				 * we add the window scroll position which is either a positive
6155				 * value or 0 if the window scroll position should not be considered.
6156				 */
6157				this.obj_morphParams = {
6158					'margin-top': [this.el_resizeBox.getStyle('margin-top').toInt(), this.el_resizeBox.getStyle('margin-top').toInt() - int_beginHeight]
6159				};
6160				
6161				/*
6162				 * After the morph we set display: none to make
6163				 * sure that the element is hidden.
6164				 */
6165				this.obj_endStyles = {
6166					'display': 'none'
6167				};
6168				break;
6169		}
6170		
6171		/*
6172		 * If the paddings should also be morphed, we have to adjust the morphParams
6173		 * as well as the startStyles and endStyles accordingly.
6174		 */
6175		if (this.__models.options.data.bln_morphPaddings) {
6176			this.obj_morphParams['padding-top'] = [this.obj_initialPaddings.top, 0];
6177			this.obj_morphParams['padding-bottom'] = [this.obj_initialPaddings.bottom, 0];
6178
6179			this.obj_startStyles['padding-top'] = this.obj_initialPaddings.top;
6180			this.obj_startStyles['padding-bottom'] = this.obj_initialPaddings.bottom;
6181
6182			this.obj_endStyles['padding-top'] = 0;
6183			this.obj_endStyles['padding-bottom'] = 0;
6184		}
6185		
6186		/*
6187		 * We apply the start styles ...
6188		 */
6189		this.el_resizeBox.setStyles(this.obj_startStyles);
6190		
6191		/*
6192		 * ... and then we start the morph effect.
6193		 */
6194		if (this.bln_skipAnimation) {
6195			this.obj_morphSkipAnimation.start(this.obj_morphParams);
6196		} else {
6197			this.obj_morph.start(this.obj_morphParams);
6198		}
6199	},
6200	
6201	/*
6202	 * We keep the current toggle status (open or closed) in a variable and
6203	 * with this function, we toggle the status. This function gets called
6204	 * after the morph has finisehd.
6205	 */
6206	toggleStatus: function() {
6207		if (this.str_toggleStatus === 'open') {
6208			this.str_toggleStatus = 'closed';			
6209		} else {
6210			this.str_toggleStatus = 'open';			
6211		}
6212		
6213		if (this.__models.options.data.str_cookieIdentifierName) {
6214			Cookie.write(this.__models.options.data.str_cookieIdentifierName, this.str_toggleStatus);
6215		}
6216	},
6217	
6218	/*
6219	 * The styles to apply after finishing the morph are stored in a variable
6220	 * and these styles are being applied with this function.
6221	 */
6222	setEndStyles: function() {
6223		this.el_resizeBox.setStyles(this.obj_endStyles);
6224		
6225		/*
6226		 * Reset the end styles so that the end styles of the opening morph
6227		 * will not be used with the closing morph if the closing morph doesn't
6228		 * explicitly define end styles.
6229		 */
6230		this.obj_endStyles = {};
6231	},
6232	
6233	adjustClasses: function() {
6234		if (!this.obj_morph.isRunning()) {
6235			/*
6236			 * Remove the running class from the elements
6237			 */
6238			if (this.el_wrapper !== undefined && this.el_wrapper !== null) {
6239				this.el_wrapper.removeClass(this.__models.options.data.str_classRunning);
6240			}
6241			this.el_resizeBox.removeClass(this.__models.options.data.str_classRunning);
6242			this.els_toggler.removeClass(this.__models.options.data.str_classRunning);
6243		}
6244
6245		switch (this.str_toggleStatus) {
6246			case 'open':
6247				/*
6248				 * Setting the classes indicating the current status
6249				 */
6250				if (this.el_wrapper !== undefined && this.el_wrapper !== null) {
6251					this.el_wrapper.removeClass(this.__models.options.data.str_classClosed);
6252					this.el_wrapper.addClass(this.__models.options.data.str_classOpen);
6253				}
6254				this.el_resizeBox.removeClass(this.__models.options.data.str_classClosed);
6255				this.el_resizeBox.addClass(this.__models.options.data.str_classOpen);
6256				this.els_toggler.removeClass(this.__models.options.data.str_classClosed);
6257				this.els_toggler.addClass(this.__models.options.data.str_classOpen);
6258				break;
6259			
6260			case 'closed':
6261				/*
6262				 * Setting the classes indicating the current status
6263				 */
6264				if (this.el_wrapper !== undefined && this.el_wrapper !== null) {
6265					this.el_wrapper.removeClass(this.__models.options.data.str_classOpen);
6266					this.el_wrapper.addClass(this.__models.options.data.str_classClosed);
6267				}
6268				this.el_resizeBox.removeClass(this.__models.options.data.str_classOpen);
6269				this.el_resizeBox.addClass(this.__models.options.data.str_classClosed);
6270				this.els_toggler.removeClass(this.__models.options.data.str_classOpen);
6271				this.els_toggler.addClass(this.__models.options.data.str_classClosed);
6272				break;
6273		}
6274	},
6275	
6276	/*
6277	 * Things to do when the morph is completed
6278	 */
6279	onMorphComplete: function() {
6280		this.adjustClasses();
6281		switch (this.str_toggleStatus) {
6282			case 'open':
6283				/*
6284				 * Set the interval for sliding to the scroll position if necessary
6285				 */
6286				if (this.__models.options.data.bln_moveWithWindowScrollInMarginAnimationMode) {
6287					this.interval_scrollPositionChecker = setInterval(this.slideToScrollPosition.bind(this), 100);
6288				}
6289				break;
6290			
6291			case 'closed':
6292				/*
6293				 * Clear the interval for sliding to the scroll position if necessary
6294				 */
6295				if (this.__models.options.data.bln_moveWithWindowScrollInMarginAnimationMode) {
6296					if (this.interval_scrollPositionChecker !== null) {
6297						clearInterval(this.interval_scrollPositionChecker);
6298					}
6299				}
6300				break;
6301		}
6302	},
6303	
6304	/*
6305	 * This function slides the resizeBox to the current scroll position, using
6306	 * a quite complex logic to determine what the correct position is, if the
6307	 * resizeBox is bigger than the window.
6308	 * 
6309	 * It is very important, that scrolling the window allows the user to also
6310	 * scroll to the top and bottom of the resizeBox so that he is able to see
6311	 * its full content.
6312	 */
6313	slideToScrollPosition: function() {
6314		var morph_options = {},
6315			int_slideTarget,
6316			str_edgeToSlideTo,
6317			obj_windowCoordinates,
6318			obj_elementCoordinates,
6319			obj_elementCoordinatesWithOffset;
6320		
6321		/*
6322		 * Don't start a new animation if the scroll position has not changed
6323		 * since the last time that this function has been called.
6324		 */
6325		if (window.getScroll().y === this.int_windowScrollY) {
6326			return;
6327		}
6328		
6329		/*
6330		 * Store the current position
6331		 */
6332		this.int_windowScrollY = window.getScroll().y;
6333		
6334		obj_windowCoordinates = {
6335			'top': window.getScroll().y,
6336			'bottom': window.getScroll().y + window.getSize().y,
6337			'width': window.getSize().x,
6338			'height': window.getSize().y
6339		};
6340
6341		obj_elementCoordinates = this.el_resizeBox.getCoordinates();
6342		obj_elementCoordinatesWithOffset = obj_elementCoordinates;
6343		obj_elementCoordinatesWithOffset.height = obj_elementCoordinatesWithOffset.height + this.__models.options.data.obj_moveWithWindowScrollInMarginAnimationModeOffsets.top + this.__models.options.data.obj_moveWithWindowScrollInMarginAnimationModeOffsets.bottom;
6344		obj_elementCoordinatesWithOffset.top -= this.__models.options.data.obj_moveWithWindowScrollInMarginAnimationModeOffsets.top;
6345		obj_elementCoordinatesWithOffset.bottom -= this.__models.options.data.obj_moveWithWindowScrollInMarginAnimationModeOffsets.bottom;
6346		
6347		
6348		if (obj_elementCoordinatesWithOffset.height <= obj_windowCoordinates.height) {
6349			/*
6350			 * The element including its offsets is smaller than or equal the window.
6351			 * This means that no scrolling is required for the user to see its
6352			 * full content. In this situation, we slide the element to its
6353			 * top position.
6354			 */
6355			str_edgeToSlideTo = 'top';
6356		} else {
6357			/*
6358			 * The element including its offsets is bigger than the window.
6359			 * Scrolling is required for the user to see its full content.
6360			 */
6361			if (obj_elementCoordinatesWithOffset.top > obj_windowCoordinates.top && obj_elementCoordinatesWithOffset.top < obj_windowCoordinates.bottom) {
6362				/*
6363				 * The element's top edge is visible
6364				 */
6365				str_edgeToSlideTo = 'top';
6366			} else if (obj_elementCoordinatesWithOffset.bottom > obj_windowCoordinates.top && obj_elementCoordinatesWithOffset.bottom < obj_windowCoordinates.bottom) {
6367				/*
6368				 * The element's bottom edge is visible
6369				 */
6370				str_edgeToSlideTo = 'bottom';
6371			} else {
6372				/*
6373				 * None of the element's edges is visible
6374				 */
6375				if (obj_elementCoordinatesWithOffset.bottom < obj_windowCoordinates.top) {
6376					/*
6377					 * The complete element is above the window's top edge, so we
6378					 * slide to the element's bottom edge
6379					 */
6380					str_edgeToSlideTo = 'bottom';
6381				} else if (obj_elementCoordinatesWithOffset.top > obj_windowCoordinates.bottom) {
6382					/*
6383					 * The complete element is below the window's top edge, so we
6384					 * slide to the element's top edge
6385					 */
6386					str_edgeToSlideTo = 'top';
6387				} else {
6388					/*
6389					 * The element must be higher than the screen and the visible
6390					 * screen is right between the element's top and bottom edge.
6391					 * In this situation, we don't slide anywhere which allows the
6392					 * user to actually scroll to the top and bottom of the element
6393					 * and to see its full content.
6394					 */
6395					str_edgeToSlideTo = 'none';
6396				}
6397			}
6398		}
6399		
6400		if (str_edgeToSlideTo === 'top') {
6401			int_slideTarget = this.int_windowScrollY + this.__models.options.data.obj_moveWithWindowScrollInMarginAnimationModeOffsets.top;
6402		} else if (str_edgeToSlideTo === 'bottom') {
6403			int_slideTarget = (this.int_windowScrollY - (obj_elementCoordinatesWithOffset.height - obj_windowCoordinates.height));
6404		} else {
6405			// don't slide anywhere
6406			return;
6407		}
6408				
6409		morph_options[this.__models.options.data.str_animationMode] = int_slideTarget;
6410		this.obj_morph_slideToScrollPosition.start(morph_options);
6411	},
6412	
6413	/*
6414	 * Closing the element with a click outside the wrapper element
6415	 */
6416	closeOnOutsideClick: function(event) {
6417		var bln_clickedInside = false;
6418		
6419		/*
6420		 * Ignore the click if the morph is running because we don't want
6421		 * the resizeBox to be closed before it is even completely open.
6422		 */
6423		if (this.obj_morph.isRunning()) {
6424			return;
6425		}
6426
6427		/* -->
6428		 * Don't close if the click was inside, which is the case if the
6429		 * event.target itself or one of its parents is either el_resizeBox
6430		 * or el_wrapper
6431		 */
6432		if (event.target === this.el_resizeBox || event.target === this.el_wrapper) {
6433			bln_clickedInside = true;
6434		}
6435
6436		Array.each(event.target.getParents(), function(el_parent) {
6437			if (el_parent === this.el_resizeBox || el_parent === this.el_wrapper) {
6438				bln_clickedInside = true;
6439			}
6440		}.bind(this));
6441
6442		if (bln_clickedInside) {
6443			return;
6444		}
6445		/*
6446		 * <--
6447		 */
6448		
6449		/*
6450		 * Call the onToggleEvent function which actually closes the resizeBox
6451		 */
6452		this.onToggleEvent();
6453	},
6454	
6455	/*
6456	 * Close the resizeBox if the close button is clicked.
6457	 */
6458	closeOnCloseButtonClick: function() {
6459		/*
6460		 * Ignore the click if the morph is running because we don't want
6461		 * the resizeBox to be closed before it is even completely open.
6462		 */
6463		if (this.obj_morph.isRunning()) {
6464			return;
6465		}
6466
6467		/*
6468		 * Call the onToggleEvent function which actually closes the resizeBox
6469		 */
6470		this.onToggleEvent();
6471	},
6472	
6473	/*
6474	 * This function determines the resizeBox's height and also considers the
6475	 * element's box-sizing, which requires the element's padding and border to
6476	 * be subtracted (content-box) or not (border-box).
6477	 */
6478	getResizeBoxHeight: function() {
6479		var obj_dimensions,
6480			int_height;
6481	
6482		obj_dimensions = this.el_resizeBox.getDimensions({computeSize: true});
6483		int_height = obj_dimensions.height;
6484		
6485		if (this.el_resizeBox.getStyle('box-sizing') !== 'border-box') {
6486			int_height = int_height - obj_dimensions['padding-top'] - obj_dimensions['padding-bottom'] - obj_dimensions['border-top-width'] - obj_dimensions['border-bottom-width'];
6487		}
6488		
6489		return int_height;
6490	}
6491};
6492
6493lsjs.addViewClass(str_moduleName, obj_classdef);
6494
6495})();
6496// < ###### VIEW ##
6497// 
6498// ###### CONTROLLER ## >
6499(function() {
6500	
6501// ### ENTER MODULE NAME HERE ######
6502var str_moduleName = 'unfold';
6503// #################################
6504
6505var obj_classdef = {
6506	start: function() {
6507	}
6508};
6509
6510lsjs.addControllerClass(str_moduleName, obj_classdef);
6511
6512lsjs.__moduleHelpers[str_moduleName] = {
6513	self: null,
6514	
6515	start: function(obj_options) {
6516		this.self = lsjs.createModule({
6517			__name: str_moduleName
6518		});
6519		
6520		this.self.__models.options.set(obj_options);
6521	}
6522};
6523
6524})();
6525// < ###### CONTROLLER ##
6526// < #### MODULE ##
6527
6528
6529
6530
6531// #### MODULE ## >
6532// ###### TEMPLATES ## >
6533
6534// < ###### TEMPLATES ##
6535
6536// ###### MODELS ## >
6537
6538// < ###### MODELS ##
6539
6540// ###### VIEW ## >
6541(function() {
6542	
6543// ### ENTER MODULE NAME HERE ######
6544var str_moduleName = 'zoomInstance';
6545// #################################
6546
6547var obj_classdef = 	{
6548	el_zoomImage: null,
6549	el_zoomImageWrapper: null,
6550	el_zoomBigBox: null,
6551	el_zoomedImage: null,
6552	el_zoomAreaMarker: null,
6553	el_zoomedImagePreloader: null,
6554	
6555	obj_zoomImageSize: null,
6556	obj_zoomImagePosition: null,
6557	
6558	obj_zoomedImageSize: null,
6559	
6560	int_zoomAreaMarkerWidth: 0,
6561	int_zoomAreaMarkerHeight: 0,
6562	obj_zoomAreaMarkerBorderSize: null,
6563	
6564	int_zoomAreaMarkerTop: 0,
6565	int_zoomAreaMarkerLeft: 0,
6566
6567	start: function() {
6568		if (typeOf(this.__module.__parentModule.__models.options.data.func_beforeInit) === 'function') {
6569			this.__module.__parentModule.__models.options.data.func_beforeInit(this);
6570		}
6571		
6572		this.el_zoomImage = this.__module.obj_args.el_zoomImage;
6573		this.el_zoomImage.addClass('zoomImage');
6574
6575		window.addEvent('resize', this.startZoomer.bind(this));
6576				
6577		this.el_zoomImageWrapper = new Element('span.lsZoomImageWrapper');
6578		this.el_zoomImageWrapper.inject(this.el_zoomImage, 'before');
6579		this.el_zoomImage.inject(this.el_zoomImageWrapper);
6580		
6581		this.el_zoomAreaMarker = new Element('span.lsZoomAreaMarker');
6582		this.el_zoomAreaMarker.inject(this.el_zoomImageWrapper, 'bottom');
6583
6584		var elementTypeZoomBigBox = this.__module.__parentModule.__models.options.data.bigBoxFitsThumb == true ? 'span' : 'div';
6585		this.el_zoomBigBox = new Element(elementTypeZoomBigBox + '.lsZoomBigBox');
6586		
6587		if (this.__module.__parentModule.__models.options.data.bigBoxFitsThumb == true) {
6588			this.el_zoomBigBox.inject(this.el_zoomImageWrapper, 'top');
6589		} else if (this.__module.__parentModule.__models.options.data.bigBoxContainerSelector != '' && $$(this.__module.__parentModule.__models.options.data.bigBoxContainerSelector)[0] != undefined) {
6590			this.el_zoomBigBox.inject($$(this.__module.__parentModule.__models.options.data.bigBoxContainerSelector)[0], 'bottom');
6591		} else {
6592			this.el_zoomBigBox.inject(this.el_zoomImageWrapper, 'top');
6593		}
6594
6595		this.el_zoomedImage = new Element('img').setProperties({'src': this.__module.obj_args.str_href, 'alt': ''}).setStyle('display', 'none');
6596		this.el_zoomedImagePreloader = new Element('div.lsZoomPreloader');
6597		this.el_zoomBigBox.adopt(
6598			this.el_zoomedImagePreloader,
6599			this.el_zoomedImage
6600		);
6601		
6602		this.el_zoomedImage.addEvent('load', this.removePreloader.bind(this));
6603		
6604		this.el_zoomImageWrapper.addEvent('mouseenter', this.showBigBox.bind(this));
6605		this.el_zoomImageWrapper.addEvent('mouseleave', this.hideBigBox.bind(this));
6606		
6607		if (typeOf(this.__module.__parentModule.__models.options.data.func_afterInit) === 'function') {
6608			this.__module.__parentModule.__models.options.data.func_afterInit(this);
6609		}
6610	},
6611	
6612	startZoomer: function() {
6613		this.el_zoomImageWrapper.setStyles({
6614			'position': 'relative',
6615			'width': 'auto',
6616			'height': 'auto'
6617		});
6618		
6619		this.obj_zoomImageSize = this.el_zoomImage.measure(function() {
6620			return this.getSize();
6621		});
6622
6623		if (this.__module.__parentModule.__models.options.data.bigBoxFitsThumb == true) {
6624			this.__module.__parentModule.__models.options.data.bigBoxSize = this.obj_zoomImageSize;
6625		}
6626		
6627		this.getZoomImagePosition();
6628		
6629		this.el_zoomImageWrapper.setStyles({
6630			'width': this.obj_zoomImageSize.x,
6631			'height': this.obj_zoomImageSize.y
6632		});
6633		
6634		this.el_zoomBigBox.setStyles({
6635			'width': this.__module.__parentModule.__models.options.data.bigBoxSize.x,
6636			'height': this.__module.__parentModule.__models.options.data.bigBoxSize.y,
6637			'left': 0,
6638			'top': 0,
6639			'position': 'absolute'
6640		});
6641		
6642		if (
6643				this.__module.__parentModule.__models.options.data.bigBoxFitsThumb == false
6644			&&	(this.__module.__parentModule.__models.options.data.bigBoxContainerSelector == '' || $$(this.__module.__parentModule.__models.options.data.bigBoxContainerSelector)[0] == undefined)
6645		) {
6646			var topDistance = 0;
6647			var leftDistance = 0;
6648			switch(this.__module.__parentModule.__models.options.data.side) {
6649				case 'top':
6650					topDistance = (topDistance - this.__module.__parentModule.__models.options.data.bigBoxSize.y) + this.__module.__parentModule.__models.options.data.offset.y;
6651					leftDistance = this.__module.__parentModule.__models.options.data.offset.x;
6652					break;
6653	
6654				case 'bottom':
6655					topDistance = (topDistance + this.obj_zoomImageSize.y) + this.__module.__parentModule.__models.options.data.offset.y;
6656					leftDistance = this.__module.__parentModule.__models.options.data.offset.x;
6657					break;
6658	
6659				case 'left':
6660					topDistance = this.__module.__parentModule.__models.options.data.offset.y;
6661					leftDistance = (leftDistance - this.__module.__parentModule.__models.options.data.bigBoxSize.x) + this.__module.__parentModule.__models.options.data.offset.x;
6662					break;
6663	
6664				case 'right':
6665					topDistance = this.__module.__parentModule.__models.options.data.offset.y;
6666					leftDistance = (leftDistance + this.obj_zoomImageSize.x) + this.__module.__parentModule.__models.options.data.offset.x;
6667					break;
6668			}
6669			
6670			this.el_zoomBigBox.setStyles({
6671				'left': leftDistance + 'px',
6672				'top': topDistance + 'px'
6673			});
6674		}
6675		
6676		this.setZoomAreaMarkerSize();
6677		this.initializeZoomAreaMarkerMovement();
6678	},
6679	
6680	showBigBox: function() {
6681		this.el_zoomBigBox.setStyle('display', 'block');
6682		this.showZoomAreaMarker();
6683	},
6684	
6685	hideBigBox: function() {
6686		this.el_zoomBigBox.setStyle('display', 'none');
6687		this.hideZoomAreaMarker();
6688	},
6689	
6690	removePreloader: function() {
6691		this.el_zoomedImage.setStyle('display', 'inline');
6692		this.el_zoomedImagePreloader.destroy();
6693		this.startZoomer();
6694	},
6695	
6696	setZoomAreaMarkerSize: function() {
6697		var objZoomAreaMarkerStyles;
6698		
6699		this.obj_zoomedImageSize = this.el_zoomedImage.measure(function() {
6700			return this.getSize();
6701		});
6702
6703		this.obj_zoomAreaMarkerBorderSize = {
6704			'left': this.el_zoomAreaMarker.getStyle('border-left-width'),
6705			'right': this.el_zoomAreaMarker.getStyle('border-right-width'),
6706			'top': this.el_zoomAreaMarker.getStyle('border-top-width'),
6707			'bottom': this.el_zoomAreaMarker.getStyle('border-bottom-width')
6708		};
6709
6710		this.obj_zoomAreaMarkerBorderSize = {
6711			'left': parseInt(this.obj_zoomAreaMarkerBorderSize.left.substring(0,this.obj_zoomAreaMarkerBorderSize.left.length - 2)),
6712			'right': parseInt(this.obj_zoomAreaMarkerBorderSize.right.substring(0,this.obj_zoomAreaMarkerBorderSize.right.length - 2)),
6713			'top': parseInt(this.obj_zoomAreaMarkerBorderSize.top.substring(0,this.obj_zoomAreaMarkerBorderSize.top.length - 2)),
6714			'bottom': parseInt(this.obj_zoomAreaMarkerBorderSize.bottom.substring(0,this.obj_zoomAreaMarkerBorderSize.bottom.length - 2))
6715		};
6716		
6717		this.int_zoomAreaMarkerWidth = this.obj_zoomImageSize.x / (this.obj_zoomedImageSize.x / this.__module.__parentModule.__models.options.data.bigBoxSize.x);
6718		this.int_zoomAreaMarkerWidth = this.int_zoomAreaMarkerWidth > this.obj_zoomImageSize.x ? this.obj_zoomImageSize.x : this.int_zoomAreaMarkerWidth;
6719		
6720		this.int_zoomAreaMarkerHeight = this.obj_zoomImageSize.y / (this.obj_zoomedImageSize.y / this.__module.__parentModule.__models.options.data.bigBoxSize.y);
6721		this.int_zoomAreaMarkerHeight = this.int_zoomAreaMarkerHeight > this.obj_zoomImageSize.y ? this.obj_zoomImageSize.y : this.int_zoomAreaMarkerHeight;
6722		
6723		objZoomAreaMarkerStyles = {
6724			'width': (this.int_zoomAreaMarkerWidth - this.obj_zoomAreaMarkerBorderSize.left - this.obj_zoomAreaMarkerBorderSize.right).round(),
6725			'height': (this.int_zoomAreaMarkerHeight - this.obj_zoomAreaMarkerBorderSize.top - this.obj_zoomAreaMarkerBorderSize.bottom).round()
6726		};
6727		
6728		this.el_zoomAreaMarker.setStyles(objZoomAreaMarkerStyles);
6729	},
6730	
6731	initializeZoomAreaMarkerMovement: function() {
6732		this.el_zoomImageWrapper.addEvent('mousemove', this.moveZoomAreaMarker.bind(this));
6733	},
6734	
6735	moveZoomAreaMarker: function(event) {
6736		/*
6737		 * The mouse position (relative to the hovered image) represents the center of the zoomAreaMarker
6738		 */
6739		var mouseX = event.page.x - this.obj_zoomImagePosition.x;
6740		mouseX = mouseX > this.obj_zoomImageSize.x ? this.obj_zoomImageSize.x : mouseX;
6741		
6742		var mouseY = event.page.y - this.obj_zoomImagePosition.y;
6743		mouseY = mouseY > this.obj_zoomImageSize.y ? this.obj_zoomImageSize.y : mouseY;
6744		
6745		this.int_zoomAreaMarkerLeft = mouseX - (this.int_zoomAreaMarkerWidth / 2);
6746		if (this.int_zoomAreaMarkerLeft + this.int_zoomAreaMarkerWidth > this.obj_zoomImageSize.x) {
6747			this.int_zoomAreaMarkerLeft = this.obj_zoomImageSize.x - this.int_zoomAreaMarkerWidth;
6748		}
6749		if (this.int_zoomAreaMarkerLeft < 0) {
6750			this.int_zoomAreaMarkerLeft = 0;
6751		} 
6752		
6753		this.int_zoomAreaMarkerTop = mouseY - (this.int_zoomAreaMarkerHeight / 2);
6754		if (this.int_zoomAreaMarkerTop + this.int_zoomAreaMarkerHeight > this.obj_zoomImageSize.y) {
6755			this.int_zoomAreaMarkerTop = this.obj_zoomImageSize.y - this.int_zoomAreaMarkerHeight;
6756		}
6757		if (this.int_zoomAreaMarkerTop < 0) {
6758			this.int_zoomAreaMarkerTop = 0;
6759		}
6760		
6761		this.el_zoomAreaMarker.setStyles({
6762			'left': this.int_zoomAreaMarkerLeft,
6763			'top': this.int_zoomAreaMarkerTop
6764		});
6765		
6766		this.adjustBigBoxOffset();
6767	},
6768	
6769	adjustBigBoxOffset: function() {
6770		var objOffsets = {
6771			x: this.obj_zoomedImageSize.x / (this.obj_zoomImageSize.x / this.int_zoomAreaMarkerLeft),
6772			y: this.obj_zoomedImageSize.y / (this.obj_zoomImageSize.y / this.int_zoomAreaMarkerTop)
6773		}
6774		this.el_zoomedImage.setStyles({
6775			'left': objOffsets.x * -1,
6776			'top': objOffsets.y * -1
6777		});
6778	},
6779	
6780	showZoomAreaMarker: function() {
6781		this.el_zoomAreaMarker.setStyle('display', 'block');
6782	},
6783	
6784	hideZoomAreaMarker: function() {
6785		this.el_zoomAreaMarker.setStyle('display', 'none');
6786	},
6787	
6788	getZoomImagePosition: function() {
6789		if (typeOf(this.__module.__parentModule.__models.options.data.func_beforePositionDetection) === 'function') {
6790			this.__module.__parentModule.__models.options.data.func_beforePositionDetection(this);
6791		}
6792		
6793		this.obj_zoomImagePosition = this.el_zoomImage.measure(function() {
6794			return this.getPosition();
6795		});
6796
6797		if (typeOf(this.__module.__parentModule.__models.options.data.func_afterPositionDetection) === 'function') {
6798			this.__module.__parentModule.__models.options.data.func_afterPositionDetection(this);
6799		}
6800	}
6801};
6802
6803lsjs.addViewClass(str_moduleName, obj_classdef);
6804
6805})();
6806// < ###### VIEW ##
6807// 
6808// ###### CONTROLLER ## >
6809(function() {
6810	
6811// ### ENTER MODULE NAME HERE ######
6812var str_moduleName = 'zoomInstance';
6813// #################################
6814
6815var obj_classdef = {
6816	start: function() {
6817	}
6818};
6819
6820lsjs.addControllerClass(str_moduleName, obj_classdef);
6821
6822})();
6823// < ###### CONTROLLER ##
6824// < #### MODULE ##
6825
6826
6827
6828
6829// #### MODULE ## >
6830// ###### TEMPLATES ## >
6831
6832// < ###### TEMPLATES ##
6833
6834// ###### MODELS ## >
6835(function() {
6836	
6837var obj_classdef_model = {
6838	name: 'options',
6839	
6840	data: {},
6841	
6842	start: function() {
6843		/*
6844		 * Initializing the options in the data object with default values which
6845		 * can later be overwritten when the "set" method is called with other options
6846		 */
6847		this.data = {
6848			el_domReference: null,
6849			selector: '.lsZoom a img',
6850			func_beforeInit: null,
6851			func_afterInit: null,
6852			func_beforePositionDetection: null,
6853			func_afterPositionDetection: null,
6854			bigBoxFitsThumb: false,
6855			bigBoxContainerSelector: '',
6856			bigBoxSize: {
6857				x: 400,
6858				y: 200
6859			},
6860			bigImageUrlAttribute: 'href', // defines which attribute holds the url of the big image, defaults to href
6861			useAnchorElement: true, // defines whether the bigImageUrlAttribute will be checked for the image itself or it's parent anchor element 
6862			side: 'right', // left, right, top or bottom
6863			offset: {
6864				x: 10,
6865				y: 10
6866			} // offset in px
6867		};
6868	},
6869	
6870	set: function(obj_options) {
6871		Object.merge(this.data, obj_options);
6872		this.__module.onModelLoaded();
6873	}
6874};
6875
6876lsjs.addModelClass('zoomManager', obj_classdef_model);
6877
6878})();
6879
6880
6881
6882
6883// < ###### MODELS ##
6884
6885// ###### VIEW ## >
6886
6887// < ###### VIEW ##
6888// 
6889// ###### CONTROLLER ## >
6890(function() {
6891	
6892// ### ENTER MODULE NAME HERE ######
6893var str_moduleName = 'zoomManager';
6894// #################################
6895
6896var obj_classdef = {
6897	start: function() {
6898		var els_toEnhance;
6899		
6900		if (this.__models.options.data.el_domReference !== undefined && typeOf(this.__models.options.data.el_domReference) === 'element') {
6901			els_toEnhance = this.__models.options.data.el_domReference.getElements(this.__models.options.data.selector);
6902		} else {
6903			els_toEnhance = $$(this.__models.options.data.selector);
6904		}
6905
6906		Array.each(els_toEnhance, function(el_zoomImage) {
6907			/* ->
6908			 * Make sure not to handle an element more than once
6909			 */
6910			if (!el_zoomImage.retrieve('alreadyHandledBy_' + str_moduleName)) {
6911				el_zoomImage.store('alreadyHandledBy_' + str_moduleName, true);
6912			} else {
6913				return;
6914			}
6915			/*
6916			 * <-
6917			 */
6918
6919			/*
6920			 * Try to verify that the element identified with the selector is an
6921			 * image and return if it isn't. We check for an existing src attribute
6922			 * because an image without a src attribute wouldn't be okay as well.
6923			 */
6924			var str_checkSrcProperty = el_zoomImage.getProperty('src');
6925			if (str_checkSrcProperty === undefined || str_checkSrcProperty === null || str_checkSrcProperty == '') {
6926				return;
6927			}
6928			
6929			var el_toDetermineHref =
6930				(
6931						this.__models.options.data.useAnchorElement != undefined
6932					&&	this.__models.options.data.useAnchorElement == true
6933				)
6934				?	el_zoomImage.getParent('a')
6935				:	el_zoomImage;
6936			if (el_toDetermineHref === undefined || el_toDetermineHref === null) {
6937				return;
6938			}
6939			
6940			var str_href = el_toDetermineHref.getProperty(this.__models.options.data.bigImageUrlAttribute);
6941			if (str_href === undefined || str_href === null || str_href === '' || !str_href) {
6942				return;
6943			}
6944
6945			var str_tmpImageSrc = el_zoomImage.getProperty('src');
6946			el_zoomImage.removeProperty('src');
6947			el_zoomImage.addEvent('load', function() {
6948				lsjs.createModule({
6949					__name: 'zoomInstance',
6950					__parentModule: this.__module,
6951					__useLoadingIndicator: false,
6952					el_zoomImage: el_zoomImage,
6953					str_href: str_href
6954				});
6955			}.bind(this));
6956			el_zoomImage.setProperty('src', str_tmpImageSrc);
6957
6958		}.bind(this));
6959	}
6960};
6961
6962lsjs.addControllerClass(str_moduleName, obj_classdef);
6963
6964lsjs.__moduleHelpers[str_moduleName] = {
6965	self: null,
6966	
6967	start: function(obj_options) {
6968		this.self = lsjs.createModule({
6969			__name: str_moduleName
6970		});
6971		this.self.__models.options.set(obj_options);
6972	}
6973};
6974
6975}
6975)();
6976// < ###### CONTROLLER ##
6977// < #### MODULE ##
6978
6979
6980// < ## CORE MODULES ##
6981
6982// ## APP MODULES ## >
6983
6984// < ## APP MODULES ##
6985
6986// ## APP ## >
6987
6988// < ## APP ##
6989})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.