<commit_before>/**
 * Backbone-Nested 1.1.0 - An extension of Backbone.js that keeps track of nested attributes
 *
 * http://afeld.github.com/backbone-nested/
 *
 * Copyright (c) 2011-2012 Aidan Feldman
 * MIT Licensed (LICENSE)
 */
/*global Backbone, _, $ */
(function(){
  'use strict';

  Backbone.NestedModel = Backbone.Model.extend({

    get: function(attrStrOrPath, opts){
      opts = opts || {};

      var attrPath = Backbone.NestedModel.attrPath(attrStrOrPath),
        childAttr = attrPath[0],
        result = Backbone.NestedModel.__super__.get.call(this, childAttr);

      // walk through the child attributes
      for (var i = 1; i < attrPath.length; i++){
        if (!result){
          // value not present
          break;
        }
        childAttr = attrPath[i];
        result = result[childAttr];
      }

      // check if the result is an Object, Array, etc.
      if (!opts.silent && _.isObject(result) && window.console){
        window.console.log("Backbone-Nested syntax is preferred for accesing values of attribute '" + attrStrOrPath + "'.");
      }
      // else it's a leaf

      return result;
    },

    has: function(attr){
      // for some reason this is not how Backbone.Model is implemented - it accesses the attributes object directly
      var result = this.get(attr, {silent: true});
      return !(result === null || _.isUndefined(result));
    },

    set: function(key, value, opts){
      // if it's an attribute path, convert back to string
      if (_.isArray(key)) {
        key = Backbone.NestedModel.createAttrStr(key);
      }

      var attrs;
      // check that it's an object, or null/undefined
      if (_.isObject(key) || key == null) {
        attrs = key;
        opts = value;
      } else {
        // Backbone 0.9.0+ syntax - `model.set(key, val)`
        attrs = {};
        attrs[key] = value;
      }
      opts = opts || {};

      var newAttrs = Backbone.NestedModel.deepClone(this.attributes),
        attrVal, attrPath, attrObj;

      for (var attrStr in attrs){
        attrPath = Backbone.NestedModel.attrPath(attrStr);
        attrObj = Backbone.NestedModel.createAttrObj(attrPath, attrs[attrStr]);

        this._mergeAttrs(newAttrs, attrObj, opts);
      }

      return Backbone.NestedModel.__super__.set.call(this, newAttrs, opts);
    },

    unset: function(attrStr, opts){
      opts = _.extend({}, opts, {unset: true});
      this.set(attrStr, null, opts);

      return this;
    },

    add: function(attrStr, value, opts){
      var current = this.get(attrStr, {silent: true});
      this.set(attrStr + '[' + current.length + ']', value, opts);
    },

    remove: function(attrStr, opts){
      opts = opts || {};

      var attrPath = Backbone.NestedModel.attrPath(attrStr),
        aryPath = _.initial(attrPath),
        val = this.get(aryPath, {silent: true}),
        i = _.last(attrPath);

      if (!_.isArray(val)){
        throw new Error("remove() must be called on a nested array");
      }

      // only trigger if an element is actually being removed
      var trigger = !opts.silent && (val.length > i + 1),
        oldEl = val[i];

      // remove the element from the array
      val.splice(i, 1);
      this.set(aryPath, val, opts);

      if (trigger){
        this.trigger('remove:' + Backbone.NestedModel.createAttrStr(aryPath), this, oldEl);
      }

      return this;
    },

    toJSON: function(){
      return Backbone.NestedModel.deepClone(this.attributes);
    },

    // private

    _mergeAttrs: function(dest, source, opts, stack){
      stack = stack || [];

      _.each(source, function(sourceVal, prop){
        if (prop === '-1'){
          prop = dest.length;
        }

        var destVal = dest[prop],
          newStack = stack.concat([prop]),
          attrStr;

        var isChildAry = _.isObject(sourceVal) && _.any(sourceVal, function(val, attr){
          return attr === '-1' || _.isNumber(attr);
        });

        if (isChildAry && !_.isArray(destVal)){
          destVal = dest[prop] = [];
        }

        if (prop in dest && _.isObject(sourceVal) && _.isObject(destVal)){
          destVal = dest[prop] = this._mergeAttrs(destVal, sourceVal, opts, newStack);
        } else {
          var oldVal = destVal;

          destVal = dest[prop] = sourceVal;

          if (_.isArray(dest) && !opts.silent){
            attrStr = Backbone.NestedModel.createAttrStr(stack);

            if (!oldVal && destVal){
              this.trigger('add:' + attrStr, this, destVal);
            } else if (oldVal && !destVal){
              this.trigger('remove:' + attrStr, this, oldVal);
            }
          }
        }

        // let the superclass handle change events for top-level attributes
        if (!opts.silent && newStack.length > 1){
          attrStr = Backbone.NestedModel.createAttrStr(newStack);
          this.trigger('change:' + attrStr, this, destVal);
        }
      }, this);

      return dest;
    }

  }, {
    // class methods

    attrPath: function(attrStrOrPath){
      var path;

      if (_.isString(attrStrOrPath)){
        // change all appends to '-1'
        attrStrOrPath = attrStrOrPath.replace(/\[\]/g, '[-1]');
        // TODO this parsing can probably be more efficient
        path = (attrStrOrPath === '') ? [''] : attrStrOrPath.match(/[^\.\[\]]+/g);
        path = _.map(path, function(val){
          // convert array accessors to numbers
          return val.match(/^\d+$/) ? parseInt(val, 10) : val;
        });
      } else {
        path = attrStrOrPath;
      }

      return path;
    },

    createAttrObj: function(attrStrOrPath, val){
      var attrPath = this.attrPath(attrStrOrPath),
        newVal;

      switch (attrPath.length){
        case 0:
          throw "no valid attributes: '" + attrStrOrPath + "'";

        case 1: // leaf
          newVal = val;
          break;

        default: // nested attributes
          var otherAttrs = _.rest(attrPath);
          newVal = this.createAttrObj(otherAttrs, val);
          break;
      }

      var childAttr = attrPath[0],
        result = _.isNumber(childAttr) ? [] : {};

      result[childAttr] = newVal;
      return result;
    },

    createAttrStr: function(attrPath){
      var attrStr = attrPath[0];
      _.each(_.rest(attrPath), function(attr){
        attrStr += _.isNumber(attr) ? ('[' + attr + ']') : ('.' + attr);
      });

      return attrStr;
    },

    deepClone: function(obj){
      return $.extend(true, {}, obj);
    }

  });

})();
<commit_msg>refactored set() to be more efficient<commit_after>/**
 * Backbone-Nested 1.1.0 - An extension of Backbone.js that keeps track of nested attributes
 *
 * http://afeld.github.com/backbone-nested/
 *
 * Copyright (c) 2011-2012 Aidan Feldman
 * MIT Licensed (LICENSE)
 */
