20111124..29 ============ Continued with Calendar Panel ----------------------------- I managed to have the Calendar panel filled with Lino's Events. (I added a new report PanelEvents, virtual fields Event.dt_start and Event.dt_end) But another problem appeared: I want (or rather need) to use the FormPanel defined by Lino when editing the details of an event. That seems not so easy because Lino.FormPanel constructor takes a special parameter `ww`. All Lino panels have this pointer to their containing window wrapper. But `Ext.ensible.cal.CalendarPanel` has no API to specify a custom function that creates the editor form, I must replace the 'extensible.eventeditform' with my own class (whose constructor must work with the standard ExtJS signature). :file:`extensible-all.js` does:: Ext.reg('extensible.eventeditform', Ext.ensible.cal.EventEditForm); And I plan to do:: Ext.reg('extensible.eventeditform', Lino.EventEditForm); But first I want to make Lino.FormPanel usable without a `containing_window` in the constructor. This causes an avalanche of changes: Moved the `save()` functions from DetailWrapper and InsertWrapper to a single function in `FormPanel`. It tests rec.phantom to decide whether it's a POST or a PUT. Seems to work. Checkin. Changed the generated javascript code. Changed `analyze_models()` in `kernel.py`, preparing to the new concept for defining Details. The mechanism for rendering the picture of a person was very sophisticated and required even an additional AJAX call to get the URL of the picture. And the result was suboptimal since the picture were stretched to fill the rectangle. Replaced by a simple :class:`lino.fields.HtmlBox` field:: def image(self,request): url = self.get_image_url() s = '' % url s = '%s' % (url,s) return s image2.return_type = fields.HtmlBox() Layouts are now still Report-based, but everything is ready to make them model-based. Checkin 20111126 07:03 because it seems to work so far (except that `before_row_edit` isn't called on a GridPanel and probably some more bugs). DetailLayouts are now stored in the model, no longer in the Report. New classes :class:`lino.reports.Detail` and :class:`lino.reports.DetailHandle`. There is one `Detail` instance per model, stored in `model._lino_detail` which is `None` for models that have no `.dtl` files. There is still no possibility to define different Detail Windows per Model and there's no need for it yet, but that's now possible. :meth:`lino.ui.extjs3.ext_ui.ExtUI.build_lino_js` now generates the definition of a FormPanel subclass for each Detail. Checkin 20111127 01:27 because it seems to work so far. Next step was to generate also a class definition of a GridPanel subclass for each Report. Something like... :: Ext.namespace('Lino.thirds.ThirdsByOwner') Lino.thirds.ThirdsByOwner.GridPanel = Ext.Extend(Lino.GridPanel,{ ls_url : "/thirds/ThirdsByOwner", ls_bbar_actions: [ { "text": "L\u00f6schen", "name": "DeleteSelected", "panel_btn_handler": Lino.delete_selected } ], gc_name: 0, stripeRows: true, ls_quick_edit: true, ls_store_fields: [ { "type": "int", "name": "seqno" }, { "name": "person" },'personHidden', { "name": "company" },'companyHidden', { "type": "int", "name": "id" }, { "name": "owner_type" },'owner_typeHidden', { "name": "owner_id" },'owner_idHidden', { "name": "remark" }, { "type": "int", "name": "id" } ], pk_index: 5, ls_grid_configs: [ ], ls_id_property: "id", page_length: 30, ls_columns: [ { "colIndex": 0, "sortable": true, "header": "Seq.-Nr.", "editable": true, "filter": { "type": "numeric" }, "width": 45, "dataIndex": "seqno", "hidden": false, "editor": this.seqno8973 }, { "colIndex": 1, "sortable": true, "header": "Person", "editable": true, "filter": { "type": "string" }, "width": 90, "renderer": Lino.fk_renderer('personHidden','Lino.contacts.AllPersons.detail'), "hidden": false, "editor": this.person8974, "dataIndex": "person" }, { "colIndex": 2, "sortable": true, "header": "Organisation", "editable": true, "filter": { "type": "string" }, "width": 90, "renderer": Lino.fk_renderer('companyHidden','Lino.contacts.Companies.detail'), "hidden": false, "editor": this.company8975, "dataIndex": "company" }, { "colIndex": 3, "sortable": true, "header": "ID", "editable": true, "filter": { "type": "numeric" }, "width": 45, "renderer": Lino.id_renderer, "hidden": false, "editor": this.id8976, "dataIndex": "id" }, { "colIndex": 4, "sortable": true, "header": "Verkn\u00fcpft mit (Modell)", "editable": true, "filter": { "type": "string" }, "width": 90, "dataIndex": "owner_type", "hidden": false, "editor": this.owner_type8977 }, { "colIndex": 5, "sortable": true, "header": "Verkn\u00fcpft mit (Objekt)", "editable": true, "filter": { "type": "string" }, "width": 90, "dataIndex": "owner_id", "hidden": false, "editor": this.owner_id8978 }, { "colIndex": 6, "sortable": false, "header": "Bemerkung", "editable": true, "filter": { "type": "string" }, "width": 540, "renderer": Lino.text_renderer, "hidden": false, "editor": this.remark8979, "dataIndex": "remark" } ], before_row_edit : function(record) { ... }, initComponent : function() { var ww = this.containing_window; this.seqno8973 = { "xtype": "numberfield" }; this.person8974 = new Lino.RemoteComboFieldElement({ "store": new Lino.ComplexRemoteComboStore({ "proxy": new Ext.data.HttpProxy({ "url": "/choices/thirds/Third/person", "method": "GET" }) }), "pageSize": 30, "emptyText": "Person ausw\u00e4hlen..." }); this.company8975 = new Lino.RemoteComboFieldElement({ "store": new Lino.ComplexRemoteComboStore({ "proxy": new Ext.data.HttpProxy({ "url": "/choices/thirds/Third/company", "method": "GET" }) }), "pageSize": 30, "emptyText": "Organisation ausw\u00e4hlen..." }); this.id8976 = { "xtype": "numberfield" }; this.owner_type8977 = new Lino.RemoteComboFieldElement({ "store": new Lino.ComplexRemoteComboStore({ "proxy": new Ext.data.HttpProxy({ "url": "/choices/thirds/Third/owner_type", "method": "GET" }) }), "pageSize": 30, "emptyText": "Inhaltstyp ausw\u00e4hlen..." }); this.owner_id8978 = new Lino.RemoteComboFieldElement({ "store": new Lino.ComplexRemoteComboStore({ "proxy": new Ext.data.HttpProxy({ "url": "/choices/thirds/Third/owner_id", "method": "GET" }) }) }); this.remark8979 = new Ext.form.TextArea({ "growMax": 2000 }); } Lino.thirds.ThirdsByOwner.grid = function(caller,params) { var ww = new Lino.GridMasterWrapper(caller,{ "content_type": 29, "action_name": "grid" },params); ww.main_item = this.new Lino.thirds.ThirdsByOwner.GridPanel({"containing_window": ww}); ww.show(); } ... instead of... :: Ext.namespace('Lino.thirds.ThirdsByOwner') Lino.thirds.ThirdsByOwner.grid = function(caller,params) { var ww = new Lino.GridMasterWrapper(caller,{ "content_type": 29, "action_name": "grid" },params); this.seqno8973 = { "xtype": "numberfield" }; this.person8974 = new Lino.RemoteComboFieldElement({ "store": new Lino.ComplexRemoteComboStore({ "proxy": new Ext.data.HttpProxy({ "url": "/choices/thirds/Third/person", "method": "GET" }) }), "pageSize": 30, "emptyText": "Person ausw\u00e4hlen..." }); this.company8975 = new Lino.RemoteComboFieldElement({ "store": new Lino.ComplexRemoteComboStore({ "proxy": new Ext.data.HttpProxy({ "url": "/choices/thirds/Third/company", "method": "GET" }) }), "pageSize": 30, "emptyText": "Organisation ausw\u00e4hlen..." }); this.id8976 = { "xtype": "numberfield" }; this.owner_type8977 = new Lino.RemoteComboFieldElement({ "store": new Lino.ComplexRemoteComboStore({ "proxy": new Ext.data.HttpProxy({ "url": "/choices/thirds/Third/owner_type", "method": "GET" }) }), "pageSize": 30, "emptyText": "Inhaltstyp ausw\u00e4hlen..." }); this.owner_id8978 = new Lino.RemoteComboFieldElement({ "store": new Lino.ComplexRemoteComboStore({ "proxy": new Ext.data.HttpProxy({ "url": "/choices/thirds/Third/owner_id", "method": "GET" }) }) }); this.remark8979 = new Ext.form.TextArea({ "growMax": 2000 }); this.main_grid8980 = new Lino.GridPanel({ "ls_url": "/thirds/ThirdsByOwner", "ls_bbar_actions": [ { "text": "L\u00f6schen", "name": "DeleteSelected", "panel_btn_handler": Lino.delete_selected } ], "gc_name": 0, "stripeRows": true, "ls_quick_edit": true, "ls_store_fields": [ { "type": "int", "name": "seqno" }, { "name": "person" },'personHidden', { "name": "company" },'companyHidden', { "type": "int", "name": "id" }, { "name": "owner_type" },'owner_typeHidden', { "name": "owner_id" },'owner_idHidden', { "name": "remark" }, { "type": "int", "name": "id" } ], "pk_index": 5, "ls_grid_configs": [ ], "ls_id_property": "id", "page_length": 30, "ls_columns": [ { "colIndex": 0, "sortable": true, "header": "Seq.-Nr.", "editable": true, "filter": { "type": "numeric" }, "width": 45, "dataIndex": "seqno", "hidden": false, "editor": this.seqno8973 }, { "colIndex": 1, "sortable": true, "header": "Person", "editable": true, "filter": { "type": "string" }, "width": 90, "renderer": Lino.fk_renderer('personHidden','Lino.contacts.AllPersons.detail'), "hidden": false, "editor": this.person8974, "dataIndex": "person" }, { "colIndex": 2, "sortable": true, "header": "Organisation", "editable": true, "filter": { "type": "string" }, "width": 90, "renderer": Lino.fk_renderer('companyHidden','Lino.contacts.Companies.detail'), "hidden": false, "editor": this.company8975, "dataIndex": "company" }, { "colIndex": 3, "sortable": true, "header": "ID", "editable": true, "filter": { "type": "numeric" }, "width": 45, "renderer": Lino.id_renderer, "hidden": false, "editor": this.id8976, "dataIndex": "id" }, { "colIndex": 4, "sortable": true, "header": "Verkn\u00fcpft mit (Modell)", "editable": true, "filter": { "type": "string" }, "width": 90, "dataIndex": "owner_type", "hidden": false, "editor": this.owner_type8977 }, { "colIndex": 5, "sortable": true, "header": "Verkn\u00fcpft mit (Objekt)", "editable": true, "filter": { "type": "string" }, "width": 90, "dataIndex": "owner_id", "hidden": false, "editor": this.owner_id8978 }, { "colIndex": 6, "sortable": false, "header": "Bemerkung", "editable": true, "filter": { "type": "string" }, "width": 540, "renderer": Lino.text_renderer, "hidden": false, "editor": this.remark8979, "dataIndex": "remark" } ], "containing_window": ww }); ww.main_item = this.main_grid8980; ww.show(); } And then I noticed that we must subclass the Model's FormPanel, once for detail and once for insert. First success: the Calendar Panel now shows `Lino.cal.Events.detailPanel` when we click on "Details..." of an Event. TODO: - The `Lino.cal.Events.detailPanel` is not yet filled with data. Must subclass `Lino.cal.Events.detailPanel` so that it behaves like the Ext.ensible detail form. - bug with master params Checkin Some internals documentation: A slave panel needs to know the **master parameters** to get its data: - `mk` ("master key") is the primary key of the current record, required by all slave report requests, - `mt` ("master type", the ContentType of the master) is needed only for Reports on a generic master (XxxByOwner). Grids and Forms on a slave report need a `caller` (either a FormPanel or a GridPanel) which will act as their master (providing a method `get_master_params()` which they call (1) upon render and (2) when `on_master_changed`) Another thing are **base parameters**. The concept is taken over from the ExtJS Store `baseParams` config parameter. - `query` (:attr:`lino.ui.requests.URL_PARAM_FILTER`) : the current value of the `search_field` in the top toolbar of a grid. - `mk` and `mt` are stored in the base_params of a slave panel. A FormPanel takes the base