• If you are citizen of an European Union member nation, you may not use this service unless you are at least 16 years old.

  • You already know Dokkio is an AI-powered assistant to organize & manage your digital files & messages. Very soon, Dokkio will support Outlook as well as One Drive. Check it out today!

View
 

Grid-Editing

This version was saved 12 years, 8 months ago View current version     Page history
Saved by Jörn Zaefferer
on August 12, 2011 at 9:53:22 am
 

A widget for editing cells. The editing ability can be specified for each column, the editor to use depends on the column type (see 3.1.3 Type). Editing can be cancelled (ie. throwing away all changes), or persisted (ie. synchronizing changes with the data model). Activating inline editing can happen for a single cell or a full row (or even full grid). Activating editing must interact with selection to avoid conflicts, e.g. a selection musn't start editing.

 

As for events binding UI, model and datastores together, the seperate Editing Events page has all the details on that. The prototype below doesn't yet implement these, but the ObservableData prototype does.

As for editing API, see Editing-API.

As for bidirectional bindings (aka data linking), see Editing-Databinding.

 

Open issues

  • How to handle nested arrays? Could be an array with scalar values (list of strings), or more nested objects. Important for detail view, while grid should be able to display a serialized form.
    • use custom template for rendering the grid cell
    • use template to generate inputs for editing 
  • There is overlap with Selection: if a selection is just a fancy way to represent a boolean field in the data, its also a way of editing it. The same flag could be displayed as a checkbox, which should be always visible (inlike inline editing, which shows only active inputs).
    • should start integrating editing and selecting demos soon  
  • Do we need row refresh? What should be the API for that? Index-based? How does the user get the index of a data object? Object-identiy? How does the grid map an object to a row? 
    • probably the way to go: grid.refresh(rowIndex)  

 

TODO  

  • Make selection compatible with navigator and inline editing, integrate into main editing demo. Extract the code into a gridSelectable widget. 
  • Start speccing out the grid API, especially the columns options and how to initialize it from data-attributes 
    • Also write specs for inline editor and navigator widgets 
  • Prevent the page from scrolling when navigation the spreadsheet with cursor up/down 
  • Add a timestamp-property, and output it using a custom template
    • make it editable!
    • maybe render it in two different columns, one showing the date, one the time, both editing the original timestamp 
  • Refactor todo-app example, giving it a bit more structure, maybe ala the backbone todo example 
  • Add full-row editing 
  • Try generating the edit form for non-grid example to allow editing of nested array 
    • displaying array works, but serializing still needs to handle it 
    • still need to apply to non-grid master/detail demo 
    • deserializing multiple nested properties will fail right now, too 

 

Research:

    • SproutCore 2.0 
      • SC.ArrayProxy acts as the controller in the Todo app demo. Its similar to the observable array, in that it wraps array methods and adds events for each modification. 
      • There are a various methods for selecting and manipulating objects within the array, see also the enumerables guide
        • to set isDone on all items: this.setEach('isDone', value)
        • to check if all items have isDone set to true: this.everyProperty('isDone', true) 
    • Backbone
      • both models and collections
        • have a fetch method to retrieve content from server
        • have a save method to create a new object and update changes
        • all remote operations are handled by a single method, Backbone.sync
          • has some basic customization options, like mapping PUT to POST
          • heavier customizations possible by overriding, its just one method
        • have a toJSON() method - misleading name, but "exports" as object suitable for passing to template 
      • models have
        • a get method to retrieve an attribute, though just maps to model[attribute], no support to retrieve nested properties with a single call
        • a set method to set one or many attributes (as hash), triggers a "change" as well as "change:attribute" events for each one
        • unset removes an attribute, fires "change" event 
        • destroy method to delete on server 
      • collections
        • proxy to underscore.js for iteration methods
        • add/remove method accepts a single object or an array to add/remove to/from collection, triggers "add/remove" event
        • get retrieves object in collection by model id
        • at retrieves by index
        • sort method triggers "refresh" event
        • .pluck("attribute") is short for .map(function(model) { return model.attribute; }) 
      • events can be supressed with additional, optional {silent:true} argument, .add(model, { supress: true })
    • recent Adobe data model work once avaible (via John Brinkman) 
      • working on getting access to code
    • JsViews (and related grid-based demos)
      • JsViews integrates JsRender (new version of jQuery templates which does pure string-based rendering, with no DOM dependency) and data linking. By integrating templates and data linking, JsViews provides a consistent approach to mapping data to UI and presentation, with, in particular, a declarative approach to specifying how UI renders against data, and how it updates when the data changes.
      • The following demos show JsViews in action. In addition, the following demos (code here) explore the use of JsViews with the jQuery UI Grid. 

               The following comments are from Joern Zaefferer, after looking at the above demos, with replies from Boris Moore and Brad Olenick: 

      • [Joern] How to handle presentation data? In one of the grid examples, a global variable 'adding' is set in order to decide whether to display "Add" instead of "Edit" in the template. There needs to be an alternative for that.
        [Boris] The user has to deal with page presentation data, and the grid with grid presentation data. 
        In this case it is for the detail view, which is part of the user-created page. In reality they would put it on a namespace they own in the page, not as a global. But alternatively they can pass data through with options as in .link( selectedData, "#detail-template", { title: detailTitle }) and have it visible to the template rendering, as ${$options.title}. (I'll change it to this in the demo, though right now there are a couple of related bugs which I am currently fixing)
        • The approach of converting data for presentation was so far countered with the argument that the data can't be touched, as it needs to be sent back to the server. That part could be solved by making the link back to the original model, while using the intermediate representation for output.
        • Whatever the solution, there's plenty of usescases for modifying data for display, e.g. converting a date/time string to a "x [minutes|hours|days|months|years] ago" label.
          [Boris] Yes you often want to format differently for display, but this doesn't mean you have to change the data, or (if you are round-tripping data so cannot change it) clone the data, mapped to the original data - which is expensive and complex. Instead you would just use converter functions for display - such as: ${myConverters.dateFormat(date)} within a template, or data-from="myConverters.dateFormat([date])" for a data-linked value. You can add convert functions to the converters collection as in linkSettings.converters.dateFormat = function() {...} for convenience, so you can then just write data-from="dateFormat([date])", or you can pass them in with options, and then do $options.dateFormat(…)
      • [Joern] The selected-state visualization (.ui-selected) is added using the grid's refresh callback, then iterating through all rows, getting the tmplItem.data, then checking if that is $.inArray in selectedData. That needs to be refactored into a selectable-grid component, along with the custom code for applying selectable to the grid.
        [Boris] Yes, that would be integrated into the grid, and the samples are a progressive step-by-step sequence suggesting just that: how to move selection and observability etc. into the grid. It is only the final sample which attempts to actually get nearer to real integration of that code into the grid itself. That is also indicated in comments in the code: //Selection support - to be integrated into grid// 
        (For review purposes, I had suggested you only need to look at the final step, grid-master-detail.html, as well as looking at grid-edit.html, and skip all the other ones.)
        .
        [Joern] The user should be able to call gridElement.gridSelectable() or something like that, with one or more callbacks that inform about selection-state changes, once they are done. Not so much about jsviews, more a Selecting issue.
        [Boris] By making the grid return a ‘selected’ array you don’t need to invent new callbacks for selection – you can just use the observable collection change events and you are done…
        • [Joern] This relates to the more generic issue of incremental rendering. In the clearSelection-click-handler, the ui-selected class is removed manually, 'for better perf'. The mentioned gridSelectable component should include that optimiziation, if actually necessary. The clear-click-handler should be able to call gridSelectable("clear") and not having to care about anything else.
          [Boris] Yes, it would be part of the built-in selectable implementation. This was just a progressive demo towards that encapsulated version 
        • [Joern] Same for inverting the selection: gridSelectable("invert").
        • Would also be interesting to investigate if that can be pushed up to Selectable
        • There should be a way to retrieve the first and last index of the current selection, to insert a new element before or after the selection. Trivial for single-selection, more involved for multi-selection. 
      • [Joern] Deleting in the readonly and editable demos (first two modified grid demos) happens by splicing the underlying array. In the observer demo, that is replaced by $.observable(array).remove(index) + $.observable(selectedData).remove(selectedIndex). The index is different, once referring to the actual data, once to the seperate selectedData.
        • If the selected-state would be stored on the data itself, this could be done with one call.
        • If the selected-state would be stored as decoration-data, avoiding to clutter the data itself, but keeping it linked to each data object, removing the object should also remove the selected-state. Whatever that structure would actually look like. 
        • If $.observable would offer a .remove(object) method, looking up the index manually could be skipped, while $.observable would have the opportunity to keep a (hash)map of the data for efficient lookup. Which would probably require some kind of unique key, which also isn't present in the examples, yet.

[Boris] Putting it in the data means that a) it round trips to the server, b) it implies selection in one UI view will have side-effects on selection on another, if the same data shows up in more than one place - which it often does, as in the different grids in this demo. Having it as “data decoration” requires some kind of ID definition of data items as keys for maps, or implies storing object references, and again likely implies the side-effect b) above.
More generally (as [Brad] put it in an email) we have to be sure that these scenarios are considered and likely covered:

        • An item of data should be selectable in multiple different views at the same time.
        • We should agree on selection behavior when an item of data is present N>1 times in a given array.  Can I select an individual TR and not find that the other TRs for that data item are selected?
        • We should make sure however selection is presented to the client of a grid that the scheme is reactive/event-driven.
      • [Joern] In the observer example, when adding a new object, $.observable(developers).insert(index, newDeveloper) is called, which makes sense. Afterwards, $.observable(selectedData).refresh(newDeveloper) is called, with the effect of replacing the current selection with that new object. That should be replaced with a combo like .clear().add(newDeveloper). Using refresh to replace the array's content is odd.
        [Boris] As Brad put it in email, the reason this is “refresh” is that there would be a single event here.  With clear+add, you’d get two events and suboptimal repaint of controls.
      • [Joern] Again in the observer example, instead of binding the grid.refresh to the observable's (change) events, grid.refresh() is called manually where ever needed. Only in the grid-edit example (using grid5-editable.js) is that replaced, moving the necessary binding into the grid itself. There's also a lot of other cleanup here, e.g. adding a developer is now a single operation via $.observable(developers).insert(0, newDeveloper).
        [Boris] Yup – that was the intention: progressive demos towards the encapsulated version. 

 