/*global Backbone, _, $ */
(function(){
  'use strict';

  Backbone.NestedModel = Backbone.Model.extend({

    get: function(attrStrOrPath, opts){
      opts = opts || {};

      var attrPath = Backbone.NestedModel.attrPath(attrStrOrPath),
        childAttr = attrPath[0],
        result = Backbone.NestedModel.__super__.get.call(this, childAttr);

      // walk through the child attributes
      for (var i = 1; i < attrPath.length; i++){
        if (!result){
          // value not present
          break;
        }
        childAttr = attrPath[i];
        result = result[childAttr];
      }

      // check if the result is an Object, Array, etc.
      if (!opts.silent && _.isObject(result) && window.console){
        window.console.log("Backbone-Nested syntax is preferred for accesing values of attribute '" + attrStrOrPath + "'.");
      }
      // else it's a leaf

      return result;
    },

    has: function(attr){
      // for some reason this is not how Backbone.Model is implemented - it accesses the attributes object directly
      var result = this.get(attr, {silent: true});
      return !(result === null || _.isUndefined(result));
    },

    set: function(key, value, opts){
      var newAttrs = Backbone.NestedModel.deepClone(this.attributes);

      if (_.isString(key)) {
        // Backbone 0.9.0+ syntax: `model.set(key, val)` - convert the key to an attribute path
        key = Backbone.NestedModel.attrPath(key);
      }

      if (_.isArray(key)) {
        // attribute path
        this._mergeAttr(newAttrs, key, value, opts);
      } else { // it's an Object
        opts = value;
        var attrs = key,
          attrPath;

        for (var attrStr in attrs){
          attrPath = Backbone.NestedModel.attrPath(attrStr);
          this._mergeAttr(newAttrs, attrPath, attrs[attrStr], opts);
        }
      }

      return Backbone.NestedModel.__super__.set.call(this, newAttrs, opts);
    },

    unset: function(attrStr, opts){
      opts = _.extend({}, opts, {unset: true});
      this.set(attrStr, null, opts);

      return this;
    },

    add: function(attrStr, value, opts){
      var current = this.get(attrStr, {silent: true});
      this.set(attrStr + '[' + current.length + ']', value, opts);
    },

    remove: function(attrStr, opts){
      opts = opts || {};

      var attrPath = Backbone.NestedModel.attrPath(attrStr),
        aryPath = _.initial(attrPath),
        val = this.get(aryPath, {silent: true}),
        i = _.last(attrPath);

      if (!_.isArray(val)){
        throw new Error("remove() must be called on a nested array");
      }

      // only trigger if an element is actually being removed
      var trigger = !opts.silent && (val.length > i + 1),
        oldEl = val[i];

      // remove the element from the array
      val.splice(i, 1);
      this.set(aryPath, val, opts);

      if (trigger){
        this.trigger('remove:' + Backbone.NestedModel.createAttrStr(aryPath), this, oldEl);
      }

      return this;
    },

    toJSON: function(){
      return Backbone.NestedModel.deepClone(this.attributes);
    },

    // private

    // note: modifies `newAttrs`
    _mergeAttr: function(newAttrs, attrPath, value, opts){
      var attrObj = Backbone.NestedModel.createAttrObj(attrPath, value);
      this._mergeAttrs(newAttrs, attrObj, opts);
    },

    // note: modifies `dest`
    _mergeAttrs: function(dest, source, opts, stack){
      opts = opts || {};
      stack = stack || [];

      _.each(source, function(sourceVal, prop){
        if (prop === '-1'){
          prop = dest.length;
        }

        var destVal = dest[prop],
          newStack = stack.concat([prop]),
          attrStr;

        var isChildAry = _.isObject(sourceVal) && _.any(sourceVal, function(val, attr){
          return attr === '-1' || _.isNumber(attr);
        });

        if (isChildAry && !_.isArray(destVal)){
          destVal = dest[prop] = [];
        }

        if (prop in dest && _.isObject(sourceVal) && _.isObject(destVal)){
          destVal = dest[prop] = this._mergeAttrs(destVal, sourceVal, opts, newStack);
        } else {
          var oldVal = destVal;

          destVal = dest[prop] = sourceVal;

          if (_.isArray(dest) && !opts.silent){
            attrStr = Backbone.NestedModel.createAttrStr(stack);

            if (!oldVal && destVal){
              this.trigger('add:' + attrStr, this, destVal);
            } else if (oldVal && !destVal){
              this.trigger('remove:' + attrStr, this, oldVal);
            }
          }
        }

        // let the superclass handle change events for top-level attributes
        if (!opts.silent && newStack.length > 1){
          attrStr = Backbone.NestedModel.createAttrStr(newStack);
          this.trigger('change:' + attrStr, this, destVal);
        }
      }, this);

      return dest;
    }

  }, {
    // class methods

    attrPath: function(attrStrOrPath){
      var path;

      if (_.isString(attrStrOrPath)){
        // change all appends to '-1'
        attrStrOrPath = attrStrOrPath.replace(/\[\]/g, '[-1]');
        // TODO this parsing can probably be more efficient
        path = (attrStrOrPath === '') ? [''] : attrStrOrPath.match(/[^\.\[\]]+/g);
        path = _.map(path, function(val){
          // convert array accessors to numbers
          return val.match(/^\d+$/) ? parseInt(val, 10) : val;
        });
      } else {
        path = attrStrOrPath;
      }

      return path;
    },

    createAttrObj: function(attrStrOrPath, val){
      var attrPath = this.attrPath(attrStrOrPath),
        newVal;

      switch (attrPath.length){
        case 0:
          throw "no valid attributes: '" + attrStrOrPath + "'";

        case 1: // leaf
          newVal = val;
          break;

        default: // nested attributes
          var otherAttrs = _.rest(attrPath);
          newVal = this.createAttrObj(otherAttrs, val);
          break;
      }

      var childAttr = attrPath[0],
        result = _.isNumber(childAttr) ? [] : {};

      result[childAttr] = newVal;
      return result;
    },

    createAttrStr: function(attrPath){
      var attrStr = attrPath[0];
      _.each(_.rest(attrPath), function(attr){
        attrStr += _.isNumber(attr) ? ('[' + attr + ']') : ('.' + attr);
      });

      return attrStr;
    },

    deepClone: function(obj){
      return $.extend(true, {}, obj);
    }

  });

})();
<|endoftext|>


<commit_before>
module.exports = (function () {
    var state = false;
    var timer;
    var btSerial = new (require('bluetooth-serial-port')).BluetoothSerialPort();
    var btAddress;
    var btChannel;

    btSerial.on('found', function(address, name) {
        btSerial.findSerialPortChannel(address, function(channel) {
            btSerial.connect(address, channel, function() {
                console.log('blanket connected : ' + address + ' ' + channel);
                btAddress = address;
                btChannel = channel;
                power_off();
            }, function () {
                console.log('blanket cannot connect');
            });

            // close the connection when you're ready
            btSerial.close();
        }, function() {
            console.log('found nothing');
        });
    });

    btSerial.inquire();

    var power_on = function(hour) {
        if (btSerial.isOpen()) {
            state = true;
            btSerial.write(new Buffer('35', 'utf-8'), function (err, bytesWritten) {
                if (err) console.log(err);
            });
            timer = setTimeout(power_off, hour * 60 * 60 * 1000);
        } else {
            console.log("blanket connection error, re-connect");
            btSerial.connect(btAddress, btChannel, function() {
                console.log('blanket connected : ' + btAddress + ' ' + btChannel);
                power_on(hour);
            });
        }
    };

    var power_off = function() {
        if (btSerial.isOpen()) {
            state = false;
            btSerial.write(new Buffer('24', 'utf-8'), function (err, bytesWritten) {
                if (err) console.log(err);
            });
            clearTimeout(timer);
        } else {
            console.log("blanket connection error, re-connect");
            btSerial.connect(btAddress, btChannel, function() {
                console.log('blanket connected : ' + btAddress + ' ' + btChannel);
                power_off();
            });
        }
    };

    return {
        on: function (hour) {
            console.log("blanket on: timeout = " + hour + " hour");
            power_on(hour);
        },
        off: function () {
            console.log("blanket off");
            power_off();
        },
        getState: function () {
            return state;
        }
    };
}());<commit_msg>test bluetooth<commit_after>
module.exports = (function () {
    var state = false;
    var timer;
    var btSerial = new (require('bluetooth-serial-port')).BluetoothSerialPort();
    var btAddress;
    var btChannel;

    btSerial.on('found', function(address, name) {
        btSerial.findSerialPortChannel(address, function(channel) {
            btSerial.connect(address, channel, function() {
                console.log('blanket connected : ' + address + ' ' + channel);
                btAddress = address;
                btChannel = channel;
                power_off();

                btSerial.on('data', function (buffer) {
                    console.log(buffer.toString('utf-8'));
                });
            }, function () {
                console.log('blanket cannot connect');
            });

            // close the connection when you're ready
            btSerial.close();
        }, function() {
            console.log('found nothing');
        });
    });

    btSerial.inquire();

    var power_on = function(hour) {
        if (btSerial.isOpen()) {
            state = true;
            btSerial.write(new Buffer('35', 'utf-8'), function (err, bytesWritten) {
                if (err) console.log(err);
            });
            timer = setTimeout(power_off, hour * 60 * 60 * 1000);
        } else {
            console.log("blanket connection error, re-connect");
            btSerial.connect(btAddress, btChannel, function() {
                console.log('blanket connected : ' + btAddress + ' ' + btChannel);
                power_on(hour);
            });
            // close the connection when you're ready
            btSerial.close();
        }
    };

    var power_off = function() {
        if (btSerial.isOpen()) {
            state = false;
            btSerial.write(new Buffer('24', 'utf-8'), function (err, bytesWritten) {
                if (err) console.log(err);
            });
            clearTimeout(timer);
        } else {
            console.log("blanket connection error, re-connect");
            btSerial.connect(btAddress, btChannel, function() {
                console.log('blanket connected : ' + btAddress + ' ' + btChannel);
                power_off();
            });
            // close the connection when you're ready
            btSerial.close();
        }
    };

    return {
        on: function (hour) {
            console.log("blanket on: timeout = " + hour + " hour");
            power_on(hour);
        },
        off: function () {
            console.log("blanket off");
            power_off();
        },
        getState: function () {
            return state;
        }
    };
}());<|endoftext|>


