First commit
This commit is contained in:
		
						commit
						c6e2478c40
					
				
					 13918 changed files with 2303184 additions and 0 deletions
				
			
		
							
								
								
									
										1263
									
								
								sites/all/modules/civicrm/packages/backbone-forms/src/editors.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1263
									
								
								sites/all/modules/civicrm/packages/backbone-forms/src/editors.js
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										65
									
								
								sites/all/modules/civicrm/packages/backbone-forms/src/editors/jquery-ui.css
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								sites/all/modules/civicrm/packages/backbone-forms/src/editors/jquery-ui.css
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,65 @@
 | 
			
		|||
.bbf-jui-date input, .bbf-jui-datetime input {
 | 
			
		||||
    width: 100px;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    .bbf-jui-datetime input {
 | 
			
		||||
        margin-right: 1em;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .bbf-jui-datetime select {
 | 
			
		||||
        width: 50px;
 | 
			
		||||
        text-align: center;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
.bbf-jui-list {
 | 
			
		||||
    list-style-type: none;
 | 
			
		||||
}
 | 
			
		||||
    
 | 
			
		||||
    .bbf-jui-list ul {
 | 
			
		||||
        border: 1px solid #ccc;
 | 
			
		||||
        border-bottom: none;
 | 
			
		||||
        max-height: 150px;
 | 
			
		||||
        overflow: auto;
 | 
			
		||||
        margin: 0;
 | 
			
		||||
        padding: 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .bbf-jui-list li {
 | 
			
		||||
        border-top: 1px solid #ccc;
 | 
			
		||||
        border-bottom: 1px solid #ccc;
 | 
			
		||||
        height: 16px;
 | 
			
		||||
        background: #fff;
 | 
			
		||||
        padding: 4px;
 | 
			
		||||
        margin: 0;
 | 
			
		||||
        list-style-type: none;
 | 
			
		||||
        margin-top: -1px;
 | 
			
		||||
        position: relative;
 | 
			
		||||
        
 | 
			
		||||
    }    
 | 
			
		||||
        .bbf-jui-list .bbf-list-sortable li {
 | 
			
		||||
            cursor: move;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        .bbf-jui-list .bbf-list-actions {
 | 
			
		||||
            position: absolute;
 | 
			
		||||
            top: 2px;
 | 
			
		||||
            right: 2px;
 | 
			
		||||
        }
 | 
			
		||||
    
 | 
			
		||||
        .bbf-jui-list .bbf-list-actions button {
 | 
			
		||||
            width: 19px;
 | 
			
		||||
            height: 19px;
 | 
			
		||||
        }
 | 
			
		||||
    
 | 
			
		||||
        .bbf-jui-list .bbf-list-add {
 | 
			
		||||
            width: 100%;
 | 
			
		||||
            height: 20px;
 | 
			
		||||
            margin-top: -2px;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
    .bbf-jui-list .bbf-list-editor {
 | 
			
		||||
        width: 98%;
 | 
			
		||||
    }
 | 
			
		||||
							
								
								
									
										500
									
								
								sites/all/modules/civicrm/packages/backbone-forms/src/editors/jquery-ui.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										500
									
								
								sites/all/modules/civicrm/packages/backbone-forms/src/editors/jquery-ui.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,500 @@
 | 
			
		|||
;(function() {
 | 
			
		||||
  
 | 
			
		||||
  var Form = Backbone.Form,
 | 
			
		||||
      Base = Form.editors.Base,
 | 
			
		||||
      createTemplate = Form.helpers.createTemplate,
 | 
			
		||||
      triggerCancellableEvent = Form.helpers.triggerCancellableEvent,
 | 
			
		||||
      exports = {};
 | 
			
		||||
  
 | 
			
		||||
  /**
 | 
			
		||||
   * Additional editors that depend on jQuery UI
 | 
			
		||||
   */
 | 
			
		||||
  
 | 
			
		||||
  //DATE
 | 
			
		||||
  exports['jqueryui.Date'] = Base.extend({
 | 
			
		||||
 | 
			
		||||
    className: 'bbf-jui-date',
 | 
			
		||||
    
 | 
			
		||||
    initialize: function(options) {
 | 
			
		||||
      Base.prototype.initialize.call(this, options);
 | 
			
		||||
      
 | 
			
		||||
      //Cast to Date
 | 
			
		||||
      if (this.value && !_.isDate(this.value)) {
 | 
			
		||||
        this.value = new Date(this.value);
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      //Set default date
 | 
			
		||||
      if (!this.value) {
 | 
			
		||||
        var date = new Date();
 | 
			
		||||
        date.setSeconds(0);
 | 
			
		||||
        date.setMilliseconds(0);
 | 
			
		||||
        
 | 
			
		||||
        this.value = date;
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    render: function() {
 | 
			
		||||
      var $el = this.$el;
 | 
			
		||||
 | 
			
		||||
      $el.html('<input>');
 | 
			
		||||
 | 
			
		||||
      var input = $('input', $el);
 | 
			
		||||
 | 
			
		||||
      input.datepicker({
 | 
			
		||||
        dateFormat: 'dd/mm/yy',
 | 
			
		||||
        showButtonPanel: true
 | 
			
		||||
      });
 | 
			
		||||
      
 | 
			
		||||
      this._observeDatepickerEvents();
 | 
			
		||||
 | 
			
		||||
      //Make sure setValue of this object is called, not of any objects extending it (e.g. DateTime)
 | 
			
		||||
      exports['jqueryui.Date'].prototype.setValue.call(this, this.value);
 | 
			
		||||
 | 
			
		||||
      return this;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
    * @return {Date}   Selected date
 | 
			
		||||
    */
 | 
			
		||||
    getValue: function() {
 | 
			
		||||
      var input = $('input', this.el),
 | 
			
		||||
          date = input.datepicker('getDate');
 | 
			
		||||
 | 
			
		||||
      return date;
 | 
			
		||||
    },
 | 
			
		||||
    
 | 
			
		||||
    setValue: function(value) {
 | 
			
		||||
      $('input', this.el).datepicker('setDate', value);
 | 
			
		||||
    },
 | 
			
		||||
    
 | 
			
		||||
    focus: function() {
 | 
			
		||||
      if (this.hasFocus) return;
 | 
			
		||||
      
 | 
			
		||||
      this.$('input').datepicker('show');
 | 
			
		||||
    },
 | 
			
		||||
    
 | 
			
		||||
    blur: function() {
 | 
			
		||||
      if (!this.hasFocus) return;
 | 
			
		||||
      
 | 
			
		||||
      this.$('input').datepicker('hide');
 | 
			
		||||
    },
 | 
			
		||||
    
 | 
			
		||||
    _observeDatepickerEvents: function() {
 | 
			
		||||
      var self = this;
 | 
			
		||||
      this.$('input').datepicker('option', 'onSelect', function() {
 | 
			
		||||
        self.trigger('change', self);
 | 
			
		||||
      });
 | 
			
		||||
      this.$('input').datepicker('option', 'onClose', function() {
 | 
			
		||||
        if (!self.hasFocus) return;
 | 
			
		||||
        self.trigger('blur', self);
 | 
			
		||||
      });
 | 
			
		||||
      this.$('input').datepicker('option', 'beforeShow', function() {
 | 
			
		||||
        if (self.hasFocus) return {};
 | 
			
		||||
        self.trigger('focus', self);
 | 
			
		||||
        
 | 
			
		||||
        return {};
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  //DATETIME
 | 
			
		||||
  exports['jqueryui.DateTime'] = exports['jqueryui.Date'].extend({
 | 
			
		||||
 | 
			
		||||
    className: 'bbf-jui-datetime',
 | 
			
		||||
 | 
			
		||||
    template: createTemplate('<select>{{hours}}</select> : <select>{{mins}}</select>'),
 | 
			
		||||
 | 
			
		||||
    render: function() {
 | 
			
		||||
      function pad(n) {
 | 
			
		||||
        return n < 10 ? '0' + n : n;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      //Render the date element first
 | 
			
		||||
      exports['jqueryui.Date'].prototype.render.call(this);
 | 
			
		||||
 | 
			
		||||
      //Setup hour options
 | 
			
		||||
      var hours = _.range(0, 24),
 | 
			
		||||
          hoursOptions = [];
 | 
			
		||||
 | 
			
		||||
      _.each(hours, function(hour) {
 | 
			
		||||
        hoursOptions.push('<option value="'+hour+'">' + pad(hour) + '</option>');
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      //Setup minute options
 | 
			
		||||
      var minsInterval = this.schema.minsInterval || 15,
 | 
			
		||||
          mins = _.range(0, 60, minsInterval),
 | 
			
		||||
          minsOptions = [];
 | 
			
		||||
 | 
			
		||||
      _.each(mins, function(min) {
 | 
			
		||||
        minsOptions.push('<option value="'+min+'">' + pad(min) + '</option>');
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      //Render time selects
 | 
			
		||||
      this.$el.append(this.template({
 | 
			
		||||
        hours: hoursOptions.join(),
 | 
			
		||||
        mins: minsOptions.join()
 | 
			
		||||
      }));
 | 
			
		||||
      
 | 
			
		||||
      this._observeDatepickerEvents();
 | 
			
		||||
 | 
			
		||||
      //Store references to selects
 | 
			
		||||
      this.$hours = $('select:eq(0)', this.el);
 | 
			
		||||
      this.$mins = $('select:eq(1)', this.el);
 | 
			
		||||
      
 | 
			
		||||
      //Set time
 | 
			
		||||
      this.setValue(this.value);
 | 
			
		||||
 | 
			
		||||
      return this;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
    * @return {Date}   Selected datetime
 | 
			
		||||
    */
 | 
			
		||||
    getValue: function() {
 | 
			
		||||
      var input = $('input', this.el),
 | 
			
		||||
          date = input.datepicker('getDate');
 | 
			
		||||
 | 
			
		||||
      date.setHours(this.$hours.val());
 | 
			
		||||
      date.setMinutes(this.$mins.val());
 | 
			
		||||
      date.setMilliseconds(0);
 | 
			
		||||
 | 
			
		||||
      return date;
 | 
			
		||||
    },
 | 
			
		||||
    
 | 
			
		||||
    setValue: function(date) {
 | 
			
		||||
      exports['jqueryui.Date'].prototype.setValue.call(this, date);
 | 
			
		||||
      
 | 
			
		||||
      this.$hours.val(date.getHours());
 | 
			
		||||
      this.$mins.val(date.getMinutes());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  //LIST
 | 
			
		||||
  exports['jqueryui.List'] = Base.extend({
 | 
			
		||||
 | 
			
		||||
    className: 'bbf-jui-list',
 | 
			
		||||
 | 
			
		||||
    //Note: The extra div around the <ul> is used to limit the drag area
 | 
			
		||||
    template: createTemplate('\
 | 
			
		||||
      <ul></ul>\
 | 
			
		||||
      <div><button class="bbf-list-add">Add</div>\
 | 
			
		||||
    '),
 | 
			
		||||
 | 
			
		||||
    itemTemplate: createTemplate('\
 | 
			
		||||
      <li rel="{{id}}">\
 | 
			
		||||
        <span class="bbf-list-text">{{text}}</span>\
 | 
			
		||||
        <div class="bbf-list-actions">\
 | 
			
		||||
          <button class="bbf-list-edit">Edit</button>\
 | 
			
		||||
          <button class="bbf-list-del">Delete</button>\
 | 
			
		||||
        </div>\
 | 
			
		||||
      </li>\
 | 
			
		||||
    '),
 | 
			
		||||
    
 | 
			
		||||
    editorTemplate: createTemplate('\
 | 
			
		||||
      <div class="bbf-field">\
 | 
			
		||||
          <div class="bbf-list-editor"></div>\
 | 
			
		||||
      </div>\
 | 
			
		||||
    '),
 | 
			
		||||
 | 
			
		||||
    events: {
 | 
			
		||||
      'click .bbf-list-add':   'addNewItem',
 | 
			
		||||
      'click .bbf-list-edit':  'editItem',
 | 
			
		||||
      'click .bbf-list-del':   'deleteItem'
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    initialize: function(options) {
 | 
			
		||||
      Base.prototype.initialize.call(this, options);
 | 
			
		||||
 | 
			
		||||
      if (!this.schema) throw "Missing required option 'schema'";
 | 
			
		||||
      
 | 
			
		||||
      this.schema.listType = this.schema.listType || 'Text';
 | 
			
		||||
      
 | 
			
		||||
      if (this.schema.listType === 'NestedModel' && !this.schema.model)
 | 
			
		||||
          throw "Missing required option 'schema.model'";
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    render: function() {
 | 
			
		||||
      var $el = this.$el;
 | 
			
		||||
      
 | 
			
		||||
      //Main element
 | 
			
		||||
      $el.html(this.template());
 | 
			
		||||
      
 | 
			
		||||
      //Create list
 | 
			
		||||
      var self = this,
 | 
			
		||||
          data = this.value || [],
 | 
			
		||||
          schema = this.schema,
 | 
			
		||||
          itemToString = this.itemToString,
 | 
			
		||||
          itemTemplate = this.itemTemplate,
 | 
			
		||||
          listEl = $('ul', $el);
 | 
			
		||||
      
 | 
			
		||||
      _.each(data, function(itemData) {     
 | 
			
		||||
        var text = itemToString.call(self, itemData);
 | 
			
		||||
 | 
			
		||||
        //Create DOM element
 | 
			
		||||
        var li = $(itemTemplate({
 | 
			
		||||
          id: itemData.id || '',
 | 
			
		||||
          text: text
 | 
			
		||||
        }));
 | 
			
		||||
 | 
			
		||||
        //Attach data
 | 
			
		||||
        $.data(li[0], 'data', itemData);
 | 
			
		||||
 | 
			
		||||
        listEl.append(li);
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      //Make sortable
 | 
			
		||||
      if (schema.sortable !== false) {
 | 
			
		||||
        listEl.sortable({
 | 
			
		||||
          axis: 'y',
 | 
			
		||||
          cursor: 'move',
 | 
			
		||||
          containment: 'parent'
 | 
			
		||||
        });
 | 
			
		||||
        
 | 
			
		||||
        $el.addClass('bbf-list-sortable');
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      //jQuery UI buttonize
 | 
			
		||||
      $('button.bbf-list-add', $el).button({
 | 
			
		||||
        text: false,
 | 
			
		||||
        icons: { primary: 'ui-icon-plus' }
 | 
			
		||||
      });
 | 
			
		||||
      $('button.bbf-list-edit', $el).button({
 | 
			
		||||
        text: false,
 | 
			
		||||
        icons: { primary: 'ui-icon-pencil' }
 | 
			
		||||
      });
 | 
			
		||||
      $('button.bbf-list-del', $el).button({
 | 
			
		||||
        text: false,
 | 
			
		||||
        icons: { primary: 'ui-icon-trash' }
 | 
			
		||||
      });
 | 
			
		||||
      
 | 
			
		||||
      if (this.hasFocus) this.trigger('blur', this);
 | 
			
		||||
 | 
			
		||||
      return this;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Formats an item for display in the list
 | 
			
		||||
     * For example objects, dates etc. can have a custom
 | 
			
		||||
     * itemToString method which says how it should be formatted.
 | 
			
		||||
     */
 | 
			
		||||
    itemToString: function(data) {
 | 
			
		||||
      if (!data) return data;
 | 
			
		||||
      
 | 
			
		||||
      var schema = this.schema;
 | 
			
		||||
      
 | 
			
		||||
      //If there's a specified toString use that
 | 
			
		||||
      if (schema.itemToString) return schema.itemToString(data);
 | 
			
		||||
      
 | 
			
		||||
      //Otherwise check if it's NestedModel with it's own toString() method
 | 
			
		||||
      if (this.schema.listType === 'NestedModel') {
 | 
			
		||||
        var model = new (this.schema.model)(data);
 | 
			
		||||
      
 | 
			
		||||
        return model.toString();
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      //Last resort, just return the data as is
 | 
			
		||||
      return data;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add a new item to the list if it is completed in the editor
 | 
			
		||||
     */
 | 
			
		||||
    addNewItem: function(event) {
 | 
			
		||||
      if (event) event.preventDefault();
 | 
			
		||||
               
 | 
			
		||||
      var self = this;
 | 
			
		||||
 | 
			
		||||
      this.openEditor(null, function(value, editor) {
 | 
			
		||||
        //Fire 'addItem' cancellable event
 | 
			
		||||
        triggerCancellableEvent(self, 'addItem', [value, editor], function() {
 | 
			
		||||
          var text = self.itemToString(value);
 | 
			
		||||
 | 
			
		||||
          //Create DOM element
 | 
			
		||||
          var li = $(self.itemTemplate({
 | 
			
		||||
            id: value.id || '',
 | 
			
		||||
            text: text
 | 
			
		||||
          }));
 | 
			
		||||
 | 
			
		||||
          //Store data
 | 
			
		||||
          $.data(li[0], 'data', value);
 | 
			
		||||
 | 
			
		||||
          $('ul', self.el).append(li);
 | 
			
		||||
 | 
			
		||||
          //jQuery UI buttonize
 | 
			
		||||
          $('button.bbf-list-edit', this.el).button({
 | 
			
		||||
            text: false,
 | 
			
		||||
            icons: { primary: 'ui-icon-pencil' }
 | 
			
		||||
          });
 | 
			
		||||
          $('button.bbf-list-del', this.el).button({
 | 
			
		||||
            text: false,
 | 
			
		||||
            icons: { primary: 'ui-icon-trash' }
 | 
			
		||||
          });
 | 
			
		||||
          
 | 
			
		||||
          self.trigger('add', self, value);
 | 
			
		||||
          self.trigger('item:change', self, editor);
 | 
			
		||||
          self.trigger('change', self);
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Edit an existing item in the list
 | 
			
		||||
     */
 | 
			
		||||
    editItem: function(event) {
 | 
			
		||||
      event.preventDefault();
 | 
			
		||||
             
 | 
			
		||||
      var self = this,
 | 
			
		||||
          li = $(event.target).closest('li'),
 | 
			
		||||
          originalValue = $.data(li[0], 'data');
 | 
			
		||||
 | 
			
		||||
      this.openEditor(originalValue, function(newValue, editor) {
 | 
			
		||||
        //Fire 'editItem' cancellable event
 | 
			
		||||
        triggerCancellableEvent(self, 'editItem', [newValue, editor], function() {
 | 
			
		||||
          //Update display
 | 
			
		||||
          $('.bbf-list-text', li).html(self.itemToString(newValue));
 | 
			
		||||
 | 
			
		||||
          //Store data
 | 
			
		||||
          $.data(li[0], 'data', newValue);
 | 
			
		||||
 | 
			
		||||
          self.trigger('item:change', self, editor);
 | 
			
		||||
          self.trigger('change', self);
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    deleteItem: function(event) {
 | 
			
		||||
      event.preventDefault();
 | 
			
		||||
  
 | 
			
		||||
      var self = this,
 | 
			
		||||
          li = $(event.target).closest('li'),
 | 
			
		||||
          data = $.data(li[0], 'data');
 | 
			
		||||
 | 
			
		||||
      var confirmDelete = (this.schema.confirmDelete) ? this.schema.confirmDelete : false,
 | 
			
		||||
          confirmMsg = this.schema.confirmDeleteMsg || 'Are you sure?';
 | 
			
		||||
                  
 | 
			
		||||
      function remove() {
 | 
			
		||||
        triggerCancellableEvent(self, 'removeItem', [data], function() {
 | 
			
		||||
          li.remove();
 | 
			
		||||
          
 | 
			
		||||
          self.trigger('remove', self, data);
 | 
			
		||||
          self.trigger('change', self);
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      if (this.schema.confirmDelete) {
 | 
			
		||||
        if (confirm(confirmMsg)) remove();
 | 
			
		||||
      } else {
 | 
			
		||||
        remove();
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Opens the sub editor dialog
 | 
			
		||||
     * @param {Mixed}       Data (if editing existing list item, null otherwise)
 | 
			
		||||
     * @param {Function}    Save callback. receives: value
 | 
			
		||||
     */
 | 
			
		||||
    openEditor: function(data, callback) {
 | 
			
		||||
      var self = this,
 | 
			
		||||
          schema = this.schema,
 | 
			
		||||
          listType = schema.listType || 'Text';
 | 
			
		||||
 | 
			
		||||
      var editor = Form.helpers.createEditor(listType, {
 | 
			
		||||
        key: '',
 | 
			
		||||
        schema: schema,
 | 
			
		||||
        value: data
 | 
			
		||||
      }).render();
 | 
			
		||||
      
 | 
			
		||||
      var container = this.editorContainer = $(this.editorTemplate());
 | 
			
		||||
      $('.bbf-list-editor', container).html(editor.el);
 | 
			
		||||
      
 | 
			
		||||
      var saveAndClose = function() {        
 | 
			
		||||
        var errs = editor.validate();
 | 
			
		||||
        if (errs) return;
 | 
			
		||||
        
 | 
			
		||||
        callback(editor.getValue(), editor);
 | 
			
		||||
        container.dialog('close');
 | 
			
		||||
      };
 | 
			
		||||
      
 | 
			
		||||
      var handleEnterPressed = function(event) {
 | 
			
		||||
        if (event.keyCode !== 13) return;
 | 
			
		||||
        
 | 
			
		||||
        saveAndClose();
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      $(container).dialog({
 | 
			
		||||
        resizable:  false,
 | 
			
		||||
        modal:      true,
 | 
			
		||||
        width:      500,
 | 
			
		||||
        title:      data ? 'Edit item' : 'New item',
 | 
			
		||||
        buttons: {
 | 
			
		||||
          'OK': saveAndClose, 
 | 
			
		||||
          'Cancel': function() {
 | 
			
		||||
            container.dialog('close');
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        close: function() {
 | 
			
		||||
          self.editorContainer = null;
 | 
			
		||||
          
 | 
			
		||||
          $(document).unbind('keydown', handleEnterPressed);
 | 
			
		||||
 | 
			
		||||
          editor.remove();
 | 
			
		||||
          container.remove();
 | 
			
		||||
          
 | 
			
		||||
          self.trigger('item:close', self, editor);
 | 
			
		||||
          self.trigger('item:blur', self, editor);
 | 
			
		||||
          self.trigger('blur', self);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
      
 | 
			
		||||
      this.trigger('item:open', this, editor);
 | 
			
		||||
      this.trigger('item:focus', this, editor);
 | 
			
		||||
      this.trigger('focus', this);
 | 
			
		||||
 | 
			
		||||
      //Save and close dialog on Enter keypress
 | 
			
		||||
      $(document).bind('keydown', handleEnterPressed);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    getValue: function() {
 | 
			
		||||
      var data = [];
 | 
			
		||||
 | 
			
		||||
      $('li', this.el).each(function(index, li) {
 | 
			
		||||
        data.push($.data(li, 'data'));
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      return data;
 | 
			
		||||
    },
 | 
			
		||||
    
 | 
			
		||||
    setValue: function(value) {
 | 
			
		||||
      this.value = value;
 | 
			
		||||
      this.render();
 | 
			
		||||
    },
 | 
			
		||||
    
 | 
			
		||||
    focus: function() {
 | 
			
		||||
      if (this.hasFocus) return;
 | 
			
		||||
      
 | 
			
		||||
      var item = this.$('li .bbf-list-edit').first();
 | 
			
		||||
      if (item.length > 0) {
 | 
			
		||||
        item.click();
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        this.addNewItem();
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    
 | 
			
		||||
    blur: function() {
 | 
			
		||||
      if (!this.hasFocus) return;
 | 
			
		||||
      
 | 
			
		||||
      if (this.editorContainer) this.editorContainer.dialog('close');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  //Exports
 | 
			
		||||
  _.extend(Form.editors, exports);
 | 
			
		||||
  
 | 
			
		||||
})();
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,579 @@
 | 
			
		|||
;(function() {
 | 
			
		||||
 | 
			
		||||
  var Form = Backbone.Form,
 | 
			
		||||
      editors = Form.editors;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * LIST
 | 
			
		||||
   * 
 | 
			
		||||
   * An array editor. Creates a list of other editor items.
 | 
			
		||||
   *
 | 
			
		||||
   * Special options:
 | 
			
		||||
   * @param {String} [options.schema.itemType]          The editor type for each item in the list. Default: 'Text'
 | 
			
		||||
   * @param {String} [options.schema.confirmDelete]     Text to display in a delete confirmation dialog. If falsey, will not ask for confirmation.
 | 
			
		||||
   */
 | 
			
		||||
  editors.List = editors.Base.extend({
 | 
			
		||||
    
 | 
			
		||||
    events: {
 | 
			
		||||
      'click [data-action="add"]': function(event) {
 | 
			
		||||
        event.preventDefault();
 | 
			
		||||
        this.addItem(null, true);
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    initialize: function(options) {
 | 
			
		||||
      editors.Base.prototype.initialize.call(this, options);
 | 
			
		||||
 | 
			
		||||
      var schema = this.schema;
 | 
			
		||||
      if (!schema) throw "Missing required option 'schema'";
 | 
			
		||||
 | 
			
		||||
      //List schema defaults
 | 
			
		||||
      this.schema = _.extend({
 | 
			
		||||
        listTemplate: 'list',
 | 
			
		||||
        listItemTemplate: 'listItem'
 | 
			
		||||
      }, schema);
 | 
			
		||||
 | 
			
		||||
      //Determine the editor to use
 | 
			
		||||
      this.Editor = (function() {
 | 
			
		||||
        var type = schema.itemType;
 | 
			
		||||
 | 
			
		||||
        //Default to Text
 | 
			
		||||
        if (!type) return editors.Text;
 | 
			
		||||
 | 
			
		||||
        //Use List-specific version if available
 | 
			
		||||
        if (editors.List[type]) return editors.List[type];
 | 
			
		||||
 | 
			
		||||
        //Or whichever was passed
 | 
			
		||||
        return editors[type];
 | 
			
		||||
      })();
 | 
			
		||||
 | 
			
		||||
      this.items = [];
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    render: function() {
 | 
			
		||||
      var self = this,
 | 
			
		||||
          value = this.value || [];
 | 
			
		||||
 | 
			
		||||
      //Create main element
 | 
			
		||||
      var $el = $(Form.templates[this.schema.listTemplate]({
 | 
			
		||||
        items: '<b class="bbf-tmp"></b>'
 | 
			
		||||
      }));
 | 
			
		||||
 | 
			
		||||
      //Store a reference to the list (item container)
 | 
			
		||||
      this.$list = $el.find('.bbf-tmp').parent().empty();
 | 
			
		||||
 | 
			
		||||
      //Add existing items
 | 
			
		||||
      if (value.length) {
 | 
			
		||||
        _.each(value, function(itemValue) {
 | 
			
		||||
          self.addItem(itemValue);
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      //If no existing items create an empty one, unless the editor specifies otherwise
 | 
			
		||||
      else {
 | 
			
		||||
        if (!this.Editor.isAsync) this.addItem();
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      this.setElement($el);
 | 
			
		||||
      this.$el.attr('id', this.id);
 | 
			
		||||
      this.$el.attr('name', this.key);
 | 
			
		||||
            
 | 
			
		||||
      if (this.hasFocus) this.trigger('blur', this);
 | 
			
		||||
      
 | 
			
		||||
      return this;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add a new item to the list
 | 
			
		||||
     * @param {Mixed} [value]           Value for the new item editor
 | 
			
		||||
     * @param {Boolean} [userInitiated] If the item was added by the user clicking 'add'
 | 
			
		||||
     */
 | 
			
		||||
    addItem: function(value, userInitiated) {
 | 
			
		||||
      var self = this;
 | 
			
		||||
 | 
			
		||||
      //Create the item
 | 
			
		||||
      var item = new editors.List.Item({
 | 
			
		||||
        list: this,
 | 
			
		||||
        schema: this.schema,
 | 
			
		||||
        value: value,
 | 
			
		||||
        Editor: this.Editor,
 | 
			
		||||
        key: this.key
 | 
			
		||||
      }).render();
 | 
			
		||||
      
 | 
			
		||||
      var _addItem = function() {
 | 
			
		||||
        self.items.push(item);
 | 
			
		||||
        self.$list.append(item.el);
 | 
			
		||||
        
 | 
			
		||||
        item.editor.on('all', function(event) {
 | 
			
		||||
          if (event === 'change') return;
 | 
			
		||||
 | 
			
		||||
          // args = ["key:change", itemEditor, fieldEditor]
 | 
			
		||||
          var args = _.toArray(arguments);
 | 
			
		||||
          args[0] = 'item:' + event;
 | 
			
		||||
          args.splice(1, 0, self);
 | 
			
		||||
          // args = ["item:key:change", this=listEditor, itemEditor, fieldEditor]
 | 
			
		||||
 | 
			
		||||
          editors.List.prototype.trigger.apply(this, args);
 | 
			
		||||
        }, self);
 | 
			
		||||
 | 
			
		||||
        item.editor.on('change', function() {
 | 
			
		||||
          if (!item.addEventTriggered) {
 | 
			
		||||
            item.addEventTriggered = true;
 | 
			
		||||
            this.trigger('add', this, item.editor);
 | 
			
		||||
          }
 | 
			
		||||
          this.trigger('item:change', this, item.editor);
 | 
			
		||||
          this.trigger('change', this);
 | 
			
		||||
        }, self);
 | 
			
		||||
 | 
			
		||||
        item.editor.on('focus', function() {
 | 
			
		||||
          if (this.hasFocus) return;
 | 
			
		||||
          this.trigger('focus', this);
 | 
			
		||||
        }, self);
 | 
			
		||||
        item.editor.on('blur', function() {
 | 
			
		||||
          if (!this.hasFocus) return;
 | 
			
		||||
          var self = this;
 | 
			
		||||
          setTimeout(function() {
 | 
			
		||||
            if (_.find(self.items, function(item) { return item.editor.hasFocus; })) return;
 | 
			
		||||
            self.trigger('blur', self);
 | 
			
		||||
          }, 0);
 | 
			
		||||
        }, self);
 | 
			
		||||
        
 | 
			
		||||
        if (userInitiated || value) {
 | 
			
		||||
          item.addEventTriggered = true;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (userInitiated) {
 | 
			
		||||
          self.trigger('add', self, item.editor);
 | 
			
		||||
          self.trigger('change', self);
 | 
			
		||||
        }
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      //Check if we need to wait for the item to complete before adding to the list
 | 
			
		||||
      if (this.Editor.isAsync) {
 | 
			
		||||
        item.editor.on('readyToAdd', _addItem, this);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      //Most editors can be added automatically
 | 
			
		||||
      else {
 | 
			
		||||
        _addItem();
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      return item;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Remove an item from the list
 | 
			
		||||
     * @param {List.Item} item
 | 
			
		||||
     */
 | 
			
		||||
    removeItem: function(item) {
 | 
			
		||||
      //Confirm delete
 | 
			
		||||
      var confirmMsg = this.schema.confirmDelete;
 | 
			
		||||
      if (confirmMsg && !confirm(confirmMsg)) return;
 | 
			
		||||
 | 
			
		||||
      var index = _.indexOf(this.items, item);
 | 
			
		||||
 | 
			
		||||
      this.items[index].remove();
 | 
			
		||||
      this.items.splice(index, 1);
 | 
			
		||||
      
 | 
			
		||||
      if (item.addEventTriggered) {
 | 
			
		||||
        this.trigger('remove', this, item.editor);
 | 
			
		||||
        this.trigger('change', this);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (!this.items.length && !this.Editor.isAsync) this.addItem();
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    getValue: function() {
 | 
			
		||||
      var values = _.map(this.items, function(item) {
 | 
			
		||||
        return item.getValue();
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      //Filter empty items
 | 
			
		||||
      return _.without(values, undefined, '');
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    setValue: function(value) {
 | 
			
		||||
      this.value = value;
 | 
			
		||||
      this.render();
 | 
			
		||||
    },
 | 
			
		||||
    
 | 
			
		||||
    focus: function() {
 | 
			
		||||
      if (this.hasFocus) return;
 | 
			
		||||
 | 
			
		||||
      if (this.items[0]) this.items[0].editor.focus();
 | 
			
		||||
    },
 | 
			
		||||
    
 | 
			
		||||
    blur: function() {
 | 
			
		||||
      if (!this.hasFocus) return;
 | 
			
		||||
 | 
			
		||||
      var focusedItem = _.find(this.items, function(item) { return item.editor.hasFocus; });
 | 
			
		||||
      
 | 
			
		||||
      if (focusedItem) focusedItem.editor.blur();
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Override default remove function in order to remove item views
 | 
			
		||||
     */
 | 
			
		||||
    remove: function() {
 | 
			
		||||
      _.invoke(this.items, 'remove');
 | 
			
		||||
 | 
			
		||||
      editors.Base.prototype.remove.call(this);
 | 
			
		||||
    },
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Run validation
 | 
			
		||||
     * 
 | 
			
		||||
     * @return {Object|Null}
 | 
			
		||||
     */
 | 
			
		||||
    validate: function() {
 | 
			
		||||
      if (!this.validators) return null;
 | 
			
		||||
 | 
			
		||||
      //Collect errors
 | 
			
		||||
      var errors = _.map(this.items, function(item) {
 | 
			
		||||
        return item.validate();
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      //Check if any item has errors
 | 
			
		||||
      var hasErrors = _.compact(errors).length ? true : false;
 | 
			
		||||
      if (!hasErrors) return null;
 | 
			
		||||
 | 
			
		||||
      //If so create a shared error
 | 
			
		||||
      var fieldError = {
 | 
			
		||||
        type: 'list',
 | 
			
		||||
        message: 'Some of the items in the list failed validation',
 | 
			
		||||
        errors: errors
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      return fieldError;
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * A single item in the list
 | 
			
		||||
   *
 | 
			
		||||
   * @param {editors.List} options.list The List editor instance this item belongs to
 | 
			
		||||
   * @param {Function} options.Editor   Editor constructor function
 | 
			
		||||
   * @param {String} options.key        Model key
 | 
			
		||||
   * @param {Mixed} options.value       Value
 | 
			
		||||
   * @param {Object} options.schema     Field schema
 | 
			
		||||
   */
 | 
			
		||||
  editors.List.Item = Backbone.View.extend({
 | 
			
		||||
    events: {
 | 
			
		||||
      'click [data-action="remove"]': function(event) {
 | 
			
		||||
        event.preventDefault();
 | 
			
		||||
        this.list.removeItem(this);
 | 
			
		||||
      },
 | 
			
		||||
      'keydown input[type=text]': function(event) {
 | 
			
		||||
        if(event.keyCode !== 13) return;
 | 
			
		||||
        event.preventDefault();
 | 
			
		||||
        this.list.addItem();
 | 
			
		||||
        this.list.$list.find("> li:last input").focus();
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    initialize: function(options) {
 | 
			
		||||
      this.list = options.list;
 | 
			
		||||
      this.schema = options.schema || this.list.schema;
 | 
			
		||||
      this.value = options.value;
 | 
			
		||||
      this.Editor = options.Editor || editors.Text;
 | 
			
		||||
      this.key = options.key;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    render: function() {
 | 
			
		||||
      //Create editor
 | 
			
		||||
      this.editor = new this.Editor({
 | 
			
		||||
        key: this.key,
 | 
			
		||||
        schema: this.schema,
 | 
			
		||||
        value: this.value,
 | 
			
		||||
        list: this.list,
 | 
			
		||||
        item: this
 | 
			
		||||
      }).render();
 | 
			
		||||
 | 
			
		||||
      //Create main element
 | 
			
		||||
      var $el = $(Form.templates[this.schema.listItemTemplate]({
 | 
			
		||||
        editor: '<b class="bbf-tmp"></b>'
 | 
			
		||||
      }));
 | 
			
		||||
 | 
			
		||||
      $el.find('.bbf-tmp').replaceWith(this.editor.el);
 | 
			
		||||
 | 
			
		||||
      //Replace the entire element so there isn't a wrapper tag
 | 
			
		||||
      this.setElement($el);
 | 
			
		||||
        
 | 
			
		||||
      return this;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    getValue: function() {
 | 
			
		||||
      return this.editor.getValue();
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    setValue: function(value) {
 | 
			
		||||
      this.editor.setValue(value);
 | 
			
		||||
    },
 | 
			
		||||
    
 | 
			
		||||
    focus: function() {
 | 
			
		||||
      this.editor.focus();
 | 
			
		||||
    },
 | 
			
		||||
    
 | 
			
		||||
    blur: function() {
 | 
			
		||||
      this.editor.blur();
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    remove: function() {
 | 
			
		||||
      this.editor.remove();
 | 
			
		||||
 | 
			
		||||
      Backbone.View.prototype.remove.call(this);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    validate: function() {
 | 
			
		||||
      var value = this.getValue(),
 | 
			
		||||
          formValues = this.list.form ? this.list.form.getValue() : {},
 | 
			
		||||
          validators = this.schema.validators,
 | 
			
		||||
          getValidator = Form.helpers.getValidator;
 | 
			
		||||
 | 
			
		||||
      if (!validators) return null;
 | 
			
		||||
 | 
			
		||||
      //Run through validators until an error is found
 | 
			
		||||
      var error = null;
 | 
			
		||||
      _.every(validators, function(validator) {
 | 
			
		||||
        error = getValidator(validator)(value, formValues);
 | 
			
		||||
 | 
			
		||||
        return error ? false : true;
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      //Show/hide error
 | 
			
		||||
      if (error){
 | 
			
		||||
        this.setError(error);
 | 
			
		||||
      } else {
 | 
			
		||||
        this.clearError();
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      //Return error to be aggregated by list
 | 
			
		||||
      return error ? error : null;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Show a validation error
 | 
			
		||||
     */
 | 
			
		||||
    setError: function(err) {
 | 
			
		||||
      this.$el.addClass(Form.classNames.error);
 | 
			
		||||
      this.$el.attr('title', err.message);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Hide validation errors
 | 
			
		||||
     */
 | 
			
		||||
    clearError: function() {
 | 
			
		||||
      this.$el.removeClass(Form.classNames.error);
 | 
			
		||||
      this.$el.attr('title', null);
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Modal object editor for use with the List editor.
 | 
			
		||||
   * To use it, set the 'itemType' property in a List schema to 'Object' or 'NestedModel'
 | 
			
		||||
   */
 | 
			
		||||
  editors.List.Modal = editors.List.Object = editors.List.NestedModel = editors.Base.extend({
 | 
			
		||||
    events: {
 | 
			
		||||
      'click': 'openEditor'
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {Object} options
 | 
			
		||||
     * @param {Function} [options.schema.itemToString]  Function to transform the value for display in the list.
 | 
			
		||||
     * @param {String} [options.schema.itemType]        Editor type e.g. 'Text', 'Object'.
 | 
			
		||||
     * @param {Object} [options.schema.subSchema]       Schema for nested form,. Required when itemType is 'Object'
 | 
			
		||||
     * @param {Function} [options.schema.model]         Model constructor function. Required when itemType is 'NestedModel'
 | 
			
		||||
     */
 | 
			
		||||
    initialize: function(options) {
 | 
			
		||||
      editors.Base.prototype.initialize.call(this, options);
 | 
			
		||||
 | 
			
		||||
      var schema = this.schema;
 | 
			
		||||
      
 | 
			
		||||
      //Dependencies
 | 
			
		||||
      if (!editors.List.Modal.ModalAdapter) throw 'A ModalAdapter is required';
 | 
			
		||||
 | 
			
		||||
      //Get nested schema if Object
 | 
			
		||||
      if (schema.itemType === 'Object') {
 | 
			
		||||
        if (!schema.subSchema) throw 'Missing required option "schema.subSchema"';
 | 
			
		||||
 | 
			
		||||
        this.nestedSchema = schema.subSchema;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      //Get nested schema if NestedModel
 | 
			
		||||
      if (schema.itemType === 'NestedModel') {
 | 
			
		||||
        if (!schema.model) throw 'Missing required option "schema.model"';
 | 
			
		||||
 | 
			
		||||
        this.nestedSchema = schema.model.prototype.schema;
 | 
			
		||||
        if (_.isFunction(this.nestedSchema)) this.nestedSchema = this.nestedSchema();
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Render the list item representation
 | 
			
		||||
     */
 | 
			
		||||
    render: function() {
 | 
			
		||||
      var self = this;
 | 
			
		||||
 | 
			
		||||
      //New items in the list are only rendered when the editor has been OK'd
 | 
			
		||||
      if (_.isEmpty(this.value)) {
 | 
			
		||||
        this.openEditor();
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      //But items with values are added automatically
 | 
			
		||||
      else {
 | 
			
		||||
        this.renderSummary();
 | 
			
		||||
 | 
			
		||||
        setTimeout(function() {
 | 
			
		||||
          self.trigger('readyToAdd');
 | 
			
		||||
        }, 0);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (this.hasFocus) this.trigger('blur', this);
 | 
			
		||||
 | 
			
		||||
      return this;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Renders the list item representation
 | 
			
		||||
     */
 | 
			
		||||
    renderSummary: function() {
 | 
			
		||||
      var template = Form.templates['list.Modal'];
 | 
			
		||||
 | 
			
		||||
      this.$el.html(template({
 | 
			
		||||
        summary: this.getStringValue()
 | 
			
		||||
      }));
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Function which returns a generic string representation of an object
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Object} value
 | 
			
		||||
     * 
 | 
			
		||||
     * @return {String}
 | 
			
		||||
     */
 | 
			
		||||
    itemToString: function(value) {
 | 
			
		||||
      value = value || {};
 | 
			
		||||
 | 
			
		||||
      //Pretty print the object keys and values
 | 
			
		||||
      var parts = [];
 | 
			
		||||
      _.each(this.nestedSchema, function(schema, key) {
 | 
			
		||||
        var desc = schema.title ? schema.title : Form.helpers.keyToTitle(key),
 | 
			
		||||
            val = value[key];
 | 
			
		||||
 | 
			
		||||
        if (_.isUndefined(val) || _.isNull(val)) val = '';
 | 
			
		||||
 | 
			
		||||
        parts.push(desc + ': ' + val);
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      return parts.join('<br />');
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the string representation of the object value
 | 
			
		||||
     */
 | 
			
		||||
    getStringValue: function() {
 | 
			
		||||
      var schema = this.schema,
 | 
			
		||||
          value = this.getValue();
 | 
			
		||||
 | 
			
		||||
      if (_.isEmpty(value)) return '[Empty]';
 | 
			
		||||
 | 
			
		||||
      //If there's a specified toString use that
 | 
			
		||||
      if (schema.itemToString) return schema.itemToString(value);
 | 
			
		||||
      
 | 
			
		||||
      //Otherwise check if it's NestedModel with it's own toString() method
 | 
			
		||||
      if (schema.itemType === 'NestedModel') {
 | 
			
		||||
        return new (schema.model)(value).toString();
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      //Otherwise use the generic method or custom overridden method
 | 
			
		||||
      return this.itemToString(value);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    openEditor: function() {
 | 
			
		||||
      var self = this;
 | 
			
		||||
 | 
			
		||||
      var form = new Form({
 | 
			
		||||
        schema: this.nestedSchema,
 | 
			
		||||
        data: this.value
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      var modal = this.modal = new Backbone.BootstrapModal({
 | 
			
		||||
        content: form,
 | 
			
		||||
        animate: true
 | 
			
		||||
      }).open();
 | 
			
		||||
 | 
			
		||||
      this.trigger('open', this);
 | 
			
		||||
      this.trigger('focus', this);
 | 
			
		||||
 | 
			
		||||
      modal.on('cancel', function() {
 | 
			
		||||
        this.modal = null;
 | 
			
		||||
 | 
			
		||||
        this.trigger('close', this);
 | 
			
		||||
        this.trigger('blur', this);
 | 
			
		||||
      }, this);
 | 
			
		||||
      
 | 
			
		||||
      modal.on('ok', _.bind(this.onModalSubmitted, this, form, modal));
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when the user clicks 'OK'.
 | 
			
		||||
     * Runs validation and tells the list when ready to add the item
 | 
			
		||||
     */
 | 
			
		||||
    onModalSubmitted: function(form, modal) {
 | 
			
		||||
      var isNew = !this.value;
 | 
			
		||||
 | 
			
		||||
      //Stop if there are validation errors
 | 
			
		||||
      var error = form.validate();
 | 
			
		||||
      if (error) return modal.preventClose();
 | 
			
		||||
      this.modal = null;
 | 
			
		||||
 | 
			
		||||
      //If OK, render the list item
 | 
			
		||||
      this.value = form.getValue();
 | 
			
		||||
 | 
			
		||||
      this.renderSummary();
 | 
			
		||||
 | 
			
		||||
      if (isNew) this.trigger('readyToAdd');
 | 
			
		||||
      
 | 
			
		||||
      this.trigger('change', this);
 | 
			
		||||
      
 | 
			
		||||
      this.trigger('close', this);
 | 
			
		||||
      this.trigger('blur', this);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    getValue: function() {
 | 
			
		||||
      return this.value;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    setValue: function(value) {
 | 
			
		||||
      this.value = value;
 | 
			
		||||
    },
 | 
			
		||||
    
 | 
			
		||||
    focus: function() {
 | 
			
		||||
      if (this.hasFocus) return;
 | 
			
		||||
 | 
			
		||||
      this.openEditor();
 | 
			
		||||
    },
 | 
			
		||||
    
 | 
			
		||||
    blur: function() {
 | 
			
		||||
      if (!this.hasFocus) return;
 | 
			
		||||
      
 | 
			
		||||
      if (this.modal) {
 | 
			
		||||
        this.modal.trigger('cancel');
 | 
			
		||||
        this.modal.close();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }, {
 | 
			
		||||
    //STATICS
 | 
			
		||||
 | 
			
		||||
    //The modal adapter that creates and manages the modal dialog.
 | 
			
		||||
    //Defaults to BootstrapModal (http://github.com/powmedia/backbone.bootstrap-modal)
 | 
			
		||||
    //Can be replaced with another adapter that implements the same interface.
 | 
			
		||||
    ModalAdapter: Backbone.BootstrapModal,
 | 
			
		||||
    
 | 
			
		||||
    //Make the wait list for the 'ready' event before adding the item to the list
 | 
			
		||||
    isAsync: true
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
})();
 | 
			
		||||
							
								
								
									
										258
									
								
								sites/all/modules/civicrm/packages/backbone-forms/src/field.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										258
									
								
								sites/all/modules/civicrm/packages/backbone-forms/src/field.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,258 @@
 | 
			
		|||
 | 
			
		||||
//==================================================================================================
 | 
			
		||||
//FIELD
 | 
			
		||||
//==================================================================================================
 | 
			
		||||
 | 
			
		||||
Form.Field = (function() {
 | 
			
		||||
 | 
			
		||||
  var helpers = Form.helpers,
 | 
			
		||||
      templates = Form.templates;
 | 
			
		||||
 | 
			
		||||
  return Backbone.View.extend({
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {Object}  Options
 | 
			
		||||
     *      Required:
 | 
			
		||||
     *          key     {String} : The model attribute key
 | 
			
		||||
     *      Optional:
 | 
			
		||||
     *          schema  {Object} : Schema for the field
 | 
			
		||||
     *          value       {Mixed} : Pass value when not using a model. Use getValue() to get out value
 | 
			
		||||
     *          model       {Backbone.Model} : Use instead of value, and use commit().
 | 
			
		||||
     *          idPrefix    {String} : Prefix to add to the editor DOM element's ID
 | 
			
		||||
     */
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a new field
 | 
			
		||||
     * 
 | 
			
		||||
     * @param {Object} options
 | 
			
		||||
     * @param {Object} [options.schema]     Field schema. Defaults to { type: 'Text' }
 | 
			
		||||
     * @param {Model} [options.model]       Model the field relates to. Required if options.data is not set.
 | 
			
		||||
     * @param {String} [options.key]        Model key/attribute the field relates to.
 | 
			
		||||
     * @param {Mixed} [options.value]       Field value. Required if options.model is not set.
 | 
			
		||||
     * @param {String} [options.idPrefix]   Prefix for the editor ID. By default, the model's CID is used.
 | 
			
		||||
     *
 | 
			
		||||
     * @return {Field}
 | 
			
		||||
     */
 | 
			
		||||
    initialize: function(options) {
 | 
			
		||||
      options = options || {};
 | 
			
		||||
 | 
			
		||||
      this.form = options.form;
 | 
			
		||||
      this.key = options.key;
 | 
			
		||||
      this.value = options.value;
 | 
			
		||||
      this.model = options.model;
 | 
			
		||||
 | 
			
		||||
      //Turn schema shorthand notation (e.g. 'Text') into schema object
 | 
			
		||||
      if (_.isString(options.schema)) options.schema = { type: options.schema };
 | 
			
		||||
      
 | 
			
		||||
      //Set schema defaults
 | 
			
		||||
      this.schema = _.extend({
 | 
			
		||||
        type: 'Text',
 | 
			
		||||
        title: helpers.keyToTitle(this.key),
 | 
			
		||||
        template: 'field'
 | 
			
		||||
      }, options.schema);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Provides the context for rendering the field
 | 
			
		||||
     * Override this to extend the default context
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Object} schema
 | 
			
		||||
     * @param {View} editor
 | 
			
		||||
     *
 | 
			
		||||
     * @return {Object}     Locals passed to the template
 | 
			
		||||
     */
 | 
			
		||||
    renderingContext: function(schema, editor) {
 | 
			
		||||
      return {
 | 
			
		||||
        key: this.key,
 | 
			
		||||
        title: schema.title,
 | 
			
		||||
        id: editor.id,
 | 
			
		||||
        type: schema.type,
 | 
			
		||||
        editor: '<b class="bbf-tmp-editor"></b>',
 | 
			
		||||
        help: '<b class="bbf-tmp-help"></b>',
 | 
			
		||||
        error: '<b class="bbf-tmp-error"></b>'
 | 
			
		||||
      };
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Renders the field
 | 
			
		||||
     */
 | 
			
		||||
    render: function() {
 | 
			
		||||
      var schema = this.schema,
 | 
			
		||||
          templates = Form.templates;
 | 
			
		||||
 | 
			
		||||
      //Standard options that will go to all editors
 | 
			
		||||
      var options = {
 | 
			
		||||
        form: this.form,
 | 
			
		||||
        key: this.key,
 | 
			
		||||
        schema: schema,
 | 
			
		||||
        idPrefix: this.options.idPrefix,
 | 
			
		||||
        id: this.getId()
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      //Decide on data delivery type to pass to editors
 | 
			
		||||
      if (this.model) {
 | 
			
		||||
        options.model = this.model;
 | 
			
		||||
      } else {
 | 
			
		||||
        options.value = this.value;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      //Decide on the editor to use
 | 
			
		||||
      var editor = this.editor = helpers.createEditor(schema.type, options);
 | 
			
		||||
      
 | 
			
		||||
      //Create the element
 | 
			
		||||
      var $field = $(templates[schema.template](this.renderingContext(schema, editor)));
 | 
			
		||||
 | 
			
		||||
      //Remove <label> if it's not wanted
 | 
			
		||||
      if (schema.title === false) {
 | 
			
		||||
        $field.find('label[for="'+editor.id+'"]').first().remove();
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      //Render editor
 | 
			
		||||
      $field.find('.bbf-tmp-editor').replaceWith(editor.render().el);
 | 
			
		||||
 | 
			
		||||
      //Set help text
 | 
			
		||||
      this.$help = $('.bbf-tmp-help', $field).parent();
 | 
			
		||||
      this.$help.empty();
 | 
			
		||||
      if (this.schema.help) this.$help.html(this.schema.help);
 | 
			
		||||
 | 
			
		||||
      //Create error container
 | 
			
		||||
      this.$error = $($('.bbf-tmp-error', $field).parent()[0]);
 | 
			
		||||
      if (this.$error) this.$error.empty();
 | 
			
		||||
 | 
			
		||||
      //Add custom CSS class names
 | 
			
		||||
      if (this.schema.fieldClass) $field.addClass(this.schema.fieldClass);
 | 
			
		||||
      
 | 
			
		||||
      //Add custom attributes
 | 
			
		||||
      if (this.schema.fieldAttrs) $field.attr(this.schema.fieldAttrs);
 | 
			
		||||
      
 | 
			
		||||
      //Replace the generated wrapper tag
 | 
			
		||||
      this.setElement($field);
 | 
			
		||||
 | 
			
		||||
      return this;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates the ID that will be assigned to the editor
 | 
			
		||||
     *
 | 
			
		||||
     * @return {String}
 | 
			
		||||
     */
 | 
			
		||||
    getId: function() {
 | 
			
		||||
      var prefix = this.options.idPrefix,
 | 
			
		||||
          id = this.key;
 | 
			
		||||
 | 
			
		||||
      //Replace periods with underscores (e.g. for when using paths)
 | 
			
		||||
      id = id.replace(/\./g, '_');
 | 
			
		||||
 | 
			
		||||
      //If a specific ID prefix is set, use it
 | 
			
		||||
      if (_.isString(prefix) || _.isNumber(prefix)) return prefix + id;
 | 
			
		||||
      if (_.isNull(prefix)) return id;
 | 
			
		||||
 | 
			
		||||
      //Otherwise, if there is a model use it's CID to avoid conflicts when multiple forms are on the page
 | 
			
		||||
      if (this.model) return this.model.cid + '_' + id;
 | 
			
		||||
 | 
			
		||||
      return id;
 | 
			
		||||
    },
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Check the validity of the field
 | 
			
		||||
     *
 | 
			
		||||
     * @return {String}
 | 
			
		||||
     */
 | 
			
		||||
    validate: function() {
 | 
			
		||||
      var error = this.editor.validate();
 | 
			
		||||
 | 
			
		||||
      if (error) {
 | 
			
		||||
        this.setError(error.message);
 | 
			
		||||
      } else {
 | 
			
		||||
        this.clearError();
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return error;
 | 
			
		||||
    },
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the field into an error state, adding the error class and setting the error message
 | 
			
		||||
     *
 | 
			
		||||
     * @param {String} msg     Error message
 | 
			
		||||
     */
 | 
			
		||||
    setError: function(msg) {
 | 
			
		||||
      //Object and NestedModel types set their own errors internally
 | 
			
		||||
      if (this.editor.hasNestedForm) return;
 | 
			
		||||
      
 | 
			
		||||
      var errClass = Form.classNames.error;
 | 
			
		||||
 | 
			
		||||
      this.$el.addClass(errClass);
 | 
			
		||||
      
 | 
			
		||||
      if (this.$error) {
 | 
			
		||||
        this.$error.html(msg);
 | 
			
		||||
      } else if (this.$help) {
 | 
			
		||||
        this.$help.html(msg);
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Clear the error state and reset the help message
 | 
			
		||||
     */
 | 
			
		||||
    clearError: function() {
 | 
			
		||||
      var errClass = Form.classNames.error;
 | 
			
		||||
       
 | 
			
		||||
      this.$el.removeClass(errClass);
 | 
			
		||||
      
 | 
			
		||||
      // some fields (e.g., Hidden), may not have a help el
 | 
			
		||||
      if (this.$error) {
 | 
			
		||||
        this.$error.empty();
 | 
			
		||||
      } else if (this.$help) {
 | 
			
		||||
        this.$help.empty();
 | 
			
		||||
      
 | 
			
		||||
        //Reset help text if available
 | 
			
		||||
        var helpMsg = this.schema.help;
 | 
			
		||||
        if (helpMsg) this.$help.html(helpMsg);
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Update the model with the new value from the editor
 | 
			
		||||
     */
 | 
			
		||||
    commit: function() {
 | 
			
		||||
      return this.editor.commit();
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the value from the editor
 | 
			
		||||
     *
 | 
			
		||||
     * @return {Mixed}
 | 
			
		||||
     */
 | 
			
		||||
    getValue: function() {
 | 
			
		||||
      return this.editor.getValue();
 | 
			
		||||
    },
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Set/change the value of the editor
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Mixed} value
 | 
			
		||||
     */
 | 
			
		||||
    setValue: function(value) {
 | 
			
		||||
      this.editor.setValue(value);
 | 
			
		||||
    },
 | 
			
		||||
    
 | 
			
		||||
    focus: function() {
 | 
			
		||||
      this.editor.focus();
 | 
			
		||||
    },
 | 
			
		||||
    
 | 
			
		||||
    blur: function() {
 | 
			
		||||
      this.editor.blur();
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Remove the field and editor views
 | 
			
		||||
     */
 | 
			
		||||
    remove: function() {
 | 
			
		||||
      this.editor.remove();
 | 
			
		||||
 | 
			
		||||
      Backbone.View.prototype.remove.call(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
})();
 | 
			
		||||
							
								
								
									
										389
									
								
								sites/all/modules/civicrm/packages/backbone-forms/src/form.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										389
									
								
								sites/all/modules/civicrm/packages/backbone-forms/src/form.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,389 @@
 | 
			
		|||
 | 
			
		||||
//==================================================================================================
 | 
			
		||||
//FORM
 | 
			
		||||
//==================================================================================================
 | 
			
		||||
  
 | 
			
		||||
var Form = (function() {
 | 
			
		||||
 | 
			
		||||
  return Backbone.View.extend({
 | 
			
		||||
    
 | 
			
		||||
    hasFocus: false,
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a new form
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Object} options
 | 
			
		||||
     * @param {Model} [options.model]                 Model the form relates to. Required if options.data is not set
 | 
			
		||||
     * @param {Object} [options.data]                 Date to populate the form. Required if options.model is not set
 | 
			
		||||
     * @param {String[]} [options.fields]             Fields to include in the form, in order
 | 
			
		||||
     * @param {String[]|Object[]} [options.fieldsets] How to divide the fields up by section. E.g. [{ legend: 'Title', fields: ['field1', 'field2'] }]        
 | 
			
		||||
     * @param {String} [options.idPrefix]             Prefix for editor IDs. By default, the model's CID is used.
 | 
			
		||||
     * @param {String} [options.template]             Form template key/name
 | 
			
		||||
     * @param {String} [options.fieldsetTemplate]     Fieldset template key/name
 | 
			
		||||
     * @param {String} [options.fieldTemplate]        Field template key/name
 | 
			
		||||
     *
 | 
			
		||||
     * @return {Form}
 | 
			
		||||
     */
 | 
			
		||||
    initialize: function(options) { 
 | 
			
		||||
      //Check templates have been loaded
 | 
			
		||||
      if (!Form.templates.form) throw new Error('Templates not loaded');
 | 
			
		||||
 | 
			
		||||
      //Get the schema
 | 
			
		||||
      this.schema = (function() {
 | 
			
		||||
        if (options.schema) return options.schema;
 | 
			
		||||
      
 | 
			
		||||
        var model = options.model;
 | 
			
		||||
        if (!model) throw new Error('Could not find schema');
 | 
			
		||||
      
 | 
			
		||||
        if (_.isFunction(model.schema)) return model.schema();
 | 
			
		||||
      
 | 
			
		||||
        return model.schema;
 | 
			
		||||
      })();
 | 
			
		||||
 | 
			
		||||
      //Option defaults
 | 
			
		||||
      options = _.extend({
 | 
			
		||||
        template: 'form',
 | 
			
		||||
        fieldsetTemplate: 'fieldset',
 | 
			
		||||
        fieldTemplate: 'field'
 | 
			
		||||
      }, options);
 | 
			
		||||
 | 
			
		||||
      //Determine fieldsets
 | 
			
		||||
      if (!options.fieldsets) {
 | 
			
		||||
        var fields = options.fields || _.keys(this.schema);
 | 
			
		||||
 | 
			
		||||
        options.fieldsets = [{ fields: fields }];
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      //Store main attributes
 | 
			
		||||
      this.options = options;
 | 
			
		||||
      this.model = options.model;
 | 
			
		||||
      this.data = options.data;
 | 
			
		||||
      this.fields = {};
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Renders the form and all fields
 | 
			
		||||
     */
 | 
			
		||||
    render: function() {
 | 
			
		||||
      var self = this,
 | 
			
		||||
          options = this.options,
 | 
			
		||||
          template = Form.templates[options.template];
 | 
			
		||||
      
 | 
			
		||||
      //Create el from template
 | 
			
		||||
      var $form = $(template({
 | 
			
		||||
        fieldsets: '<b class="bbf-tmp"></b>'
 | 
			
		||||
      }));
 | 
			
		||||
 | 
			
		||||
      //Render fieldsets
 | 
			
		||||
      var $fieldsetContainer = $('.bbf-tmp', $form);
 | 
			
		||||
 | 
			
		||||
      _.each(options.fieldsets, function(fieldset) {
 | 
			
		||||
        $fieldsetContainer.append(self.renderFieldset(fieldset));
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      $fieldsetContainer.children().unwrap();
 | 
			
		||||
 | 
			
		||||
      //Set the template contents as the main element; removes the wrapper element
 | 
			
		||||
      this.setElement($form);
 | 
			
		||||
      
 | 
			
		||||
      if (this.hasFocus) this.trigger('blur', this);
 | 
			
		||||
 | 
			
		||||
      return this;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Renders a fieldset and the fields within it
 | 
			
		||||
     *
 | 
			
		||||
     * Valid fieldset definitions:
 | 
			
		||||
     * ['field1', 'field2']
 | 
			
		||||
     * { legend: 'Some Fieldset', fields: ['field1', 'field2'] }
 | 
			
		||||
     *
 | 
			
		||||
     * @param {Object|Array} fieldset     A fieldset definition
 | 
			
		||||
     * 
 | 
			
		||||
     * @return {jQuery}                   The fieldset DOM element
 | 
			
		||||
     */
 | 
			
		||||
    renderFieldset: function(fieldset) {
 | 
			
		||||
      var self = this,
 | 
			
		||||
          template = Form.templates[this.options.fieldsetTemplate],
 | 
			
		||||
          schema = this.schema,
 | 
			
		||||
          getNested = Form.helpers.getNested;
 | 
			
		||||
 | 
			
		||||
      //Normalise to object
 | 
			
		||||
      if (_.isArray(fieldset)) {
 | 
			
		||||
        fieldset = { fields: fieldset };
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      //Concatenating HTML as strings won't work so we need to insert field elements into a placeholder
 | 
			
		||||
      var $fieldset = $(template(_.extend({}, fieldset, {
 | 
			
		||||
        legend: '<b class="bbf-tmp-legend"></b>',
 | 
			
		||||
        fields: '<b class="bbf-tmp-fields"></b>'
 | 
			
		||||
      })));
 | 
			
		||||
 | 
			
		||||
      //Set legend
 | 
			
		||||
      if (fieldset.legend) {
 | 
			
		||||
        $fieldset.find('.bbf-tmp-legend').replaceWith(fieldset.legend);
 | 
			
		||||
      }
 | 
			
		||||
      //or remove the containing tag if there isn't a legend
 | 
			
		||||
      else {
 | 
			
		||||
        $fieldset.find('.bbf-tmp-legend').parent().remove();
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      var $fieldsContainer = $('.bbf-tmp-fields', $fieldset);
 | 
			
		||||
 | 
			
		||||
      //Render fields
 | 
			
		||||
      _.each(fieldset.fields, function(key) {
 | 
			
		||||
        //Get the field schema
 | 
			
		||||
        var itemSchema = (function() {
 | 
			
		||||
          //Return a normal key or path key
 | 
			
		||||
          if (schema[key]) return schema[key];
 | 
			
		||||
 | 
			
		||||
          //Return a nested schema, i.e. Object
 | 
			
		||||
          var path = key.replace(/\./g, '.subSchema.');
 | 
			
		||||
          return getNested(schema, path);
 | 
			
		||||
        })();
 | 
			
		||||
 | 
			
		||||
        if (!itemSchema) throw "Field '"+key+"' not found in schema";
 | 
			
		||||
 | 
			
		||||
        //Create the field
 | 
			
		||||
        var field = self.fields[key] = self.createField(key, itemSchema);
 | 
			
		||||
 | 
			
		||||
        //Render the fields with editors, apart from Hidden fields
 | 
			
		||||
        var fieldEl = field.render().el;
 | 
			
		||||
        
 | 
			
		||||
        field.editor.on('all', function(event) {
 | 
			
		||||
          // args = ["change", editor]
 | 
			
		||||
          var args = _.toArray(arguments);
 | 
			
		||||
          args[0] = key + ':' + event;
 | 
			
		||||
          args.splice(1, 0, this);
 | 
			
		||||
          // args = ["key:change", this=form, editor]
 | 
			
		||||
 | 
			
		||||
          this.trigger.apply(this, args);
 | 
			
		||||
        }, self);
 | 
			
		||||
        
 | 
			
		||||
        field.editor.on('change', function() {
 | 
			
		||||
          this.trigger('change', self);
 | 
			
		||||
        }, self);
 | 
			
		||||
 | 
			
		||||
        field.editor.on('focus', function() {
 | 
			
		||||
          if (this.hasFocus) return;
 | 
			
		||||
          this.trigger('focus', this);
 | 
			
		||||
        }, self);
 | 
			
		||||
        field.editor.on('blur', function() {
 | 
			
		||||
          if (!this.hasFocus) return;
 | 
			
		||||
          var self = this;
 | 
			
		||||
          setTimeout(function() {
 | 
			
		||||
            if (_.find(self.fields, function(field) { return field.editor.hasFocus; })) return;
 | 
			
		||||
            self.trigger('blur', self);
 | 
			
		||||
          }, 0);
 | 
			
		||||
        }, self);
 | 
			
		||||
        
 | 
			
		||||
        if (itemSchema.type !== 'Hidden') {
 | 
			
		||||
          $fieldsContainer.append(fieldEl);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      $fieldsContainer = $fieldsContainer.children().unwrap();
 | 
			
		||||
 | 
			
		||||
      return $fieldset;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Renders a field and returns it
 | 
			
		||||
     *
 | 
			
		||||
     * @param {String} key            The key for the field in the form schema
 | 
			
		||||
     * @param {Object} schema         Field schema
 | 
			
		||||
     *
 | 
			
		||||
     * @return {Field}                The field view
 | 
			
		||||
     */
 | 
			
		||||
    createField: function(key, schema) {
 | 
			
		||||
      schema.template = schema.template || this.options.fieldTemplate;
 | 
			
		||||
 | 
			
		||||
      var options = {
 | 
			
		||||
        form: this,
 | 
			
		||||
        key: key,
 | 
			
		||||
        schema: schema,
 | 
			
		||||
        idPrefix: this.options.idPrefix,
 | 
			
		||||
        template: this.options.fieldTemplate
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      if (this.model) {
 | 
			
		||||
        options.model = this.model;
 | 
			
		||||
      } else if (this.data) {
 | 
			
		||||
        options.value = this.data[key];
 | 
			
		||||
      } else {
 | 
			
		||||
        options.value = null;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return new Form.Field(options);
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Validate the data
 | 
			
		||||
     *
 | 
			
		||||
     * @return {Object} Validation errors
 | 
			
		||||
     */
 | 
			
		||||
    validate: function() {
 | 
			
		||||
      var self = this,
 | 
			
		||||
          fields = this.fields,
 | 
			
		||||
          model = this.model,
 | 
			
		||||
          errors = {};
 | 
			
		||||
 | 
			
		||||
      //Collect errors from schema validation
 | 
			
		||||
      _.each(fields, function(field) {
 | 
			
		||||
        var error = field.validate();
 | 
			
		||||
        if (error) {
 | 
			
		||||
          errors[field.key] = error;
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      //Get errors from default Backbone model validator
 | 
			
		||||
      if (model && model.validate) {
 | 
			
		||||
        var modelErrors = model.validate(this.getValue());
 | 
			
		||||
        
 | 
			
		||||
        if (modelErrors) {
 | 
			
		||||
          var isDictionary = _.isObject(modelErrors) && !_.isArray(modelErrors);
 | 
			
		||||
          
 | 
			
		||||
          //If errors are not in object form then just store on the error object
 | 
			
		||||
          if (!isDictionary) {
 | 
			
		||||
            errors._others = errors._others || [];
 | 
			
		||||
            errors._others.push(modelErrors);
 | 
			
		||||
          }
 | 
			
		||||
          
 | 
			
		||||
          //Merge programmatic errors (requires model.validate() to return an object e.g. { fieldKey: 'error' })
 | 
			
		||||
          if (isDictionary) {
 | 
			
		||||
            _.each(modelErrors, function(val, key) {
 | 
			
		||||
              //Set error on field if there isn't one already
 | 
			
		||||
              if (self.fields[key] && !errors[key]) {
 | 
			
		||||
                self.fields[key].setError(val);
 | 
			
		||||
                errors[key] = val;
 | 
			
		||||
              }
 | 
			
		||||
              
 | 
			
		||||
              else {
 | 
			
		||||
                //Otherwise add to '_others' key
 | 
			
		||||
                errors._others = errors._others || [];
 | 
			
		||||
                var tmpErr = {};
 | 
			
		||||
                tmpErr[key] = val;
 | 
			
		||||
                errors._others.push(tmpErr);
 | 
			
		||||
              }
 | 
			
		||||
            });
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return _.isEmpty(errors) ? null : errors;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Update the model with all latest values.
 | 
			
		||||
     *
 | 
			
		||||
     * @return {Object}  Validation errors
 | 
			
		||||
     */
 | 
			
		||||
    commit: function() {
 | 
			
		||||
      //Validate
 | 
			
		||||
      var errors = this.validate();
 | 
			
		||||
      if (errors) return errors;
 | 
			
		||||
 | 
			
		||||
      //Commit
 | 
			
		||||
      var modelError;
 | 
			
		||||
      this.model.set(this.getValue(), {
 | 
			
		||||
        error: function(model, e) {
 | 
			
		||||
          modelError = e;
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
      
 | 
			
		||||
      if (modelError) return modelError;
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get all the field values as an object.
 | 
			
		||||
     * Use this method when passing data instead of objects
 | 
			
		||||
     * 
 | 
			
		||||
     * @param {String} [key]    Specific field value to get
 | 
			
		||||
     */
 | 
			
		||||
    getValue: function(key) {
 | 
			
		||||
      //Return only given key if specified
 | 
			
		||||
      if (key) return this.fields[key].getValue();
 | 
			
		||||
      
 | 
			
		||||
      //Otherwise return entire form      
 | 
			
		||||
      var values = {};
 | 
			
		||||
      _.each(this.fields, function(field) {
 | 
			
		||||
        values[field.key] = field.getValue();
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      return values;
 | 
			
		||||
    },
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Update field values, referenced by key
 | 
			
		||||
     * @param {Object|String} key     New values to set, or property to set
 | 
			
		||||
     * @param val                     Value to set
 | 
			
		||||
     */
 | 
			
		||||
    setValue: function(prop, val) {
 | 
			
		||||
      var data = {};
 | 
			
		||||
      if (typeof prop === 'string') {
 | 
			
		||||
        data[prop] = val;
 | 
			
		||||
      } else {
 | 
			
		||||
        data = prop;
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      var key;
 | 
			
		||||
      for (key in this.schema) {
 | 
			
		||||
        if (data[key] !== undefined) {
 | 
			
		||||
          this.fields[key].setValue(data[key]);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    
 | 
			
		||||
    focus: function() {
 | 
			
		||||
      if (this.hasFocus) return;
 | 
			
		||||
      
 | 
			
		||||
      var fieldset = this.options.fieldsets[0];
 | 
			
		||||
      if (fieldset) {
 | 
			
		||||
        var field;
 | 
			
		||||
        if (_.isArray(fieldset)) {
 | 
			
		||||
          field = fieldset[0];
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
          field = fieldset.fields[0];
 | 
			
		||||
        }
 | 
			
		||||
        if (field) {
 | 
			
		||||
          this.fields[field].editor.focus();
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    
 | 
			
		||||
    blur: function() {
 | 
			
		||||
      if (!this.hasFocus) return;
 | 
			
		||||
      
 | 
			
		||||
      var focusedField = _.find(this.fields, function(field) { return field.editor.hasFocus; });
 | 
			
		||||
      
 | 
			
		||||
      if (focusedField) focusedField.editor.blur();
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Override default remove function in order to remove embedded views
 | 
			
		||||
     */
 | 
			
		||||
    remove: function() {
 | 
			
		||||
      var fields = this.fields;
 | 
			
		||||
      
 | 
			
		||||
      for (var key in fields) {
 | 
			
		||||
        fields[key].remove();
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      Backbone.View.prototype.remove.call(this);
 | 
			
		||||
    },
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
    trigger: function(event) {
 | 
			
		||||
      if (event === 'focus') {
 | 
			
		||||
        this.hasFocus = true;
 | 
			
		||||
      }
 | 
			
		||||
      else if (event === 'blur') {
 | 
			
		||||
        this.hasFocus = false;
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      return Backbone.View.prototype.trigger.apply(this, arguments);
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
})();
 | 
			
		||||
							
								
								
									
										212
									
								
								sites/all/modules/civicrm/packages/backbone-forms/src/helpers.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										212
									
								
								sites/all/modules/civicrm/packages/backbone-forms/src/helpers.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,212 @@
 | 
			
		|||
 | 
			
		||||
//==================================================================================================
 | 
			
		||||
//HELPERS
 | 
			
		||||
//==================================================================================================
 | 
			
		||||
 | 
			
		||||
Form.helpers = (function() {
 | 
			
		||||
 | 
			
		||||
  var helpers = {};
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets a nested attribute using a path e.g. 'user.name'
 | 
			
		||||
   *
 | 
			
		||||
   * @param {Object} obj    Object to fetch attribute from
 | 
			
		||||
   * @param {String} path   Attribute path e.g. 'user.name'
 | 
			
		||||
   * @return {Mixed}
 | 
			
		||||
   * @api private
 | 
			
		||||
   */
 | 
			
		||||
  helpers.getNested = function(obj, path) {
 | 
			
		||||
    var fields = path.split(".");
 | 
			
		||||
    var result = obj;
 | 
			
		||||
    for (var i = 0, n = fields.length; i < n; i++) {
 | 
			
		||||
      result = result[fields[i]];
 | 
			
		||||
    }
 | 
			
		||||
    return result;
 | 
			
		||||
  };
 | 
			
		||||
  
 | 
			
		||||
  /**
 | 
			
		||||
   * This function is used to transform the key from a schema into the title used in a label.
 | 
			
		||||
   * (If a specific title is provided it will be used instead).
 | 
			
		||||
   * 
 | 
			
		||||
   * By default this converts a camelCase string into words, i.e. Camel Case
 | 
			
		||||
   * If you have a different naming convention for schema keys, replace this function.
 | 
			
		||||
   * 
 | 
			
		||||
   * @param {String}  Key
 | 
			
		||||
   * @return {String} Title
 | 
			
		||||
   */
 | 
			
		||||
  helpers.keyToTitle = function(str) {
 | 
			
		||||
    //Add spaces
 | 
			
		||||
    str = str.replace(/([A-Z])/g, ' $1');
 | 
			
		||||
 | 
			
		||||
    //Uppercase first character
 | 
			
		||||
    str = str.replace(/^./, function(str) { return str.toUpperCase(); });
 | 
			
		||||
 | 
			
		||||
    return str;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Helper to compile a template with the {{mustache}} style tags. Template settings are reset
 | 
			
		||||
   * to user's settings when done to avoid conflicts.
 | 
			
		||||
   * @param {String}    Template string
 | 
			
		||||
   * @return {Template} Compiled template
 | 
			
		||||
   */
 | 
			
		||||
  helpers.compileTemplate = function(str) {
 | 
			
		||||
      //Store user's template options
 | 
			
		||||
      var _interpolateBackup = _.templateSettings.interpolate;
 | 
			
		||||
 | 
			
		||||
      //Set custom template settings
 | 
			
		||||
      _.templateSettings.interpolate = /\{\{(.+?)\}\}/g;
 | 
			
		||||
 | 
			
		||||
      var template = _.template(str);
 | 
			
		||||
 | 
			
		||||
      //Reset to users' template settings
 | 
			
		||||
      _.templateSettings.interpolate = _interpolateBackup;
 | 
			
		||||
 | 
			
		||||
      return template;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Helper to create a template with the {{mustache}} style tags.
 | 
			
		||||
   * If context is passed in, the template will be evaluated.
 | 
			
		||||
   * @param {String}             Template string
 | 
			
		||||
   * @param {Object}             Optional; values to replace in template
 | 
			
		||||
   * @return {Template|String}   Compiled template or the evaluated string
 | 
			
		||||
   */
 | 
			
		||||
  helpers.createTemplate = function(str, context) {
 | 
			
		||||
    var template = helpers.compileTemplate(str);
 | 
			
		||||
    
 | 
			
		||||
    if (!context) {
 | 
			
		||||
      return template;
 | 
			
		||||
    } else {
 | 
			
		||||
      return template(context);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Sets the template compiler to the given function
 | 
			
		||||
   * @param {Function} Template compiler function
 | 
			
		||||
   */
 | 
			
		||||
  helpers.setTemplateCompiler = function(compiler) {
 | 
			
		||||
    helpers.compileTemplate = compiler;
 | 
			
		||||
  };
 | 
			
		||||
  
 | 
			
		||||
  
 | 
			
		||||
  /**
 | 
			
		||||
   * Sets the templates to be used.
 | 
			
		||||
   * 
 | 
			
		||||
   * If the templates passed in are strings, they will be compiled, expecting Mustache style tags,
 | 
			
		||||
   * i.e. <div>{{varName}}</div>
 | 
			
		||||
   *
 | 
			
		||||
   * You can also pass in previously compiled Underscore templates, in which case you can use any style
 | 
			
		||||
   * tags.
 | 
			
		||||
   * 
 | 
			
		||||
   * @param {Object} templates
 | 
			
		||||
   * @param {Object} classNames
 | 
			
		||||
   */
 | 
			
		||||
  helpers.setTemplates = function(templates, classNames) {
 | 
			
		||||
    var createTemplate = helpers.createTemplate;
 | 
			
		||||
    
 | 
			
		||||
    Form.templates = Form.templates || {};
 | 
			
		||||
    Form.classNames = Form.classNames || {};
 | 
			
		||||
    
 | 
			
		||||
    //Set templates, compiling them if necessary
 | 
			
		||||
    _.each(templates, function(template, key, index) {
 | 
			
		||||
      if (_.isString(template)) template = createTemplate(template);
 | 
			
		||||
      
 | 
			
		||||
      Form.templates[key] = template;
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    //Set class names
 | 
			
		||||
    _.extend(Form.classNames, classNames);
 | 
			
		||||
  };
 | 
			
		||||
  
 | 
			
		||||
  
 | 
			
		||||
  /**
 | 
			
		||||
   * Return the editor constructor for a given schema 'type'.
 | 
			
		||||
   * Accepts strings for the default editors, or the reference to the constructor function
 | 
			
		||||
   * for custom editors
 | 
			
		||||
   * 
 | 
			
		||||
   * @param {String|Function} The schema type e.g. 'Text', 'Select', or the editor constructor e.g. editors.Date
 | 
			
		||||
   * @param {Object}          Options to pass to editor, including required 'key', 'schema'
 | 
			
		||||
   * @return {Mixed}          An instance of the mapped editor
 | 
			
		||||
   */
 | 
			
		||||
  helpers.createEditor = function(schemaType, options) {
 | 
			
		||||
    var constructorFn;
 | 
			
		||||
 | 
			
		||||
    if (_.isString(schemaType)) {
 | 
			
		||||
      constructorFn = Form.editors[schemaType];
 | 
			
		||||
    } else {
 | 
			
		||||
      constructorFn = schemaType;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return new constructorFn(options);
 | 
			
		||||
  };
 | 
			
		||||
  
 | 
			
		||||
  /**
 | 
			
		||||
   * Triggers an event that can be cancelled. Requires the user to invoke a callback. If false
 | 
			
		||||
   * is passed to the callback, the action does not run.
 | 
			
		||||
   *
 | 
			
		||||
   * NOTE: This helper uses private Backbone apis so can break when Backbone is upgraded
 | 
			
		||||
   * 
 | 
			
		||||
   * @param {Mixed}       Instance of Backbone model, view, collection to trigger event on
 | 
			
		||||
   * @param {String}      Event name
 | 
			
		||||
   * @param {Array}       Arguments to pass to the event handlers
 | 
			
		||||
   * @param {Function}    Callback to run after the event handler has run.
 | 
			
		||||
   *                      If any of them passed false or error, this callback won't run
 | 
			
		||||
   */ 
 | 
			
		||||
  helpers.triggerCancellableEvent = function(subject, event, args, callback) { 
 | 
			
		||||
    //Return if there are no event listeners
 | 
			
		||||
    if (!subject._callbacks || !subject._callbacks[event]) return callback();
 | 
			
		||||
    
 | 
			
		||||
    var next = subject._callbacks[event].next;
 | 
			
		||||
    if (!next) return callback();
 | 
			
		||||
    
 | 
			
		||||
    var fn = next.callback,
 | 
			
		||||
        context = next.context || this;
 | 
			
		||||
    
 | 
			
		||||
    //Add the callback that will be used when done
 | 
			
		||||
    args.push(callback);
 | 
			
		||||
    
 | 
			
		||||
    fn.apply(context, args);
 | 
			
		||||
  };
 | 
			
		||||
  
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns a validation function based on the type defined in the schema
 | 
			
		||||
   *
 | 
			
		||||
   * @param {RegExp|String|Function} validator
 | 
			
		||||
   * @return {Function}
 | 
			
		||||
   */
 | 
			
		||||
  helpers.getValidator = function(validator) {
 | 
			
		||||
    var validators = Form.validators;
 | 
			
		||||
 | 
			
		||||
    //Convert regular expressions to validators
 | 
			
		||||
    if (_.isRegExp(validator)) {
 | 
			
		||||
      return validators.regexp({ regexp: validator });
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    //Use a built-in validator if given a string
 | 
			
		||||
    if (_.isString(validator)) {
 | 
			
		||||
      if (!validators[validator]) throw new Error('Validator "'+validator+'" not found');
 | 
			
		||||
      
 | 
			
		||||
      return validators[validator]();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    //Functions can be used directly
 | 
			
		||||
    if (_.isFunction(validator)) return validator;
 | 
			
		||||
 | 
			
		||||
    //Use a customised built-in validator if given an object
 | 
			
		||||
    if (_.isObject(validator) && validator.type) {
 | 
			
		||||
      var config = validator;
 | 
			
		||||
      
 | 
			
		||||
      return validators[config.type](config);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    //Unkown validator type
 | 
			
		||||
    throw new Error('Invalid validator: ' + validator);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  return helpers;
 | 
			
		||||
 | 
			
		||||
})();
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,8 @@
 | 
			
		|||
 | 
			
		||||
  //SETUP
 | 
			
		||||
  
 | 
			
		||||
  //Add function shortcuts
 | 
			
		||||
  Form.setTemplates = Form.helpers.setTemplates;
 | 
			
		||||
  Form.setTemplateCompiler = Form.helpers.setTemplateCompiler;
 | 
			
		||||
 | 
			
		||||
  Form.templates = {};
 | 
			
		||||
							
								
								
									
										43
									
								
								sites/all/modules/civicrm/packages/backbone-forms/src/templates/bootstrap.css
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								sites/all/modules/civicrm/packages/backbone-forms/src/templates/bootstrap.css
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,43 @@
 | 
			
		|||
/* Date */
 | 
			
		||||
.bbf-date .bbf-date {
 | 
			
		||||
  width: 4em
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.bbf-date .bbf-month {
 | 
			
		||||
  width: 9em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.bbf-date .bbf-year {
 | 
			
		||||
  width: 5em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* DateTime */
 | 
			
		||||
.bbf-datetime select {
 | 
			
		||||
  width: 4em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* List */
 | 
			
		||||
.bbf-list .bbf-add {
 | 
			
		||||
  margin-top: -10px
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.bbf-list li {
 | 
			
		||||
  margin-bottom: 5px
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.bbf-list .bbf-del {
 | 
			
		||||
  margin-left: 4px
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* List.Modal */
 | 
			
		||||
.bbf-list-modal {
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  border: 1px solid #ccc;
 | 
			
		||||
  width: 208px;
 | 
			
		||||
  border-radius: 3px;
 | 
			
		||||
  padding: 4px;
 | 
			
		||||
  color: #555;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										80
									
								
								sites/all/modules/civicrm/packages/backbone-forms/src/templates/bootstrap.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								sites/all/modules/civicrm/packages/backbone-forms/src/templates/bootstrap.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,80 @@
 | 
			
		|||
  
 | 
			
		||||
  //TWITTER BOOTSTRAP TEMPLATES
 | 
			
		||||
  //Requires Bootstrap 2.x
 | 
			
		||||
  Form.setTemplates({
 | 
			
		||||
 | 
			
		||||
    //HTML
 | 
			
		||||
    form: '\
 | 
			
		||||
      <form class="form-horizontal">{{fieldsets}}</form>\
 | 
			
		||||
    ',
 | 
			
		||||
 | 
			
		||||
    fieldset: '\
 | 
			
		||||
      <fieldset>\
 | 
			
		||||
        <legend>{{legend}}</legend>\
 | 
			
		||||
        {{fields}}\
 | 
			
		||||
      </fieldset>\
 | 
			
		||||
    ',
 | 
			
		||||
 | 
			
		||||
    field: '\
 | 
			
		||||
      <div class="control-group field-{{key}}">\
 | 
			
		||||
        <label class="control-label" for="{{id}}">{{title}}</label>\
 | 
			
		||||
        <div class="controls">\
 | 
			
		||||
          {{editor}}\
 | 
			
		||||
          <div class="help-inline">{{error}}</div>\
 | 
			
		||||
          <div class="help-block">{{help}}</div>\
 | 
			
		||||
        </div>\
 | 
			
		||||
      </div>\
 | 
			
		||||
    ',
 | 
			
		||||
 | 
			
		||||
    nestedField: '\
 | 
			
		||||
      <div class="field-{{key}}">\
 | 
			
		||||
        <div title="{{title}}" class="input-xlarge">{{editor}}\
 | 
			
		||||
          <div class="help-inline">{{error}}</div>\
 | 
			
		||||
        </div>\
 | 
			
		||||
        <div class="help-block">{{help}}</div>\
 | 
			
		||||
      </div>\
 | 
			
		||||
    ',
 | 
			
		||||
 | 
			
		||||
    list: '\
 | 
			
		||||
      <div class="bbf-list">\
 | 
			
		||||
        <ul class="unstyled clearfix">{{items}}</ul>\
 | 
			
		||||
        <button class="btn bbf-add" data-action="add">Add</button>\
 | 
			
		||||
      </div>\
 | 
			
		||||
    ',
 | 
			
		||||
 | 
			
		||||
    listItem: '\
 | 
			
		||||
      <li class="clearfix">\
 | 
			
		||||
        <div class="pull-left">{{editor}}</div>\
 | 
			
		||||
        <button type="button" class="btn bbf-del" data-action="remove">×</button>\
 | 
			
		||||
      </li>\
 | 
			
		||||
    ',
 | 
			
		||||
 | 
			
		||||
    date: '\
 | 
			
		||||
      <div class="bbf-date">\
 | 
			
		||||
        <select data-type="date" class="bbf-date">{{dates}}</select>\
 | 
			
		||||
        <select data-type="month" class="bbf-month">{{months}}</select>\
 | 
			
		||||
        <select data-type="year" class="bbf-year">{{years}}</select>\
 | 
			
		||||
      </div>\
 | 
			
		||||
    ',
 | 
			
		||||
 | 
			
		||||
    dateTime: '\
 | 
			
		||||
      <div class="bbf-datetime">\
 | 
			
		||||
        <p>{{date}}</p>\
 | 
			
		||||
        <p>\
 | 
			
		||||
          <select data-type="hour" style="width: 4em">{{hours}}</select>\
 | 
			
		||||
          :\
 | 
			
		||||
          <select data-type="min" style="width: 4em">{{mins}}</select>\
 | 
			
		||||
        </p>\
 | 
			
		||||
      </div>\
 | 
			
		||||
    ',
 | 
			
		||||
 | 
			
		||||
    'list.Modal': '\
 | 
			
		||||
      <div class="bbf-list-modal">\
 | 
			
		||||
        {{summary}}\
 | 
			
		||||
      </div>\
 | 
			
		||||
    '
 | 
			
		||||
  }, {
 | 
			
		||||
  
 | 
			
		||||
    //CLASSNAMES
 | 
			
		||||
    error: 'error' //Set on the field tag when validation fails
 | 
			
		||||
  });
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,140 @@
 | 
			
		|||
/* Form */
 | 
			
		||||
.bbf-form {
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  padding: 0;
 | 
			
		||||
  border: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* Field */
 | 
			
		||||
.bbf-field {
 | 
			
		||||
  margin: 1em 0;
 | 
			
		||||
  list-style-type: none;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  clear: both;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
  .bbf-field label {
 | 
			
		||||
    float: left;
 | 
			
		||||
    width: 25%;
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  .bbf-field .bbf-editor {
 | 
			
		||||
    margin-left: 25%;
 | 
			
		||||
    width: 74%;
 | 
			
		||||
  }
 | 
			
		||||
      
 | 
			
		||||
    .bbf-field input, .bbf-field textarea, .bbf-field select {
 | 
			
		||||
      width: 100%;
 | 
			
		||||
    }
 | 
			
		||||
  
 | 
			
		||||
  .bbf-field .bbf-help {
 | 
			
		||||
    margin-left: 25%;
 | 
			
		||||
    width: 74%;
 | 
			
		||||
    color: #999;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .bbf-field .bbf-error {
 | 
			
		||||
    margin-left: 25%;
 | 
			
		||||
    width: 74%;
 | 
			
		||||
    color: red;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .bbf-field.bbf-error .bbf-editor {
 | 
			
		||||
    outline: 1px solid red;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* Radio */
 | 
			
		||||
.bbf-radio {
 | 
			
		||||
  list-style-type: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
  .bbf-radio input {
 | 
			
		||||
    width: auto;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .bbf-radio label {
 | 
			
		||||
    float: none;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* Checkboxes */
 | 
			
		||||
.bbf-checkboxes {
 | 
			
		||||
  list-style-type: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
  .bbf-checkboxes input {
 | 
			
		||||
    width: auto;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .bbf-checkboxes label {
 | 
			
		||||
    float: none;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* List */
 | 
			
		||||
.bbf-list ul {
 | 
			
		||||
  list-style-type: none;
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  padding: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
  .bbf-list .bbf-error {
 | 
			
		||||
    border: 1px solid red;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .bbf-list li {
 | 
			
		||||
    clear: both;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    .bbf-list li .bbf-editor-container {
 | 
			
		||||
      margin-right: 2em;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .bbf-list li .bbf-remove {
 | 
			
		||||
      float: right;
 | 
			
		||||
      width: 2em;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
.bbf-list .bbf-actions {
 | 
			
		||||
  text-align: center;
 | 
			
		||||
  clear: both;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* List.Modal */
 | 
			
		||||
.bbf-list-modal {
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  border: 1px solid #ccc;
 | 
			
		||||
  width: 208px;
 | 
			
		||||
  border-radius: 3px;
 | 
			
		||||
  padding: 4px;
 | 
			
		||||
  color: #555;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* Date */
 | 
			
		||||
.bbf-date .bbf-date {
 | 
			
		||||
  width: 4em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.bbf-date .bbf-month {
 | 
			
		||||
  width: 9em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.bbf-date .bbf-year {
 | 
			
		||||
  width: 6em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* DateTime */
 | 
			
		||||
.bbf-datetime .bbf-date-container {
 | 
			
		||||
  float: left;
 | 
			
		||||
  margin-right: 1em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.bbf-datetime select {
 | 
			
		||||
  width: 4em;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,76 @@
 | 
			
		|||
 | 
			
		||||
  //DEFAULT TEMPLATES
 | 
			
		||||
  Form.setTemplates({
 | 
			
		||||
    
 | 
			
		||||
    //HTML
 | 
			
		||||
    form: '\
 | 
			
		||||
      <form class="bbf-form">{{fieldsets}}</form>\
 | 
			
		||||
    ',
 | 
			
		||||
    
 | 
			
		||||
    fieldset: '\
 | 
			
		||||
      <fieldset>\
 | 
			
		||||
        <legend>{{legend}}</legend>\
 | 
			
		||||
        <ul>{{fields}}</ul>\
 | 
			
		||||
      </fieldset>\
 | 
			
		||||
    ',
 | 
			
		||||
    
 | 
			
		||||
    field: '\
 | 
			
		||||
      <li class="bbf-field field-{{key}}">\
 | 
			
		||||
        <label for="{{id}}">{{title}}</label>\
 | 
			
		||||
        <div class="bbf-editor">{{editor}}</div>\
 | 
			
		||||
        <div class="bbf-help">{{help}}</div>\
 | 
			
		||||
        <div class="bbf-error">{{error}}</div>\
 | 
			
		||||
      </li>\
 | 
			
		||||
    ',
 | 
			
		||||
 | 
			
		||||
    nestedField: '\
 | 
			
		||||
      <li class="bbf-field bbf-nested-field field-{{key}}" title="{{title}}">\
 | 
			
		||||
        <label for="{{id}}">{{title}}</label>\
 | 
			
		||||
        <div class="bbf-editor">{{editor}}</div>\
 | 
			
		||||
        <div class="bbf-help">{{help}}</div>\
 | 
			
		||||
        <div class="bbf-error">{{error}}</div>\
 | 
			
		||||
      </li>\
 | 
			
		||||
    ',
 | 
			
		||||
 | 
			
		||||
    list: '\
 | 
			
		||||
      <div class="bbf-list">\
 | 
			
		||||
        <ul>{{items}}</ul>\
 | 
			
		||||
        <div class="bbf-actions"><button type="button" data-action="add">Add</div>\
 | 
			
		||||
      </div>\
 | 
			
		||||
    ',
 | 
			
		||||
 | 
			
		||||
    listItem: '\
 | 
			
		||||
      <li>\
 | 
			
		||||
        <button type="button" data-action="remove" class="bbf-remove">×</button>\
 | 
			
		||||
        <div class="bbf-editor-container">{{editor}}</div>\
 | 
			
		||||
      </li>\
 | 
			
		||||
    ',
 | 
			
		||||
 | 
			
		||||
    date: '\
 | 
			
		||||
      <div class="bbf-date">\
 | 
			
		||||
        <select data-type="date" class="bbf-date">{{dates}}</select>\
 | 
			
		||||
        <select data-type="month" class="bbf-month">{{months}}</select>\
 | 
			
		||||
        <select data-type="year" class="bbf-year">{{years}}</select>\
 | 
			
		||||
      </div>\
 | 
			
		||||
    ',
 | 
			
		||||
 | 
			
		||||
    dateTime: '\
 | 
			
		||||
      <div class="bbf-datetime">\
 | 
			
		||||
        <div class="bbf-date-container">{{date}}</div>\
 | 
			
		||||
        <select data-type="hour">{{hours}}</select>\
 | 
			
		||||
        :\
 | 
			
		||||
        <select data-type="min">{{mins}}</select>\
 | 
			
		||||
      </div>\
 | 
			
		||||
    ',
 | 
			
		||||
 | 
			
		||||
    'list.Modal': '\
 | 
			
		||||
      <div class="bbf-list-modal">\
 | 
			
		||||
        {{summary}}\
 | 
			
		||||
      </div>\
 | 
			
		||||
    '
 | 
			
		||||
  }, {
 | 
			
		||||
 | 
			
		||||
    //CLASSNAMES
 | 
			
		||||
    error: 'bbf-error'
 | 
			
		||||
 | 
			
		||||
  });
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,105 @@
 | 
			
		|||
  
 | 
			
		||||
//==================================================================================================
 | 
			
		||||
//VALIDATORS
 | 
			
		||||
//==================================================================================================
 | 
			
		||||
 | 
			
		||||
Form.validators = (function() {
 | 
			
		||||
 | 
			
		||||
  var validators = {};
 | 
			
		||||
 | 
			
		||||
  validators.errMessages = {
 | 
			
		||||
    required: 'Required',
 | 
			
		||||
    regexp: 'Invalid',
 | 
			
		||||
    email: 'Invalid email address',
 | 
			
		||||
    url: 'Invalid URL',
 | 
			
		||||
    match: 'Must match field "{{field}}"'
 | 
			
		||||
  };
 | 
			
		||||
  
 | 
			
		||||
  validators.required = function(options) {
 | 
			
		||||
    options = _.extend({
 | 
			
		||||
      type: 'required',
 | 
			
		||||
      message: this.errMessages.required
 | 
			
		||||
    }, options);
 | 
			
		||||
     
 | 
			
		||||
    return function required(value) {
 | 
			
		||||
      options.value = value;
 | 
			
		||||
      
 | 
			
		||||
      var err = {
 | 
			
		||||
        type: options.type,
 | 
			
		||||
        message: Form.helpers.createTemplate(options.message, options)
 | 
			
		||||
      };
 | 
			
		||||
      
 | 
			
		||||
      if (value === null || value === undefined || value === false || value === '') return err;
 | 
			
		||||
    };
 | 
			
		||||
  };
 | 
			
		||||
  
 | 
			
		||||
  validators.regexp = function(options) {
 | 
			
		||||
    if (!options.regexp) throw new Error('Missing required "regexp" option for "regexp" validator');
 | 
			
		||||
  
 | 
			
		||||
    options = _.extend({
 | 
			
		||||
      type: 'regexp',
 | 
			
		||||
      message: this.errMessages.regexp
 | 
			
		||||
    }, options);
 | 
			
		||||
    
 | 
			
		||||
    return function regexp(value) {
 | 
			
		||||
      options.value = value;
 | 
			
		||||
      
 | 
			
		||||
      var err = {
 | 
			
		||||
        type: options.type,
 | 
			
		||||
        message: Form.helpers.createTemplate(options.message, options)
 | 
			
		||||
      };
 | 
			
		||||
      
 | 
			
		||||
      //Don't check empty values (add a 'required' validator for this)
 | 
			
		||||
      if (value === null || value === undefined || value === '') return;
 | 
			
		||||
 | 
			
		||||
      if (!options.regexp.test(value)) return err;
 | 
			
		||||
    };
 | 
			
		||||
  };
 | 
			
		||||
  
 | 
			
		||||
  validators.email = function(options) {
 | 
			
		||||
    options = _.extend({
 | 
			
		||||
      type: 'email',
 | 
			
		||||
      message: this.errMessages.email,
 | 
			
		||||
      regexp: /^[\w\-]{1,}([\w\-\+.]{1,1}[\w\-]{1,}){0,}[@][\w\-]{1,}([.]([\w\-]{1,})){1,3}$/
 | 
			
		||||
    }, options);
 | 
			
		||||
    
 | 
			
		||||
    return validators.regexp(options);
 | 
			
		||||
  };
 | 
			
		||||
  
 | 
			
		||||
  validators.url = function(options) {
 | 
			
		||||
    options = _.extend({
 | 
			
		||||
      type: 'url',
 | 
			
		||||
      message: this.errMessages.url,
 | 
			
		||||
      regexp: /^(http|https):\/\/(([A-Z0-9][A-Z0-9_\-]*)(\.[A-Z0-9][A-Z0-9_\-]*)+)(:(\d+))?\/?/i
 | 
			
		||||
    }, options);
 | 
			
		||||
    
 | 
			
		||||
    return validators.regexp(options);
 | 
			
		||||
  };
 | 
			
		||||
  
 | 
			
		||||
  validators.match = function(options) {
 | 
			
		||||
    if (!options.field) throw new Error('Missing required "field" options for "match" validator');
 | 
			
		||||
    
 | 
			
		||||
    options = _.extend({
 | 
			
		||||
      type: 'match',
 | 
			
		||||
      message: this.errMessages.match
 | 
			
		||||
    }, options);
 | 
			
		||||
    
 | 
			
		||||
    return function match(value, attrs) {
 | 
			
		||||
      options.value = value;
 | 
			
		||||
      
 | 
			
		||||
      var err = {
 | 
			
		||||
        type: options.type,
 | 
			
		||||
        message: Form.helpers.createTemplate(options.message, options)
 | 
			
		||||
      };
 | 
			
		||||
      
 | 
			
		||||
      //Don't check empty values (add a 'required' validator for this)
 | 
			
		||||
      if (value === null || value === undefined || value === '') return;
 | 
			
		||||
      
 | 
			
		||||
      if (value !== attrs[options.field]) return err;
 | 
			
		||||
    };
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  return validators;
 | 
			
		||||
 | 
			
		||||
})();
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue