donejs / donejs

Your app. Done.
https://donejs.com
MIT License
1.34k stars 164 forks source link

Proposal: Rapid Prototyping with Scaffolding (can-crud) #1050

Open justinbmeyer opened 6 years ago

justinbmeyer commented 6 years ago

tldr; Create some custom elements that help prototype Creating, Updating, Reading, and Deleting (CRUD) model and other observable data. This will make both bootstrapping an application and creating demo pages easier.

Related Proposal: generate bidirectionally bound html form from map

This was discussed on a recent live stream (21:08). If you haven't watched the live stream, watch the live stream. The live stream does the best job of explaining this proposal.

Proposal

We propose 5 components:

And we propose the following methods for extracting information about an object or Type:

canReflect.getOwnKeyDescriptor

This would work just like Object.getOwnPropertyDescriptor, but include a type property:

canReflect.getOwnKeyDescriptor(object, key) //->
{
  get, set, value, writable, configurable, enumerable,
  type
}

The type property would be used to know how to display or create a form for the particular property.

canReflect.getOwnKeyDescriptor

This would work just like Object.getOwnPropertyDescriptor, but include a type property:

canReflect.getOwnKeyDescriptor(object, key) //->
{
  get, set, value, writable, configurable, enumerable,
  type, 
}

The type property would be used to know how to display or create a form for the particular property.

NOTE: It might be useful to include the ability to see if this value is producing an error.

canReflect.getInstanceKeyDescriptor(Type, key)

Returns the key descriptor for an instance of type Type's key.

Person = DefineMap.extend({
  first: String,
  last: String,
  get fullName(){ ... }
});

canReflect.getInstanceKeyDescriptor(Person, "first") //-> 
{
  get: function(){ ... }, 
  set: function(){ ... }, 
  writable: true, 
  configurable: true, 
  enumerable: true,
  type: String
}

canReflect.getInstanceKeyDescriptor(Person, "fullName") //-> 
{
  get: function(){ ... }, 
  set: function(){ ... }, 
  writable: true, 
  configurable: true, 
  enumerable: false,
  type: String
}

canReflect.getInstanceKeys(Type)

Returns the possible keys for instances of this Type.

Person = DefineMap.extend({
  first: String,
  last: String,
  get fullName(){ ... }
});

// notice that fullName is returned.  
// It's not enumerable, so we wouldn't build a control for it.
canReflect.getInstanceKeys(Person) //-> ["first","last", "fullName"]

Use

Creating a demo page

Say we have the following <my-component> that contains a ViewModel with a Date, Number and String type:

ViewModel =  DefineMap.extend({
  dueDate: Date,
  age: Number,
  name: String
})

Component.extend({
  tag: "my-component",
  ViewModel: ViewModel
})

We could use <can-edit> to create a form that would be able to adjust the properties of the component's viewModel instance as follows:

<my-component this:to="scope.vars.myComponentVM"/>
<can-edit data:from="scope.vars.myComponentVM"/>

<can-edit> would produce 3 controls, each of which would be able to display and update the values on myComponentVM. The output of <can-edit> might look like:

<form>
  <label>dueDate: Fri Nov 03 2017 10:40:00 GMT-0500 (CDT)</label>
  <input type="datetime-local" value="11/03/2017 10:40 AM">
  <label>age: 35</label>
  <input type="number" value="35">
  <label>name: Alfredo</label>
  <input type="text" value="Alfredo">
  <input type="submit"> <input type="reset">
</form>

Bootstrapping/Scaffolding application development

If a user has a simple connected model like:

Todo = DefineMap.extend({
  id: Number,
  complete: Boolean,
  name: String
})

connect.baseMap({
  Map: Todo,
  service: new Service({id: "id"})
})

By adding <can-crud Type:from="Todo"> in the page, a user should be able to:

<can-crud>'s template would look like:

<h2>{{plural Type.name}}</h2>
<can-list Type:from="Type" on:editing="showEdit()"/>

<h3>Create {{Type.name}}</h3>
<can-create Type:from="Type"/>

{{#if isEditing}}
  <maybe-a-modal>
    <can-edit data:from="editing"/>
  </maybe-a-modal>
{{/if}}

<can-list>'s template would look something like:

{{#each itemsPromise.value}}
  <tr>
     {{#eachDescriptorAndValueOf(this, des=descriptor, value=value)}}
       <td>{{value}}</td>
     {{/eachDescriptorAndValueOf}}
     <td>
       <button on:click="edit(this)">edit</button>
       <button on:click="destroy(this)">X</button>
     </td>
  </tr>
{{/each}}

Progressively enhancing

Ideally, <can-crud>'s behavior could be progressively enhanced with <can-slot>. For example, a different from was desired for creation, <can-crud> could be called like:

<can-crud Type:from="Todo">
  <can-slot name="create"> <todo-create/> </can-slot>
</can-crud>
justinbmeyer commented 6 years ago
<can-crud Type:from="Person">

    <can-template name="edit">
        <can-edit Type:from="Person">
            <can-template name="firstName">
                ENTER YOUR first name Please!!!!!
                <can-field Type:from="Person" key:from="firstName"/>
            </can-template>
        </can-edit>
    </can-template>

</can-crud>
justinbmeyer commented 6 years ago

https://forums.donejs.com/t/help-to-get-started/811/2

The previous live stream (49:09).

green3g commented 6 years ago

Related: https://github.com/roemhildtg/spectre-canjs/tree/sp-admin-component/sp-admin

matthewp commented 5 years ago

@justinbmeyer can this be closed? I believe can-crud is basically this.