<commit_before>/**
 * @author:		Angelo Dini
 * @copyright:	http://www.divio.ch under the BSD Licence
 * @requires:	Classy, jQuery, jQuery.cookie
 */

/*##################################################|*/
/* #CUSTOM APP# */
jQuery(document).ready(function ($) {
	/**
	 * Toolbar
	 * @version: 0.1.2
	 * @description: Implements and controls toolbar
	 * @public_methods:
	 *	- CMS.Toolbar.toggleToolbar();
	 *	- CMS.Toolbar.registerItem(obj);
	 *	- CMS.Toolbar.registerItems(array);
	 *	- CMS.Toolbar.removeItem(id);
	 *	- CMS.Toolbar.registerType(function);
	 */
	CMS.Toolbar = Class.$extend({

		implement: [CMS.Helpers, CMS.Security],

		options: {
			// not integrated yet
			debug: false,
			items: [],
			// type definitions used in registerItem()
			// TODO: needs proper implementation
			types: [
				{ 'anchor': '_registerAnchor' },
				{ 'html': '_reigsterHtml' },
				{ 'switcher': '_reigsterSwitcher' },
				{ 'button': '_reigsterButton' },
				{ 'list': '_reigsterList' }
			]
		},

		initialize: function (container, options) {
			// save reference to this class
			var that = this;
			// check if only one element is given
			if($(container).length > 2) { log('Toolbar Error: one element expected, multiple elements given.'); return false; }
			// merge passed argument options with internal options
			this.options = $.extend(this.options, options);

			// set initial variables
			this.wrapper = $(container);
			this.toolbar = this.wrapper.find('#cms_toolbar-toolbar');
			this.toolbar.left = this.toolbar.find('.cms_toolbar-left');
			this.toolbar.right = this.toolbar.find('.cms_toolbar-right');

			// bind event to toggle button so toolbar can be shown/hidden
			this.toggle = this.wrapper.find('#cms_toolbar-toggle');
			this.toggle.bind('click', function (e) {
				e.preventDefault();
				that.toggleToolbar();
			});

			// initial setups
			this._setup();
		},

		_setup: function () {
			// save reference to this class
			var that = this;

			// scheck if toolbar should be shown or hidden
			($.cookie('CMS_toolbar-collapsed') == 'false') ? this.toolbar.data('collapsed', true) : this.toolbar.data('collapsed', false);
			// follow up script to set the current state
			this.toggleToolbar();

			// set toolbar to visible
			this.wrapper.show();
			// some browsers have problem showing it directly (loading css...)
			setTimeout(function () { that.wrapper.show(); }, 50);

			// start register items if any given
			if(this.options.items.length) this.registerItems(this.options.items);

			// apply csrf patch to toolbar from cms.base.js
			this.csrf();
		},

		toggleToolbar: function () {
			(this.toolbar.data('collapsed')) ? this._showToolbar() : this._hideToolbar();

			return this;
		},

		_showToolbar: function () {
			// add toolbar padding
			var padding = parseInt($(document.body).css('margin-top'));
				$(document.body).css('margin-top', (padding+43)); // 43 = height of toolbar
			// show toolbar
			this.toolbar.show();
			// change data information
			this.toolbar.data('collapsed', false);
			// add class to trigger
			this.toggle.addClass('cms_toolbar-collapsed');
			// save as cookie
			$.cookie('CMS_toolbar-collapsed', false, { path:'/', expires:7 });
			// add show event to toolbar
			this.toolbar.trigger('cms.toolbar.show');
		},

		_hideToolbar: function () {
			// remove toolbar padding
			var padding = parseInt($(document.body).css('margin-top'));
				$(document.body).css('margin-top', (padding-this.toolbar.height()-1)); // substract 1 cause of the border
			// hide toolbar
			this.toolbar.hide();
			// change data information
			this.toolbar.data('collapsed', true);
			// remove class from trigger
			this.toggle.removeClass('cms_toolbar-collapsed');
			// save as cookie
			$.cookie('CMS_toolbar-collapsed', true, { path:'/', expires:7 });
			// add hide event to toolbar
			this.toolbar.trigger('cms.toolbar.hide');
		},

		registerItem: function (obj) {
			// error handling
			if(!obj.order) obj.dir = 0;

			// check for internal types
			switch(obj.type) {
				case 'anchor':
					this._registerAnchor(obj);
					break;
				case 'html':
					this._registerHtml(obj);
					break;
				case 'switcher':
					this._registerSwitcher(obj);
					break;
				case 'button':
					this._registerButton(obj);
					break;
				case 'list':
					this._registerList(obj);
					break;
				default:
					this.registerType(obj);
			}

			return obj;
		},

		registerItems: function (items) {
			// make sure an array is passed
			if(typeof(items) != 'object') return false;
			// save reference to this class
			var that = this;
			// loopp through all items and pass them to single function
			$(items).each(function (index, value) {
				that.registerItem(value);
			});

			return items;
		},

		removeItem: function (index) {
			// function to remove an item
			if(index) $($('.cms_toolbar-item:visible')[index]).remove();

			return index;
		},

		_registerAnchor: function (obj) {
			// take a copy of the template, append it, remove it, copy html... because jquery is stupid
			var template = this._processTemplate('#cms_toolbar-item_anchor', obj);
			this._injectItem(template, obj.dir, obj.order);
		},

		_registerHtml: function (obj) {
			// here we dont need processTemplate cause we create the template
			var template = (obj.html) ? $(obj.html) : $(obj.htmlElement);
			// add order, show item
			template.data('order', obj.order).css('display', 'block');
			// add class if neccessary
			if(obj.cls) template.addClass(obj.cls);
			// add events
			template.find('.cms_toolbar-btn').bind('click', function (e) {
				e.preventDefault();
				(obj.redirect) ? document.location = obj.redirect : $(this).parentsUntil('form').parent().submit();
			});
			// append item
			this._injectItem(template, obj.dir, obj.order);
		},

		_registerSwitcher: function (obj) {
			// save reference to this class
			var that = this;
			// take a copy of the template, append it, remove it, copy html... because jquery is stupid
			var template = this._processTemplate('#cms_toolbar-item_switcher', obj);
			// should btn be shown?
			var btn = template.find('.cms_toolbar-item_switcher-link span');

			// initial setup
			if(obj.state) {
				btn.data('state', true).css('backgroundPosition', '0px -198px');
			} else {
				btn.data('state', false).css('backgroundPosition', '-40px -198px');
			}

			// add events
			template.find('.cms_toolbar-item_switcher-link').bind('click', function (e) {
				e.preventDefault();

				// animate toggle effect and trigger handler
				if(btn.data('state')) {
					btn.stop().animate({'backgroundPosition': '-40px -198px'}, function () {
						// disable link
						var url = that.removeUrl(document.location.href, obj.addParameter);
						document.location = that.insertUrl(url, obj.removeParameter, "")
					});
				} else {
					btn.stop().animate({'backgroundPosition': '0px -198px'}, function () {
						// enable link
						document.location = that.insertUrl(location.href, obj.addParameter, "");
					});
				}
			});
			// append item
			this._injectItem(template, obj.dir, obj.order);
		},

		_registerButton: function (obj) {
			// take a copy of the template, append it, remove it, copy html... because jquery is stupid
			var template = this._processTemplate('#cms_toolbar-item_button', obj);
			// append item
			this._injectItem(template, obj.dir, obj.order);
		},

		_registerList: function (obj) {
			// take a copy of the template, append it, remove it, copy html... because jquery is stupid
			var template = this._processTemplate('#cms_toolbar-item_list', obj);

			// item injection logic
			var list = template.find('.cms_toolbar-item_list').html();
			var tmp = '';
			// lets loop through the items
			$(obj.items).each(function (index, value) {
				// add icon if available
				// TODO: backend needs to return '' instead of '/media/None'
				var icon = (value.icon !== '/media/None') ? 'cms_toolbar_icon cms_toolbar_icon-enabled ' : '';
				// replace attributes
				tmp += list.replace('[list_title]', value.title)
						   .replace('[list_url]', value.url)
						   .replace('<span>', '<span class="'+icon+'" style="background-image:url('+value.icon+');">');
			});
			// add items
			template.find('.cms_toolbar-item_list').html($(tmp));

			// add events
			var container = template.find('.cms_toolbar-item_list');
			var btn = template.find('.cms_toolbar-btn');
				btn.data('collapsed', true)
				   .bind('click', function (e) {
					e.preventDefault();
					($(this).data('collapsed')) ? show_list() : hide_list();
			});

			function show_list() {
				// add event to body to hide the list needs a timout for late trigger
				setTimeout(function () {
					$(document).bind('click', hide_list);
				}, 100);

				// show element and save data
				container.show();
				btn.addClass('cms_toolbar-btn-active').data('collapsed', false);
			}
			function hide_list() {
				// remove the body event
				$(document).unbind('click');

				// show element and save data
				container.hide();
				btn.removeClass('cms_toolbar-btn-active').data('collapsed', true);
			}

			// append item
			this._injectItem(template, obj.dir, obj.order);
		},

		registerType: function (handler) {
			// invoke function
			if(typeof(handler) == 'function') handler();

			return handler;
		},

		/* this private method processes each template and replaces the placeholders with the passed values */
		_processTemplate: function (cls, obj) {
			// lets find the template and clone it
			var template = this.wrapper.find(cls).clone();
				template = $('<div>').append(template).clone().remove().html();
			// replace placeholders
			if(obj.title) template = template.replace('[title]', obj.title);
			if(obj.url) template = template.replace('[url]', obj.url);
			if(!obj.icon && obj.type == 'button') template = template.replace(' ', '').replace(' ', '');
			// template = template.replace('[token]', this.options.csrf_token);
			template = (obj.action) ? template.replace('[action]', obj.action) : template.replace('[action]', '');
			template = (obj.hidden) ? template.replace('[hidden]', obj.hidden) : template.replace('[hidden]', '');
			// back to jquery object
			template = $(template);
			if(obj.cls) template.addClass(obj.cls);
			// TODO: backend should return '' or undefined instead of /media/
			if(obj.icon && obj.icon !== '/media/') {
				template.find('.cms_toolbar-btn_right .toolbar_icon-prefix')
						.addClass('cms_toolbar_icon-enabled')
						.css('background-image', 'url('+obj.icon+')');
			}
			// add events
			template.find('.cms_toolbar-btn').bind('click', function (e) {
				e.preventDefault();
				(obj.redirect) ? document.location = obj.redirect : $(this).parentsUntil('form').parent().submit();
			});
			// save order remove id and show element
			template.data('order', obj.order)
					.attr('id', '') /* remove initial id */
					.css('display', 'block');

			return template;
		},

		_injectItem: function (el, dir, order) {
			// save some vars
			var left = this.toolbar.left;
			var right = this.toolbar.right;

			if(dir == 'left') {
				var leftContent = left.find('> *');
					if(!leftContent.length) { left.append(el); return false; }

				// first insert it at start position
				el.insertBefore($(leftContent[0]));

				// and what happens if there is already an element?
				leftContent.each(function (index, item) {
					// sava data from element
					var current = $(item).data('order');
					// inject data when current is lower, repeat till element fits position
					if(order >= current || order == current) el.insertAfter($(item));
				});
			}

			if(dir == 'right') {
				var rightContent = right.find('> *');
					if(!rightContent.length) { right.append(el); return false; }

				// first insert it at start position
				el.insertBefore($(rightContent[0]));

				rightContent.each(function (index, item) {
					// save data from element
					var current = $(item).data('order');
					// inject data when current is lower, repeat till element fits position
					if(order >= current || order == current) el.insertAfter($(item));
				});
			}
		}

	});
});
<commit_msg>added specific resize fixes for ie6 sucker<commit_after>/**
 * @author:		Angelo Dini
 * @copyright:	http://www.divio.ch under the BSD Licence
 * @requires:	Classy, jQuery, jQuery.cookie
 */