Grid Editing demo

http://view.jqueryui.com/grid/grid-editing/grid.html

Applies a custom inline editor widget to each cell after rendering the current page, writes the result back to the datasource and calls a (custom) save method. The editor supports text and number types (for now), via Spinner (the random column is a customized spinner editor, with a random step-option). The country column is a text input with autocomplete attached.

Initializing the inline editor is done using an additional wrapper widget (currently called gridEditor) to enable lazy init - intializing the editor widget for all cells on grid refresh is way too slow.

The grid can be navigated, when focussed, using the cursor keys. Enter activates the inline editor on the active cell. Doubleclicking a cell has the same effect.

 

Non-Grid Editing demo, master/detail

http://view.jqueryui.com/grid/grid-editing/master-detail.html

Renders a list of movies. Click on any of them to edit in a seperate form. Changes are reflected in the view and stored.

Todo app

http://view.jqueryui.com/grid/grid-editing/todo-app.html

The popular Todo app example. Currently only using basic jQuery methods and the button widget, along with a small helper for storing data in localStorage (also used in the other two demos). Works on mobile devices (tested on iOS 4.x and Android 2.3.x.)

 

Early design notes

 

   - Can make column  editable="false"

 

    .dataStore("edit"[, ID[, column]]) // How do we specify true or "date"?

   .dataStore("cancelEdit"[,  ID]) // remove dom nodes from editing, call refresh

    // All of these are  stored on the data store view

   editing = {

     "ID1": true,

     "ID2": "date"

   }

   editing = true;

   editing = "date";

   // Wipe out state on  refresh

    editing: {}

 

   Check editing state on  pagination

 

   .dataStore("update", ID,  dataObj?)

    Need to specify how frequently to update

 

 

       - Row-level blur event  (focusenter, focusleave)

     - Enter says done with row editing, moves to next row -  go to first column

     - Tab says done with cell editing, moves to next cell

     - Click on cell - turns  on editing for whole row (or just cell, if only that cell is on)

     - Make sure not to  conflate selection with triggering editing - use checkboxes for  selection when doing editing as well

 

   - Inline Edit Row

     - Generate 

 

   - Click to Edit Cell

   - Entire Table Editing

   - Column Editing

 

 

   - Add New Item POST

     - Dialog for adding in  an item?

      - Will return data item in format we're using (must contain ID)

     - Update the items with  the new IDs

    - Delete Row POST

     - Works like update (success / error / timeout)

     - On success, remove  and refresh

    - Reorder Row // Useful, will not work on at first

     - Communicate an  insertBefore event?

 

   - Event for when column  is changed, event for when entire row is done being edited

   - Ways of triggering  those events (columnchange and rowdone?)

   - Do we have a submit  button - if not handle enter key

 

   - Inputs, selects can be used in the page to trigger PE  editing

   -  Depends upon l10n plugin

    - Theming

      - Active Row / Active Cell / Editing Row/Cell / Error  Row/Cell

 

Default: <tr>{{each  columns}}<td>{{field this}}</td>{{/each}}</tr>

Custom:

<fieldset>

  <legend>${firstname}  ${lastname}</legend>

  <label>First name: </label>{{field "firstname"}}  {{editing "firstname"}}Uppercase!{{/editing}}

  <label>Last name:  </label>{{field "lastname"}}

</fieldset>

// Note: Names must be exact  matches from header names

 

$("#row5").dataModel("refresh");

 

name -> StringFilter -> "John"

city -> StringFilter ->  "Boston"

date  -> StringFilterEdit -> <input type="text" ....>

 

Need template methods:

  - field

  - editing

  - editable

 

Comments (0)

You don't have permission to comment on this page.