Giter VIP home page Giter VIP logo

backbone.stickit's Introduction

Help Wanted!

-> Documentation for current/stable release: 0.9.2

The following is documentation for the code in master/edge version...

Introduction

Backbone's philosophy is for a View, the display of a Model's state, to re-render after any changes have been made to the Model. This works beautifully for simple apps, but rich apps often need to render, respond, and synchronize changes with finer granularity.

Stickit is a Backbone data binding plugin that binds Model attributes to View elements with a myriad of options for fine-tuning a rich app experience. Unlike most model binding plugins, Stickit does not require any extra markup in your html; in fact, Stickit will clean up your templates, as you will need to interpolate fewer variables (if any at all) while rendering. In Backbone style, Stickit has a simple and flexible api which plugs in nicely to a View's lifecycle.

Download + Source

download v0.9.2

download master/edge

view annotated source

Usage

Similar to view.events, you can define view.bindings to map selectors to binding configurations. The following bindings configuration will bind the view.$('#title') element to the title model attribute and the view.$('#author') element to the authorName model attribute:

  bindings: {
    '#title': 'title',
    '#author': 'authorName'
  }

When the view's html is rendered, usually the last call will be to stickit. By convention, and in the following example, stickit will use view.model and the view.bindings configuration to initialize:

  render: function() {
    this.$el.html('<div id="title"/> <input id="author" type="text">');
    this.stickit();
  }

On the initial call, stickit will initialize the innerHTML of view.$('#title') with the value of the title model attribute, and will setup a one-way binding (model->view) so that any time a model change:title event is triggered, the view.$('#title') element will reflect those changes. For form elements, like view.$('#author'), stickit will configure a two-way binding (model<->view), connecting and reflecting changes in the view elements with changes in bound model attributes.

API

stickit

view.stickit(optionalModel, optionalBindingsConfig)

Uses view.bindings and view.model to setup bindings. Optionally, you can pass in a model and bindings hash. Note, it is safe to re-render or call stickit multiple times, as stickit will match any previously bound selectors and their associated models and unbind them before reinitializing.

  render: function() {
    this.$el.html(/* ... */);
    // Initialize stickit with view.bindings and view.model
    this.stickit();
    // In addition to, or instead, call stickit with a different model and bindings configuration.
    this.stickit(this.otherModel, this.otherBindings);
  }

addBinding

view.addBinding(optionalModel, selector, configuration)