/*##################################################|*/
/* #CUSTOM APP# */
jQuery(document).ready(function ($) {
	/**
	 * Toolbar
	 * @version: 0.1.2
	 * @description: Implements and controls toolbar
	 * @public_methods:
	 *	- CMS.Toolbar.toggleToolbar();
	 *	- CMS.Toolbar.registerItem(obj);
	 *	- CMS.Toolbar.registerItems(array);
	 *	- CMS.Toolbar.removeItem(id);
	 *	- CMS.Toolbar.registerType(function);
	 * TODO: login needs special treatment (errors, login on enter)
	 */
	CMS.Toolbar = Class.$extend({

		implement: [CMS.Helpers, CMS.Security],

		options: {
			'debug': false, // not integrated yet
			'items': []
		},

		initialize: function (container, options) {
			// save reference to this class
			var that = this;
			// check if only one element is given
			if($(container).length > 2) { log('Toolbar Error: one element expected, multiple elements given.'); return false; }
			// merge passed argument options with internal options
			this.options = $.extend(this.options, options);

			// set initial variables
			this.wrapper = $(container);
			this.toolbar = this.wrapper.find('#cms_toolbar-toolbar');
			this.toolbar.left = this.toolbar.find('.cms_toolbar-left');
			this.toolbar.right = this.toolbar.find('.cms_toolbar-right');

			// bind event to toggle button so toolbar can be shown/hidden
			this.toggle = this.wrapper.find('#cms_toolbar-toggle');
			this.toggle.bind('click', function (e) {
				e.preventDefault();
				that.toggleToolbar();
			});

			// initial setups
			this._setup();
		},

		_setup: function () {
			// save reference to this class
			var that = this;

			// scheck if toolbar should be shown or hidden
			($.cookie('CMS_toolbar-collapsed') == 'false') ? this.toolbar.data('collapsed', true) : this.toolbar.data('collapsed', false);
			// follow up script to set the current state
			this.toggleToolbar();

			// set toolbar to visible
			this.wrapper.show();
			// some browsers have problem showing it directly (loading css...)
			setTimeout(function () { that.wrapper.show(); }, 50);

			// start register items if any given
			if(this.options.items.length) this.registerItems(this.options.items);

			// apply csrf patch to toolbar from cms.base.js
			this.csrf();

			// the toolbar needs to resize depending on the window size on motherfucking ie6
			if($.browser.msie && $.browser.version <= '6.0') {
				$(window).bind('resize', function () { that.wrapper.css('width', $(window).width()); })
				$(window).trigger('resize');
			}
		},

		toggleToolbar: function () {
			(this.toolbar.data('collapsed')) ? this._showToolbar() : this._hideToolbar();

			return this;
		},

		_showToolbar: function () {
			// add toolbar padding
			var padding = parseInt($(document.body).css('margin-top'));
				$(document.body).css('margin-top', (padding+43)); // 43 = height of toolbar
			// show toolbar
			this.toolbar.show();
			// change data information
			this.toolbar.data('collapsed', false);
			// add class to trigger
			this.toggle.addClass('cms_toolbar-collapsed');
			// save as cookie
			$.cookie('CMS_toolbar-collapsed', false, { path:'/', expires:7 });
			// add show event to toolbar
			this.toolbar.trigger('cms.toolbar.show');
		},

		_hideToolbar: function () {
			// remove toolbar padding
			var padding = parseInt($(document.body).css('margin-top'));
				$(document.body).css('margin-top', (padding-this.toolbar.height()-1)); // substract 1 cause of the border
			// hide toolbar
			this.toolbar.hide();
			// change data information
			this.toolbar.data('collapsed', true);
			// remove class from trigger
			this.toggle.removeClass('cms_toolbar-collapsed');
			// save as cookie
			$.cookie('CMS_toolbar-collapsed', true, { path:'/', expires:7 });
			// add hide event to toolbar
			this.toolbar.trigger('cms.toolbar.hide');
		},

		registerItem: function (obj) {
			// error handling
			if(!obj.order) obj.dir = 0;

			// check for internal types
			switch(obj.type) {
				case 'anchor':
					this._registerAnchor(obj);
					break;
				case 'html':
					this._registerHtml(obj);
					break;
				case 'switcher':
					this._registerSwitcher(obj);
					break;
				case 'button':
					this._registerButton(obj);
					break;
				case 'list':
					this._registerList(obj);
					break;
				default:
					this.registerType(obj);
			}

			return obj;
		},

		registerItems: function (items) {
			// make sure an array is passed
			if(typeof(items) != 'object') return false;
			// save reference to this class
			var that = this;
			// loopp through all items and pass them to single function
			$(items).each(function (index, value) {
				that.registerItem(value);
			});

			return items;
		},

		removeItem: function (index) {
			// function to remove an item
			if(index) $($('.cms_toolbar-item:visible')[index]).remove();

			return index;
		},

		_registerAnchor: function (obj) {
			// take a copy of the template, append it, remove it, copy html... because jquery is stupid
			var template = this._processTemplate('#cms_toolbar-item_anchor', obj);
			this._injectItem(template, obj.dir, obj.order);
		},

		_registerHtml: function (obj) {
			// here we dont need processTemplate cause we create the template
			var template = (obj.html) ? $(obj.html) : $(obj.htmlElement);
			// add order, show item
			template.data('order', obj.order).css('display', 'block');
			// add class if neccessary
			if(obj.cls) template.addClass(obj.cls);
			// add events
			template.find('.cms_toolbar-btn').bind('click', function (e) {
				e.preventDefault();
				(obj.redirect) ? document.location = obj.redirect : $(this).parentsUntil('form').parent().submit();
			});
			// append item
			this._injectItem(template, obj.dir, obj.order);
		},

		_registerSwitcher: function (obj) {
			// save reference to this class
			var that = this;
			// take a copy of the template, append it, remove it, copy html... because jquery is stupid
			var template = this._processTemplate('#cms_toolbar-item_switcher', obj);
			// should btn be shown?
			var btn = template.find('.cms_toolbar-item_switcher-link span');

			// initial setup
			if(obj.state) {
				btn.data('state', true).css('backgroundPosition', '0px -198px');
			} else {
				btn.data('state', false).css('backgroundPosition', '-40px -198px');
			}

			// add events
			template.find('.cms_toolbar-item_switcher-link').bind('click', function (e) {
				e.preventDefault();

				// animate toggle effect and trigger handler
				if(btn.data('state')) {
					btn.stop().animate({'backgroundPosition': '-40px -198px'}, function () {
						// disable link
						var url = that.removeUrl(document.location.href, obj.addParameter);
						document.location = that.insertUrl(url, obj.removeParameter, "")
					});
				} else {
					btn.stop().animate({'backgroundPosition': '0px -198px'}, function () {
						// enable link
						document.location = that.insertUrl(location.href, obj.addParameter, "");
					});
				}
			});
			// append item
			this._injectItem(template, obj.dir, obj.order);
		},

		_registerButton: function (obj) {
			// take a copy of the template, append it, remove it, copy html... because jquery is stupid
			var template = this._processTemplate('#cms_toolbar-item_button', obj);
			// append item
			this._injectItem(template, obj.dir, obj.order);
		},

		_registerList: function (obj) {
			// take a copy of the template, append it, remove it, copy html... because jquery is stupid
			var template = this._processTemplate('#cms_toolbar-item_list', obj);

			// item injection logic
			var list = template.find('.cms_toolbar-item_list').html();
			var tmp = '';
			// lets loop through the items
			$(obj.items).each(function (index, value) {
				// add icon if available
				// TODO: backend needs to return '' instead of '/media/None'
				var icon = (value.icon !== '/media/None') ? 'cms_toolbar_icon cms_toolbar_icon-enabled ' : '';
				// replace attributes
				tmp += list.replace('[list_title]', value.title)
						   .replace('[list_url]', value.url)
						   .replace('<span>', '<span class="'+icon+'" style="background-image:url('+value.icon+');">');
			});
			// add items
			template.find('.cms_toolbar-item_list').html($(tmp));

			// add events
			var container = template.find('.cms_toolbar-item_list');
			var btn = template.find('.cms_toolbar-btn');
				btn.data('collapsed', true)
				   .bind('click', function (e) {
					e.preventDefault();
					($(this).data('collapsed')) ? show_list() : hide_list();
			});

			function show_list() {
				// add event to body to hide the list needs a timout for late trigger
				setTimeout(function () {
					$(document).bind('click', hide_list);
				}, 100);

				// show element and save data
				container.show();
				btn.addClass('cms_toolbar-btn-active').data('collapsed', false);
			}
			function hide_list() {
				// remove the body event
				$(document).unbind('click');

				// show element and save data
				container.hide();
				btn.removeClass('cms_toolbar-btn-active').data('collapsed', true);
			}

			// append item
			this._injectItem(template, obj.dir, obj.order);
		},

		registerType: function (handler) {
			// invoke function
			if(typeof(handler) == 'function') handler();

			return handler;
		},

		/* this private method processes each template and replaces the placeholders with the passed values */
		_processTemplate: function (cls, obj) {
			// lets find the template and clone it
			var template = this.wrapper.find(cls).clone();
				template = $('<div>').append(template).clone().remove().html();
			// replace placeholders
			if(obj.title) template = template.replace('[title]', obj.title);
			if(obj.url) template = template.replace('[url]', obj.url);
			if(!obj.icon && obj.type == 'button') template = template.replace(' ', '').replace(' ', '');
			// template = template.replace('[token]', this.options.csrf_token);
			template = (obj.action) ? template.replace('[action]', obj.action) : template.replace('[action]', '');
			template = (obj.hidden) ? template.replace('[hidden]', obj.hidden) : template.replace('[hidden]', '');
			// back to jquery object
			template = $(template);
			if(obj.cls) template.addClass(obj.cls);
			// TODO: backend should return '' or undefined instead of /media/
			if(obj.icon && obj.icon !== '/media/') {
				template.find('.cms_toolbar-btn_right .toolbar_icon-prefix')
						.addClass('cms_toolbar_icon-enabled')
						.css('background-image', 'url('+obj.icon+')');
			}
			// add events
			template.find('.cms_toolbar-btn').bind('click', function (e) {
				e.preventDefault();
				(obj.redirect) ? document.location = obj.redirect : $(this).parentsUntil('form').parent().submit();
			});
			// save order remove id and show element
			template.data('order', obj.order)
					.attr('id', '') /* remove initial id */
					.css('display', 'block');

			return template;
		},

		_injectItem: function (el, dir, order) {
			// save some vars
			var left = this.toolbar.left;
			var right = this.toolbar.right;

			if(dir == 'left') {
				var leftContent = left.find('> *');
					if(!leftContent.length) { left.append(el); return false; }

				// first insert it at start position
				el.insertBefore($(leftContent[0]));

				// and what happens if there is already an element?
				leftContent.each(function (index, item) {
					// sava data from element
					var current = $(item).data('order');
					// inject data when current is lower, repeat till element fits position
					if(order >= current || order == current) el.insertAfter($(item));
				});
			}

			if(dir == 'right') {
				var rightContent = right.find('> *');
					if(!rightContent.length) { right.append(el); return false; }

				// first insert it at start position
				el.insertBefore($(rightContent[0]));

				rightContent.each(function (index, item) {
					// save data from element
					var current = $(item).data('order');
					// inject data when current is lower, repeat till element fits position
					if(order >= current || order == current) el.insertAfter($(item));
				});
			}
		}

	});
});
<|endoftext|>


<commit_before>/**
 * Frontend logic for Geierlein HTML5 interface.
 *
 * @author Stefan Siegl
 *
 * Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

(function($, geierlein) {
    var $jahr = $('#jahr');
    var $monat = $('#monat');

    var datenlieferant, ustva;

    /*
     * private functions
     */

    /**
     * Print iframe by element ID.
     *
     * Requires printPage javascript function on the document loaded into
     * the iframe.  File ustva.xsl is extended accordingly.
     *
     * http://stackoverflow.com/questions/472951/how-do-i-print-an-iframe-from-javascript-in-safari-chrome
     *
     * @param id The iframe element's ID.
     */
    function printIframe(id) {
        var iframe = document.frames ? document.frames[id] : document.getElementById(id);
        var ifWin = iframe.contentWindow || iframe;
        ifWin.focus();
        ifWin.printPage();
        return false;
    }

    /**
     * Show the modal protocol dialog and display the provided protocol.
     *
     * A reference to the XSL file needed to display the protocol is
     * added automatically.
     *
     * @param res The XML result document as a string.
     * @return void
     */
    function showProtocol(res) {
        /* Add XSL reference to XML document. */
        var xslUrl = location.href.replace(/[^\/]+$/, 'xsl/ustva.xsl');
        res = geierlein.util.addStylesheetHref(res, xslUrl);

        $('#protocol-frame')[0].src = 'data:text/xml;charset=ISO8859-1,' + escape(res);
        $('#protocol').modal();
    }

    function updateModelHandler(el, model) {
        var kz = el.id.toLowerCase();
        var $cg = $(el).parents('.control-group');
        var value = el.value;

        if(el.type === 'checkbox' && !el.checked) {
            value = '';
        }

        if(value === '') {
            delete model[kz];
        } else {
            model[kz] = value.replace(',', '.');
        }

        if(model.validate(kz) === true) {
            $cg.removeClass('error');
        } else {
            $cg.addClass('error');
        }
    }

    /*
     * Public API
     */

    /**
     * Submit the tax declaration and display protocol afterwards.
     *
     * @param asTestcase Whether to set test-marker in the declaration or not.
     * @return void
     */
    geierlein.sendData = function(asTestcase) {
        if(ustva.validate() !== true) {
            alert('Das Formular enthält noch ungültige Feldwerte, ' +
                'Übertragung nicht möglich.');
            return;
        }

        if(!asTestcase && !confirm('Die Daten werden als Echtfall an die Finanzverwaltung übergeben\n' +
            'Bist du sicher?')) {
            return;
        }

        ustva.toEncryptedXml(asTestcase, function(data, cb) {
            $('#wait').modal();
            geierlein.transfer(data, cb);
        }, function(res) {
            $('#wait').modal('hide');
            if(res === false) {
                alert('Bei der Datenübertragung ist ein Fehler aufgetreten.');
                return;
            }
            showProtocol(res);
        });
    };

    geierlein.isDatenlieferantValid = function() {
        return datenlieferant.validate() === true;
    };

    geierlein.resetForm = function() {
        $('form')[0].reset();

        /* Pre-select previous month and year. */
        var d = new Date();
        d.setMonth(d.getMonth() - 1);
        $jahr.val(d.getFullYear());
        $monat.val(d.getMonth() + 1);

        /* Copy over all field data initially to consider browser's
         * auto-fill data, etc.pp
         */
        $('.datenlieferant, .ustva').change();
    };

    geierlein.serialize = function() {
        var res = '#\n# Adresse des Steuerpflichtigen\n#\n';
        $('.datenlieferant').each(function(i, el) {
            res += el.id + ' = ' + el.value + '\n';
        });

        res += '\n';
        $('.ustva').each(function(i, el) {
            var field = el.id;
            var help = $(el).data('original-title');

            if(field.substr(0, 2) === 'Kz') {
                field = 'k' + field.substr(1);
            }

            if(help !== '') {
                help = help.replace(/<br\/>Kennzahl im Formular: \d+/, '');
                res += '# ' + help + '\n';
            }

            if(el.type === 'checkbox' && !el.checked) {
                res += '# '; /* Comment out next line */
            }
            res += field + ' = ' + el.value + '\n\n';
        });

        return res;
    };

    /**
     * Unserialize data from "geierlein file format" into the form.
     *
     * @param data The file's content as a string.
     * @return <boolean> True if file was loaded successfully, false otherwise.
     */
    geierlein.unserialize = function(data) {
        try {
            data = geierlein.util.parseFile(data);
        } catch(e) {
            alert('Das Format der Datei ist fehlerhaft.  ' +
                'Die Datei kann nicht geöffnet werden');
            return false;
        }

        geierlein.resetForm();
        for(var key in data) {
            if(data.hasOwnProperty(key)) {
                /* The IDs of the input elements in the form start with
                 * an upper-case K.
                 */
                var newValue = data[key];

                if(key.substr(0, 2) === 'kz') {
                    key = 'K' + key.substr(1);
                }

                var $el = $('#' + key);
                if($el.length) {
                    $el.val(newValue).change();
                }
            }
        }

        return true;
    };

    /*
     * Model handling.
     */

    (function() {
        /* Fill year drop-down, supported is 2011 until now. */
        var d = new Date();
        for(var year = d.getFullYear(); year >= 2011; year --) {
            $('<option>').text(year).appendTo($jahr);
        }
    })();

    datenlieferant = new geierlein.Datenlieferant();
    ustva = new geierlein.UStVA(datenlieferant);

    /* Bind datenlieferant input fields to the model.  We bind change as
       well as keyup, so we are able to revalidate the form on every keystroke
       and still don't miss events in case our user copy & pastes the data. */
    $('.datenlieferant').on('change keyup', function(ev) {
        return updateModelHandler(this, datenlieferant);
    });

    /* Bind UStVA form fields to the corresponding model.  Like as for the
       datenlieferant fields we bind to both, change and keyup, events. */
    $('.ustva').on('change keyup', function(ev) {
        return updateModelHandler(this, ustva);
    });

    $('#land').on('change keyup', function(ev) {
        $('#steuernummer').attr('placeholder', ustva.getTaxNumberSample());
    });

    geierlein.resetForm();

    /*
     * Further event listeners needed to make the GUI work.
     */

    /**
     * Show/hide rarely used elements, when fast entry checkbox is toggled.
     */
    $('#schnell').on('click', function(ev) {
        if($('#schnell').prop('checked')) {
            $('.schnell').hide('slow');
        } else {
            $('.schnell').show('slow');
        }
    });

    /**
     * Send button, click event.
     */
    $('#send').on('click', function(ev) {
        geierlein.sendData(false);
        return false;
    });

    /**
     * Send-as-testcase button, click event.
     */
    $('#send-testcase').on('click', function(ev) {
        geierlein.sendData(true);
        return false;
    });

    /**
     * Initialize tooltips on all input elements.
     */
    $('.ustva').tooltip();

    /**
     * Trigger browser's print functionality when print-button is clicked in
     * protocol dialog.
     */
    $('#protocol-print').click(function() {
        printIframe('protocol-frame');
    });
}(jQuery, geierlein));
<commit_msg>Check error code in returned XML document<commit_after>/**
 * Frontend logic for Geierlein HTML5 interface.
 *
 * @author Stefan Siegl
 *
 * Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

(function($, geierlein) {
    var $jahr = $('#jahr');
    var $monat = $('#monat');

    var datenlieferant, ustva;

    /*
     * private functions
     */

    /**
     * Print iframe by element ID.
     *
     * Requires printPage javascript function on the document loaded into
     * the iframe.  File ustva.xsl is extended accordingly.
     *
     * http://stackoverflow.com/questions/472951/how-do-i-print-an-iframe-from-javascript-in-safari-chrome
     *
     * @param id The iframe element's ID.
     */
    function printIframe(id) {
        var iframe = document.frames ? document.frames[id] : document.getElementById(id);
        var ifWin = iframe.contentWindow || iframe;
        ifWin.focus();
        ifWin.printPage();
        return false;
    }

    /**
     * Show the modal protocol dialog and display the provided protocol.
     *
     * A reference to the XSL file needed to display the protocol is
     * added automatically.
     *
     * @param res The XML result document as a string.
     * @return void
     */
    function showProtocol(res) {
        /* Add XSL reference to XML document. */
        var xslUrl = location.href.replace(/[^\/]+$/, 'xsl/ustva.xsl');
        res = geierlein.util.addStylesheetHref(res, xslUrl);

        $('#protocol-frame')[0].src = 'data:text/xml;charset=ISO8859-1,' + escape(res);
        $('#protocol').modal();
    }

    function updateModelHandler(el, model) {
        var kz = el.id.toLowerCase();
        var $cg = $(el).parents('.control-group');
        var value = el.value;

        if(el.type === 'checkbox' && !el.checked) {
            value = '';
        }

        if(value === '') {
            delete model[kz];
        } else {
            model[kz] = value.replace(',', '.');
        }

        if(model.validate(kz) === true) {
            $cg.removeClass('error');
        } else {
            $cg.addClass('error');
        }
    }

    /*
     * Public API
     */

    /**
     * Submit the tax declaration and display protocol afterwards.
     *
     * @param asTestcase Whether to set test-marker in the declaration or not.
     * @return void
     */
    geierlein.sendData = function(asTestcase) {
        if(ustva.validate() !== true) {
            alert('Das Formular enthält noch ungültige Feldwerte, ' +
                'Übertragung nicht möglich.');
            return;
        }

        if(!asTestcase && !confirm('Die Daten werden als Echtfall an die Finanzverwaltung übergeben\n' +
            'Bist du sicher?')) {
            return;
        }

        ustva.toEncryptedXml(asTestcase, function(data, cb) {
            $('#wait').modal();
            geierlein.transfer(data, cb);
        }, function(res) {
            $('#wait').modal('hide');
            if(res === false) {
                alert('Bei der Datenübertragung ist ein Fehler aufgetreten.');
                return;
            }

            var status = res.match(/<Code>(.*?)<\/Code>\s*<Text>(.*?)<\/Text>/);
            if(+status[1] === 0) {
                showProtocol(res);
            } else {
                alert('Die Datenübertragung wurde vom Server abgebrochen:\n' +
                    status[2] + '\nFehlercode: ' + status[1]);
            }
        });
    };

    geierlein.isDatenlieferantValid = function() {
        return datenlieferant.validate() === true;
    };

    geierlein.resetForm = function() {
        $('form')[0].reset();

        /* Pre-select previous month and year. */
        var d = new Date();
        d.setMonth(d.getMonth() - 1);
        $jahr.val(d.getFullYear());
        $monat.val(d.getMonth() + 1);

        /* Copy over all field data initially to consider browser's
         * auto-fill data, etc.pp
         */
        $('.datenlieferant, .ustva').change();
    };

    geierlein.serialize = function() {
        var res = '#\n# Adresse des Steuerpflichtigen\n#\n';
        $('.datenlieferant').each(function(i, el) {
            res += el.id + ' = ' + el.value + '\n';
        });

        res += '\n';
        $('.ustva').each(function(i, el) {
            var field = el.id;
            var help = $(el).data('original-title');

            if(field.substr(0, 2) === 'Kz') {
                field = 'k' + field.substr(1);
            }

            if(help !== '') {
                help = help.replace(/<br\/>Kennzahl im Formular: \d+/, '');
                res += '# ' + help + '\n';
            }

            if(el.type === 'checkbox' && !el.checked) {
                res += '# '; /* Comment out next line */
            }
            res += field + ' = ' + el.value + '\n\n';
        });

        return res;
    };

    /**
     * Unserialize data from "geierlein file format" into the form.
     *
     * @param data The file's content as a string.
     * @return <boolean> True if file was loaded successfully, false otherwise.
     */
    geierlein.unserialize = function(data) {
        try {
            data = geierlein.util.parseFile(data);
        } catch(e) {
            alert('Das Format der Datei ist fehlerhaft.  ' +
                'Die Datei kann nicht geöffnet werden');
            return false;
        }

        geierlein.resetForm();
        for(var key in data) {
            if(data.hasOwnProperty(key)) {
                /* The IDs of the input elements in the form start with
                 * an upper-case K.
                 */
                var newValue = data[key];

                if(key.substr(0, 2) === 'kz') {
                    key = 'K' + key.substr(1);
                }

                var $el = $('#' + key);
                if($el.length) {
                    $el.val(newValue).change();
                }
            }
        }

        return true;
    };

    /*
     * Model handling.
     */

    (function() {
        /* Fill year drop-down, supported is 2011 until now. */
        var d = new Date();
        for(var year = d.getFullYear(); year >= 2011; year --) {
            $('<option>').text(year).appendTo($jahr);
        }
    })();

    datenlieferant = new geierlein.Datenlieferant();
    ustva = new geierlein.UStVA(datenlieferant);

    /* Bind datenlieferant input fields to the model.  We bind change as
       well as keyup, so we are able to revalidate the form on every keystroke
       and still don't miss events in case our user copy & pastes the data. */
    $('.datenlieferant').on('change keyup', function(ev) {
        return updateModelHandler(this, datenlieferant);
    });

    /* Bind UStVA form fields to the corresponding model.  Like as for the
       datenlieferant fields we bind to both, change and keyup, events. */
    $('.ustva').on('change keyup', function(ev) {
        return updateModelHandler(this, ustva);
    });

    $('#land').on('change keyup', function(ev) {
        $('#steuernummer').attr('placeholder', ustva.getTaxNumberSample());
    });

    geierlein.resetForm();

    /*
     * Further event listeners needed to make the GUI work.
     */

    /**
     * Show/hide rarely used elements, when fast entry checkbox is toggled.
     */
    $('#schnell').on('click', function(ev) {
        if($('#schnell').prop('checked')) {
            $('.schnell').hide('slow');
        } else {
            $('.schnell').show('slow');
        }
    });

    /**
     * Send button, click event.
     */
    $('#send').on('click', function(ev) {
        geierlein.sendData(false);
        return false;
    });

    /**
     * Send-as-testcase button, click event.
     */
    $('#send-testcase').on('click', function(ev) {
        geierlein.sendData(true);
        return false;
    });

    /**
     * Initialize tooltips on all input elements.
     */
    $('.ustva').tooltip();

    /**
     * Trigger browser's print functionality when print-button is clicked in
     * protocol dialog.
     */
    $('#protocol-print').click(function() {
        printIframe('protocol-frame');
    });
}(jQuery, geierlein));
<|endoftext|>