Adds a single binding to the view, using the given model, or view.model, and the given selector and configuration. It's also possible to pass in a bindings hash as the second parameter. If you use a selector that was already used for a binding, then the old binding will be destroyed before initializing the new binding.

  // Short-form selector.
  this.addBinding(null, '#author', 'author');
  // With configuration.
  this.addBinding(null, '#author', {observe:'author', onGet: function() {/* ... */}});
  // Or, with a bindings hash.
  this.addBinding(null, {
    '#author': {
      observe: 'author',
      onGet: function() {/* ... */}
  });

unstickit

view.unstickit(optionalModel, optionalSelector)

Removes event bindings from all models. Optionally, a model can be passed in which will remove events for the given model and its corresponding bindings configuration only. Another option is to pass in a binding selector or bindings hash to granularly remove any bindings that are associated with this.model or the given model. Note, Stickit is setup to automatically unbind all bindings associated with a view on view.remove().

For each model that is unbound, a stickit:unstuck event will be triggered, and for each binding that is unbound the destroy callback will be executed.

Bindings

The view.bindings is a hash of jQuery or Zepto selector keys with binding configuration values. Similar to the callback definitions configured in view.events, bindings callbacks can be defined as the name of a method on the view or a direct function body. view.bindings may also be defined as a function.

Once you are familiarized with the bindings callbacks, use this reference for a better idea of when they are called.

observe

A string, function, or array which is used to map a model attribute to a view element. If binding to observe is the only configuration needed, then it can be written in short form where the attribute name is the value of the whole binding configuration.

Notes on binding to an array of attributes: when binding from model->view, this configuration should be paired with an onGet callback that can unpack/format the values. When binding from view->model, then onSet or getVal should be defined and should return an array of values that stickit will set into the model.

  bindings: {
    // Short form binding
    '#author': 'author',

    // Normal binding
    '#title': {
      observe: 'title'
    },

    // Bind to multiple model attributes
    '#header': {
      observe: ['title', 'author'],
      onGet: function(values) {
        // onGet called after title *or* author model attributes change.
        return values[0] + '-' + values[1];
      },
      onSet: function(value) {
        return value.split('-');
      }
    }
  }

  // Defined bindings as a function.
  bindings: function() {
    return {
      '#title': {
        observe: 'title'
      }
    };
  }

:el (selector)

A special selector value that binds to the view delegate (view.$el).

  tagName: 'form',
  bindings: {
    ':el': {
      observe: 'title'
      onGet: function(value) { /* ... */ }
    }
  }

onGet

A callback which returns a formatted version of the model attribute value that is passed in before setting it in the bound view element.

  bindings: {
    '#header': {
      observe: 'headerName',
      onGet: 'formatHeader'
    }
  },
  formatHeader: function(value, options) {
    return options.observe + ': ' + value;
  }

onSet

A callback which prepares a formatted version of the view value before setting it in the model.

  bindings: {
    '#author': {
      observe: 'author',
      onSet: 'addByline'
    }
  },
  addByline: function(val, options) {
    return 'by ' + val;
  }

getVal

A callback which overrides stickit's default handling for retrieving the value from the bound view element. Use onSet to format values - this is better used in handlers or when extra/different dom operations need to be handled.

  bindings: {
    '#author': {
      observe: 'author',
      getVal: function($el, event, options) { return $el.val(); }
    }
  }

update

A callback which overrides stickit's default handling for updating the value of a bound view element. Use onGet to format model values - this is better used in handlers or when extra/different dom operations need to be handled .

  bindings: {
    '#author': {
      observe: 'author',
      update: function($el, val, model, options) { $el.val(val); }
    }
  }

updateModel

A boolean value or a function that returns a boolean value which controls whether or not the model gets changes/updates from the view (model<-view). This is only relevant to form elements, as they have two-way bindings with changes that can be reflected into the model. Defaults to true.

  bindings: {
    '#title': {
      observe: 'title',
      updateModel: 'confirmFormat'
    }
  },
  confirmFormat: function(val, event, options) {
    // Only update the title attribute if the value starts with "by".
    return val.startsWith('by ');
  }

updateView

A boolean value or a function that returns a boolean value which controls whether or not the bound view element gets changes/updates from the model (view<-model). Defaults to true.

bindings: {
  '#title': {
    observe: 'title',
    // Any changes to the model will not be reflected to the view.
    updateView: false
  }
}

afterUpdate

Called after a value is updated in the dom.

  bindings: {
    '#warning': {
      observe: 'warningMessage',
      afterUpdate: 'highlight'
    }
  },
  highlight: function($el, val, options) {
    $el.fadeOut(500, function() { $(this).fadeIn(500); });
  }

updateMethod

Method used to update the inner value of the view element. Defaults to 'text', but 'html' may also be used to update the dom element's innerHTML.

  bindings: {
    '#header': {
      observe: 'headerName',
      updateMethod: 'html',
      onGet: function(val) { return '<div id="headerVal">' + val + '</div>'; }
    }
  }

escape

A boolean which when true escapes the model before setting it in the view - internally, gets the attribute value by calling model.escape('attribute'). This is only useful when updateMethod is "html".

  bindings: {
    '#header': {
      observe: 'headerName',
      updateMethod: 'html',
      escape: true
    }
  }

initialize

Called for each binding after it is configured in the initial call to stickit(). Useful for setting up third-party plugins, see the handlers section for examples.

  bindings: {
    '#album': {
      observe: 'exai',
      initialize: function($el, model, options) {
        // Setup a Chosen or thirdy-party plugin for this bound element.
      }
    }
  }

destroy

Called for each binding after it is unstuck from the model and view. Useful for tearing down third-party plugins or events that were configured in initialze.

  bindings: {
    '#album': {
      observe: 'Tomorrow\'s Harvest',
      destroy: function($el, model, options) {
        // Tear down any events or clean up.
      }
    }
  }

visible and visibleFn

When true, visible shows or hides the view element based on the model attribute's truthiness. visible may also be defined with a callback which should return a truthy value. The updateView option defaults to false when using visible. You must opt-in to updateView in order to have both view element visibility and value changes bound to the observed attribute.

If more than the standard jQuery show/hide is required, then you can manually take control by defining visibleFn with a callback.

  bindings: {
    '#author': {
      observe: 'isDeleuze',
      visible: true
    }
  }
  bindings: {
    '#title': {
      observe: 'title',
      visible: function(val, options) { return val == 'Mille Plateaux'; },
      updateView: true
    }
  }
  bindings: {
    '#body': {
      observe: 'isWithoutOrgans',
      visible: true,
      visibleFn: 'slideFast'
    }
  },
  slideFast: function($el, isVisible, options) {
    if (isVisible) $el.slideDown('fast');
    else $el.slideUp('fast');
  }

Form Element Bindings and Contenteditable

By default, form and contenteditable elements will be configured with two-way bindings, syncing changes in the view elements with model attributes. Optionally, one-way bindings can be configured with updateView or updateModel. With the events, you can specify a different set of events to use for reflecting changes to the model.

The following is a list of the supported form elements, their binding details, and the default events used for binding:

  • input, textarea, and contenteditable
    • element value synced with model attribute value
    • propertychange, input, change events are used for handling
  • input[type=checkbox]
    • checked property determined by the truthiness of the model attribute or if the checkbox "value" attribute is defined, then its value is used to match against the model. If a binding selector matches multiple checkboxes then it is expected that the observed model attribute will be an array of values to match against the checkbox value attributes.
    • change event is used for handling
  • input[type=radio]
    • model attribute value matched to a radio group value attribute
    • change event is used for handling
  • select
    • (recommended) specify selectOptions to have Stickit handle the rendering and option bindings of your select (see selectOptions)
    • if you choose to pre-render your select-options (not recommended) then there are two ways of configuring the bindings:
      • "data-stickit-bind-val" attributes in the DOM. This allows for binding non-string values from a prerendered <select>, assuming that you are using jQuery or a build of Zepto that includes the "data" module.
      • "option[value]" attributes in the DOM (used if no data-stickit-bind-val is present)
    • change event is used for handling

events

Specify a list of events which will override stickit's default events for a form element. Bound events control when the model is updated with changes in the view element.

  bindings: {
    'input#title': {
      observe: 'title',
      // Normally, stickit would bind `keyup`, `change`, `cut`, and `paste` events
      // to an input:text element. The following will override these events and only
      // update/set the model after the input#title element is blur'ed.
      events: ['blur']
    }
  }

selectOptions

With the given collection, creates <option>s for the bound <select>, and binds their selected values to the observed model attribute. It is recommended to use selectOptions instead of pre-rendering select-options since Stickit will render them and can bind Objects, Arrays, and non-String values as data to the <option> values. The following are configuration options for binding:

  • collection: an object path of a collection relative to window or view/this, or a string function reference which returns a collection of objects. A collection should be an array of objects, a Backbone.Collection or a value/label map.
  • labelPath: the path to the label value for select options within the collection of objects. Default value when undefined is label.
  • valuePath: the path to the values for select options within the collection of objects. When an options is selected, the value that is defined for the given option is set in the model. Leave this undefined if the whole object is the value or to use the default value.
  • defaultOption: an object or method that returns an object with label and value keys, used to define a default option value. A common use case would be something like the following: {label: "Choose one...", value: null}.

When bindings are initialized, Stickit will build the <select> element with the <option>s and bindings configured. selectOptions are not required - if left undefined, then Stickit will expect that the <option>s are pre-rendered and build the collection from the DOM.

Note: if you are using Zepto and referencing object values for your select options, like in the second example, then you will need to also include the Zepto data module. Also, <select> bindings are two-way bindings only - updateView:false will be ignored.

The following example references a collection of stooges at window.app.stooges and uses the age attribute for labels and the name attribute for option values:

  window.app.stooges = [{name:'moe', age:40}, {name:'larry', age:50}, {name:'curly', age:60}];
  bindings: {
    'select#stooges': {
      observe: 'stooge',
      selectOptions: {
        // Alternatively, `this` can be used to reference anything in the view's scope.
        // For example: `collection:'this.stooges'` would reference `view.stooges`.
        collection: 'window.app.stooges',
        labelPath: 'age',
        valuePath: 'name'
    }
  }

The following is an example where the default label and value are used along with a defaultOption:

  bindings: {
    'select#stooges': {
      observe: 'stooge',
      selectOptions: {
        collection: function() {
          // No need for `labelPath` or `valuePath` since the defaults
          // `label` and `value` are used in the collection.
          return [{value:1, label:'OH'}, {value:2, label:{name:'IN'}}];
        },
        defaultOption: {
          label: 'Choose one...',
          value: null
        }
    }
  }

The following is an example where a collection is returned by callback and the collection objects are used as option values:

  bindings: {
    'select#states': {
      observe: 'state',
      selectOptions: {
        collection: function() {
          return [{id:1, data:{name:'OH'}}, {id:2, data:{name:'IN'}}];
        },
        labelPath: 'data.name'
        // Leaving `valuePath` undefined so that the collection objects are used
        // as option values. For example, if the "OH" option was selected, then the
        // following value would be set into the model: `model.set('state', {id:1, data:{name:'OH'}});`
    }
  }

Optgroups are supported, where the collection is formatted into an object with an opt_labels key that specifies the opt label names and order.

  bindings: {
    'select#tv-characters': {
      observe: 'character',
      selectOptions: {
        collection: function() {
          return {
            'opt_labels': ['Looney Tunes', 'Three Stooges'],
            'Looney Tunes': [{id: 1, name: 'Bugs Bunny'}, {id: 2, name: 'Donald Duck'}],
            'Three Stooges': [{id: 3, name: 'moe'}, {id: 4, name: 'larry'}, {id: 5, name: 'curly'}]
          };
        },
        labelPath: 'name',
        valuePath: 'id'
      }
    }
  }

It is often useful to have a lookup table for converting between underlying values which are actually stored and transmitted and the human-readable labels that represent them. Such a lookup table (an object like { value1: label1, value2: label2 }) can be used to populate a select directly. By default, the options will be sorted alphabetically by label; pass a comparatorfunction or property name string to override this ordering (which delegates to _.sortBy).

  bindings: {
    'select#sounds': {
      observe: 'sound',
      selectOptions: {
        collection: {
          moo: 'cow',
          baa: 'sheep',
          oink: 'pig'
        }
      }
    }
  }

Finally, multiselects are supported if the select element contains the [multiple="true"] attribute. By default stickit will expect that the model attribute is an array of values, but if your model has a formatted value, you can use onGet and onSet to format attribute values (this applies to any select bindings).

//
// model.get('books') returns a dash-delimited list of book ids: "1-2-4"

bindings: {
  '#books': {
    observe: 'books',
    onGet: function(val) {
      // Return an array of the ids so that stickit can match them to select options.
      return _.map(val.split('-'), Number);
    },
    onSet: function(vals) {
      // Format the array of ids into a dash-delimited String before setting.
      return vals.join('-');
    },
    selectOptions: {
      collection: 'app.books',
      labelPath: 'name',
      valuePath: 'id'
    }
  }
}

setOptions

An object which is used as the set options when setting values in the model. This is only used when binding to form elements, as their changes would update the model.

  bindings: {
    'input#name': {
      observe: 'name',
      setOptions: {silent:true}
    }
  }

stickitChange

A property that is passed into the set options when stickit changes a model attribute. The value of this property is assigned to the binding configuration.

model.on('change:observed', function(m, v, options) {
  if (options.stickitChange) {
    ...
  } else {
    ...
  }
});

Attribute and Property Bindings

attributes

Binds element attributes and properties with observed model attributes, using the following options:

  • name: attribute or property name.
  • observe: observes the given model attribute. If left undefined, then the main configuration observe is observed.
  • onGet: formats the observed model attribute value before it is set in the matched element.
  bindings: {
    '#header': {
      attributes: [{
        name: 'class',
        observe: 'hasWings',
        onGet: 'formatWings'
      }, {
        name: 'readonly',
        observe: 'isLocked'
      }]
    }
  },
  formatWings: function(val) {
    return val ? 'has-wings' : 'no-wings';
  }

classes

Binds element classes with observed model attributes. Following bindings configuration will bind view.$('#header') element classes to hasWater, hasFire and airVolume model attributes. water class will be set to element if hasWater model attribute has a truthy value. The same logic applies to fire class. air class will be set to element only if airVolume model attribute value will satisfy value > 1 condition.

  bindings: {
    '#header': {
      classes: {
        water: 'hasWater',
        fire: {
          observe: 'hasFire'
        },
        air: {
          observe: 'airVolume',
          onGet: function(volume) {
            return volume > 1;
          }
        }
      }
    }
  }

Custom Handlers

addHandler

Backbone.Stickit.addHandler(handler_s)

Adds the given handler or array of handlers to Stickit. A handler is a binding configuration, with an additional selector key, that is used to customize or override any of Stickit's default binding handling. To derive a binding configuration, the selectors are used to match against a bound element, and any matching handlers are mixed/extended in the order that they were added.

Internally, Stickit uses addHandler to add configuration for its default handling. For example, the following is the internal handler that matches against textarea elements:

Backbone.Stickit.addHandler({
  selector: 'textarea',
  events: ['keyup', 'change', 'paste', 'cut'],
  update: function($el, val) { $el.val(val); },
  getVal: function($el) { return $el.val(); }
})

Except for the selector, those keys should look familiar since they belong to the binding configuration api. If unspecified, the following keys are defaulted for handlers: updateModel:true, updateView:true, updateMethod:'text'.

By adding your own selector:'textarea' handler, you can override any or all of Stickit's default textarea handling. Since binding configurations are derived from handlers with matching selectors, another customization trick would be to add a handler that matches textareas with a specific class name. For example:

Backbone.Stickit.addHandler({
  selector: 'textarea.trim',
  getVal: function($el) { return $.trim($el.val()); }
})

With this handler in place, anytime you bind to a textarea, if the textarea contains a trim class then this handler will be mixed into the default textarea handler and getVal will be overridden.

Another good use for handlers is setup code for third-party plugins. At the end of View.render, it is common to include boilerplate third-party initialization code. For example the following sets up a Chosen multiselect,

render: function() {
  this.$el.html(this.template());
  this.setupChosenSelect(this.$('.friends'), 'friends');
  this.setupChosenSelect(this.$('.albums'), 'albums');
}

setupChosenSelect: function($el, modelAttr) { /* initialize Chosen for the el and map to model */ }

Instead, a handler could be setup to match bound elements that have a chosen class and initialize a Chosen multiselect for the element:

// Setup a generic, global handler for the Chosen plugin.
Backbone.Stickit.addHandler({
  selector: 'select.chosen',
  initialize: function($el, model, options) {
    $el.chosen();
    var up = function(m, v, opt) {
      if (!opt.bindKey) $el.trigger('liszt:updated');
    };
    this.listenTo(model, 'change:' + options.observe, up)
  }
});
<!-- A template for the View, marked with the chosen class -->
<select class="friends chosen" multiple="multiple"></select>
// In a View ...
bindings: {
  '.friends': {
    observe: 'friends',
    selectOptions: {
      collection: 'this.friendsCollection'
    }
  }
},
render: function() {
  this.$el.html(this.template());
  this.stickit(); // Chosen is initialized.
}

Binding Callbacks Flowchart

The following image demonstrates the order in which bindings callbacks are called after stickit is initialized, a bound model attribute changes, and a bound view element changes.

alt tag

F.A.Q.

Why Stickit?

JavaScript frameworks seem to be headed in the wrong direction - controller callbacks/directives, configuration, and special tags are being forced into the template/presentation layer. Who wants to program and debug templates?

If you are writing a custom frontend, then you're going to need to write custom JavaScript. Backbone helps you organize with a strong focus on the model, but stays the hell out of your presentation. Configuration and callbacks should only be in one place - the View/JavaScript.

Dependencies

Backbone 1.0, underscore.js, and jQuery or Zepto (with data module; see selectOptions)

License

MIT

Change Log

Master

0.9.2

Bumped allowed underscore and Backbone versions.

0.9.0

  • Breaking Change: Classes are now treated separately from other attribute bindings. Use the new classes hash to bind element classes to your attributes.
  • defaultOption can be defined as a function.
  • Passing a Backbone Collection to selectOptions will keep the select dropdown in sync with add, remove, and sort events on the collection.
  • view#stickit returns this for easier chaining.
  • Non-string values can be bound to a pre-rendered <select> dropdown by setting data-stickit-bind-val on <option> elements.

0.8.0

  • Breaking Change: Calling view#stickit a second time with the same model, will no longer unbind all previously bound bindings associated with that model; instead, it will unbind any duplicate bindings (selectors) found in the given bindings hash (or whatever's in view.bindings) before initializing.
  • Added an view#addBinding which will initiate a single, or hash, of bindings.
  • view#unstickit now takes a second, optional, parameter which gives you the control to granularly remove a single, or hash, of bindings.

0.7.0

  • Breaking Change: the bindKey that was passed into the Backbone change:attr (undocumented) options was changed to stickitChange which is assigned the binding options which have a unique bindId.
  • Breaking Change: the default events for input, textarea, and contenteditable form elements changed from [keyup, cut, paste, change] to [propertychange, input, change].
  • Breaking Change: removed support for input[type="number"]. Instead, use onSet to format Number values, if needed.
  • Breaking Change: The updateModel method parameters changed so the event is now passed as the second parameter. updateModel(val, options) -> updateModel(val, event, options)
  • Stickit will now load using the UMD pattern so it is compatible with AMD, Node.js, and CommonJS.
  • A view's bindings configuration can be defined as a function.
  • When observing an array, if onSet or getVal return an array of values, Stickit will match the values to their respective attributes defined in observe and set them in the model. If you don't desire this change, then you can override this behavior with the following change:
  • Added a set callback which by default calls model#set
  • Added the destroy binding callback to compliment initialize.
  • Trigger stickit:unstick for each model that is unbound in unstickit (or view.remove).
  • Added handling for observe in function form.
  • When binding with visible the {updateView:false} property is defaulted.
  • Stickit will no longer sanitize (convert a null/undefined model attribute value to empty string) values if onGet is defined.
  • Added support for the use of dot-notation in binding callbacks that are defined with a string that names a method on the view. For example - onGet: "myObj.myCallback".
  • Added Backbone.Stickit.getConfiguration which exposes the method of deriving configurations from handlers.
  • Fixed a bug where "null" would show in Chrome when binding attribute:null to an element value.
  • Fixed a bug where optgroup <select> handlers were rendering multiple collection.defaultOptions.

0.6.3

  • Added Backbone.Stickit.addHandler(), useful for defining a custom configuration for any bindings that match the handler.selector.
  • Breaking Change: eventsOverride was changed to events.
  • Breaking Change: removed the third param (original value) from the afterUpdate parameters.
  • Breaking Change: replaced unstickModel with unstickit.
  • Breaking Change: removed deprecated modelAttr from bindings api.
  • Breaking Change: removed deprecated format from bindings api.
  • Breaking Change: removed support for null value default/empty options in selectOptions.collection.
  • Added defaultOption to the selectOptions.
  • Added initialize to the bindings api which is called for each binding after it is initialized.
  • Fixed a bug introduced in 0.6.2 where re-rendering/re-sticking wasn't unbinding view events #66.
  • Added update to the bindings api which is an override for handling how the View element gets updated with Model changes.
  • Added getVal to the bindings api which is an override for retrieving the value of the View element.
  • Added support for passing in Backbone.Collection's into selectOptions.collection.
  • Added support for referencing the view's scope with a String selectOptions.collection reference. For example: collection:'this.viewCollection'.

0.6.2

  • Breaking Change: Changed the last parameter from the model attribute name to the bindings hash in most of the binding callbacks. Note the model attribute name can still be gleaned from the bindings hash - options.observe. The following are the callbacks that were affected and their parameters (options are the bindings hash): onGet(value, options) onSet(value, options) updateModel(value, options) updateView(value, options) afterUpdate($el, value, originalVal, options) visible(value, options) visibleFn($el, isVisible, options)
  • Added support for handling multiple checkboxes with one binding/selector and using the value attribute, if present, for checkboxes.
  • Added default values for labelPath and valuePath in selectOptions: label and value respectively.
  • Refactored event registration to use $.on and $.off instead of delegating through Backbone which fixed the following bugs:
    • view.events selectors and binding selectors that are equal were overriding #49
    • view.events declared as a function was not supported #51
  • Fixed some bugs and added support requirements for zepto.js; #58.
  • Bug Fixes: #38, #42,

0.6.1

  • Added observe in place of modelAttr (deprecated modelAttr but maintained for backward-compatibility).
  • Added onGet in place of format (deprecated format but maintained for backward-compatibility).
  • Added onSet binding for formatting values before setting into the model.
  • Added updateModel, a boolean to control changes being reflected from view to model.
  • Added updateView, a boolean to control changes being reflected from model to view.
  • Added eventsOverride which can be used to specify events for form elements that update the model.
  • Breaking Change: Removed manual event configuration/handling (no keyup, submit, etc, in binding configurations).
  • Added support for multiselect select elements.
  • Added support for optgroups within a select element.
  • Bug Fixes: #29, #31

0.6.0

  • Breaking Change: Removed readonly configurtion option.
  • Element properties (like readonly, disabled, etc.) can be configured in attributes.
  • Added custom event handling to the api - see events section in docs.
  • Added support for binding multiple model attributes in modelAttr configuration.
  • Added the visible and visibleFn binding configurations.
  • Added support for :el selector for selecting the view delegate.
  • Bug Fixes: #10, #11, #16

0.5.2

  • Fix IE7/8 select options bug (issue #9)

0.5.1

  • Shorthand binding for model attributes: '#selector':attrName.
  • Added support for input[type=number] where values will be bound to model attributes as the Number type.
  • Attribute name is passed in as the second parameter of format callbacks.
  • Bug fixes: issue #1, #2, #4, #6, #8

0.5.0

  • Initial release (extracted and cleaned up from the backend of cn.nytimes.com).

backbone.stickit's People

Contributors

akre54 avatar andriijas avatar arjenw avatar bazineta avatar bregenspan avatar buzzedword avatar caleb avatar chodge avatar costa avatar danteoh avatar davidfou avatar delambo avatar fabriziofortino avatar hlindset avatar jaysoo avatar jeremywadsack avatar joeblynch avatar johanneszorn avatar kidkarolis avatar masterlambaster avatar megawac avatar noobiek avatar raganw avatar rwhitmire avatar sixdayz avatar trabianmatt avatar underbluewaters avatar viczam avatar yamsellem avatar yousefcisco avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

backbone.stickit's Issues

Support Multiple Selects

Hello,

It looks like stickit does not support multiple select values. I propose two methods:

  1. By default join all selected items with commas (e.g. "option1,option2,option3") and store in model.
  2. Have a callback option which lets the developer format the multiple select options as they desire.

e.g.

formatData : function( values, attribute ) {
  // values is array [ 'option1', 'option2', 'option3' ]
  return values.join('-');
}

Issues with select list

I found the following issues

  1. Line 197 has no reference to model, the function "updateViewBindEl" should pass the reference to the model. changes to line 92, 95 and 168 is required to pass the model
  2. In the function to build the list box, the value of the is not getting set. This has to be added "option.val(optionVal);" after line 215 to set it.
  3. I don't like the way set (on the model) is called on each key press for text box and text area. Any validations in the model gets immediately applied and it fails as all the attributes in the model (for example - that is mapped to fields in a form) are not completed yet. Set should called upon an event which could be set as an option

Add options hash as last parameter to binding callbacks

Hi

What do you think of extending the onSet and onGet to get the options hash as third argument?

What I want to do is to have a way of updating the view (my form elements val) with whatever result that comes out of my onSet callback, not just set it on the model.

I dont have any use case for it right now, but if we get this we might as well have the other way around as well ie onGet returned value set on model.

Thanks

Change modelAttr to observe

I would like to change the modelAttr binding configuration key/name to observe for a couple of reasons:

  • observe is a common term in binding contexts and it is much more intuitive
  • it matches the observe key that is used in the attributes bindings for similar reasons
  • modelAttr isn't intuitive since we added support for binding to multiple model attributes
  • observe is succinct - "modelAttr" is two words, one which is abbreviated, and it looks ugly in the api

Since this key is the cornerstone of model view binding, I'll keep backward-compatibility for modelAttr, and I'll leave this open for discussion...

Provide first class support for showing/hiding elements

It's very common to show/hide UI elements based on properties of a model (especially common - a boolean value that maps to visible/hidden). You can currently do by binding class names with a formatter, but it's kind of clunky. It would be nice if there was first class support for controlling element visibility in stickit.

Binding image or custom binding (and some naming considerations)

If we could have a way to bind images to source URLs that would be good. Right now it just places the URL inside the body of the <img /> tag (unless I'm missing something).

Also for naming and extensibility I'm thinking of something like this:

bindings: {
  ".photo": {
    observe: "photo",

    // Methods 'get' and 'set' which are in charge of reading / writing the model
    // If set is null then model may not be written to at all
    // This could also be called 'read' and 'write'
    get: function (model, name) { model.get(name); },
    set: function (model, value, name) { model.set(name, value); },

    // Methods 'clean' and 'prepare' which are what onSet and onGet were
    // Instead of updateModel, `clean` could return undefined to signal not to set it
    prepare: function (value, name) { return _(value).prune(15); },
    clean: function (value, name) { return _(value).dasherize(); },

    // Methods 'afterUpdate' and 'update' that correspond to the view; if update is used 
    // it replaces what ever method you're using to update -- afterUpdate is just a hook that 
    // is called after the update method to do whatever (fading, etc)
    // Instead of `updateView`, just set `update` to null
    // `updateMethod` could be kept to allow easy overriding instead of replacing update
    update: function ($el, value, name) { $el.attr('src', value); },

    // Remove visibleFn and have `hide` and `show` callbacks to remove unneeded logic 
    // from consumers code.
    hide: function($el, name) { $el.fadeOut(500); },

    // For.. eventsOverride.. I prefer just 'events' but I can see why you
    // went with eventsOverride.. its just not very succinct -- perhaps another name 
  }
}

Just some thoughts to provide flexibility and make it more terse in naming. Not saying that the names must be this. These are just my thoughts on a direction I would go after seeing your awesome library if I were to build it myself. I just don't like clunky names like updateModel, updateView (one that takes a function and one that takes a boolean) everywhere. I do want to have the functionality that update provides.


For some more ideas (that should really have separate issues to discuss them):

As @andriijas mentioned, we do have some nice prototype chain code. I'll submit a PR to add this in if you're interested. This would add the ability for the binding hashes to respect inheritance.

Another thing is that you could wrap View#render to automatically invoke stickit if it has not yet been invoked (this would allow for backwards compatibility and more advanced uses like two binding hashes).

Select doesn't work when prepopulated

I see that you've added selectOptions, but it's mandatory and the library doesn't work on a select that's already pre-populated. Is there any interest in making it work for this case?

delegateEvents unbinds all already bound events?

Hi

I think the new way of binding to elements by passing an optional event hash to backbone delegateEvents unbinds already bound events via the view.events hash - ive did some testing and got very strange results.

Support configurable event bindings

Currently, stickit binds form elements with keydown and change events and doesn't provide a callback or a way to configure custom event options. Since it has been requested and I want stickit to be flexible, I would like to get some feedback/help on planning for custom event bindings. If added, I would like to include the following criteria:

  • Keep the default event handling in place so bindings can work with minimal configuration, and make any custom event configuration an override.
  • Delegate off of Backbone's events object, so the following events can be supported: keydown, keyup, keypress, mousedown, mouseup, click, doubleclick, mousemove,
    focusin, focusout, mouseenter, mouseleave, submit, change
  • Support multiple events/callbacks for one bound element.
  • Possibly handle events for non-form elements.
  • Possible callback parameters:
    • the wrapped event object ($event)
    • the bound element value
    • the current model value
    • the wrapped bound element ($el)
    • the name of the model attribute bound to the element

An idea for configuration:

bindings: {
  '#header': {
    modelAttr: 'header',
    keyup: 'keyupHandler',
    click: 'clickHandler'
  }
}

If implemented, I think that the setOptions could possibly be deprecated from the API since the user can take control of managing events and setting the model when needed.

Thoughts?

Don't break iterate on bindings's selectors after an element is not found

Hello!

I'm thinking about replacing Backbone.Modelbinder with Stickit and the bigger feature I'm missing is the fact that Modelbinder automatically search the view el for elements that has the same name attribute as a model attribute.

So I wrote a simple function to generate a valid hash for all the model attributes:

generateNameHash: function() {
      var obj;
      obj = {};
      _.each(this.model.attributes, function(val, key) {
        obj["[name=" + key + "]"] = key;
      });
      return obj;
},

The problem lies on line 59 of Stickit:

// Fail fast if the selector didn't match an element.
if (!$el.length) return false;

If Stickit found that no element exists with that selector it justs break the whole _.each cycle and so no more selector is parsed.

I understand that this behaviour may be good to catch errors but replacing return false with return true will just skip the current iteration and go to the next selector, without break anything, at least for what I can tell.

Sorry for my bad english, I hope you understand my point.

Support optgroup

I've overlooked the documentation or it is not supported-stickit does not like optgroups and will remove them from my select elements if they are in there!

Great stuff otherwise!

bind input type by name

Hi,
just started working with stickit and i would like to know if and how i can bind to
input type by 'name' property, so instead of 'input#nativeAdTitle' i can do 'input[name=nativeAdTitle]'

Thanks

Add Handlers

This is a continuation of the work that was started in #55. Big thanks to @trabianmatt for starting and inspiring this. I started a handlers branch where hopefully a few more passes can produce a final implementation.

The basic idea is to add an interface to extend or override stickit with custom handling. To achieve this, I created a Backbone.Stickit.addHandler namespace and added update and getVal to the bindings configuration. A handler is a binding configuration with an extra selector key. The selector is used to match against a bound view element and will be mixed in with other matching handlers in the order that they were added to derive a binding configuration, starting with the view bindings at the top of the mixin chain. For example, Stickit's default handler for textarea looks like the following:

Backbone.Stickit.addHandler({
  selector: 'textarea',
  events: ['keyup', 'change', 'paste', 'cut'],
  update: function($el, val) { $el.val(val); },
  getVal: function($el) { return $el.val(); }
});

A user could choose to override all or a subset of the keys in the default handler by adding a new handler with a selector:'textarea', or by adding a more selective selector that matches against a class when binding, like the following:

Backbone.Stickit.addHandler({
  selector: 'textarea.trimmed',
  update: function($el, val) { $el.val($.trim(val)); }
});

With those handlers in place, a user can override any or all handling in a binding configuration. Most of this complexity will be hidden from the user, as the default functionality of Stickit won't be changing, but it gives users a lot of flexibility to customize, like in #64 (which we may still add into core) or all the requests to add to/change select handling.

You can checkout the default handlers here.

Any thoughts on this implementation, naming, ...?

CC'ing @andriijas, @mehcode, and @trabianmatt - would love to hear your opinions if you have time.

Keyup event fired too late

There seems to be an issue with the keyup event being fired on the wrong field when quickly tabbing to the next field after typing. The keyup event is being fired on the field that was tabbed to instead of on the field that was typed in.

Steps to reproduce:

  1. Type "testing" in an input field
  2. Quickly tab away from the field

After doing this the model's attribute will be "testin", as the field never received the last keyup event.

One way bindings

Would be nice if there was a way that you could have model->dom only binding.

I've got a case where the DOM representation technically is changing which causes stickit to re-render (undesirably) even though there wasn't a model change event.

Bindings hash targets only elements of same type

The bindings hash currently assumes that the view selector targets only elements of the same type.

For instance:

<input type="range" data-bind="myAttribute">
<span data-bind="myAttribute">

wouldn't work correctly/expectedly with:

bindings: {
    [data-bind="myAttribute"]': 'myAttribute'
},

but instead could be worked around with e.g.:

bindings: {
    input[data-bind="myAttribute"]': 'myAttribute',
    span[data-bind="myAttribute"]': 'myAttribute'
},

Just fiddled around with the code, and it would require some refactoring. E.g. make sure other view elements within same selector get updated; just iterating over $el (in updateViewBindEl) needs some extra work re. checkboxes, etc.

Please let me know if

  • I'm missing something,
  • you'd prefer a PR to make it happen,
  • you'd like to leave it as it is, but make the docs a bit more clear.

Add a createDefaultBindings method

I've been, thus far, using Backbone.ModelBinder as my model binding library of choice, but that's only because I've only just stumbled onto this project. I have to say, I modified ModelBinder extensively to add features I needed, but I wish I had found your project earlier, because it already includes all of the features I added to ModelBinder.

There is, however, one convenience method in Backbone.ModelBinder that I use heavily in my projects, and that's Backbone.ModelBinder.createDefaultBindings. You can see from the comments at that link what it's supposed to do, but essentially you can pass it a DOM element (the root el from which to scan), an attribute type (usually name or ID), an optional universal converter method (like a combo version of onGet/onSet), and an optional elAttribute parameter (that specifies which element attribute you'd like the binding to act on). It scans the DOM, starting at the specified root $el, for any elements with the specified attribute, creating a binding object for each one, applying the converter/elAttribute if supplied, and returns the resulting bindings hash. You can then modify/merge/extend it with custom bindings and/or overrides before actually calling modelbinder.bind(finalBindings);

It comes in really handy when a majority of the bindings on a given view are simple "keep this field value updated" style bindings - you can just call the method to auto-create a bindings object for the majority of the elements in a view, then add or modify any non-standard bindings before bind is called. It really helps cut down on the amount of code necessary in a LOT of cases.

bindings seem to be un-sticking

// Backbone.js 0.9.2
// backbone.stickit v0.5.2

seeing an issue where bindings between attributes and DOM seem to stop working in both directions after the first @model.save is called.

not sure where to start looking to troubleshoot.

thoughts?

if one binding dom element doesn't exist, nothing renders

in your bindings object, if one of the elements doesn't exist, it appears as though it's failing silently and nothing is being rendered.

this makes it difficult for instance if you want to hide things in the dom if the user isn't logged in.

First try at adding button support

Hi

I need help with this one, if you want button support that is :)

https://github.com/andriijas/backbone.stickit/tree/buttons

The test cases I added fail, which implies there is more work to do on my changes.

For test case 1 single buttons I dont know whats wrong - the options.bindKey != bindKey check fail

For test case 2 multiple buttons I think it has to do with #62, the value of the first button is used, not the clicked one.

Thanks

Add ability to select the views $el via the selector

Hi,
first of all, thanks for this. This is the first sane approach & model <-> view bindings in backbone i´ve seen so far.

I recently was in the situation that i needed to change the 'view.el' elements class,
after digging into the sources i was not able to find a way to do that (maybe I missed smth.)

So, i added a 'custom selector' named ':el', by changing line 51 from

$el = self.$(selector);

to

$el = selector = ':el' ? self.$el : self.$(selector);

Now you can use your bindings to do smth. like this:

bindings: {
    ':el': {
        attributes: [{
            name: 'class',
            observe: 'hidden',
            format: 'toggleVisibility'
        }]
    }
}

Your thoughts?
Would love to see this in core.

Regards
Sebastian

Handling for multiple checkboxes

Looking through the project documentation, tests and getElVal(), it appears that checkbox use cases are primarily designed for a 'per checkbox' case, but documented for an ambiguous number of checkboxes. This has been causing me some issues, which I'll try to explain.

As an example, you'll see in getElVal() backbone.stickit.js function the following code:

        if (isCheckbox($el)) val = $el.prop('checked');
        else if (isNumber($el)) val = Number($el.val());
        else if (isRadio($el)) val = $el.filter(':checked').val();

For a radio button, one would actually get the 'value' of the radio button input. For checkboxes, as expected based on the above code, I'm only seeing true/false being returned. This expectation is confirmed by the checkbox unit tests that I saw in the project, which all seemed to only have 1 checkbox in them.

This all works fine, as I referenced, when only 1 checkbox is in play, but it seems to run into some issues when we start talking multiple checkboxes. You can see the start of such checkbox handling issues (I believe) in action in a simple use case in this jsfiddle:

http://jsfiddle.net/zlendon/baYtM/7/

My issue is that I don't just care what checkbox is checked, but I want to know what the value of that checkbox is. Since only the true/false value is set on the model, I don't have any access outside the 'stickit' layer to any other information about the DOM checkbox element that triggered the 'change' (or similar) JS event. If one was generating each of their DOM elements from their model, this might be less of an issue, but considering the ability to define 1-way bindings, and to be able to define binding selectors across multiple elements, this seems like it runs into some significant problems. It certainly is becoming somewhat of a significant blocker for my backbone.stickit usage efforts.

In the end, I'm not sure if this is truly an 'issue' or if it is an inappropriate usage of the 'stickit'. If you could please provide guidance/insight into how best to move forward with 'stickit' in this type of use case, I'd be most appreciative.

thanks,
Zach

Expected post-change event callbacks not happening due to bindKey constraint

We're having some issues/confusion surrounding this block of stickit code:

// Setup a change:modelAttr observer to keep the view element in sync.
// modelAttr may be an array of attributes or a single string value.
.each(.flatten([modelAttr]), function(attr) {
observeModelEvent(model, self, 'change:'+attr, function(model, val, options) {
if (options == null || options.bindKey != bindKey)
updateViewBindEl(self, $el, config, getVal(model, modelAttr, config, self), model);
});
});

What we are seeing is that if we update a DOM element that is bound to a model attribute, we do not see callbacks for the binding definition - such as 'onGet' and 'afterUpdate' invoked. Instead, DOM elements that were not modified in the UI are called. You can see this on display in the following crude JSfiddle:

http://jsfiddle.net/zlendon/USjpy/

Where the 'other' callbacks are getting logged to the console, but the input's (that were changed by the user) callbacks are not getting called. This all becomes an issue when one leverages 'onSet' to modify the user's entered value before it gets saved in the model. Because we don't get any callback after the model's change event, we are seemingly missing a hook to go back and update the DOM element. I can somewhat understand the argument that we don't want to "surprise" the user by changing a value from underneath them, but I can give you an example use-case where we auto-format a user's entered date, adding dashes (or slashes) when the input has reached the appropriate length. We want to store this formatted value both on the model and update the UI. Because of the 'bindKey' constraint, we seem to be missing out on the potential two-binding that would occur to allow updating of the UI. Additionally, while I'm not sure if the 'bindKey' value matching option.bindKey is also at fault for afterUpdate/onGet/etal callbacks as well, it certainly seems limiting to not be allowing any callback on the DOM element that changed (but on others instead).

We'd be very interested to hear if there is a reason for this behavior and what the strategies to employ instead would be. Or if it is a bug that we could help out with we would be happy to submit a pull request as well.

Thanks for all your work to help make this a great project.

thanks,
Zach

No way to define 'value' for select options

By pointing to a backbone collection with the appropriate data attributes, we are leveraging stickit to generate options for a select dropdown. Through 2-way binding and proper stickit 'selectOptions' configuration, when an option is selected from the list, the proper 'value' is being set on the model side. However, there's nothing in stickit that populates the 'value' attribute for individual options (on initial rendering) within the DOM. This is now becoming a problem for our functional automated test suite, as it was keying off those values. It also seems that it makes sense to have the DOM match up with the models on key attributes like this. Is there a way people are defining value attributes for options that keeps with the 2-way binding, generate input elements from models/collections "spirit" of stickit? And/or can/should we enhance stickit to populate these value attributes? At least on the latter (presuming there's not clear answer to the former question) I would vote in favor of such an enhancement.

Add an example with short form binding in "Usage"

Hi and thanks for stickit !

When I first was linked this plugin (thanks @mehcode) I read through the doc fastly and I must admit I didn't notice at first the short form.
I think this is interesting to have in the "Usage" section, to directly show stickit's powerfulness/usefulness/clarity/brevity/blahy.

Adding 'value' attribute to option and not just stickit_bind_val

I'm trying to integrate Select2 and Stickit.

Select2 requires that each option in a select has the value attribute assigned or it considers the item, "unselectable", making it not useful unfortunately.

I've seen a few issues opened and closed about this, but as an option, I'd like to see Stickit be able to set the standard value attribute for an option element.

I don't see an event where the values could be added post binding, collection updates, etc. I'd considered creating an addHandler for select, but apparently it runs before the list has been constructed. (If there was a two-phase pass, update, updated, that would work as well).

I'm aware of the code that stores the value in the option's stickit_bind_val, but, I don't know how to transfer the value from there to the value attribute.

Thanks for considering this idea.

visibleFn not executing on model change

Hi,

Is the visibleFn supposed to be run on model change events to observed attributes? I have the following binding in my application and the onGet function is run every time either of the model attributes being observed is changed but the visibleFn only runs on the initial stickit call.

'#ModifiedInfo': {
    observe: ['ModifiedByUser', 'ModifiedDate'],
    visible: function (values) {
        if (values && values.length && values[0]) {
            return true;
        }
        else {
            return false;
        }
    },
    visibleFn: function ($el, isVisible, options) {
        if (!isVisible) {
            $el.parent().hide();
        }
    },
    onGet: function (values) {
        if (values && values.length && values[0]) {
            var dt = app.utilities.formatDate(values[1], "M/d/yyyy");
            return values[0].FirstName + ' ' + values[0].LastName + ', ' + dt;
        }
    }
}

Binding Collections

I'm new to stickit and was wondering if it can be used to bind collections instead of just models. I have it working for models, but not my collection. Thanks in advance!

If anyone has a quick code snippet, that'd be appreciated.

Take advantage of new listenTo

first: thanks for making stickit better everyday, and my life as frontend developer easier and more fun!

Lets investigate if stickit can take any advantage of the new event features in backbone 0.9.9 (listenTo, stopListening, once etc)

jQuery events are not unbound when stickit is called multiple times

It seems that the switch from Backbone delegated events to jQuery events has introduced a bug whereby re-rendering a Backbone view and calling stickit again causes the bound elements to continue to set attributes on the model.

For example, I have a view that I render multiple times. Each time I render, I call stickit(). The first time it renders, there are no problems. But the second time, when I try to enter text into a field it keeps getting replaced with the previously set value from the model.

I see that stickit() calls unstickModel, but judging from the behavior it seems that the jQuery events are staying around. I see that you've wrapped view.remove to unbind these events but when I rerender I don't call view.remove.

I might be wrong and there is some other issue going on but we just upgraded stickit and it appears that this is what's causing the problem.

Dynamic bindings for form events

Looking at https://github.com/NYTimes/backbone.stickit/blob/master/backbone.stickit.js#L115-L136; I notice you're throwing events into this.events and then re-invoking this.delegateEvents.

Can we change that to invoking this.$el.on and then (on unstickit) calling this.$el.off?

The main problem is as follows:

List = Backbone.View.extend({

  bindings: {
    '.actions .selector': {
      observe: '$checked'
    }
  },

  events: {
    'change .selector': function(event) {
      // ...
    }
  }

);

I only want the manual event binding to be invoked when directly changing the checkbox via a click. However, I do want the the bindings hash to sync the state of the checkbox with the model.

Investigate/Remove the need for Model.set wrapper

I originally included the set wrapper so that I could always intercept an attribute change, but this can most likely be handled by listening to change:attribute events. My original understanding/ignorance of the {silent:true} set option was that it silenced change and change:attribute events, but I recently learned otherwise - I'm surprised that I haven't been burned by that yet!

Thanks to @asciidisco for spotting this.

Change event firing too soon in set function

This might be a problem just for me, but I thought I'd report it anyways.

In the wrapping of the set function, the Backbone.model.set() call comes before the attributes are iterated though and have a bind event triggered on each of them:

// Delegating to Backbone's model.set().
ret = oldSet.call(this, attrs, options);

// Iterate through the attributes that were just set.
.each(.keys(attrs || {}), .bind(function(attr) {
// Trigger a custom "bind" event for each attribute that has changed, unless {bind:false} option.
if (( !
.isEqual(now[attr], val) || (options.unset && _.has(now, attr))))
this.trigger('bind:' + attr, attrs[attr], options);
}, this));

If the view is listening to a change event on the model to render, and the render function calls this.stickit(), the bindings end up being applied twice. Moving the call to the Backbone.model.set() to after the bind events are triggered seems to fix this:

// Iterate through the attributes that were just set.
.each(.keys(attrs || {}), .bind(function(attr) {
// Trigger a custom "bind" event for each attribute that has changed, unless {bind:false} option.
if (( !
.isEqual(now[attr], val) || (options.unset && _.has(now, attr))))
this.trigger('bind:' + attr, attrs[attr], options);
}, this));

// Delegating to Backbone's model.set().
ret = oldSet.call(this, attrs, options);

How to unselect values on select lists

I am using a list to build up the options for my select lists. When I first bind and there is no selection in the model I get a blank option for no selection. However if there is a value in the model to bind to the option then the blank selection is not rendered and therefore I can never deselect the option. I tried to render my own "select one" option in my collection but then if the value is unselected I get my blank option as well as the one rendered by stickit. Here is an example

No Selection in the model

<select id="manufacturerId" name="manufacturer">
    <option></option>
    <option>Joe</option>
</select>

With selection in the model

<select id="manufacturerId" name="manufacturer">
    <option>Joe</option>
</select>

Many other frameworks allow you to configure whether there is a select one option and to customize the label.

Any suggestions would be appreciated. The problem is that once selected I cannot deselect to a blank value and if I add a blank it gets duplicated when there is now selection.

Can selectOptions collection be made to hang off this.model or optionalModel also in addition to window?

The question basically says it all - and the docs are pretty clear about the current options (pun not intended), but if I'm in an app that has pulled in the relevant collection to be displayed as a select dropdown, it seems a rather frequent use case to have that collection data be around the same model that I'm binding to in my view. Why the requirement and inflexibility to hang this off 'window' only (or as a String, I get it...)? Is there a good design reason, is it just easier for the framework to deal with, or?

What I'm finding to work for us is to right before calling 'this.stickit(..,..) to put the collection on the window. And then point to it in selectOptions. We do this because we feed our models into our template rendering, as they are smart enough to know how to render the collection. I get that the desire is for stickit to do that rendering, and to keep templates uber simple, but it seems like flexibility could be provided by the framework here with minor code changes. But I could be (am?) wrong. Thoughts?

Reasoning behind allowing event registration

First I'd like to say that the library looks great. Good job. 👍

Just a minor question that came to mind as I was browsing through the documentation / code.

What is the reasoning behind allowing event registration like click in the bindings hash when the events hash already does this?

Attributes didn't use observe's value

Thanks so much for the great plugin, it is really useful. There is a bug that I found for attributes in bindings. Sorry I didn't do a pull requests but here is the patch:

*** backbone.stickit.js Tue Sep 18 15:39:16 2012
--- backbone.stickit.new.js Tue Sep 18 15:39:27 2012
***************
*** 64,70 ****
                                  _.each(attributes, function(attrConfig) {
                                          var lastClass = '',
                                                  updateAttr = function() {
!                                                         var val = applyViewFn(self, attrConfig.format) || model.get(modelAttr);
                                                          // If it is a class then we need to remove the last value and add the new.
                                                          if (attrConfig.name == 'class') {
                                                                  $el.removeClass(lastClass).addClass(val);
--- 64,70 ----
                                  _.each(attributes, function(attrConfig) {
                                          var lastClass = '',
                                                  updateAttr = function() {
!                                                         var val = applyViewFn(self, attrConfig.format, model.get(attrConfig.observe)) || model.get(modelAttr);
                                                          // If it is a class then we need to remove the last value and add the new.
                                                          if (attrConfig.name == 'class') {
                                                                  $el.removeClass(lastClass).addClass(val);

Access nested model attribute

It doesn't seem possible to bind a nested model attribute to a dom element.

For example:

bindings:
    '#title':
      modelAttr: 'model_data.client.name'

updateViewBindEl not playing nicely with backbone-nested

Hi there,

First off - great app! Love simply reading the code that is in stickit.

I am using https://github.com/afeld/backbone-nested with stickit and I've run into a small issue. It's probably not a problem of your library, but I was hoping for a solution. So, example:

 var Profile = Backbone.Model.extend({
      defaults: {
          name: { legal_first_name: 'John', legal_last_name: 'Carmack'},
          email: '[email protected]'
      }
 });

 var ProfileView = Backbone.View.extend({
       bindings: {
           '#first_name': 'name.legal_first_name',
           '#last_name': 'name.legal_last_name'
       }

       render: function() {
             this.stickit();
       }
 });

When I begin using this view with a rendered form template, here's what happens:

  • All fields are rendered correctly initially.

  • When I try to modify #first_name, by adding an "a" to the end of "John", updateViewBindEl is called, and contains the following context

    val: "John"
    originalVal: "Johna"

Eventually, val hits this line:

 else if (isContenteditable($el)) $el.html(val);

And updates the textbox back to "John", replacing what the user just typed.

However!

When I edit email, updateViewBindEl is never called, and the model is updated and nothing wonky happens in the textbox.. I don't truly understand what's going on, as I quickly took a debugger to the stickit code and it seems everything, including email is bound just the same.

Any help would be much appreciated, thanks again :)

Cursor jumps to end of input when using Stickit + Html5 email input type and webkit

Hey all,

I just started using stickit and I love it, BUT, I have this issue.

If my template is using the html5 email input type like so:

<input type="email" name="email" class="span12" required />

In my view I have these bindings:

  bindings:
    "input[name=email]":
      modelAttr: "email"

Typing initially into the textbox is fine, but when I try to change it, my cursor gets moved to the end of the line.

Here's a short video: https://dl.dropbox.com/u/80354/stickit-thing.swf

I was able to reproduce this in Safari, Chrome, and the iOS Simulator, but not Firefox.

Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.