<commit_before>// -------------------------------------------------------------------------- \\
// File: AutoCompleteView.js                                                  \\
// Module: View                                                               \\
// Requires: Core, Foundation, DOM, View.js                                   \\
// Author: Neil Jenkins                                                       \\
// License: © 2010–2012 Opera Software ASA. All rights reserved.              \\
// -------------------------------------------------------------------------- \\

/*global O */

"use strict";

( function ( NS ) {

var AutoCompleteSource = NS.Class({
    init: function ( options ) {
        NS.extend( this, options );
    },
    maxResults: 10,
    suggestions: [],
    getTestRegExp: function ( value ) {
        return new RegExp( '\b' + value, 'i' );
    },
    renderSuggestion: function ( suggestion, searchTerm ) {
        return suggestion.escapeHTML();
    },
    acceptSuggestion: function ( suggestion, current ) {
        return suggestion;
    }
});

var AutoCompleteView = NS.Class({

    Extends: NS.View,

    Mixin: NS.AbstractMenu,

    layout: {},
    className: 'AutoCompleteView',

    minChars: 1,
    sources: [],

    // ---

    init: function ( options ) {
        AutoCompleteView.parent.init.call( this, options );
        var view = this.get( 'inputView' ),
            _;
        if ( view ) {
            this.inputViewDidChange( _, _, _, view );
        }
    },

    didCreateLayer: function ( layer ) {
        AutoCompleteView.parent.didCreateLayer.call( this, layer );
        layer.addEventListener( 'mouseover', this, false );
        layer.addEventListener( 'mouseout', this, false );
    },

    willDestroyLayer: function ( layer ) {
        layer.removeEventListener( 'mouseout', this, false );
        layer.removeEventListener( 'mouseover', this, false );
        AutoCompleteView.parent.willDestroyLayer.call( this, layer );
    },

    inputView: null,
    inputViewDidChange: function ( _, __, oldView, newView ) {
        if ( oldView ) {
            oldView.detach( 'focus', this, 'inputDidReceiveFocus' );
            oldView.detach( 'blur', this, 'inputDidLoseFocus' );
            oldView.detach( 'keydown', this, 'inputDidKeydown' );
        }
        if ( newView ) {
            newView.on( 'focus', this, 'inputDidReceiveFocus' );
            newView.on( 'blur', this, 'inputDidLoseFocus' );
            newView.on( 'keydown', this, 'inputDidKeydown' );
            if ( newView.get( 'isFocussed' ) ) {
                this.inputDidReceiveFocus();
            }
        }
    }.observes( 'inputView' ),

    inputDidReceiveFocus: function () {
        this._isActive = true;
        if ( this.get( 'filterValue' ).length >= this.get( 'minChars' ) ) {
            this.showSuggestions();
        }
    },

    inputDidLoseFocus: function () {
        this._isActive = false;
        this.hideSuggestions();
    },

    // Fire keydown event to trigger AbstractMenu key bindings.
    // However, also be sure to stop propagation as otherwise if the
    // autocomplete view is actually a child of the input view you get an
    // infinite loop!
    inputDidKeydown: function ( event ) {
        if ( this._isShowing ) {
            this.fire( 'keydown', event );
        }
    },
    stopPropagation: function ( event ) {
        event.stopPropagation();
    }.on( 'keydown' ),

    filterValue: NS.bind( 'inputView.value' ),
    filterValueDidChange: function ( _, __, ___, value ) {
        if ( this._isActive ) {
            if ( value.length >= this.get( 'minChars' ) ) {
                this.showSuggestions();
            } else {
                this.hideSuggestions();
            }
        }
    }.observes( 'filterValue' ),

    // ---

    _keyBindings: {
        up: 'focusPrevious',
        down: 'focusNext',
        tab: 'selectFocussed',
        enter: 'selectFocussed'
    },

    // filterItem: function ( item, pattern ) {},
    // isItemHidden: function ( item ) { return false; },
    didBlurItem: function ( item ) {
        NS.Element.removeClass( item.element, 'focussed' );
    },
    didFocusItem: function ( item ) {
        NS.Element.addClass( item.element, 'focussed' );
    },
    didSelectItem: function ( item ) {
        var inputView = this.get( 'inputView' ),
            current = inputView.get( 'value' ),
            result = item.source.acceptSuggestion( item.suggestion, current );

        if ( result !== current ) {
            inputView
                .set( 'value', result )
                .set( 'selection', { start: result.length } );
        }
    },
    getItemFromElement: function ( el ) {
        var layer = this.get( 'layer' );
        while ( el.nodeName !== 'LI' &&
            ( el = el.parentNode ) && el !== layer ) {/* Empty */}
        return ( el && el !== layer ) ? this.get( 'items' )[ el._index ] : null;
    },
    // hide: function () {},

    // ---

    showSuggestions: function () {
        var value = this.get( 'filterValue' );

        if ( this._lastFilterValue !== value ) {
            var sources = this.get( 'sources' ),
                items = [],
                itemsLength = 0,
                layer = this.get( 'layer' ),
                el = NS.Element.create,
                ul = el( 'ul' ),
                i, l, source, count, max, suggestions, accept,
                j, m, suggestion, element;

            for ( i = 0, l = sources.length; i < l; i += 1 ) {
                source = sources[i];
                count = 0;
                max = source.maxResults;
                suggestions = source.suggestions;
                accept = source.getTestFn( value );

                for ( j = 0, m = suggestions.get( 'length' );
                        j < m && count < max; j += 1 ) {
                    if ( accept( suggestion = suggestions.getObjectAt( j ) ) ) {
                        element = el( 'li', {
                            html: source.renderSuggestion( suggestion, value )
                        });
                        element._index = itemsLength;
                        ul.appendChild( element );
                        items.push({
                            source: source,
                            suggestion: suggestion,
                            element: element
                        });
                        count += 1;
                        itemsLength += 1;
                    }
                }
            }

            if ( this._ul ) {
                layer.replaceChild( ul, this._ul );
            } else {
                layer.appendChild( ul );
            }

            this._ul = ul;
            this._lastFilterValue = value;

            this.set( 'items', items );

            this.focusItem( items[0] );
        }

        if ( !this.get( 'items' ).length ) {
            this.hideSuggestions();
        } else if ( !this._isShowing ) {
            // Focus the first item
            this.focusItem( this.get( 'items' )[0] );
            this.get( 'layer' ).style.display = 'block';
            this._isShowing = true;
        }
    },

    hideSuggestions: function () {
        this.get( 'layer' ).style.display = 'none';
        this.focusItem( null );
        this._isShowing = false;
    }
});

NS.AutoCompleteSource = AutoCompleteSource;
NS.AutoCompleteView = AutoCompleteView;

}( O ) );<commit_msg>Make AutoCompleteSource extend O.Object.<commit_after>// -------------------------------------------------------------------------- \\
// File: AutoCompleteView.js                                                  \\
// Module: View                                                               \\
// Requires: Core, Foundation, DOM, View.js                                   \\
// Author: Neil Jenkins                                                       \\
// License: © 2010–2012 Opera Software ASA. All rights reserved.              \\
// -------------------------------------------------------------------------- \\

/*global O */

"use strict";

( function ( NS ) {

var AutoCompleteSource = NS.Class({

    Extends: NS.Object,

    maxResults: 10,
    suggestions: [],

    getTestRegExp: function ( value ) {
        return new RegExp( '\b' + value, 'i' );
    },
    renderSuggestion: function ( suggestion, searchTerm ) {
        return suggestion.escapeHTML();
    },
    acceptSuggestion: function ( suggestion, current ) {
        return suggestion;
    }
});

var AutoCompleteView = NS.Class({

    Extends: NS.View,

    Mixin: NS.AbstractMenu,

    layout: {},
    className: 'AutoCompleteView',

    minChars: 1,
    sources: [],

    // ---

    init: function ( options ) {
        AutoCompleteView.parent.init.call( this, options );
        var view = this.get( 'inputView' ),
            _;
        if ( view ) {
            this.inputViewDidChange( _, _, _, view );
        }
    },

    didCreateLayer: function ( layer ) {
        AutoCompleteView.parent.didCreateLayer.call( this, layer );
        layer.addEventListener( 'mouseover', this, false );
        layer.addEventListener( 'mouseout', this, false );
    },

    willDestroyLayer: function ( layer ) {
        layer.removeEventListener( 'mouseout', this, false );
        layer.removeEventListener( 'mouseover', this, false );
        AutoCompleteView.parent.willDestroyLayer.call( this, layer );
    },

    inputView: null,
    inputViewDidChange: function ( _, __, oldView, newView ) {
        if ( oldView ) {
            oldView.detach( 'focus', this, 'inputDidReceiveFocus' );
            oldView.detach( 'blur', this, 'inputDidLoseFocus' );
            oldView.detach( 'keydown', this, 'inputDidKeydown' );
        }
        if ( newView ) {
            newView.on( 'focus', this, 'inputDidReceiveFocus' );
            newView.on( 'blur', this, 'inputDidLoseFocus' );
            newView.on( 'keydown', this, 'inputDidKeydown' );
            if ( newView.get( 'isFocussed' ) ) {
                this.inputDidReceiveFocus();
            }
        }
    }.observes( 'inputView' ),

    inputDidReceiveFocus: function () {
        this._isActive = true;
        if ( this.get( 'filterValue' ).length >= this.get( 'minChars' ) ) {
            this.showSuggestions();
        }
    },

    inputDidLoseFocus: function () {
        this._isActive = false;
        this.hideSuggestions();
    },

    // Fire keydown event to trigger AbstractMenu key bindings.
    // However, also be sure to stop propagation as otherwise if the
    // autocomplete view is actually a child of the input view you get an
    // infinite loop!
    inputDidKeydown: function ( event ) {
        if ( this._isShowing ) {
            this.fire( 'keydown', event );
        }
    },
    stopPropagation: function ( event ) {
        event.stopPropagation();
    }.on( 'keydown' ),

    filterValue: NS.bind( 'inputView.value' ),
    filterValueDidChange: function ( _, __, ___, value ) {
        if ( this._isActive ) {
            if ( value.length >= this.get( 'minChars' ) ) {
                this.showSuggestions();
            } else {
                this.hideSuggestions();
            }
        }
    }.observes( 'filterValue' ),

    // ---

    _keyBindings: {
        up: 'focusPrevious',
        down: 'focusNext',
        tab: 'selectFocussed',
        enter: 'selectFocussed'
    },

    // filterItem: function ( item, pattern ) {},
    // isItemHidden: function ( item ) { return false; },
    didBlurItem: function ( item ) {
        NS.Element.removeClass( item.element, 'focussed' );
    },
    didFocusItem: function ( item ) {
        NS.Element.addClass( item.element, 'focussed' );
    },
    didSelectItem: function ( item ) {
        var inputView = this.get( 'inputView' ),
            current = inputView.get( 'value' ),
            result = item.source.acceptSuggestion( item.suggestion, current );

        if ( result !== current ) {
            inputView
                .set( 'value', result )
                .set( 'selection', { start: result.length } );
        }
    },
    getItemFromElement: function ( el ) {
        var layer = this.get( 'layer' );
        while ( el.nodeName !== 'LI' &&
            ( el = el.parentNode ) && el !== layer ) {/* Empty */}
        return ( el && el !== layer ) ? this.get( 'items' )[ el._index ] : null;
    },
    // hide: function () {},

    // ---

    showSuggestions: function () {
        var value = this.get( 'filterValue' );

        if ( this._lastFilterValue !== value ) {
            var sources = this.get( 'sources' ),
                items = [],
                itemsLength = 0,
                layer = this.get( 'layer' ),
                el = NS.Element.create,
                ul = el( 'ul' ),
                i, l, source, count, max, suggestions, accept,
                j, m, suggestion, element;

            for ( i = 0, l = sources.length; i < l; i += 1 ) {
                source = sources[i];
                count = 0;
                max = source.get( 'maxResults' );
                suggestions = source.get( 'suggestions' );
                accept = source.getTestFn( value );

                for ( j = 0, m = suggestions.get( 'length' );
                        j < m && count < max; j += 1 ) {
                    if ( accept( suggestion = suggestions.getObjectAt( j ) ) ) {
                        element = el( 'li', {
                            html: source.renderSuggestion( suggestion, value )
                        });
                        element._index = itemsLength;
                        ul.appendChild( element );
                        items.push({
                            source: source,
                            suggestion: suggestion,
                            element: element
                        });
                        count += 1;
                        itemsLength += 1;
                    }
                }
            }

            if ( this._ul ) {
                layer.replaceChild( ul, this._ul );
            } else {
                layer.appendChild( ul );
            }

            this._ul = ul;
            this._lastFilterValue = value;

            this.set( 'items', items );

            this.focusItem( items[0] );
        }

        if ( !this.get( 'items' ).length ) {
            this.hideSuggestions();
        } else if ( !this._isShowing ) {
            // Focus the first item
            this.focusItem( this.get( 'items' )[0] );
            this.get( 'layer' ).style.display = 'block';
            this._isShowing = true;
        }
    },

    hideSuggestions: function () {
        this.get( 'layer' ).style.display = 'none';
        this.focusItem( null );
        this._isShowing = false;
    }
});

NS.AutoCompleteSource = AutoCompleteSource;
NS.AutoCompleteView = AutoCompleteView;

}( O ) );<|endoftext|>