From 7e43c5e13529c34224b510b4114e3943f89da902 Mon Sep 17 00:00:00 2001 From: Tejas Tank Date: Wed, 18 Apr 2012 11:59:43 +0530 Subject: [PATCH 001/112] q bzr revid: tta@openerp.com-20120418062943-rtvpby15skwqmywx --- addons/base_contact/i18n/cs.po | 501 -------------------------- addons/base_contact/i18n/ja.po | 264 -------------- addons/fetchmail_crm_claim/i18n/ja.po | 34 ++ 3 files changed, 34 insertions(+), 765 deletions(-) delete mode 100644 addons/base_contact/i18n/cs.po delete mode 100644 addons/base_contact/i18n/ja.po create mode 100644 addons/fetchmail_crm_claim/i18n/ja.po diff --git a/addons/base_contact/i18n/cs.po b/addons/base_contact/i18n/cs.po deleted file mode 100644 index e48443f7681..00000000000 --- a/addons/base_contact/i18n/cs.po +++ /dev/null @@ -1,501 +0,0 @@ -# Translation of OpenERP Server. -# This file contains the translation of the following modules: -# * base_contact -# -msgid "" -msgstr "" -"Project-Id-Version: openobject-addons\n" -"Report-Msgid-Bugs-To: support@openerp.com\n" -"POT-Creation-Date: 2012-02-08 00:36+0000\n" -"PO-Revision-Date: 2012-04-15 15:01+0000\n" -"Last-Translator: Jiří Hajda \n" -"Language-Team: Czech \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2012-04-16 04:40+0000\n" -"X-Generator: Launchpad (build 15099)\n" -"X-Poedit-Language: Czech\n" - -#. module: base_contact -#: field:res.partner.location,city:0 -msgid "City" -msgstr "Město" - -#. module: base_contact -#: view:res.partner.contact:0 -msgid "First/Lastname" -msgstr "Křestní jméno/Příjmení" - -#. module: base_contact -#: model:ir.actions.act_window,name:base_contact.action_partner_contact_form -#: model:ir.ui.menu,name:base_contact.menu_partner_contact_form -#: model:ir.ui.menu,name:base_contact.menu_purchases_partner_contact_form -#: model:process.node,name:base_contact.process_node_contacts0 -#: field:res.partner.location,job_ids:0 -msgid "Contacts" -msgstr "Kontakty" - -#. module: base_contact -#: view:res.partner.contact:0 -msgid "Professional Info" -msgstr "Povolání" - -#. module: base_contact -#: field:res.partner.contact,first_name:0 -msgid "First Name" -msgstr "Křestní jméno" - -#. module: base_contact -#: field:res.partner.address,location_id:0 -msgid "Location" -msgstr "Umístění" - -#. module: base_contact -#: model:process.transition,name:base_contact.process_transition_partnertoaddress0 -msgid "Partner to address" -msgstr "Partner na adresu" - -#. module: base_contact -#: help:res.partner.contact,active:0 -msgid "" -"If the active field is set to False, it will allow you to " -"hide the partner contact without removing it." -msgstr "" -"Pokud je aktivní pole nastaveno na Nepravda, umožní vám to " -"skrýt kontakt partnera bez jeho odstranění." - -#. module: base_contact -#: field:res.partner.contact,website:0 -msgid "Website" -msgstr "Webová stránka" - -#. module: base_contact -#: field:res.partner.location,zip:0 -msgid "Zip" -msgstr "PSČ" - -#. module: base_contact -#: field:res.partner.location,state_id:0 -msgid "Fed. State" -msgstr "Fed. stát" - -#. module: base_contact -#: field:res.partner.location,company_id:0 -msgid "Company" -msgstr "Společnost" - -#. module: base_contact -#: field:res.partner.contact,title:0 -msgid "Title" -msgstr "Název" - -#. module: base_contact -#: field:res.partner.location,partner_id:0 -msgid "Main Partner" -msgstr "Hlavní partner" - -#. module: base_contact -#: model:process.process,name:base_contact.process_process_basecontactprocess0 -msgid "Base Contact" -msgstr "Základní kontakt" - -#. module: base_contact -#: field:res.partner.contact,email:0 -msgid "E-Mail" -msgstr "E-mail" - -#. module: base_contact -#: field:res.partner.contact,active:0 -msgid "Active" -msgstr "Aktivní" - -#. module: base_contact -#: field:res.partner.contact,country_id:0 -msgid "Nationality" -msgstr "Národnost" - -#. module: base_contact -#: view:res.partner:0 -#: view:res.partner.address:0 -msgid "Postal Address" -msgstr "Poštovní adresa" - -#. module: base_contact -#: field:res.partner.contact,function:0 -msgid "Main Function" -msgstr "Hlavní funkce" - -#. module: base_contact -#: model:process.transition,note:base_contact.process_transition_partnertoaddress0 -msgid "Define partners and their addresses." -msgstr "Určuje partnery a jejich adresy." - -#. module: base_contact -#: field:res.partner.contact,name:0 -msgid "Name" -msgstr "Jméno" - -#. module: base_contact -#: field:res.partner.contact,lang_id:0 -msgid "Language" -msgstr "Jazyk" - -#. module: base_contact -#: field:res.partner.contact,mobile:0 -msgid "Mobile" -msgstr "Mobil" - -#. module: base_contact -#: field:res.partner.location,country_id:0 -msgid "Country" -msgstr "Země" - -#. module: base_contact -#: view:res.partner.contact:0 -#: field:res.partner.contact,comment:0 -msgid "Notes" -msgstr "Poznámky" - -#. module: base_contact -#: model:process.node,note:base_contact.process_node_contacts0 -msgid "People you work with." -msgstr "Lidé, s kterými pracujete." - -#. module: base_contact -#: view:res.partner.contact:0 -msgid "Extra Information" -msgstr "Speciální informace" - -#. module: base_contact -#: view:res.partner.contact:0 -#: field:res.partner.contact,job_ids:0 -msgid "Functions and Addresses" -msgstr "Funkce a adresy" - -#. module: base_contact -#: model:ir.model,name:base_contact.model_res_partner_contact -#: field:res.partner.address,contact_id:0 -msgid "Contact" -msgstr "Kontakt" - -#. module: base_contact -#: model:ir.model,name:base_contact.model_res_partner_location -msgid "res.partner.location" -msgstr "res.partner.location" - -#. module: base_contact -#: model:process.node,note:base_contact.process_node_partners0 -msgid "Companies you work with." -msgstr "Společnosti, se kterými pracujete." - -#. module: base_contact -#: field:res.partner.contact,partner_id:0 -msgid "Main Employer" -msgstr "Hlavní zaměstnavatel" - -#. module: base_contact -#: view:res.partner.contact:0 -msgid "Partner Contact" -msgstr "Kontakt partnera" - -#. module: base_contact -#: model:process.node,name:base_contact.process_node_addresses0 -msgid "Addresses" -msgstr "Adresy" - -#. module: base_contact -#: model:process.node,note:base_contact.process_node_addresses0 -msgid "Working and private addresses." -msgstr "Pracovní a soukromé adresy." - -#. module: base_contact -#: field:res.partner.contact,last_name:0 -msgid "Last Name" -msgstr "Příjmení" - -#. module: base_contact -#: view:res.partner.contact:0 -#: field:res.partner.contact,photo:0 -msgid "Photo" -msgstr "Fotka" - -#. module: base_contact -#: view:res.partner.location:0 -msgid "Locations" -msgstr "Umístění" - -#. module: base_contact -#: view:res.partner.contact:0 -msgid "General" -msgstr "Obecný" - -#. module: base_contact -#: field:res.partner.location,street:0 -msgid "Street" -msgstr "Ulice" - -#. module: base_contact -#: view:res.partner.contact:0 -msgid "Partner" -msgstr "Partner" - -#. module: base_contact -#: model:process.node,name:base_contact.process_node_partners0 -msgid "Partners" -msgstr "Partneři" - -#. module: base_contact -#: model:ir.model,name:base_contact.model_res_partner_address -msgid "Partner Addresses" -msgstr "Adresy partnerů" - -#. module: base_contact -#: field:res.partner.location,street2:0 -msgid "Street2" -msgstr "Ulice 2" - -#. module: base_contact -#: view:res.partner.contact:0 -msgid "Personal Information" -msgstr "Osobní informace" - -#. module: base_contact -#: field:res.partner.contact,birthdate:0 -msgid "Birth Date" -msgstr "Datum narození" - -#~ msgid "Categories" -#~ msgstr "Kategorie" - -#~ msgid "Current" -#~ msgstr "Stávající" - -#~ msgid "Fax" -#~ msgstr "Fax" - -#~ msgid "Address" -#~ msgstr "Adresa" - -#~ msgid "Invalid XML for View Architecture!" -#~ msgstr "Invalidní XML pro zobrazení architektury!" - -#~ msgid "" -#~ "The Object name must start with x_ and not contain any special character !" -#~ msgstr "" -#~ "Jméno objektu musí začínat znakem x_ a nesmí obsahovat žádný speciální znak!" - -#~ msgid "Invalid model name in the action definition." -#~ msgstr "Špatný název modelu v definici akce" - -#~ msgid "Contact Functions" -#~ msgstr "Kontaktní funkce" - -#~ msgid "Phone" -#~ msgstr "Telefon" - -#~ msgid "Function" -#~ msgstr "Funkce" - -#~ msgid "# of Contacts" -#~ msgstr "# z kontaktů" - -#~ msgid "title" -#~ msgstr "nadpis" - -#~ msgid "Start date of job(Joining Date)" -#~ msgstr "Datum zahájení práce (datum připojení)" - -#~ msgid "Select the Option for Addresses Migration" -#~ msgstr "Vyberte volbu pro přesun adres" - -#~ msgid "Function of this contact with this partner" -#~ msgstr "Funkce tohoto kontaktu s tímto partnerem" - -#~ msgid "Status of Address" -#~ msgstr "Stav adresy" - -#~ msgid "" -#~ "You may enter Address first,Partner will be linked " -#~ "automatically if any." -#~ msgstr "" -#~ "Můžete zadat nejdříve adresu. Pokud je nějaký partner, " -#~ "bude napojen automaticky." - -#~ msgid "Job FAX no." -#~ msgstr "Číslo FAXu do práce" - -#~ msgid "Define functions and address." -#~ msgstr "Určit funkce a adresu." - -#~ msgid "Last date of job" -#~ msgstr "Poslední datum práce" - -#~ msgid "Migrate" -#~ msgstr "Přesunout" - -#~ msgid "Jobs at a same partner address." -#~ msgstr "Pracovní pozice u stejné adresy partnera." - -#~ msgid "State" -#~ msgstr "Stav" - -#~ msgid "" -#~ "\n" -#~ " This module allows you to manage your contacts entirely.\n" -#~ "\n" -#~ " It lets you define\n" -#~ " *contacts unrelated to a partner,\n" -#~ " *contacts working at several addresses (possibly for different " -#~ "partners),\n" -#~ " *contacts with possibly different functions for each of its job's " -#~ "addresses\n" -#~ "\n" -#~ " It also adds new menu items located in\n" -#~ " Partners \\ Contacts\n" -#~ " Partners \\ Functions\n" -#~ "\n" -#~ " Pay attention that this module converts the existing addresses into " -#~ "\"addresses + contacts\". It means that some fields of the addresses will be " -#~ "missing (like the contact name), since these are supposed to be defined in " -#~ "an other object.\n" -#~ " " -#~ msgstr "" -#~ "\n" -#~ " Tento modul umožňuje úplně spravovat vaše kontakty.\n" -#~ "\n" -#~ " Nechá vás určit\n" -#~ " *kontakty nevztažené k partnerovi,\n" -#~ " *kontakty pracující na několika adresách (případně i pro různé " -#~ "partnery),\n" -#~ " *kontakty s možnými různými funkcemi pro každou jeho pracovní " -#~ "adresu\n" -#~ "\n" -#~ " Také přidává nové položky nabídky umístěné v\n" -#~ " Partneři \\ Kontakty\n" -#~ " Partneři \\ Funkce\n" -#~ "\n" -#~ " Berte na vědomí, že tento modul převádí existující adresy na \"adresy + " -#~ "kontakty\". To znamená, že některé pole adres budou chybět (jako jméno " -#~ "kontaktu), protože ty jsou definovány v jiném objektu.\n" -#~ " " - -#~ msgid "Date Stop" -#~ msgstr "Datum zastavení" - -#~ msgid "Contact's Jobs" -#~ msgstr "Pozice kontaktu" - -#~ msgid "" -#~ "Order of importance of this job title in the list of job " -#~ "title of the linked partner" -#~ msgstr "" -#~ "Pořadí důležitosti této pracovní pozice v seznamu titulů " -#~ "pozic napojeného partnera" - -#~ msgid "Extension" -#~ msgstr "Rozšíření" - -#~ msgid "Internal/External extension phone number" -#~ msgstr "Vnitřní/vnější telefoní číslo klapky" - -#~ msgid "Job Phone no." -#~ msgstr "Telefoní číslo do práce." - -#~ msgid "Job E-Mail" -#~ msgstr "Pracovní E-Mail" - -#~ msgid "Partner Seq." -#~ msgstr "Poř. parnera" - -#~ msgid "Function to address" -#~ msgstr "Funkce na adresu" - -#~ msgid "Configuration Progress" -#~ msgstr "Průběh nastavení" - -#~ msgid "Communication" -#~ msgstr "Komunikace" - -#~ msgid "Image" -#~ msgstr "Obrázek" - -#~ msgid "Past" -#~ msgstr "Minulý" - -#~ msgid "Address's Migration to Contacts" -#~ msgstr "Stěhování adresy na kontakty" - -#~ msgid "Contact Seq." -#~ msgstr "Poř. kontaktu" - -#~ msgid "Search Contact" -#~ msgstr "Hledat kontakt" - -#~ msgid "" -#~ "Due to changes in Address and Partner's relation, some of the details from " -#~ "address are needed to be migrated into contact information." -#~ msgstr "" -#~ "Kvůli změnám v adrese a vztahu partnera, některé podrobnosti adresy jsou " -#~ "potřebné, aby byly přesunuty do informací účtu." - -#~ msgid "Address which is linked to the Partner" -#~ msgstr "Adresa, která je napojena na partnera" - -#~ msgid "Partner Function" -#~ msgstr "Funkce partnera" - -#~ msgid "Additional phone field" -#~ msgstr "Doplňující pole telefonu" - -#~ msgid "Otherwise these details will not be visible from address/contact." -#~ msgstr "Jinak tyto podrobnosti nebudou viditelné z adresy/kontaktu." - -#~ msgid "Configure" -#~ msgstr "Nastavit" - -#~ msgid "base.contact.installer" -#~ msgstr "base.contact.installer" - -#~ msgid "Do you want to migrate your Address data in Contact Data?" -#~ msgstr "Chcete stěhovat vaše data adresy v datech kontaktu?" - -#~ msgid "Seq." -#~ msgstr "Poř." - -#~ msgid "If you select this, all addresses will be migrated." -#~ msgstr "Pokud toto vyberete, všechny adresy budou přesunuty." - -#~ msgid "Contact Partner Function" -#~ msgstr "Kontakt funkce partnera" - -#~ msgid "Other" -#~ msgstr "Jiné" - -#~ msgid "Main Job" -#~ msgstr "Hlavní práce" - -#~ msgid "Defines contacts and functions." -#~ msgstr "Určuje kontakty a funkce." - -#~ msgid "Contact to function" -#~ msgstr "Kontakt na funkci" - -#~ msgid "Open Jobs" -#~ msgstr "Otevřené pozice" - -#~ msgid "You can migrate Partner's current addresses to the contact." -#~ msgstr "Můžete stěhovat partnerovu aktuální adresu do kontaktu." - -#~ msgid "Address Migration" -#~ msgstr "Stěhování adresy" - -#~ msgid "Date Start" -#~ msgstr "Počáteční datum" - -#~ msgid "" -#~ "Order of importance of this address in the list of " -#~ "addresses of the linked contact" -#~ msgstr "" -#~ "Pořadí podle důležitosti této adresy v seznamu adres napojeného " -#~ "kontaktu" diff --git a/addons/base_contact/i18n/ja.po b/addons/base_contact/i18n/ja.po deleted file mode 100644 index 89af47fb6ac..00000000000 --- a/addons/base_contact/i18n/ja.po +++ /dev/null @@ -1,264 +0,0 @@ -# Japanese translation for openobject-addons -# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 -# This file is distributed under the same license as the openobject-addons package. -# FIRST AUTHOR , 2012. -# -msgid "" -msgstr "" -"Project-Id-Version: openobject-addons\n" -"Report-Msgid-Bugs-To: FULL NAME \n" -"POT-Creation-Date: 2012-02-08 00:36+0000\n" -"PO-Revision-Date: 2012-04-14 00:01+0000\n" -"Last-Translator: Tomomi Mengelberg \n" -"Language-Team: Japanese \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2012-04-15 04:42+0000\n" -"X-Generator: Launchpad (build 15070)\n" - -#. module: base_contact -#: field:res.partner.location,city:0 -msgid "City" -msgstr "市区町村" - -#. module: base_contact -#: view:res.partner.contact:0 -msgid "First/Lastname" -msgstr "名/姓" - -#. module: base_contact -#: model:ir.actions.act_window,name:base_contact.action_partner_contact_form -#: model:ir.ui.menu,name:base_contact.menu_partner_contact_form -#: model:ir.ui.menu,name:base_contact.menu_purchases_partner_contact_form -#: model:process.node,name:base_contact.process_node_contacts0 -#: field:res.partner.location,job_ids:0 -msgid "Contacts" -msgstr "連絡先" - -#. module: base_contact -#: view:res.partner.contact:0 -msgid "Professional Info" -msgstr "" - -#. module: base_contact -#: field:res.partner.contact,first_name:0 -msgid "First Name" -msgstr "姓" - -#. module: base_contact -#: field:res.partner.address,location_id:0 -msgid "Location" -msgstr "場所" - -#. module: base_contact -#: model:process.transition,name:base_contact.process_transition_partnertoaddress0 -msgid "Partner to address" -msgstr "" - -#. module: base_contact -#: help:res.partner.contact,active:0 -msgid "" -"If the active field is set to False, it will allow you to " -"hide the partner contact without removing it." -msgstr "" - -#. module: base_contact -#: field:res.partner.contact,website:0 -msgid "Website" -msgstr "ウェブサイト" - -#. module: base_contact -#: field:res.partner.location,zip:0 -msgid "Zip" -msgstr "郵便番号" - -#. module: base_contact -#: field:res.partner.location,state_id:0 -msgid "Fed. State" -msgstr "" - -#. module: base_contact -#: field:res.partner.location,company_id:0 -msgid "Company" -msgstr "会社" - -#. module: base_contact -#: field:res.partner.contact,title:0 -msgid "Title" -msgstr "役職" - -#. module: base_contact -#: field:res.partner.location,partner_id:0 -msgid "Main Partner" -msgstr "主なパートナ" - -#. module: base_contact -#: model:process.process,name:base_contact.process_process_basecontactprocess0 -msgid "Base Contact" -msgstr "" - -#. module: base_contact -#: field:res.partner.contact,email:0 -msgid "E-Mail" -msgstr "メール" - -#. module: base_contact -#: field:res.partner.contact,active:0 -msgid "Active" -msgstr "有効" - -#. module: base_contact -#: field:res.partner.contact,country_id:0 -msgid "Nationality" -msgstr "国籍" - -#. module: base_contact -#: view:res.partner:0 -#: view:res.partner.address:0 -msgid "Postal Address" -msgstr "住所" - -#. module: base_contact -#: field:res.partner.contact,function:0 -msgid "Main Function" -msgstr "主な役割" - -#. module: base_contact -#: model:process.transition,note:base_contact.process_transition_partnertoaddress0 -msgid "Define partners and their addresses." -msgstr "" - -#. module: base_contact -#: field:res.partner.contact,name:0 -msgid "Name" -msgstr "名前" - -#. module: base_contact -#: field:res.partner.contact,lang_id:0 -msgid "Language" -msgstr "言語" - -#. module: base_contact -#: field:res.partner.contact,mobile:0 -msgid "Mobile" -msgstr "携帯電話" - -#. module: base_contact -#: field:res.partner.location,country_id:0 -msgid "Country" -msgstr "国名" - -#. module: base_contact -#: view:res.partner.contact:0 -#: field:res.partner.contact,comment:0 -msgid "Notes" -msgstr "備考" - -#. module: base_contact -#: model:process.node,note:base_contact.process_node_contacts0 -msgid "People you work with." -msgstr "同僚" - -#. module: base_contact -#: view:res.partner.contact:0 -msgid "Extra Information" -msgstr "その他の情報" - -#. module: base_contact -#: view:res.partner.contact:0 -#: field:res.partner.contact,job_ids:0 -msgid "Functions and Addresses" -msgstr "" - -#. module: base_contact -#: model:ir.model,name:base_contact.model_res_partner_contact -#: field:res.partner.address,contact_id:0 -msgid "Contact" -msgstr "連絡先" - -#. module: base_contact -#: model:ir.model,name:base_contact.model_res_partner_location -msgid "res.partner.location" -msgstr "" - -#. module: base_contact -#: model:process.node,note:base_contact.process_node_partners0 -msgid "Companies you work with." -msgstr "同僚" - -#. module: base_contact -#: field:res.partner.contact,partner_id:0 -msgid "Main Employer" -msgstr "" - -#. module: base_contact -#: view:res.partner.contact:0 -msgid "Partner Contact" -msgstr "パートナー連絡先" - -#. module: base_contact -#: model:process.node,name:base_contact.process_node_addresses0 -msgid "Addresses" -msgstr "住所" - -#. module: base_contact -#: model:process.node,note:base_contact.process_node_addresses0 -msgid "Working and private addresses." -msgstr "勤め先住所と自宅住所" - -#. module: base_contact -#: field:res.partner.contact,last_name:0 -msgid "Last Name" -msgstr "姓" - -#. module: base_contact -#: view:res.partner.contact:0 -#: field:res.partner.contact,photo:0 -msgid "Photo" -msgstr "写真" - -#. module: base_contact -#: view:res.partner.location:0 -msgid "Locations" -msgstr "場所" - -#. module: base_contact -#: view:res.partner.contact:0 -msgid "General" -msgstr "" - -#. module: base_contact -#: field:res.partner.location,street:0 -msgid "Street" -msgstr "番地" - -#. module: base_contact -#: view:res.partner.contact:0 -msgid "Partner" -msgstr "パートナ" - -#. module: base_contact -#: model:process.node,name:base_contact.process_node_partners0 -msgid "Partners" -msgstr "取引先" - -#. module: base_contact -#: model:ir.model,name:base_contact.model_res_partner_address -msgid "Partner Addresses" -msgstr "パートナの住所" - -#. module: base_contact -#: field:res.partner.location,street2:0 -msgid "Street2" -msgstr "番地" - -#. module: base_contact -#: view:res.partner.contact:0 -msgid "Personal Information" -msgstr "個人情報" - -#. module: base_contact -#: field:res.partner.contact,birthdate:0 -msgid "Birth Date" -msgstr "生年月日" diff --git a/addons/fetchmail_crm_claim/i18n/ja.po b/addons/fetchmail_crm_claim/i18n/ja.po new file mode 100644 index 00000000000..a1f6db4a764 --- /dev/null +++ b/addons/fetchmail_crm_claim/i18n/ja.po @@ -0,0 +1,34 @@ +# Japanese translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2012-02-08 00:36+0000\n" +"PO-Revision-Date: 2012-04-08 05:43+0000\n" +"Last-Translator: Masaki Yamaya \n" +"Language-Team: Japanese \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2012-04-09 04:56+0000\n" +"X-Generator: Launchpad (build 15060)\n" + +#. module: fetchmail_crm_claim +#: model:ir.actions.act_window,help:fetchmail_crm_claim.action_create_crm_claims_from_email_account +msgid "" +"You can connect your email account with claims in OpenERP. A new email sent " +"to this account (example: support@mycompany.com) will automatically create a " +"claim for the followup in OpenERP. The whole communication by email will be " +"attached to the claim automatically to keep track of the history." +msgstr "" +"あなたのEメールアカウントをOpenERPのクレームに関連づけることはできません。このアカウントに送られた新しいEメールはOpenERPのフォローアップの" +"ためのクレームを自動的に作成します。Eメールによる全てのコミュニケーションは、履歴を記録するために、自動的にクレームに関連づけられます。" + +#. module: fetchmail_crm_claim +#: model:ir.actions.act_window,name:fetchmail_crm_claim.action_create_crm_claims_from_email_account +msgid "Create Claims from Email Account" +msgstr "Eメールアカウントからクレームを作成" From 2c780efd12a47d440e7b9a4ecae7da0cf9565390 Mon Sep 17 00:00:00 2001 From: Tejas Tank Date: Thu, 19 Apr 2012 18:59:16 +0530 Subject: [PATCH 002/112] Web Etherpad Integration bzr revid: tta@openerp.com-20120419132916-yzrdnz2y8gus2wlw --- addons/web_etherpad/__init__.py | 2 + addons/web_etherpad/__openerp__.py | 16 +++ addons/web_etherpad/res_company.py | 15 +++ addons/web_etherpad/res_company.xml | 16 +++ .../static/src/js/web_etherpad.js | 125 ++++++++++++++++++ .../web_etherpad/static/src/xml/etherpad.xml | 21 +++ 6 files changed, 195 insertions(+) create mode 100644 addons/web_etherpad/__init__.py create mode 100755 addons/web_etherpad/__openerp__.py create mode 100644 addons/web_etherpad/res_company.py create mode 100644 addons/web_etherpad/res_company.xml create mode 100755 addons/web_etherpad/static/src/js/web_etherpad.js create mode 100755 addons/web_etherpad/static/src/xml/etherpad.xml diff --git a/addons/web_etherpad/__init__.py b/addons/web_etherpad/__init__.py new file mode 100644 index 00000000000..df35bc32106 --- /dev/null +++ b/addons/web_etherpad/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +import res_company diff --git a/addons/web_etherpad/__openerp__.py b/addons/web_etherpad/__openerp__.py new file mode 100755 index 00000000000..829081e8c65 --- /dev/null +++ b/addons/web_etherpad/__openerp__.py @@ -0,0 +1,16 @@ +{ + "name": "web_etherpad", + "category" : "Hidden", + "description":'Openerp web Etherpad Widget', + "version": "2.0", + 'author': 'OpenERP SA', + 'website': 'http://openerp.com', + 'data': [ + 'res_company.xml' + ], + "depends": ['base','web'], + 'qweb' : ['static/src/xml/etherpad.xml'], + "css": [], + "js": ['static/src/js/web_etherpad.js'], + "auto_install": False, +} diff --git a/addons/web_etherpad/res_company.py b/addons/web_etherpad/res_company.py new file mode 100644 index 00000000000..0c1dc7c4929 --- /dev/null +++ b/addons/web_etherpad/res_company.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +from osv import fields, osv + +class company_pad(osv.osv): + _inherit = 'res.company' + _columns = { + 'pad_url_template': fields.char('Pad URL Template', size=128, required=True, + help="Template used to generate pad URL."), + } + _defaults = { + 'pad_url_template': 'http://ietherpad.com/%(db)s-%(model)s-%(id)d-%(salt)s-%(name)s' + } + + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/web_etherpad/res_company.xml b/addons/web_etherpad/res_company.xml new file mode 100644 index 00000000000..20b6f52d853 --- /dev/null +++ b/addons/web_etherpad/res_company.xml @@ -0,0 +1,16 @@ + + + + res.company.form.pad + res.company + form + + + + + + + + + + diff --git a/addons/web_etherpad/static/src/js/web_etherpad.js b/addons/web_etherpad/static/src/js/web_etherpad.js new file mode 100755 index 00000000000..d19fb2b9512 --- /dev/null +++ b/addons/web_etherpad/static/src/js/web_etherpad.js @@ -0,0 +1,125 @@ +openerp.web_etherpad = function (instance) { + + instance.web.form.FieldEtherpad = instance.web.form.AbstractField.extend(_.extend({}, instance.web.form.ReinitializeFieldMixin, { + template: 'FieldEtherpad', + initialize_content: function() { + this.$textarea = undefined; + if (!this.get("effective_readonly")) { + this.$textarea = this.$element.find('textarea'); + //this.$textarea.hide(); + this.$textarea.change(_.bind(function() { + this.set({'value': instance.web.parse_value(this.$textarea.val(), this)}); + }, this)); + this.resized = false; + } + }, + set_value: function(value_) { + var self = this ; + var company_id = (self.view.datarecord.hasOwnProperty('id')) ?self.view.datarecord.company_id[0]: self.view.datarecord.company_id ; + /* getting etherpad url and set pad to field. */ + self.rpc('/web/dataset/get',{ + 'model':'res.company', + 'ids':[company_id], + 'fields':['pad_url_template'], + 'context':{} + },function(companies){ + var pad_template = companies[0].pad_url_template.replace('-%(salt)s-%(name)s','').replace(/\s/g,''); + + var patt_url = (_.str.sprintf(pad_template.replace('-%(id)d',''), { + db : self.session.db, + model : self.view.model, + })).replace(/\s/g,'') ; + //alert("match or not >?"+value_.search(patt_url)); + if(value_ == false) + self.add_pad(self,pad_template,value_); + else if(value_.search(patt_url) != 0) + self.add_pad(self,pad_template,value_); + else + self.show_pad_value(self,value_); + }); + }, + show_pad_value: function(self,value_) + { + self._inhibit_on_change = true; + self.set({'value': value_ }); + self._inhibit_on_change = false; + self.invalid = false; + self.update_dom(); + self.render_value(); + }, + add_pad: function(self,pad_template,value_){ + var url = (_.str.sprintf(pad_template, { + db : self.session.db, + model : self.view.model, + id : Math.round(new Date().getTime()/100.0), + })).replace(/\s/g,''); + var show_value = instance.web.format_value(value_, this, ''); + self.show_pad_value(self,url + '\n'+ show_value); + this.dirty = true; + }, + render_value: function() { + var show_value = instance.web.format_value(this.get('value'), this, ''); + if (!this.get("effective_readonly")) { + this.$textarea.val(show_value); + this.$element.find('div').html(''); + if (!this.resized && this.view.options.resize_textareas) { + this.do_resize(this.view.options.resize_textareas); + this.resized = true; + } + } else { + this.$element.text(show_value); + if(this.get('value') != false) + { + var self = this; + $.get(show_value.split('\n')[0]+'/export/html', function(data) { + console.log(data); + self.$element.html(data); + }); + } + } + }, + validate: function() { + this.invalid = false; + if (!this.get("effective_readonly")) { + try { + var value_ = instance.web.parse_value(this.$textarea.val(), this, ''); + this.invalid = this.get("required") && value_ === ''; + } catch(e) { + this.invalid = true; + } + } + }, + focus: function($element) { + this._super($element || this.$textarea); + }, + do_resize: function(max_height) { + max_height = parseInt(max_height, 10); + var $input = this.$textarea, + $div = $('
').width($input.width()), + new_height; + $div.text($input.val()); + _.each('font-family,font-size,white-space'.split(','), function(style) { + $div.css(style, $input.css(style)); + }); + $div.appendTo($('body')); + new_height = $div.height(); + if (new_height < 90) { + new_height = 90; + } + if (!isNaN(max_height) && new_height > max_height) { + new_height = max_height; + } + $div.remove(); + $input.height(new_height); + }, + reset: function() { + this.resized = false; + } + })); + + instance.web.form.widgets = instance.web.form.widgets.extend({ + 'etherpad': 'instance.web.form.FieldEtherpad', + }); + + +} diff --git a/addons/web_etherpad/static/src/xml/etherpad.xml b/addons/web_etherpad/static/src/xml/etherpad.xml new file mode 100755 index 00000000000..64c2e2dc6a0 --- /dev/null +++ b/addons/web_etherpad/static/src/xml/etherpad.xml @@ -0,0 +1,21 @@ + + + + + + + +
+ +
+
+
+
+ +
From 4cb57e25230fb289d4e61dd2acb31953ebe7e2a2 Mon Sep 17 00:00:00 2001 From: Tejas Tank Date: Thu, 19 Apr 2012 19:05:22 +0530 Subject: [PATCH 003/112] Not show textarea bzr revid: tta@openerp.com-20120419133522-lk286h44c2y06j6g --- addons/web_etherpad/static/src/js/web_etherpad.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/web_etherpad/static/src/js/web_etherpad.js b/addons/web_etherpad/static/src/js/web_etherpad.js index d19fb2b9512..28afe60133d 100755 --- a/addons/web_etherpad/static/src/js/web_etherpad.js +++ b/addons/web_etherpad/static/src/js/web_etherpad.js @@ -6,7 +6,7 @@ openerp.web_etherpad = function (instance) { this.$textarea = undefined; if (!this.get("effective_readonly")) { this.$textarea = this.$element.find('textarea'); - //this.$textarea.hide(); + this.$textarea.hide(); this.$textarea.change(_.bind(function() { this.set({'value': instance.web.parse_value(this.$textarea.val(), this)}); }, this)); From fa035be71d8ccabd31b15fd26cf6d8eefe8ae2fc Mon Sep 17 00:00:00 2001 From: Tejas Tank Date: Fri, 20 Apr 2012 16:27:06 +0530 Subject: [PATCH 004/112] Etherpad : with edit avble to save pad. bzr revid: tta@openerp.com-20120420105706-58pf3atz6qy90ttb --- .../static/src/js/web_etherpad.js | 46 ++++++++----------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/addons/web_etherpad/static/src/js/web_etherpad.js b/addons/web_etherpad/static/src/js/web_etherpad.js index 28afe60133d..22f21cd1e67 100755 --- a/addons/web_etherpad/static/src/js/web_etherpad.js +++ b/addons/web_etherpad/static/src/js/web_etherpad.js @@ -16,27 +16,19 @@ openerp.web_etherpad = function (instance) { set_value: function(value_) { var self = this ; var company_id = (self.view.datarecord.hasOwnProperty('id')) ?self.view.datarecord.company_id[0]: self.view.datarecord.company_id ; - /* getting etherpad url and set pad to field. */ - self.rpc('/web/dataset/get',{ - 'model':'res.company', - 'ids':[company_id], - 'fields':['pad_url_template'], - 'context':{} - },function(companies){ - var pad_template = companies[0].pad_url_template.replace('-%(salt)s-%(name)s','').replace(/\s/g,''); + new instance.web.DataSet(this, 'res.company', {}).read_ids([company_id],['pad_url_template'],{}).then(function(res){ + console.log("dataset :::::" , res); + var pad_template = res[0].pad_url_template.replace('-%(salt)s-%(name)s','').replace(/\s/g,''); - var patt_url = (_.str.sprintf(pad_template.replace('-%(id)d',''), { + var patt_url = (_.str.sprintf(pad_template.replace('-%(id)d',''), { db : self.session.db, model : self.view.model, })).replace(/\s/g,'') ; - //alert("match or not >?"+value_.search(patt_url)); - if(value_ == false) - self.add_pad(self,pad_template,value_); - else if(value_.search(patt_url) != 0) - self.add_pad(self,pad_template,value_); - else - self.show_pad_value(self,value_); - }); + if(value_ == false || value_.search(patt_url) != 0) + self.add_pad(self,pad_template,value_); + else + self.show_pad_value(self,value_); + }); }, show_pad_value: function(self,value_) { @@ -44,7 +36,6 @@ openerp.web_etherpad = function (instance) { self.set({'value': value_ }); self._inhibit_on_change = false; self.invalid = false; - self.update_dom(); self.render_value(); }, add_pad: function(self,pad_template,value_){ @@ -54,14 +45,14 @@ openerp.web_etherpad = function (instance) { id : Math.round(new Date().getTime()/100.0), })).replace(/\s/g,''); var show_value = instance.web.format_value(value_, this, ''); - self.show_pad_value(self,url + '\n'+ show_value); - this.dirty = true; + self.show_pad_value(self,url + '\n'+ show_value); + self._dirty_flag = true ; }, render_value: function() { var show_value = instance.web.format_value(this.get('value'), this, ''); if (!this.get("effective_readonly")) { this.$textarea.val(show_value); - this.$element.find('div').html(''); + this.$element.find('div').html(''); if (!this.resized && this.view.options.resize_textareas) { this.do_resize(this.view.options.resize_textareas); this.resized = true; @@ -70,11 +61,14 @@ openerp.web_etherpad = function (instance) { this.$element.text(show_value); if(this.get('value') != false) { - var self = this; - $.get(show_value.split('\n')[0]+'/export/html', function(data) { - console.log(data); - self.$element.html(data); - }); + var self = this; + if(show_value.split('\n')[0] != '') + $.get(show_value.split('\n')[0]+'/export/html', function(data) { + console.log(data); + self.$element.html(data); + }); + else + self.$element.text(show_value); } } }, From ccb2731a49ea33b1a0a3a1dfca5a09ce7163e300 Mon Sep 17 00:00:00 2001 From: Tejas Tank Date: Fri, 20 Apr 2012 19:07:43 +0530 Subject: [PATCH 005/112] in project widget='etherpad' added bzr revid: tta@openerp.com-20120420133743-2o5d0spxwfxin0yo --- addons/project/project_view.xml | 2 +- addons/web_etherpad/static/src/js/web_etherpad.js | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/addons/project/project_view.xml b/addons/project/project_view.xml index 63898cb518d..a5498e9849d 100644 --- a/addons/project/project_view.xml +++ b/addons/project/project_view.xml @@ -262,7 +262,7 @@ - - - - - - diff --git a/doc/form-notes.rst b/doc/form-notes.rst new file mode 100644 index 00000000000..fc450a336e1 --- /dev/null +++ b/doc/form-notes.rst @@ -0,0 +1,49 @@ +Notes on the usage of the Form View as a sub-widget +=================================================== + +Undocumented stuff +------------------ + +* ``initial_mode`` *option* defines the starting mode of the form + view, one of ``view`` and ``edit`` (?). Default value is ``view`` + (non-editable form). + +* ``embedded_view`` *attribute* has to be set separately when + providing a view directly, no option available for that usage. + + * View arch **must** contain node with + ``@class="oe_form_container"``, otherwise everything will break + without any info + + * Root element of view arch not being ``form`` may or may not work + correctly, no idea. + + * Freeform views => ``@version="7.0"`` + +* Form is not entirely loaded (some widgets may not appear) unless + ``on_record_loaded`` is called (or ``do_show``, which itself calls + ``on_record_loaded``). + +* "Empty" form => ``on_button_new`` (...), or manually call + ``default_get`` + ``on_record_loaded`` + +* Form fields default to width: 100%, padding, !important margin, can + be reached via ``.oe_form_field`` + +* Form *will* render buttons and a pager, offers options to locate + both outside of form itself (``$buttons`` and ``$pager``), providing + empty jquery objects (``$()``) seems to stop displaying both but not + sure if there are deleterious side-effects. + + Other options: + + * Pass in ``$(document.createDocumentFragment)`` to ensure it's a + DOM-compatible tree completely outside of the actual DOM. + + * ??? + +* readonly fields probably don't have a background, beware if need of + overlay + + * What is the difference between ``readonly`` and + ``effective_readonly``? diff --git a/doc/index.rst b/doc/index.rst index 9f54b879acc..c5b85326dac 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -18,6 +18,8 @@ Contents: search-view + form-notes + Older stuff ----------- From cbba19da672a74c6e3edd7dc577e09e5f07f3bb9 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 28 Jun 2012 10:45:47 +0200 Subject: [PATCH 027/112] [IMP] automatically focus first field of edited row bzr revid: xmo@openerp.com-20120628084547-6fo7fkiy2gh2wb6m --- addons/web/static/src/js/view_list_editable.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index 4109a5bd6d5..74b51c3c686 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -147,7 +147,6 @@ openerp.web.list_editable = function (instance) { return this.form.on_record_loaded(record.attributes).pipe(function () { return self.form.do_show({reload: false}); }).then(function () { - // TODO: automatic focus of ?first field // TODO: [Save] button // TODO: save on action button? _(cells).each(function (cell, field_name) { @@ -169,6 +168,8 @@ openerp.web.list_editable = function (instance) { minHeight: $cell.outerHeight() }); }); + // TODO: actually focus clicked field (if editable) + self.form.fields[self.form.fields_order[0]].focus(); self.trigger('edit:after', record, self.form) }); From f83684265d4bf7b4d59789873cc1571810ee8df6 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 28 Jun 2012 16:14:03 +0200 Subject: [PATCH 028/112] [FIX] make edition of existing records kinda-sorta work (if the record is 'saved' by clicking on an other one) bzr revid: xmo@openerp.com-20120628141403-z8kdg24xy5thmg50 --- addons/web/static/src/js/view_list.js | 16 +- .../web/static/src/js/view_list_editable.js | 295 ++++++++++++++---- 2 files changed, 242 insertions(+), 69 deletions(-) diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js index 7d1770069f8..49e6eceb4d8 100644 --- a/addons/web/static/src/js/view_list.js +++ b/addons/web/static/src/js/view_list.js @@ -1133,6 +1133,7 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web. * @returns {String} QWeb rendering of the selected record */ render_record: function (record) { + var self = this; var index = this.records.indexOf(record); return QWeb.render('ListView.row', { columns: this.columns, @@ -1141,7 +1142,7 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web. row_parity: (index % 2 === 0) ? 'even' : 'odd', view: this.view, render_cell: function () { - return this.render_cell.apply(this, arguments); } + return self.render_cell.apply(self, arguments); } }); }, /** @@ -1843,6 +1844,19 @@ var Collection = instance.web.Class.extend(/** @lends Collection# */{ }, // underscore-type methods + find: function (callback) { + var record = null; + for(var section in this._proxies) { + if (this._proxies.hasOwnProperty(section)) { + record = this._proxies[section].find(callback); + } + if (record) { return record; } + } + for(var i=0; i Date: Mon, 2 Jul 2012 11:31:13 +0200 Subject: [PATCH 029/112] [IMP] don't handle keypresses at the editor level, do that in the view instead maybe the editor could do it and trigger an event of its own for the view to handle? bzr revid: xmo@openerp.com-20120702093113-berlms96fqzfjlyg --- .../web/static/src/js/view_list_editable.js | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index fb367c41bb4..0971edc348e 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -78,16 +78,27 @@ openerp.web.list_editable = function (instance) { } }, on_loaded: function (data, grouped) { - var self = this, form_ready = $.when(); + var self = this; // tree/@editable takes priority on everything else if present. this.options.editable = ! this.options.read_only && (data.arch.attrs.editable || this.options.editable); var result = this._super(data, grouped); if (this.options.editable || true) { this.editor = new instance.web.list.Editor(this); - return $.when( - result, - this.editor.prependTo(this.$element)); + var editor_ready = this.editor.prependTo(this.$element).then(function () { + self.editor.$element.on('keyup', function (e) { + switch (e.which) { + case KEY_RETURN: + self.save_edition(); + break; + case KEY_ESCAPE: + self.cancel_edition(); + break; + } + }); + }); + + return $.when(result, editor_ready); } return result; @@ -264,19 +275,8 @@ openerp.web.list_editable = function (instance) { var self = this; var _super = this._super(); this.form.embedded_view = this.getParent().editionView(this); - var form_ready = this.form.appendTo(this.$element).then(function () { - self.form.do_hide(); - self.form.$element.on('keyup', function (e) { - switch (e.which) { - case KEY_RETURN: - self.save(); - break; - case KEY_ESCAPE: - self.cancel(); - break; - } - }); - }); + var form_ready = this.form.appendTo(this.$element).then( + self.form.proxy('do_hide')); return $.when(_super, form_ready); }, From 718ae91920f7cafd8d7e50852f073755d5c29a32 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 2 Jul 2012 13:05:04 +0200 Subject: [PATCH 030/112] [FIX] broken implementation of collection#find bzr revid: xmo@openerp.com-20120702110504-9fga0xwo0e5bzq1c --- addons/web/static/src/js/view_list.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js index 49e6eceb4d8..6c9e244c5e3 100644 --- a/addons/web/static/src/js/view_list.js +++ b/addons/web/static/src/js/view_list.js @@ -1853,8 +1853,10 @@ var Collection = instance.web.Class.extend(/** @lends Collection# */{ if (record) { return record; } } for(var i=0; i Date: Mon, 2 Jul 2012 13:05:30 +0200 Subject: [PATCH 031/112] [FIX] only select rows if select_view_id *is set* and matches the record's id bzr revid: xmo@openerp.com-20120702110530-54t24gh0lo1cctsv --- addons/web/static/src/xml/base.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index a7c99e65b2c..66a1c02e448 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -645,7 +645,7 @@ From 3cc825994b44cb3c7d732091b95df127d2b26445 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 2 Jul 2012 13:06:07 +0200 Subject: [PATCH 032/112] [TODO] add todo note bzr revid: xmo@openerp.com-20120702110607-csi1oa4hm6lxtwat --- addons/web/static/src/js/view_list_editable.js | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index 0971edc348e..0843426a330 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -274,6 +274,7 @@ openerp.web.list_editable = function (instance) { start: function () { var self = this; var _super = this._super(); + // TODO: getParent() should be delegate defaulting to getParent() this.form.embedded_view = this.getParent().editionView(this); var form_ready = this.form.appendTo(this.$element).then( self.form.proxy('do_hide')); From d1eeaaa51eef4cf208ef74d8e888e9b0fa80ac05 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 2 Jul 2012 13:06:58 +0200 Subject: [PATCH 033/112] [FIX] better filtering of id-less rows being selected in a ListView.List body bzr revid: xmo@openerp.com-20120702110658-7t8pu12uso55enrh --- addons/web/static/src/js/view_list_editable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index 0843426a330..a0ff796ed5e 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -425,7 +425,7 @@ openerp.web.list_editable = function (instance) { record = new instance.web.list.Record(); this.records.add( record, {at: this.options.editable === 'top' ? 0 : null}); - $row = this.$current.find(':not([data-id])'); + $row = this.$current.children('tr:not([data-id])'); } else { record = this.records.get($row.data('id')); } From c262b2d1ae0c476be828aad0e1787939bebe2918 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 2 Jul 2012 13:07:33 +0200 Subject: [PATCH 034/112] [FIX] shallow-copy info passed into Editor#edit to avoid mutating an object owned by somebody else bzr revid: xmo@openerp.com-20120702110733-0qp0ki3hbw5b2nd3 --- addons/web/static/src/js/view_list_editable.js | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index a0ff796ed5e..ac39f2c43e8 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -287,6 +287,7 @@ openerp.web.list_editable = function (instance) { edit: function (record, configureField) { var self = this; var form = self.form; + record = _.extend({}, record); form.on_record_loaded(record).pipe(function () { return form.do_show({reload: false}); }).then(function () { From d6a9d86e098ede1cea51305334c2cfefd787f7df Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 2 Jul 2012 13:10:26 +0200 Subject: [PATCH 035/112] [FIX] change event handling for 'new' records (records w/o an id yet) bzr revid: xmo@openerp.com-20120702111026-twmh5gbg4i1fd4kj --- addons/web/static/src/js/view_list.js | 23 +++++++++++++------ .../web/static/src/js/view_list_editable.js | 3 ++- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js index 6c9e244c5e3..8cbe18a46cc 100644 --- a/addons/web/static/src/js/view_list.js +++ b/addons/web/static/src/js/view_list.js @@ -905,27 +905,36 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web. this.record_callbacks = { 'remove': function (event, record) { - var $row = self.$current.find( + var $row = self.$current.children( '[data-id=' + record.get('id') + ']'); var index = $row.data('index'); $row.remove(); self.refresh_zebra(index); }, 'reset': function () { return self.on_records_reset(); }, - 'change': function (event, record) { - var $row = self.$current.find('[data-id=' + record.get('id') + ']'); + 'change': function (event, record, attribute, value, old_value) { + var $row; + if (attribute === 'id') { + if (old_value) { + throw new Error("Setting 'id' attribute on existing record " + + JSON.stringify(record.attributes)); + } + // Set id on new record + $row = self.$current.children('tr:not([data-id])'); + } else { + $row = self.$current.children( + '[data-id=' + record.get('id') + ']'); + } $row.replaceWith(self.render_record(record)); }, 'add': function (ev, records, record, index) { - var $new_row = $('').attr({ - 'data-id': record.get('id') - }); + var $new_row = $(self.render_record(record)); if (index === 0) { $new_row.prependTo(self.$current); } else { var previous_record = records.at(index-1), - $previous_sibling = self.$current.find( + $previous_sibling = self.$current.children( '[data-id=' + previous_record.get('id') + ']'); $new_row.insertAfter($previous_sibling); } diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index ac39f2c43e8..f35e667c35d 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -170,8 +170,9 @@ openerp.web.list_editable = function (instance) { record = self.records.find(function (r) { return !r.get('id'); }); - record.set('id', attrs.id, {silent: true}); + record.set('id', attrs.id); } + self.reload_record(record); }); }, From 6719fb96d79959ffc1c49db54b6bf3b4a986d8fb Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 2 Jul 2012 14:29:02 +0200 Subject: [PATCH 036/112] [FIX] handling of setting/resetting ids on records in list collections bzr revid: xmo@openerp.com-20120702122902-f349shdjslbbxd3k --- addons/web/static/src/js/view_list.js | 19 ++++++++++++++++-- addons/web/static/test/list-utils.js | 29 +++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js index 8cbe18a46cc..07b24edbfcf 100644 --- a/addons/web/static/src/js/view_list.js +++ b/addons/web/static/src/js/view_list.js @@ -919,6 +919,9 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web. throw new Error("Setting 'id' attribute on existing record " + JSON.stringify(record.attributes)); } + // add record to dataset + self.dataset.ids.splice( + self.records.indexOf(record), 0, value); // Set id on new record $row = self.$current.children('tr:not([data-id])'); } else { @@ -1846,9 +1849,21 @@ var Collection = instance.web.Class.extend(/** @lends Collection# */{ return this; }, - _onRecordEvent: function (event, record, options) { + _onRecordEvent: function (event) { + switch(event) { // don't propagate reset events - if (event === 'reset') { return; } + case 'reset': return; + case 'change:id': + var record = arguments[1]; + var new_value = arguments[2]; + var old_value = arguments[3]; + // [change:id, record, new_value, old_value] + if (this._byId[old_value] === record) { + delete this._byId[old_value]; + this._byId[new_value] = record; + } + break; + } this.trigger.apply(this, arguments); }, diff --git a/addons/web/static/test/list-utils.js b/addons/web/static/test/list-utils.js index 7890e71c6bc..97d7ec90cfc 100644 --- a/addons/web/static/test/list-utils.js +++ b/addons/web/static/test/list-utils.js @@ -338,4 +338,33 @@ $(document).ready(function () { ids, [1, 2, 3, 10, 20, 30], 'tree collections should be deeply iterated'); }); + + module("list-weirds", { + setup: function () { + openerp = window.openerp.init([]); + window.openerp.web.corelib(openerp); + window.openerp.web.coresetup(openerp); + window.openerp.web.chrome(openerp); + // views loader stuff + window.openerp.web.data(openerp); + window.openerp.web.views(openerp); + window.openerp.web.list(openerp); + } + }); + test('set-from-noid', function () { + var root = new openerp.web.list.Collection(); + root.add({v: 3}); + root.at(0).set('id', 42); + var record = root.get(42); + equal(root.length, 1); + equal(record.get('v'), 3, "should have fetched the original record"); + }); + test('set-from-previd', function () { + var root = new openerp.web.list.Collection(); + root.add({id: 1, v: 2}); + root.get(1).set('id', 42); + var record = root.get(42); + equal(root.length, 1); + equal(record.get('v'), 2, "should have fetched the original record"); + }); }); From 13cf266e2ce9fe0d6130f5b1a5b04efb008a02ff Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 2 Jul 2012 15:22:17 +0200 Subject: [PATCH 037/112] [FIX] next on create/save * Move prev/next behavior after save to ListView * Have fetching of row for a record be a method chain on Group/List * Have edition behavior be fully specified in ListView bzr revid: xmo@openerp.com-20120702132217-ni1byjkf7t6tr5a4 --- .../web/static/src/js/view_list_editable.js | 90 +++++++++++++------ 1 file changed, 62 insertions(+), 28 deletions(-) diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index f35e667c35d..c1f81ccfa08 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -120,12 +120,17 @@ openerp.web.list_editable = function (instance) { /** * Set up the edition of a record of the list view "inline" * - * @param {instance.web.list.Record} record record to edit - * @param {Object} cells map of field names to the DOM elements used to display these fields for the record being edited + * @param {instance.web.list.Record} [record] record to edit, leave empty to create a new record * @return {jQuery.Deferred} */ - start_edition: function (record, cells) { + start_edition: function (record) { var self = this; + if (!record) { + record = new instance.web.list.Record(); + this.records.add(record, { + at: this.options.editable === 'top' ? 0 : null}); + } + var cells = this.getCellsFor(this.groups.getRowFor(record)); return this.ensure_saved().pipe(function () { return self.withEvent('edit', { record: record.attributes, @@ -154,6 +159,13 @@ openerp.web.list_editable = function (instance) { [record.attributes]); }); }, + getCellsFor: function ($row) { + var cells = {}; + $row.children('td').each(function (index, el) { + cells[el.getAttribute('data-field')] = el + }); + return cells; + }, /** * @return {jQuery.Deferred} */ @@ -169,8 +181,22 @@ openerp.web.list_editable = function (instance) { // new record record = self.records.find(function (r) { return !r.get('id'); - }); - record.set('id', attrs.id); + }).set('id', attrs.id); + + setTimeout(function () { + self.start_edition(); + }, 0); + + } else { + var next_index = self.records.indexOf(record) + 1; + if (next_index === self.records.length) { + next_index = 0; + } + + setTimeout(function () { + self.start_edition(self.records.at(next_index)); + }, 0); + } self.reload_record(record); @@ -351,6 +377,13 @@ openerp.web.list_editable = function (instance) { _.invoke( _.values(this.children), 'ensure_saved')); + }, + getRowFor: function (record) { + return _(this.children).chain() + .invoke('getRowFor', record) + .compact() + .first() + .value(); } }); @@ -421,24 +454,9 @@ openerp.web.list_editable = function (instance) { break; } }, - render_row_as_form: function ($row) { - var record; - if (!$row || $row.length === 0) { - record = new instance.web.list.Record(); - this.records.add( - record, {at: this.options.editable === 'top' ? 0 : null}); - $row = this.$current.children('tr:not([data-id])'); - } else { - record = this.records.get($row.data('id')); - } - - var cells = {}; - $row.children('td').each(function (index, el) { - cells[el.getAttribute('data-field')] = el - }); - - // TODO: creation (record_id === null?) - return this.view.start_edition(record, cells); + render_row_as_form: function (id) { + return this.view.start_edition( + id ? this.records.get(id) : null); }, handle_onwrite: function (source_record_id) { var self = this; @@ -524,14 +542,30 @@ openerp.web.list_editable = function (instance) { * Edits record currently selected via dataset */ edit_record: function (record_id) { - this.render_row_as_form( - this.$current.find('[data-id=' + record_id + ']')); - $(this).trigger( - 'edit', - [record_id, this.dataset]); + this.render_row_as_form(record_id); }, new_record: function () { this.render_row_as_form(); + }, + /** + * If a row mapping to the record (@data-id matching the record's id or + * no @data-id if the record has no id), returns it. Otherwise returns + * ``null``. + * + * @param {Record} record the record to get a row for + * @return {jQuery|null} + */ + getRowFor: function (record) { + var id, $row; + if (id = record.get('id')) { + $row = this.$current.children('[data-id=' + id + ']'); + } else { + $row = this.$current.children(':not([data-id])'); + } + if ($row.length) { + return $row; + } + return null; } }); }; From 5df6c47700fba8c3cf2145512990e526f38dfd26 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 2 Jul 2012 15:34:51 +0200 Subject: [PATCH 038/112] [FIX] methods capitalization bzr revid: xmo@openerp.com-20120702133451-e5b7uozm0p36jbpr --- addons/web/static/src/js/view_form.js | 4 +-- .../web/static/src/js/view_list_editable.js | 32 +++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 7792b5ffa19..76fcabe13f8 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -3067,7 +3067,7 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({ } return res; } else if (this.viewmanager.active_view === "list") { - var res = $.when(view.ensure_saved()); + var res = $.when(view.ensureSaved()); if (!res.isResolved() && !res.isRejected()) { console.warn("Asynchronous get_value() is not supported in list view."); } @@ -3282,7 +3282,7 @@ instance.web.form.One2ManyList = instance.web.ListView.List.extend({ return; } if (!self.edition_form.widget_is_stopped) { - self.view.ensure_saved(); + self.view.ensureSaved(); } }); }); diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index c1f81ccfa08..aebb549d6d5 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -89,10 +89,10 @@ openerp.web.list_editable = function (instance) { self.editor.$element.on('keyup', function (e) { switch (e.which) { case KEY_RETURN: - self.save_edition(); + self.saveEdition(); break; case KEY_ESCAPE: - self.cancel_edition(); + self.cancelEdition(); break; } }); @@ -111,11 +111,11 @@ openerp.web.list_editable = function (instance) { * * @returns {$.Deferred} */ - ensure_saved: function () { + ensureSaved: function () { if (!this.editor.isEditing()) { return $.when(); } - return this.save_edition(); + return this.saveEdition(); }, /** * Set up the edition of a record of the list view "inline" @@ -123,7 +123,7 @@ openerp.web.list_editable = function (instance) { * @param {instance.web.list.Record} [record] record to edit, leave empty to create a new record * @return {jQuery.Deferred} */ - start_edition: function (record) { + startEdition: function (record) { var self = this; if (!record) { record = new instance.web.list.Record(); @@ -131,7 +131,7 @@ openerp.web.list_editable = function (instance) { at: this.options.editable === 'top' ? 0 : null}); } var cells = this.getCellsFor(this.groups.getRowFor(record)); - return this.ensure_saved().pipe(function () { + return this.ensureSaved().pipe(function () { return self.withEvent('edit', { record: record.attributes, cancel: false @@ -169,7 +169,7 @@ openerp.web.list_editable = function (instance) { /** * @return {jQuery.Deferred} */ - save_edition: function () { + saveEdition: function () { var self = this; return this.withEvent('save', { editor: this.editor, @@ -184,7 +184,7 @@ openerp.web.list_editable = function (instance) { }).set('id', attrs.id); setTimeout(function () { - self.start_edition(); + self.startEdition(); }, 0); } else { @@ -194,7 +194,7 @@ openerp.web.list_editable = function (instance) { } setTimeout(function () { - self.start_edition(self.records.at(next_index)); + self.startEdition(self.records.at(next_index)); }, 0); } @@ -205,7 +205,7 @@ openerp.web.list_editable = function (instance) { /** * @return {jQuery.Deferred} */ - cancel_edition: function () { + cancelEdition: function () { var self = this; return this.withEvent('cancel', { editor: this.editor, @@ -372,11 +372,11 @@ openerp.web.list_editable = function (instance) { * * @returns {$.Deferred} */ - ensure_saved: function () { + ensureSaved: function () { return $.when.apply(null, _.invoke( _.values(this.children), - 'ensure_saved')); + 'ensureSaved')); }, getRowFor: function (record) { return _(this.children).chain() @@ -450,12 +450,12 @@ openerp.web.list_editable = function (instance) { }); break; case KEY_ESCAPE: - this.cancel_edition(); + this.cancelEdition(); break; } }, render_row_as_form: function (id) { - return this.view.start_edition( + return this.view.startEdition( id ? this.records.get(id) : null); }, handle_onwrite: function (source_record_id) { @@ -519,7 +519,7 @@ openerp.web.list_editable = function (instance) { /** * If the current list is being edited, ensures it's saved */ - ensure_saved: function () { + ensureSaved: function () { if (this.edition) { // kinda-hack-ish: if the user has entered data in a field, // oe_form_dirty will be set on the form so save, otherwise @@ -535,7 +535,7 @@ openerp.web.list_editable = function (instance) { /** * Cancels the edition of the row for the current dataset index */ - cancel_edition: function () { + cancelEdition: function () { this.cancel_pending_edition(); }, /** From 70fa57b9df15e8cfb30c84605441e4a20b5d6529 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 2 Jul 2012 15:58:24 +0200 Subject: [PATCH 039/112] [IMP] move onwrite handling to ListView (from List) untested bzr revid: xmo@openerp.com-20120702135824-ngcx34qfuu3t2kab --- .../web/static/src/js/view_list_editable.js | 88 +++++-------------- 1 file changed, 24 insertions(+), 64 deletions(-) diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index aebb549d6d5..35edc19c9f4 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -175,7 +175,7 @@ openerp.web.list_editable = function (instance) { editor: this.editor, form: this.editor.form, cancel: false - }, this.editor.save).then(function (attrs) { + }, this.editor.save).pipe(function (attrs) { var record = self.records.get(attrs.id); if (!record) { // new record @@ -186,7 +186,6 @@ openerp.web.list_editable = function (instance) { setTimeout(function () { self.startEdition(); }, 0); - } else { var next_index = self.records.indexOf(record) + 1; if (next_index === self.records.length) { @@ -196,10 +195,10 @@ openerp.web.list_editable = function (instance) { setTimeout(function () { self.startEdition(self.records.at(next_index)); }, 0); - } - - self.reload_record(record); + return $.when( + self.handleOnWrite(record), + self.reload_record(record)); }); }, /** @@ -268,7 +267,26 @@ openerp.web.list_editable = function (instance) { widget.attrs.modifiers = JSON.stringify(modifiers); }); return view; - } + }, + handleOnWrite: function (source_record) { + var self = this; + var on_write_callback = self.fields_view.arch.attrs.on_write; + if (!on_write_callback) { return $.when(); } + return this.dataset.call(on_write_callback, [source_record.get('id')]) + .pipe(function (ids) { + return $.when.apply(null, _(ids).map(function (id) { + var record = self.records.get(id); + if (!record) { + // insert after the source record + var index = self.records.indexOf(source_record) + 1; + record = new instance.web.list.Record({id: id}); + self.records.add(record, {at: index}); + self.dataset.ids.splice(index, 0, id); + } + return self.reload_record(record); + })); + }); + }, }); instance.web.list.Editor = instance.web.Widget.extend({ @@ -458,64 +476,6 @@ openerp.web.list_editable = function (instance) { return this.view.startEdition( id ? this.records.get(id) : null); }, - handle_onwrite: function (source_record_id) { - var self = this; - var on_write_callback = self.view.fields_view.arch.attrs.on_write; - if (!on_write_callback) { return; } - this.dataset.call(on_write_callback, [source_record_id], function (ids) { - _(ids).each(function (id) { - var record = self.records.get(id); - if (!record) { - // insert after the source record - var index = self.records.indexOf( - self.records.get(source_record_id)) + 1; - record = new instance.web.list.Record({id: id}); - self.records.add(record, {at: index}); - self.dataset.ids.splice(index, 0, id); - } - self.reload_record(record); - }); - }); - }, - /** - * Saves the current row, and returns a Deferred resolving to an object - * with the following properties: - * - * ``created`` - * Boolean flag indicating whether the record saved was being created - * (``true`` or edited (``false``) - * ``edited_record`` - * The result of saving the record (either the newly created record, - * or the post-edition record), after insertion in the Collection if - * needs be. - * - * @returns {$.Deferred<{created: Boolean, edited_record: Record}>} - */ - save_row: function () { - //noinspection JSPotentiallyInvalidConstructorUsage - var self = this; - return this.edition_form - .do_save(null, this.options.editable === 'top') - .pipe(function (result) { - if (result.created && !self.edition_id) { - self.records.add({id: result.result}, - {at: self.options.editable === 'top' ? 0 : null}); - self.edition_id = result.result; - } - var edited_record = self.records.get(self.edition_id); - - return $.when( - self.handle_onwrite(self.edition_id), - self.cancel_pending_edition().then(function () { - $(self).trigger('saved', [self.dataset]); - })).pipe(function () { - return { - created: result.created || false, - edited_record: edited_record - }; - }); - }); - }, /** * If the current list is being edited, ensures it's saved */ From 29e65ff0526bad593c84b254e7f72663f4bc6f16 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 3 Jul 2012 11:10:58 +0200 Subject: [PATCH 040/112] [FIX] move 'behavior on save' to handler for [Return] key as it's the only situation where we want to skip to next record bzr revid: xmo@openerp.com-20120703091058-b7wdebu8ak6gssyb --- .../web/static/src/js/view_list_editable.js | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index 35edc19c9f4..3e4d0d777e5 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -89,7 +89,18 @@ openerp.web.list_editable = function (instance) { self.editor.$element.on('keyup', function (e) { switch (e.which) { case KEY_RETURN: - self.saveEdition(); + self.saveEdition().then(function (saveInfo) { + if (saveInfo.created) { + self.startEdition(); + return; + } + var next_index = self.records.indexOf(saveInfo.record) + 1; + if (next_index === self.records.length) { + next_index = 0; + } + + self.startEdition(self.records.at(next_index)); + }); break; case KEY_ESCAPE: self.cancelEdition(); @@ -176,29 +187,23 @@ openerp.web.list_editable = function (instance) { form: this.editor.form, cancel: false }, this.editor.save).pipe(function (attrs) { + var created = false; var record = self.records.get(attrs.id); if (!record) { // new record + created = true; record = self.records.find(function (r) { return !r.get('id'); }).set('id', attrs.id); - - setTimeout(function () { - self.startEdition(); - }, 0); - } else { - var next_index = self.records.indexOf(record) + 1; - if (next_index === self.records.length) { - next_index = 0; - } - - setTimeout(function () { - self.startEdition(self.records.at(next_index)); - }, 0); } return $.when( self.handleOnWrite(record), - self.reload_record(record)); + self.reload_record(record)).pipe(function () { + return { + created: created, + record: record + } + }); }); }, /** From 4aa64c00338d9565a55ef4cc2b61503cdb42e6f3 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 3 Jul 2012 11:12:57 +0200 Subject: [PATCH 041/112] [FIX] removal of id-less records from list view bzr revid: xmo@openerp.com-20120703091257-3dw0qs9aw125hrc7 --- addons/web/static/src/js/view_list.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js index 07b24edbfcf..dab9fcb7727 100644 --- a/addons/web/static/src/js/view_list.js +++ b/addons/web/static/src/js/view_list.js @@ -905,8 +905,13 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web. this.record_callbacks = { 'remove': function (event, record) { - var $row = self.$current.children( + var $row; + if (!record.get('id')) { + $row = self.$current.children(':not([data-id])'); + } else { + $row = self.$current.children( '[data-id=' + record.get('id') + ']'); + } var index = $row.data('index'); $row.remove(); self.refresh_zebra(index); From 1a7ca5f6763ce278c36560e97f196b838a6c92d5 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 3 Jul 2012 11:32:00 +0200 Subject: [PATCH 042/112] [IMP] attempt to tighten concurrent operations during save phase of an edited row bzr revid: xmo@openerp.com-20120703093200-hxsr9pnwoszbgwgl --- .../web/static/src/js/view_list_editable.js | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index 3e4d0d777e5..49e0e0a551f 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -141,7 +141,8 @@ openerp.web.list_editable = function (instance) { this.records.add(record, { at: this.options.editable === 'top' ? 0 : null}); } - var cells = this.getCellsFor(this.groups.getRowFor(record)); + var $recordRow = this.groups.getRowFor(record); + var cells = this.getCellsFor($recordRow); return this.ensureSaved().pipe(function () { return self.withEvent('edit', { record: record.attributes, @@ -196,13 +197,12 @@ openerp.web.list_editable = function (instance) { return !r.get('id'); }).set('id', attrs.id); } - return $.when( - self.handleOnWrite(record), - self.reload_record(record)).pipe(function () { - return { - created: created, - record: record - } + // onwrite callback could be altering & reloading the record + // which has *just* been saved, so first perform all onwrites + // then do a final reload of the record + return self.handleOnWrite(record) + .pipe(function () { return self.reload_record(record); }) + .pipe(function () { return { created: created, record: record }; }); }); }, @@ -338,9 +338,9 @@ openerp.web.list_editable = function (instance) { var self = this; var form = self.form; record = _.extend({}, record); - form.on_record_loaded(record).pipe(function () { + return form.on_record_loaded(record).pipe(function () { return form.do_show({reload: false}); - }).then(function () { + }).pipe(function () { self.record = record; // TODO: [Save] button // TODO: save on action button? From a5940c414790699caa9955c776002839917b0407 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 3 Jul 2012 12:27:31 +0200 Subject: [PATCH 043/112] [IMP] reload record after cancelling an edition bzr revid: xmo@openerp.com-20120703102731-mh1gqbglenr4boem --- addons/web/static/src/js/view_list_editable.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index 49e0e0a551f..0d88ddf6dee 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -216,13 +216,14 @@ openerp.web.list_editable = function (instance) { form: this.editor.form, cancel: false }, this.editor.cancel).then(function (attrs) { - if (!attrs.id) { - var to_delete = self.records.find(function (r) { - return !r.get('id'); - }); - if (to_delete) { - self.records.remove(to_delete); - } + if (attrs.id) { + return self.reload_record(self.records.get(attrs.id)); + } + var to_delete = self.records.find(function (r) { + return !r.get('id'); + }); + if (to_delete) { + self.records.remove(to_delete); } }); }, From ae6c6eb8ec1a00071045581f45be754783160b72 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 3 Jul 2012 12:27:50 +0200 Subject: [PATCH 044/112] [ADD] save button instead of selector during edition bzr revid: xmo@openerp.com-20120703102750-1vmoljtbrnnl3lzo --- addons/web/static/src/css/base.css | 14 ++++++++++++++ addons/web/static/src/css/base.sass | 9 +++++++-- addons/web/static/src/js/view_list_editable.js | 18 +++++++++++++++++- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index 41d48432559..6061113642a 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -2115,6 +2115,20 @@ .openerp .oe-listview-content .numeric input { text-align: right; } +.openerp .oe-listview-content tr.oe_edition th.oe-record-selector { + padding: 0 6px; + font-size: 1px; + letter-spacing: -1px; + color: transparent; +} +.openerp .oe-listview-content tr.oe_edition th.oe-record-selector:before { + font: 21px "mnmliconsRegular"; + content: "S"; + color: #404040; +} +.openerp .oe-listview-content tr.oe_edition th.oe-record-selector input { + display: none; +} .openerp .oe_trad_field.touched { border: 1px solid green !important; } diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index 7135b474aa3..a55d1155859 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -83,9 +83,8 @@ $colour4: #8a89ba letter-spacing: -1px color: transparent &:before - font-family: "mnmliconsRegular" + font: 21px "mnmliconsRegular" content: $icon-name - font-size: 20px color: $color // }}} @@ -1667,6 +1666,12 @@ $colour4: #8a89ba width: 82px input text-align: right + + tr.oe_edition th.oe-record-selector + padding: 0 6px + @include text-to-icon("S") + input + display: none // }}} // Translation {{{ .oe_trad_field.touched diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index 0d88ddf6dee..1c477046c2b 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -169,6 +169,8 @@ openerp.web.list_editable = function (instance) { }); }], [record.attributes]); + }).then(function () { + $recordRow.addClass('oe_edition') }); }, getCellsFor: function ($row) { @@ -343,7 +345,6 @@ openerp.web.list_editable = function (instance) { return form.do_show({reload: false}); }).pipe(function () { self.record = record; - // TODO: [Save] button // TODO: save on action button? _(form.fields).each(function (field, name) { configureField(name, field); @@ -412,6 +413,21 @@ openerp.web.list_editable = function (instance) { }); instance.web.ListView.List.include(/** @lends instance.web.ListView.List# */{ + init: function () { + var self = this; + this._super.apply(this, arguments); + var selection_handler = _.find(this.$_element.data('events').click, function (h) { + return h.selector === 'th.oe-record-selector'; + }).handler; + // TODO: cleaner way to do that? + this.$_element + .off('click', 'th.oe-record-selector') + .on('click', '.oe_edition th.oe-record-selector', function (e) { + e.stopImmediatePropagation(); + self.view.saveEdition(); + }) + .on('click', 'th.oe-record-selector', selection_handler); + }, row_clicked: function (event) { if (!this.options.editable) { return this._super.apply(this, arguments); From 336443d60446f059481cdef3ce954aefc4ff5e76 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 3 Jul 2012 13:07:00 +0200 Subject: [PATCH 045/112] [IMP] hiding of readonly form fields in edition row: don't directly access DOM visibility properties bzr revid: xmo@openerp.com-20120703110700-k0dqtthwo1g36pod --- addons/web/static/src/js/view_list_editable.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index 1c477046c2b..0926e233109 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -150,18 +150,16 @@ openerp.web.list_editable = function (instance) { }, self.editor.edit, [record.attributes, function (field_name, field) { var cell = cells[field_name]; - if (!cell) { return; } + if (!cell || field.get('effective_readonly')) { + // Readonly fields can just remain the list's, form's + // usually don't have backgrounds &al + field.set({invisible: true}); + return; + } var $cell = $(cell); var position = $cell.position(); - // FIXME: this is shit. Is it possible to prefilter? - if (field.get('effective_readonly')) { - // Readonly fields can just remain the list's, form's - // usually don't have backgrounds &al - field.$element.hide(); - return; - } - field.$element.show().css({ + field.$element.css({ top: position.top, left: position.left, width: $cell.outerWidth(), From 895dfe2448931f9f21e9d93c301e3e08890d851a Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 3 Jul 2012 15:32:10 +0200 Subject: [PATCH 046/112] [FIX] replace references to parent in editor with delegate, write some basic APIDoc bzr revid: xmo@openerp.com-20120703133210-kgrj7y10cr3c8hai --- .../web/static/src/js/view_list_editable.js | 17 +- doc/index.rst | 1 + doc/list-view.rst | 295 ++++++++++++++++++ 3 files changed, 307 insertions(+), 6 deletions(-) create mode 100644 doc/list-view.rst diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index 0926e233109..826ee03a0c1 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -139,7 +139,7 @@ openerp.web.list_editable = function (instance) { if (!record) { record = new instance.web.list.Record(); this.records.add(record, { - at: this.options.editable === 'top' ? 0 : null}); + at: this.isPrependOnCreate() ? 0 : null}); } var $recordRow = this.groups.getRowFor(record); var cells = this.getCellsFor($recordRow); @@ -293,6 +293,9 @@ openerp.web.list_editable = function (instance) { })); }); }, + isPrependOnCreate: function () { + return this.options.editable === 'top'; + } }); instance.web.list.Editor = instance.web.Widget.extend({ @@ -305,18 +308,21 @@ openerp.web.list_editable = function (instance) { * @param {instance.web.Widget} parent * @param {Object} options * @param {instance.web.FormView} [options.formView=instance.web.FormView] + * @param {Object} [options.delegate] */ init: function (parent, options) { this._super(parent); this.options = options || {}; _.defaults(this.options, { - formView: instance.web.FormView + formView: instance.web.FormView, + delegate: this.getParent() }); + this.delegate = this.options.delegate; this.record = null; this.form = new (this.options.formView)( - this, this.getParent().dataset, false, { + this, this.delegate.dataset, false, { initial_mode: 'edit', $buttons: $(), $pager: $() @@ -325,8 +331,7 @@ openerp.web.list_editable = function (instance) { start: function () { var self = this; var _super = this._super(); - // TODO: getParent() should be delegate defaulting to getParent() - this.form.embedded_view = this.getParent().editionView(this); + this.form.embedded_view = this.delegate.editionView(this); var form_ready = this.form.appendTo(this.$element).then( self.form.proxy('do_hide')); return $.when(_super, form_ready); @@ -363,7 +368,7 @@ openerp.web.list_editable = function (instance) { save: function () { var self = this; return this.form - .do_save(null, this.getParent().options.editable === 'top') + .do_save(null, this.delegate.isPrependOnCreate()) .pipe(function (result) { var created = result.created && !self.record.id; if (created) { diff --git a/doc/index.rst b/doc/index.rst index c5b85326dac..8608af59d3d 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -18,6 +18,7 @@ Contents: search-view + list-view form-notes Older stuff diff --git a/doc/list-view.rst b/doc/list-view.rst new file mode 100644 index 00000000000..a7436e419dc --- /dev/null +++ b/doc/list-view.rst @@ -0,0 +1,295 @@ +List View +========= + +Editable list view +------------------ + +List view edition is an extension to the base listview providing the +capability of inline record edition by delegating to an embedded form +view. + +.. todo:: + + cleanup options and settings for editability configuration. Right + now there are: + + ``defaults.editable`` + + ``null``, ``"top"`` or ``"bottom"``, generally broken and + useless + + ``context.set_editable`` + + forces ``options.editable`` to ``"bottom"`` + + ``view.arch.attrs.editable`` + + same as ``defaults.editable``, but applied separately (after + reloading the view), if absent delegates to + ``options.editable`` which may have been set previously. + + ``options.read_only`` + + worthless shit added by niv in the listview directly (wtf?) + + .. note:: can probably be replaced by cancelling ``edit:before`` + + and :js:func:`~openerp.web.ListView.set_editable` which + ultimately behaves weird-as-fuck-ly. + +The editable list view module adds a number of methods to the list +view, on top of implementing the :js:class:`EditorDelegate` protocol: + +Interaction Methods ++++++++++++++++++++ + +.. js:function:: openerp.web.ListView.ensureSaved + + Attempts to resolve the pending edition, if any, by saving the + edited row's current state. + + :returns: delegate resolving to all editions having been saved, or + rejected if a pending edition could not be saved + (e.g. validation failure) + +.. js:function:: openerp.web.ListView.startEdition([record]) + + Starts editing the provided record inline, through an overlay form + view of editable fields in the record. + + If no record is provided, creates a new one according to the + editability configuration of the list view. + + This method resolves any pending edition when invoked, before + starting a new edition. + + :type record: :js:class:`~openerp.web.list.Record` + :returns: delegate to the form used for the edition + +.. js:function:: openerp.web.ListView.saveEdition + + Resolves the pending edition. + + :returns: delegate to the save being completed, resolves to an + object with two attributes ``created`` (flag indicating + whether the saved record was just created or was + updated) and ``record`` the reloaded record having been + edited. + +.. js:function:: openerp.web.ListView.cancelEdition + + Cancels pending edition, cleans up the list view in case of + creation (removes the empty record being created). + +Utility Methods ++++++++++++++++ + +.. js:function:: openerp.web.ListView.getCellsFor(row) + + Extracts the cells from a listview row, and puts them in a + {fieldname: cell} mapping for analysis and manipulation. + + :param jQuery row: + :rtype: Object + +.. js:function:: openerp.web.ListView.withEvent(event_name, event, action[, args][, trigger_params]) + + Executes ``action`` in the context of the view's editor, + bracketing it with cancellable event signals. + + :param String event_name: base name for the bracketing event, will + be postfixed by ``:before`` and + ``:after`` before being called + (respectively before and after + ``action`` is executed) + :param Object event: object passed to the ``:before`` event + handlers. + :param Function action: function called with the view's editor as + its ``this``. May return a deferred. + :param Array args: arguments passed to ``action`` + :param Array trigger_params: arguments passed to the ``:after`` + event handler alongside the results + of ``action`` + +Behavioral Customizations ++++++++++++++++++++++++++ + +.. js:function:: openerp.web.ListView.handleOnWrite(record) + + Implements the handling of the ``onwrite`` listview attribute: + calls the RPC methods specified by ``@onwrite``, and if that + method returns an array of ids loads or reloads the records + corresponding to those ids. + + :param record: record being written having triggered the + ``onwrite`` callback + :type record: openerp.web.list.Record + :returns: deferred to all reloadings being done + +Events +++++++ + +For simpler interactions by/with external users of the listview, the +view provides a number of dedicated events to its lifecycle. + +.. note:: if an event is defined as *cancellable*, it means its first + parameter is an object on which the ``cancel`` attribute can + be set. If the ``cancel`` attribute is set, the view will + abort its current behavior as soon as possible, and rollback + any state modification. + +``edit:before`` *cancellable* + + Invoked before the list view starts editing a record. + + Provided with an event object with a single property ``record``, + holding the attributes of the record being edited (``record`` is + empty *but not null* for a new record) + +``edit:after`` + + Invoked after the list view has gone into an edition state, + provided with the attributes of the record being edited (see + ``edit:before``) as first parameter and the form used for the + edition as second parameter. + +``save:before`` *cancellable* + + Invoked right before saving a pending edition, provided with an + event object holding the listview's editor (``editor``) and the + edition form (``form``) + +``save:after`` + + Invoked after a save has been completed + + .. todo:: currently invoked before the record has reloaded, which + is kinda shitty + +``cancel:before`` *cancellable* + + Invoked before cancelling a pending edition, provided with the + same information as ``save:before``. + +``cancel:after`` + + Invoked after a pending edition has been cancelled. + +Editor +------ + +The list-edition modules does not generally interact with the embedded +formview, delegating instead to its +:js:class:`~openerp.web.list.Editor`. + +.. js:class:: openerp.web.list.Editor(parent[, options]) + + The editor object provides a more convenient interface to form + views, and simplifies the usage of form views for semi-arbitrary + edition of stuff. + + However, the editor does *not* task itself with being internally + consistent at this point: calling + e.g. :js:func:`~openerp.web.list.Editor.edit` multiple times in a + row without saving or cancelling each edit is undefined. + + .. todo:: define this behavior + + :param parent: + :type parent: :js:class:`~openerp.web.Widget` + :param EditorOptions options: + + .. js:function:: openerp.web.list.Editor.isEditing + + Indicates whether the editor is currently in the process of + providing edition for a field. + + :rtype: Boolean + + .. js:function:: openerp.web.list.Editor.edit(record, configureField) + + Loads the provided record into the internal form view and + displays the form view. + + Will also attempt to focus the first visible field of the form + view. + + :param Object record: record to load into the form view + (key:value mapping similar to the result + of a ``read``) + :param configureField: function called with each field of the + form view right after the form is + displayed, lets whoever called this + method do some last-minute + configuration of form fields. + :type configureField: Function + :returns: jQuery delegate to the form object + + .. js:function:: openerp.web.list.Editor.save + + Attempts to save the internal form, then hide it + + :returns: delegate to the record under edition (with ``id`` + added for a creation). The record is not updated + from when it was passed in, aside from the ``id`` + attribute. + + .. js:function:: openerp.web.list.Editor.cancel + + Attemps to cancel the edition of the internal form, then hide + the form + + :returns: delegate to the record under edition + +.. js:class:: EditorOptions + + .. js:attribute:: EditorOptions.formView + + Form view (sub)-class to instantiate and delegate edition to. + + By default, :js:class:`~openerp.web.FormView` + + .. js:attribute:: EditorOptions.delegate + + Object used to get various bits of information about how to + display stuff. + + By default, uses the editor's parent widget. See + :js:class:`EditorDelegate` for the methods and attributes to + provide. + +.. js:class:: EditorDelegate + + Informal protocol defining the methods and attributes expected of + the :js:class:`~openerp.web.list.Editor`'s delegate. + + .. js:attribute:: EditorDelegate.dataset + + The dataset passed to the form view to synchronize the form + view and the outer widget. + + .. js:function:: EditorDelegate.editionView(editor) + + Called by the :js:class:`~openerp.web.list.Editor` object to + get a form view (JSON) to pass along to the form view it + created. + + The result should be a valid form view, see :doc:`Form Notes + ` for various peculiarities of the form view + format. + + :param editor: editor object asking for the view + :type editor: :js:class:`~openerp.web.list.Editor` + :returns: form view + :rtype: Object + + .. js:function:: EditorDelegate.isPrependOnCreate + + By default, the :js:class:`~openerp.web.list.Editor` will + append the ids of newly created records to the + :js:attr:`EditorDelegate.dataset`. If this method returns + ``true``, it will prepend these ids instead. + + :returns: whether new records should be prepended to the + dataset (instead of appended) + :rtype: Boolean From 58d1d6bb3961ef1774272b0481b320271d223b33 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 3 Jul 2012 16:30:35 +0200 Subject: [PATCH 047/112] [FIX] forgot to instantiate deferred bzr revid: xmo@openerp.com-20120703143035-zi336bg5lheocjpa --- addons/web/static/src/js/view_list_editable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index 826ee03a0c1..e26d74585d5 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -381,7 +381,7 @@ openerp.web.list_editable = function (instance) { var record = this.record; this.record = null; if (!this.form.can_be_discarded()) { - return $.Deferred.reject(); + return $.Deferred().reject().promise(); } this.form.do_hide(); return $.when(record); From 7ab7992fd6c958b51a5907997afb8d81e77df2c7 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 3 Jul 2012 16:31:26 +0200 Subject: [PATCH 048/112] [FIX] ensure current edition is saved before performing action bzr revid: xmo@openerp.com-20120703143126-6ludobg5o75t14ic --- addons/web/static/src/js/view_list_editable.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index e26d74585d5..90c2d29b429 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -114,6 +114,12 @@ openerp.web.list_editable = function (instance) { return result; }, + do_button_action: function () { + var self = this, _super = this._super, args = arguments; + this.ensureSaved().then(function () { + _super.apply(self, args); + }); + }, /** * Ensures the editable list is saved (saves any pending edition if * needed, or tries to) @@ -348,7 +354,6 @@ openerp.web.list_editable = function (instance) { return form.do_show({reload: false}); }).pipe(function () { self.record = record; - // TODO: save on action button? _(form.fields).each(function (field, name) { configureField(name, field); }); From db4aa8dfe77bfcf188164198faa8e3d007b3ed5b Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 3 Jul 2012 17:53:05 +0200 Subject: [PATCH 049/112] [REF] remove deprecated & unused methods; extract some other methods bzr revid: xmo@openerp.com-20120703155305-4bigm8htt3xutkyt --- .../web/static/src/js/view_list_editable.js | 190 +++++------------- doc/list-view.rst | 16 ++ 2 files changed, 65 insertions(+), 141 deletions(-) diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index 90c2d29b429..197cb3dda30 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -72,7 +72,7 @@ openerp.web.list_editable = function (instance) { if (this.options.editable) { this.$element.find('table:first').show(); this.$element.find('.oe_view_nocontent').remove(); - this.groups.new_record(); + this.startEdition(); } else { this._super(); } @@ -85,29 +85,8 @@ openerp.web.list_editable = function (instance) { if (this.options.editable || true) { this.editor = new instance.web.list.Editor(this); - var editor_ready = this.editor.prependTo(this.$element).then(function () { - self.editor.$element.on('keyup', function (e) { - switch (e.which) { - case KEY_RETURN: - self.saveEdition().then(function (saveInfo) { - if (saveInfo.created) { - self.startEdition(); - return; - } - var next_index = self.records.indexOf(saveInfo.record) + 1; - if (next_index === self.records.length) { - next_index = 0; - } - - self.startEdition(self.records.at(next_index)); - }); - break; - case KEY_ESCAPE: - self.cancelEdition(); - break; - } - }); - }); + var editor_ready = this.editor.prependTo(this.$element) + .then(this.proxy('setupEvents')); return $.when(result, editor_ready); } @@ -286,21 +265,54 @@ openerp.web.list_editable = function (instance) { if (!on_write_callback) { return $.when(); } return this.dataset.call(on_write_callback, [source_record.get('id')]) .pipe(function (ids) { - return $.when.apply(null, _(ids).map(function (id) { - var record = self.records.get(id); - if (!record) { - // insert after the source record - var index = self.records.indexOf(source_record) + 1; - record = new instance.web.list.Record({id: id}); - self.records.add(record, {at: index}); - self.dataset.ids.splice(index, 0, id); - } - return self.reload_record(record); - })); + return $.when.apply( + null, _(ids).map( + _.bind(self.handleOnWriteRecord, self, source_record))); }); }, + handleOnWriteRecord: function (id, source_record) { + var record = this.records.get(id); + if (!record) { + // insert after the source record + var index = this.records.indexOf(source_record) + 1; + record = new instance.web.list.Record({id: id}); + this.records.add(record, {at: index}); + this.dataset.ids.splice(index, 0, id); + } + return this.reload_record(record); + }, isPrependOnCreate: function () { return this.options.editable === 'top'; + }, + setupEvents: function () { + var self = this; + this.editor.$element.on('keyup', function (e) { + var key = _($.ui.keyCode).chain() + .map(function (v, k) { return {name: k, code: v}; }) + .find(function (o) { return o.code === e.which; }) + .value(); + if (!key) { return; } + var method = 'keyup_' + key.name; + if (!(method in self)) { return; } + self[method](e); + }); + }, + keyup_RETURN: function () { + var self = this; + return this.saveEdition().pipe(function (saveInfo) { + if (saveInfo.created) { + return self.startEdition(); + } + var next_index = self.records.indexOf(saveInfo.record) + 1; + if (next_index === self.records.length) { + next_index = 0; + } + + return self.startEdition(self.records.at(next_index)); + }); + }, + keyup_ESCAPE: function () { + return this.cancelEdition(); } }); @@ -395,22 +407,6 @@ openerp.web.list_editable = function (instance) { instance.web.ListView.Groups.include(/** @lends instance.web.ListView.Groups# */{ passtrough_events: instance.web.ListView.Groups.prototype.passtrough_events + " edit saved", - new_record: function () { - // TODO: handle multiple children - this.children[null].new_record(); - }, - /** - * Ensures descendant editable List instances are all saved if they have - * pending editions. - * - * @returns {$.Deferred} - */ - ensureSaved: function () { - return $.when.apply(null, - _.invoke( - _.values(this.children), - 'ensureSaved')); - }, getRowFor: function (record) { return _(this.children).chain() .invoke('getRowFor', record) @@ -442,100 +438,12 @@ openerp.web.list_editable = function (instance) { } this.edit_record($(event.currentTarget).data('id')); }, - /** - * Checks if a record is being edited, and if so cancels it - */ - cancel_pending_edition: function () { - var self = this, cancelled; - if (!this.edition) { - return $.when(); - } - - if (this.edition_id) { - cancelled = this.reload_record(this.records.get(this.edition_id)); - } else { - cancelled = $.when(); - } - cancelled.then(function () { - self.view.unpad_columns(); - self.edition_form.destroy(); - self.edition_form.$element.remove(); - delete self.edition_form; - self.dataset.index = null; - delete self.edition_id; - delete self.edition; - }); - this.pad_table_to(5); - return cancelled; - }, - on_row_keyup: function (e) { - var self = this; - switch (e.which) { - case KEY_RETURN: - $(e.target).blur(); - e.preventDefault(); - //e.stopImmediatePropagation(); - setTimeout(function () { - self.save_row().then(function (result) { - if (result.created) { - self.new_record(); - return; - } - - var next_record_id, - next_record = self.records.at( - self.records.indexOf(result.edited_record) + 1); - if (next_record) { - next_record_id = next_record.get('id'); - self.dataset.index = _(self.dataset.ids) - .indexOf(next_record_id); - } else { - self.dataset.index = 0; - next_record_id = self.records.at(0).get('id'); - } - self.edit_record(next_record_id); - }, 0); - }); - break; - case KEY_ESCAPE: - this.cancelEdition(); - break; - } - }, - render_row_as_form: function (id) { - return this.view.startEdition( - id ? this.records.get(id) : null); - }, - /** - * If the current list is being edited, ensures it's saved - */ - ensureSaved: function () { - if (this.edition) { - // kinda-hack-ish: if the user has entered data in a field, - // oe_form_dirty will be set on the form so save, otherwise - // discard the current (entirely empty) line - if (this.edition_form.$element.is('.oe_form_dirty')) { - return this.save_row(); - } - return this.cancel_pending_edition(); - } - //noinspection JSPotentiallyInvalidConstructorUsage - return $.when(); - }, - /** - * Cancels the edition of the row for the current dataset index - */ - cancelEdition: function () { - this.cancel_pending_edition(); - }, /** * Edits record currently selected via dataset */ edit_record: function (record_id) { - this.render_row_as_form(record_id); - }, - new_record: function () { - this.render_row_as_form(); + return this.view.startEdition( + record_id ? this.records.get(record_id) : null); }, /** * If a row mapping to the record (@data-id matching the record's id or diff --git a/doc/list-view.rst b/doc/list-view.rst index a7436e419dc..e71c6a828d6 100644 --- a/doc/list-view.rst +++ b/doc/list-view.rst @@ -293,3 +293,19 @@ formview, delegating instead to its :returns: whether new records should be prepended to the dataset (instead of appended) :rtype: Boolean + +Changes from 6.1 +---------------- + +* The editable listview behavior has been rewritten pretty much from + scratch, any code touching on editability will have to be modified + + * The overloading of :js:class:`~openerp.web.ListView.Groups` and + :js:class:`~openerp.web.ListView.List` for editability has been + drastically simplified, and most of the behavior has been moved to + the list view itself. Only + :js:function:`~openerp.web.ListView.List.edit_record` remains from + the methods previously added, and only + :js:function:`~openerp.web.ListView.List.row_clicked` and + :js:function:`~openerp.web.ListView.List.init` are still + overridden. From 039cf85e32c5445981d5706c8efd14c73a8619d3 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 4 Jul 2012 11:56:26 +0200 Subject: [PATCH 050/112] [TEST] start adding some testing to new editable listview, validate structure of editionView in Editor bzr revid: xmo@openerp.com-20120704095626-j7wtbgdmoti0kjie --- .../web/static/src/js/view_list_editable.js | 36 +++++++++- addons/web/static/src/js/views.js | 4 +- addons/web/static/test/list-editable.js | 71 +++++++++++++++++++ addons/web/static/test/test.html | 2 + doc/list-view.rst | 5 -- 5 files changed, 109 insertions(+), 9 deletions(-) create mode 100644 addons/web/static/test/list-editable.js diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index 197cb3dda30..a02383a5670 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -50,11 +50,12 @@ openerp.web.list_editable = function (instance) { * @param {Boolean} [force] forces the list to editability. Sets new row edition status to "bottom". */ set_editable: function (force) { + // TODO: fix handling of editability status to be simpler & clearer & more coherent // If ``force``, set editability to bottom // otherwise rely on view default // view' @editable is handled separately as we have not yet // fetched and processed the view at this point. - this.options.editable = true || ( + this.options.editable = ( ! this.options.read_only && ((force && "bottom") || this.defaults.editable)); }, /** @@ -82,7 +83,7 @@ openerp.web.list_editable = function (instance) { // tree/@editable takes priority on everything else if present. this.options.editable = ! this.options.read_only && (data.arch.attrs.editable || this.options.editable); var result = this._super(data, grouped); - if (this.options.editable || true) { + if (this.options.editable) { this.editor = new instance.web.list.Editor(this); var editor_ready = this.editor.prependTo(this.$element) @@ -168,6 +169,7 @@ openerp.web.list_editable = function (instance) { */ saveEdition: function () { var self = this; + // TODO: save:after should be invoked after reload return this.withEvent('save', { editor: this.editor, form: this.editor.form, @@ -349,16 +351,44 @@ openerp.web.list_editable = function (instance) { start: function () { var self = this; var _super = this._super(); - this.form.embedded_view = this.delegate.editionView(this); + this.form.embedded_view = this._validateView( + this.delegate.editionView(this)); var form_ready = this.form.appendTo(this.$element).then( self.form.proxy('do_hide')); return $.when(_super, form_ready); }, + _validateView: function (edition_view) { + if (!edition_view) { + throw new Error("editor delegate's #editionView must return " + + "a view descriptor"); + } + var arch = edition_view.arch; + if (!(arch && arch.children instanceof Array)) { + throw new Error("Editor delegate's #editionView must have a" + + " non-empty arch") + } + if (!(arch.tag === "form")) { + throw new Error("Editor delegate's #editionView must have a" + + " 'form' root node"); + } + if (!(arch.attrs && arch.attrs.version === "7.0")) { + throw new Error("Editor delegate's #editionView must be a" + + " version 7 view"); + } + if (!/\boe_form_container\b/.test(arch.attrs['class'])) { + throw new Error("Editor delegate's #editionView must have the" + + " class 'oe_form_container' on its root" + + " element"); + } + + return edition_view; + }, isEditing: function () { return !!this.record; }, edit: function (record, configureField) { + // TODO: specify sequence of edit calls var self = this; var form = self.form; record = _.extend({}, record); diff --git a/addons/web/static/src/js/views.js b/addons/web/static/src/js/views.js index e82d3d0ae9b..35901e90165 100644 --- a/addons/web/static/src/js/views.js +++ b/addons/web/static/src/js/views.js @@ -1238,7 +1238,9 @@ instance.web.json_node_to_xml = function(node, human_readable, indent) { if (typeof(node) === 'string') { return sindent + node; } else if (typeof(node.tag) !== 'string' || !node.children instanceof Array || !node.attrs instanceof Object) { - throw("Node a json node"); + throw new Error( + _.str.sprintf("Node [%s] is not a JSONified XML node", + JSON.stringify(node))); } for (var attr in node.attrs) { var vattr = node.attrs[attr]; diff --git a/addons/web/static/test/list-editable.js b/addons/web/static/test/list-editable.js new file mode 100644 index 00000000000..6ac3d96df2c --- /dev/null +++ b/addons/web/static/test/list-editable.js @@ -0,0 +1,71 @@ +$(document).ready(function () { + var $fix = $('#qunit-fixture'); + var xhr = QWeb2.Engine.prototype.get_xhr(); + xhr.open('GET', '/web/static/src/xml/base.xml', false); + xhr.send(null); + var doc = xhr.responseXML; + + var noop = function () {}; + /** + * Make connection RPC responses mockable by setting keys on the + * Connection#responses object (key is the URL, value is the function to + * call with the RPC request payload) + * + * @param {openerp.web.Connection} connection connection instance to mockify + * @param {Object} [responses] url:function mapping to seed the mock connection + */ + var mockifyRPC = function (connection, responses) { + connection.responses = responses || {}; + connection.rpc_function = function (url, payload) { + if (!(url.url in this.responses)) { + return $.Deferred().reject({}, 'failed', _.str.sprintf("Url %s not found in mock responses", url.url)).promise(); + } + return $.when(this.responses[url.url](payload)); + }; + }; + + var instance; + var baseSetup = function () { + instance = window.openerp.init([]); + window.openerp.web.corelib(instance); + window.openerp.web.coresetup(instance); + window.openerp.web.chrome(instance); + window.openerp.web.data(instance); + window.openerp.web.views(instance); + window.openerp.web.list(instance); + window.openerp.web.form(instance); + window.openerp.web.list_editable(instance); + + instance.web.qweb.add_template(doc); + + mockifyRPC(instance.connection); + }; + module('editor', { + setup: baseSetup + }); + asyncTest('base-state', 2, function () { + var e = new instance.web.list.Editor({ + dataset: {}, + editionView: function () { + return { + arch: { + tag: 'form', + attrs: { + version: '7.0', + 'class': 'oe_form_container' + }, + children: [] + } + }; + } + }); + e.appendTo($fix) + .always(start) + .fail(function (error) { ok(false, error && error.message); }) + .done(function () { + ok(!e.isEditing(), "should not be editing"); + ok(e.form instanceof instance.web.FormView, + "should use default form type"); + }); + }); +}); diff --git a/addons/web/static/test/test.html b/addons/web/static/test/test.html index e352f63c17c..f06d68ed4da 100644 --- a/addons/web/static/test/test.html +++ b/addons/web/static/test/test.html @@ -38,6 +38,7 @@ +

OpenERP web Test Suite

@@ -55,4 +56,5 @@ + diff --git a/doc/list-view.rst b/doc/list-view.rst index e71c6a828d6..28f22ab7ad4 100644 --- a/doc/list-view.rst +++ b/doc/list-view.rst @@ -163,9 +163,6 @@ view provides a number of dedicated events to its lifecycle. Invoked after a save has been completed - .. todo:: currently invoked before the record has reloaded, which - is kinda shitty - ``cancel:before`` *cancellable* Invoked before cancelling a pending edition, provided with the @@ -193,8 +190,6 @@ formview, delegating instead to its e.g. :js:func:`~openerp.web.list.Editor.edit` multiple times in a row without saving or cancelling each edit is undefined. - .. todo:: define this behavior - :param parent: :type parent: :js:class:`~openerp.web.Widget` :param EditorOptions options: From dda83dc1ea343e0f37bb037df773706d4c1375a2 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 4 Jul 2012 13:24:06 +0200 Subject: [PATCH 051/112] [IMP] extract some testing primitives to a dedicated module, migrate search view tests to those bzr revid: xmo@openerp.com-20120704112406-1lqwahtdo7fnla8o --- addons/web/static/test/list-editable.js | 37 +------ addons/web/static/test/search.js | 123 ++++++------------------ addons/web/static/test/test.html | 2 + addons/web/static/test/testing.js | 92 ++++++++++++++++++ 4 files changed, 129 insertions(+), 125 deletions(-) create mode 100644 addons/web/static/test/testing.js diff --git a/addons/web/static/test/list-editable.js b/addons/web/static/test/list-editable.js index 6ac3d96df2c..4a8fbae1094 100644 --- a/addons/web/static/test/list-editable.js +++ b/addons/web/static/test/list-editable.js @@ -1,44 +1,13 @@ $(document).ready(function () { var $fix = $('#qunit-fixture'); - var xhr = QWeb2.Engine.prototype.get_xhr(); - xhr.open('GET', '/web/static/src/xml/base.xml', false); - xhr.send(null); - var doc = xhr.responseXML; - - var noop = function () {}; - /** - * Make connection RPC responses mockable by setting keys on the - * Connection#responses object (key is the URL, value is the function to - * call with the RPC request payload) - * - * @param {openerp.web.Connection} connection connection instance to mockify - * @param {Object} [responses] url:function mapping to seed the mock connection - */ - var mockifyRPC = function (connection, responses) { - connection.responses = responses || {}; - connection.rpc_function = function (url, payload) { - if (!(url.url in this.responses)) { - return $.Deferred().reject({}, 'failed', _.str.sprintf("Url %s not found in mock responses", url.url)).promise(); - } - return $.when(this.responses[url.url](payload)); - }; - }; var instance; var baseSetup = function () { - instance = window.openerp.init([]); - window.openerp.web.corelib(instance); - window.openerp.web.coresetup(instance); - window.openerp.web.chrome(instance); - window.openerp.web.data(instance); - window.openerp.web.views(instance); - window.openerp.web.list(instance); - window.openerp.web.form(instance); - window.openerp.web.list_editable(instance); + instance = openerp.testing.instanceFor('list_editable'); - instance.web.qweb.add_template(doc); + openerp.testing.loadTemplate(instance); - mockifyRPC(instance.connection); + openerp.testing.mockifyRPC(instance); }; module('editor', { setup: baseSetup diff --git a/addons/web/static/test/search.js b/addons/web/static/test/search.js index e5beab22498..a57a017e666 100644 --- a/addons/web/static/test/search.js +++ b/addons/web/static/test/search.js @@ -1,37 +1,8 @@ $(document).ready(function () { - var xhr = QWeb2.Engine.prototype.get_xhr(); - xhr.open('GET', '/web/static/src/xml/base.xml', false); - xhr.send(null); - var doc = xhr.responseXML; - - var noop = function () {}; - /** - * Make connection RPC responses mockable by setting keys on the - * Connection#responses object (key is the URL, value is the function to - * call with the RPC request payload) - * - * @param {openerp.web.Connection} connection connection instance to mockify - * @param {Object} [responses] url:function mapping to seed the mock connection - */ - var mockifyRPC = function (connection, responses) { - connection.responses = responses || {}; - connection.rpc_function = function (url, payload) { - if (!(url.url in this.responses)) { - return $.Deferred().reject({}, 'failed', _.str.sprintf("Url %s not found in mock responses", url.url)).promise(); - } - return $.when(this.responses[url.url](payload)); - }; - }; - var instance; module('query', { setup: function () { - instance = window.openerp.init([]); - window.openerp.web.corelib(instance); - window.openerp.web.coresetup(instance); - window.openerp.web.chrome(instance); - window.openerp.web.data(instance); - window.openerp.web.search(instance); + instance = openerp.testing.instanceFor('search'); } }); test('Adding a facet to the query creates a facet and a value', function () { @@ -167,16 +138,11 @@ $(document).ready(function () { module('defaults', { setup: function () { - instance = window.openerp.init([]); - window.openerp.web.corelib(instance); - window.openerp.web.coresetup(instance); - window.openerp.web.chrome(instance); - window.openerp.web.data(instance); - window.openerp.web.search(instance); + instance = openerp.testing.instanceFor('search'); - instance.web.qweb.add_template(doc); + openerp.testing.loadTemplate(instance); - mockifyRPC(instance.connection); + openerp.testing.mockifyRPC(instance); } }); @@ -404,18 +370,11 @@ $(document).ready(function () { module('completions', { setup: function () { - instance = window.openerp.init([]); - window.openerp.web.corelib(instance); - window.openerp.web.coresetup(instance); - window.openerp.web.chrome(instance); - window.openerp.web.data(instance); - // date complete - window.openerp.web.formats(instance); - window.openerp.web.search(instance); + instance = openerp.testing.instanceFor('search'); - instance.web.qweb.add_template(doc); + openerp.testing.loadTemplate(instance); - mockifyRPC(instance.connection); + openerp.testing.mockifyRPC(instance); } }); asyncTest('calling', 4, function () { @@ -454,7 +413,11 @@ $(document).ready(function () { var completion = { label: "Dummy", facet: { - field: {get_domain: noop, get_context: noop, get_groupby: noop}, + field: { + get_domain: openerp.testing.noop, + get_context: openerp.testing.noop, + get_groupby: openerp.testing.noop + }, category: 'Dummy', values: [{label: 'dummy', value: 42}] } @@ -476,7 +439,11 @@ $(document).ready(function () { }); }); asyncTest('facet selection: new value existing facet', 3, function () { - var field = {get_domain: noop, get_context: noop, get_groupby: noop}; + var field = { + get_domain: openerp.testing.noop, + get_context: openerp.testing.noop, + get_groupby: openerp.testing.noop + }; var completion = { label: "Dummy", facet: { @@ -663,16 +630,11 @@ $(document).ready(function () { module('search-serialization', { setup: function () { - instance = window.openerp.init([]); - window.openerp.web.corelib(instance); - window.openerp.web.coresetup(instance); - window.openerp.web.chrome(instance); - window.openerp.web.data(instance); - window.openerp.web.search(instance); + instance = openerp.testing.instanceFor('search'); - instance.web.qweb.add_template(doc); + openerp.testing.loadTemplate(instance); - mockifyRPC(instance.connection); + openerp.testing.mockifyRPC(instance); } }); asyncTest('No facet, no call', 6, function () { @@ -919,16 +881,11 @@ $(document).ready(function () { module('removal', { setup: function () { - instance = window.openerp.init([]); - window.openerp.web.corelib(instance); - window.openerp.web.coresetup(instance); - window.openerp.web.chrome(instance); - window.openerp.web.data(instance); - window.openerp.web.search(instance); + instance = openerp.testing.instanceFor('search'); - instance.web.qweb.add_template(doc); + openerp.testing.loadTemplate(instance); - mockifyRPC(instance.connection); + openerp.testing.mockifyRPC(instance); } }); asyncTest('clear button', function () { @@ -954,16 +911,11 @@ $(document).ready(function () { module('drawer', { setup: function () { - instance = window.openerp.init([]); - window.openerp.web.corelib(instance); - window.openerp.web.coresetup(instance); - window.openerp.web.chrome(instance); - window.openerp.web.data(instance); - window.openerp.web.search(instance); + instance = openerp.testing.instanceFor('search'); - instance.web.qweb.add_template(doc); + openerp.testing.loadTemplate(instance); - mockifyRPC(instance.connection); + openerp.testing.mockifyRPC(instance); } }); asyncTest('is-drawn', 2, function () { @@ -982,16 +934,11 @@ $(document).ready(function () { module('filters', { setup: function () { - instance = window.openerp.init([]); - window.openerp.web.corelib(instance); - window.openerp.web.coresetup(instance); - window.openerp.web.chrome(instance); - window.openerp.web.data(instance); - window.openerp.web.search(instance); + instance = openerp.testing.instanceFor('search'); - instance.web.qweb.add_template(doc); + openerp.testing.loadTemplate(instance); - mockifyRPC(instance.connection, { + openerp.testing.mockifyRPC(instance, { '/web/searchview/load': function () { // view with a single group of filters return {result: {fields_view: { @@ -1096,17 +1043,11 @@ $(document).ready(function () { module('advanced', { setup: function () { - instance = window.openerp.init([]); - window.openerp.web.corelib(instance); - window.openerp.web.coresetup(instance); - window.openerp.web.chrome(instance); - window.openerp.web.data(instance); - window.openerp.web.formats(instance); - window.openerp.web.search(instance); + instance = openerp.testing.instanceFor('search'); - instance.web.qweb.add_template(doc); + openerp.testing.loadTemplate(instance); - mockifyRPC(instance.connection); + openerp.testing.mockifyRPC(instance); } }); asyncTest('single-advanced', 6, function () { diff --git a/addons/web/static/test/test.html b/addons/web/static/test/test.html index f06d68ed4da..21daf20241a 100644 --- a/addons/web/static/test/test.html +++ b/addons/web/static/test/test.html @@ -39,6 +39,8 @@ + +

OpenERP web Test Suite

diff --git a/addons/web/static/test/testing.js b/addons/web/static/test/testing.js new file mode 100644 index 00000000000..5b6e27700ab --- /dev/null +++ b/addons/web/static/test/testing.js @@ -0,0 +1,92 @@ +// Test support structures and methods for OpenERP +openerp.testing = (function () { + var xhr = QWeb2.Engine.prototype.get_xhr(); + xhr.open('GET', '/web/static/src/xml/base.xml', false); + xhr.send(null); + var doc = xhr.responseXML; + + var dependencies = { + corelib: [], + coresetup: ['corelib'], + data: ['corelib', 'coresetup'], + dates: [], + formats: ['coresetup', 'dates'], + chrome: ['corelib', 'coresetup'], + views: ['corelib', 'coresetup', 'data', 'chrome'], + search: ['data', 'coresetup', 'formats'], + list: ['views', 'data'], + form: ['data', 'views', 'list', 'formats'], + list_editable: ['list', 'form', 'data'], + }; + + return { + /** + * Function which does not do anything + */ + noop: function () { }, + /** + * Loads 'base.xml' template file into qweb for the provided instance + * + * @param instance openerp instance being initialized, to load the template file in + */ + loadTemplate: function (instance) { + instance.web.qweb.add_template(doc); + }, + /** + * Alter provided instance's ``connection`` attribute to make response + * mockable: + * + * * The ``responses`` parameter can be used to provide a map of (RPC) + * paths (e.g. ``/web/view/load``) to a function returning a response + * to the query. + * * ``instance,connection`` grows a ``responses`` attribute which is + * a map of the same (and is in fact initialized to the ``responses`` + * parameter if one is provided) + * + * Note that RPC requests to un-mocked URLs will be rejected with an + * error message: only explicitly specified urls will get a response. + * + * Mocked connections will *never* perform an actual RPC connection. + * + * @param instance openerp instance being initialized + * @param {Object} [responses] + */ + mockifyRPC: function (instance, responses) { + var connection = instance.connection; + connection.responses = responses || {}; + connection.rpc_function = function (url, payload) { + if (!(url.url in this.responses)) { + return $.Deferred().reject({}, 'failed', + _.str.sprintf("Url %s not found in mock responses", url.url)).promise(); + } + return $.when(this.responses[url.url](payload)); + }; + }, + /** + * Creates an openerp web instance loading the specified module after + * all of its dependencies. + * + * @param {String} module + * @returns OpenERP Web instance + */ + instanceFor: function (module) { + var instance = openerp.init([]); + this._load(instance, module); + return instance; + }, + _load: function (instance, module, loaded) { + if (!loaded) { loaded = []; } + + var deps = dependencies[module]; + if (!deps) { throw new Error("Unknown dependencies for " + module); } + + var to_load = _.difference(deps, loaded); + while (!_.isEmpty(to_load)) { + this._load(instance, to_load[0], loaded); + to_load = _.difference(deps, loaded); + } + openerp.web[module](instance); + loaded.push(module); + } + } +})(); From 9c6f8c53354893548e8c2d8457783ab2a0701beb Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 4 Jul 2012 14:19:57 +0200 Subject: [PATCH 052/112] [FIX] broken keyup handling for return key, incorrect name ($.ui.keyCode uses enter) bzr revid: xmo@openerp.com-20120704121957-hcwk5ove76kpo7y0 --- addons/web/static/src/js/view_list_editable.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index a02383a5670..17e1a04cf7a 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -3,10 +3,6 @@ * @namespace */ openerp.web.list_editable = function (instance) { - var KEY_RETURN = 13, - KEY_ESCAPE = 27; - var QWeb = instance.web.qweb; - // editability status of list rows instance.web.ListView.prototype.defaults.editable = null; @@ -299,7 +295,7 @@ openerp.web.list_editable = function (instance) { self[method](e); }); }, - keyup_RETURN: function () { + keyup_ENTER: function () { var self = this; return this.saveEdition().pipe(function (saveInfo) { if (saveInfo.created) { From 25284686f3ae459560c4f39bec83d045a0f3c500 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 4 Jul 2012 15:28:22 +0200 Subject: [PATCH 053/112] [TEST] saving of record being edited, improve RPC mocking to allow dispatch on ORM method name bzr revid: xmo@openerp.com-20120704132822-3p4h1c6cyr0qvn2x --- .../web/static/src/js/view_list_editable.js | 7 ++- addons/web/static/test/list-editable.js | 62 +++++++++++++++++++ addons/web/static/test/testing.js | 11 +++- 3 files changed, 75 insertions(+), 5 deletions(-) diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index 17e1a04cf7a..1c1c4cc0dfd 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -232,7 +232,9 @@ openerp.web.list_editable = function (instance) { event = event || {}; this.trigger(event_name + ':before', event); if (event.cancel) { - return $.Deferred().reject(); + return $.Deferred().reject({ + message: _.str.sprintf("Event %s:before cancelled", + event_name)}); } return $.when(action.apply(this.editor, args || [])).then(function () { self.trigger.apply(self, [event_name + ':after'] @@ -424,7 +426,8 @@ openerp.web.list_editable = function (instance) { var record = this.record; this.record = null; if (!this.form.can_be_discarded()) { - return $.Deferred().reject().promise(); + return $.Deferred().reject({ + message: "The form's data can not be discarded"}).promise(); } this.form.do_hide(); return $.when(record); diff --git a/addons/web/static/test/list-editable.js b/addons/web/static/test/list-editable.js index 4a8fbae1094..62d9d82ae83 100644 --- a/addons/web/static/test/list-editable.js +++ b/addons/web/static/test/list-editable.js @@ -37,4 +37,66 @@ $(document).ready(function () { "should use default form type"); }); }); + asyncTest('toggle-edition-new', function () { + instance.connection.responses['/web/dataset/call_kw:create'] = function () { + return { result: 42 }; + }; + instance.connection.responses['/web/dataset/call_kw:read'] = function () { + return { result: [{ + id: 42, + a: false, + b: false, + c: false + }]}; + }; + var e = new instance.web.list.Editor({ + do_warn: function (e) { + warning = e; + }, + dataset: new instance.web.DataSetSearch(), + isPrependOnCreate: function () { return false; }, + editionView: function () { + return { + arch: { + tag: 'form', + attrs: { + version: '7.0', + 'class': 'oe_form_container' + }, + children: [ + {tag: 'field', attrs: {name: 'a'}}, + {tag: 'field', attrs: {name: 'b'}}, + {tag: 'field', attrs: {name: 'c'}} + ] + }, + fields: { + a: {type: 'char'}, + b: {type: 'char'}, + c: {type: 'char'} + } + }; + } + }); + var counter = 0; + var warning = null; + e.appendTo($fix) + .pipe(function () { + return e.edit(null, function () { + ++counter; + }); + }) + .pipe(function (form) { + ok(e.isEditing(), "editor is now editing"); + equal(counter, 3, "all fields have been configured"); + strictEqual(form, e.form); + return e.save(); + }) + .always(start) + .fail(function (error) { ok(false, error && error.message); }) + .done(function (record) { + ok(!warning, "should have received no warning"); + ok(!e.isEditing(), "should have stopped editing"); + equal(record.id, 42, "should have newly created id"); + }) + }); }); diff --git a/addons/web/static/test/testing.js b/addons/web/static/test/testing.js index 5b6e27700ab..e9525fc82a0 100644 --- a/addons/web/static/test/testing.js +++ b/addons/web/static/test/testing.js @@ -55,11 +55,16 @@ openerp.testing = (function () { var connection = instance.connection; connection.responses = responses || {}; connection.rpc_function = function (url, payload) { - if (!(url.url in this.responses)) { + var fn = this.responses[url.url + ':' + payload.params.method] + || this.responses[url.url]; + + if (!fn) { return $.Deferred().reject({}, 'failed', - _.str.sprintf("Url %s not found in mock responses", url.url)).promise(); + _.str.sprintf("Url %s not found in mock responses, with arguments %s", + url.url, JSON.stringify(payload.params)) + ).promise(); } - return $.when(this.responses[url.url](payload)); + return $.when(fn(payload)); }; }, /** From cd90a9365cc936c3a4d4b0f156f2fc9214baadef Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 4 Jul 2012 15:35:21 +0200 Subject: [PATCH 054/112] [FUCK] that shit bzr revid: xmo@openerp.com-20120704133521-7x05840jzek1d8e7 --- addons/web/static/test/registry.js | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/addons/web/static/test/registry.js b/addons/web/static/test/registry.js index da0cb952783..ca689a351e0 100644 --- a/addons/web/static/test/registry.js +++ b/addons/web/static/test/registry.js @@ -9,21 +9,6 @@ $(document).ready(function () { openerp.web.Foo2 = {}; } }); - test('key fetch', function () { - var reg = new openerp.web.Registry({ - foo: 'openerp.web.Foo', - bar: 'openerp.web.Bar', - quux: 'openerp.web.Quux' - }); - - strictEqual(reg.get_object('foo'), openerp.web.Foo); - raises(function () { reg.get_object('qux'); }, - openerp.web.KeyNotFound, - "Unknown keys should raise KeyNotFound"); - raises(function () { reg.get_object('quux'); }, - openerp.web.ObjectNotFound, - "Incorrect file paths should raise ObjectNotFound"); - }); test('key set', function () { var reg = new openerp.web.Registry(); From 028f9d115f6bcc4ceccfc620d452dcca8a88f92d Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 4 Jul 2012 16:02:40 +0200 Subject: [PATCH 055/112] [FIX] strangely implemented searchview test breaking some subsequent tests randomly bzr revid: xmo@openerp.com-20120704140240-6yehb6c6ffxi7d12 --- addons/web/static/test/search.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/addons/web/static/test/search.js b/addons/web/static/test/search.js index a57a017e666..2b86bcaabd9 100644 --- a/addons/web/static/test/search.js +++ b/addons/web/static/test/search.js @@ -391,10 +391,7 @@ $(document).ready(function () { } }); view.appendTo($('#qunit-fixture')) - .always(start) - .fail(function (error) { ok(false, error.message); }) .done(function () { - stop(); view.complete_global_search({term: "dum"}, function (completions) { start(); equal(completions.length, 1, "should have a single completion"); From b289447826560b34f9e0d0125730bf2f36e3836a Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 4 Jul 2012 17:23:29 +0200 Subject: [PATCH 056/112] [TEST] saving of editor with required non-filled field, and cancelling of edition bzr revid: xmo@openerp.com-20120704152329-81ktb8umm9xbr5wt --- addons/web/static/src/js/view_form.js | 17 +-- addons/web/static/test/list-editable.js | 164 ++++++++++++++++++------ 2 files changed, 134 insertions(+), 47 deletions(-) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 76fcabe13f8..2620694b74b 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -717,14 +717,15 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM });}); }, on_invalid: function() { - var msg = "
    "; - _.each(this.fields, function(f) { - if (!f.is_valid()) { - msg += "
  • " + (f.node.attrs.string || f.field.string) + "
  • "; - } - }); - msg += "
"; - this.do_warn("The following fields are invalid :", msg); + var warnings = _(this.fields).chain() + .filter(function (f) { return !f.is_valid(); }) + .map(function (f) { + return _.str.sprintf('
  • %s
  • ', + _.escape(f.node.attrs.string || f.field.string)); + }).value(); + warnings.unshift('
      '); + warnings.push('
    '); + this.do_warn("The following fields are invalid :", warnings.join('')); }, on_saved: function(r, success) { if (!r.result) { diff --git a/addons/web/static/test/list-editable.js b/addons/web/static/test/list-editable.js index 62d9d82ae83..c96d50061b2 100644 --- a/addons/web/static/test/list-editable.js +++ b/addons/web/static/test/list-editable.js @@ -9,6 +9,64 @@ $(document).ready(function () { openerp.testing.mockifyRPC(instance); }; + + + /** + * + * @param {String} name + * @param {Object} [attrs] + * @param {String} [attrs.type="char"] + * @param {Boolean} [attrs.required] + * @param {Boolean} [attrs.invisible] + * @param {Boolean} [attrs.readonly] + * @return {Object} + */ + function field(name, attrs) { + attrs = attrs || {}; + attrs.name = name; + return _.defaults(attrs, { + type: 'char' + }); + } + + /** + * @param {Array} fields + * @return {Object} + */ + function makeFormView(fields) { + var fobj = {}; + _(fields).each(function (field) { + fobj[field.name] = { + type: field.type, + string: field.string + }; + }); + var children = _(fields).map(function (field) { + return { + tag: 'field', + attrs: { + name: field.name, + modifiers: JSON.stringify({ + required: field.required, + invisible: field.invisible, + readonly: field.readonly + }) + } + } + }); + return { + arch: { + tag: 'form', + attrs: { + version: '7.0', + 'class': 'oe_form_container' + }, + children: children + }, + fields: fobj + }; + } + module('editor', { setup: baseSetup }); @@ -16,16 +74,7 @@ $(document).ready(function () { var e = new instance.web.list.Editor({ dataset: {}, editionView: function () { - return { - arch: { - tag: 'form', - attrs: { - version: '7.0', - 'class': 'oe_form_container' - }, - children: [] - } - }; + return makeFormView(); } }); e.appendTo($fix) @@ -37,10 +86,10 @@ $(document).ready(function () { "should use default form type"); }); }); - asyncTest('toggle-edition-new', function () { + asyncTest('toggle-edition-save', 4, function () { instance.connection.responses['/web/dataset/call_kw:create'] = function () { return { result: 42 }; - }; + }; instance.connection.responses['/web/dataset/call_kw:read'] = function () { return { result: [{ id: 42, @@ -50,35 +99,13 @@ $(document).ready(function () { }]}; }; var e = new instance.web.list.Editor({ - do_warn: function (e) { - warning = e; - }, dataset: new instance.web.DataSetSearch(), isPrependOnCreate: function () { return false; }, editionView: function () { - return { - arch: { - tag: 'form', - attrs: { - version: '7.0', - 'class': 'oe_form_container' - }, - children: [ - {tag: 'field', attrs: {name: 'a'}}, - {tag: 'field', attrs: {name: 'b'}}, - {tag: 'field', attrs: {name: 'c'}} - ] - }, - fields: { - a: {type: 'char'}, - b: {type: 'char'}, - c: {type: 'char'} - } - }; + return makeFormView([ field('a'), field('b'), field('c') ]); } }); var counter = 0; - var warning = null; e.appendTo($fix) .pipe(function () { return e.edit(null, function () { @@ -86,17 +113,76 @@ $(document).ready(function () { }); }) .pipe(function (form) { - ok(e.isEditing(), "editor is now editing"); - equal(counter, 3, "all fields have been configured"); - strictEqual(form, e.form); + ok(e.isEditing(), "should be editing"); + equal(counter, 3, "should have configured all fields"); return e.save(); }) .always(start) .fail(function (error) { ok(false, error && error.message); }) .done(function (record) { - ok(!warning, "should have received no warning"); ok(!e.isEditing(), "should have stopped editing"); equal(record.id, 42, "should have newly created id"); }) }); + asyncTest('toggle-edition-cancel', 2, function () { + instance.connection.responses['/web/dataset/call_kw:create'] = function () { + return { result: 42 }; + }; + var e = new instance.web.list.Editor({ + dataset: new instance.web.DataSetSearch(), + isPrependOnCreate: function () { return false; }, + editionView: function () { + return makeFormView([ field('a'), field('b'), field('c') ]); + } + }); + var counter = 0; + e.appendTo($fix) + .pipe(function () { + return e.edit(null, function () { + ++counter; + }); + }) + .pipe(function (form) { + return e.cancel(); + }) + .always(start) + .fail(function (error) { ok(false, error && error.message); }) + .done(function (record) { + ok(!e.isEditing(), "should have stopped editing"); + ok(!record.id, "should have no id"); + }) + }); + asyncTest('toggle-save-required', 2, function () { + instance.connection.responses['/web/dataset/call_kw:create'] = function () { + return { result: 42 }; + }; + var e = new instance.web.list.Editor({ + do_warn: function () { + warnings++; + }, + dataset: new instance.web.DataSetSearch(), + isPrependOnCreate: function () { return false; }, + editionView: function () { + return makeFormView([ + field('a', {required: true}), field('b'), field('c') ]); + } + }); + var counter = 0; + var warnings = 0; + e.appendTo($fix) + .pipe(function () { + return e.edit(null, function () { + ++counter; + }); + }) + .pipe(function (form) { + return e.save(); + }) + .always(start) + .done(function () { ok(false, "cancel should not succeed"); }) + .fail(function () { + equal(warnings, 1, "should have been warned"); + ok(e.isEditing(), "should have kept editing"); + }) + }); }); From 09bf459bbc3faf5c2ee4198d703007e640575835 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 5 Jul 2012 09:08:54 +0200 Subject: [PATCH 057/112] [TESTS] add some bzr revid: xmo@openerp.com-20120705070854-tsyl27v3hdh7sbds --- .../web/static/src/js/view_list_editable.js | 103 +++++++++--------- addons/web/static/test/list-editable.js | 50 +++++++++ 2 files changed, 104 insertions(+), 49 deletions(-) diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index 1c1c4cc0dfd..8f0a036b1b9 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -125,32 +125,34 @@ openerp.web.list_editable = function (instance) { } var $recordRow = this.groups.getRowFor(record); var cells = this.getCellsFor($recordRow); + return this.ensureSaved().pipe(function () { return self.withEvent('edit', { record: record.attributes, cancel: false - }, self.editor.edit, - [record.attributes, function (field_name, field) { - var cell = cells[field_name]; - if (!cell || field.get('effective_readonly')) { - // Readonly fields can just remain the list's, form's - // usually don't have backgrounds &al - field.set({invisible: true}); - return; - } - var $cell = $(cell); - var position = $cell.position(); + }, function () { + return self.editor.edit(record.attributes, function (field_name, field) { + var cell = cells[field_name]; + if (!cell || field.get('effective_readonly')) { + // Readonly fields can just remain the list's, form's + // usually don't have backgrounds &al + field.set({invisible: true}); + return; + } + var $cell = $(cell); + var position = $cell.position(); - field.$element.css({ - top: position.top, - left: position.left, - width: $cell.outerWidth(), - minHeight: $cell.outerHeight() + field.$element.css({ + top: position.top, + left: position.left, + width: $cell.outerWidth(), + minHeight: $cell.outerHeight() + }); + }).pipe(function () { + $recordRow.addClass('oe_edition'); + return record.attributes; }); - }], - [record.attributes]); - }).then(function () { - $recordRow.addClass('oe_edition') + }); }); }, getCellsFor: function ($row) { @@ -165,27 +167,29 @@ openerp.web.list_editable = function (instance) { */ saveEdition: function () { var self = this; - // TODO: save:after should be invoked after reload return this.withEvent('save', { editor: this.editor, form: this.editor.form, cancel: false - }, this.editor.save).pipe(function (attrs) { - var created = false; - var record = self.records.get(attrs.id); - if (!record) { - // new record - created = true; - record = self.records.find(function (r) { - return !r.get('id'); - }).set('id', attrs.id); - } - // onwrite callback could be altering & reloading the record - // which has *just* been saved, so first perform all onwrites - // then do a final reload of the record - return self.handleOnWrite(record) - .pipe(function () { return self.reload_record(record); }) - .pipe(function () { return { created: created, record: record }; + }, function () { + return this.editor.save().pipe(function (attrs) { + var created = false; + var record = self.records.get(attrs.id); + if (!record) { + // new record + created = true; + record = self.records.find(function (r) { + return !r.get('id'); + }).set('id', attrs.id); + } + // onwrite callback could be altering & reloading the + // record which has *just* been saved, so first perform all + // onwrites then do a final reload of the record + return self.handleOnWrite(record) + .pipe(function () { + return self.reload_record(record); }) + .pipe(function () { + return { created: created, record: record }; }); }); }); }, @@ -198,16 +202,18 @@ openerp.web.list_editable = function (instance) { editor: this.editor, form: this.editor.form, cancel: false - }, this.editor.cancel).then(function (attrs) { - if (attrs.id) { - return self.reload_record(self.records.get(attrs.id)); - } - var to_delete = self.records.find(function (r) { - return !r.get('id'); + }, function () { + return this.editor.cancel().pipe(function (attrs) { + if (attrs.id) { + return self.reload_record(self.records.get(attrs.id)); + } + var to_delete = self.records.find(function (r) { + return !r.get('id'); + }); + if (to_delete) { + self.records.remove(to_delete); + } }); - if (to_delete) { - self.records.remove(to_delete); - } }); }, /** @@ -227,7 +233,7 @@ openerp.web.list_editable = function (instance) { * @param {Array} [trigger_params] supplementary arguments provided to the ``:after`` sub-event, before anything fetched by the ``action`` function * @return {jQuery.Deferred} */ - withEvent: function (event_name, event, action, args, trigger_params) { + withEvent: function (event_name, event, action) { var self = this; event = event || {}; this.trigger(event_name + ':before', event); @@ -236,9 +242,8 @@ openerp.web.list_editable = function (instance) { message: _.str.sprintf("Event %s:before cancelled", event_name)}); } - return $.when(action.apply(this.editor, args || [])).then(function () { + return $.when(action.call(this)).then(function () { self.trigger.apply(self, [event_name + ':after'] - .concat(trigger_params || []) .concat(_.toArray(arguments))); }); }, diff --git a/addons/web/static/test/list-editable.js b/addons/web/static/test/list-editable.js index c96d50061b2..5029f9a7643 100644 --- a/addons/web/static/test/list-editable.js +++ b/addons/web/static/test/list-editable.js @@ -185,4 +185,54 @@ $(document).ready(function () { ok(e.isEditing(), "should have kept editing"); }) }); + + module('list-edition-events', { + setup: function () { + baseSetup(); + _.extend(instance.connection.responses, { + '/web/listview/load': function () { + return {result: { + type: 'tree', + fields: { + a: {type: 'char', string: "A"}, + b: {type: 'char', string: "B"}, + c: {type: 'char', string: "C"} + }, + arch: { + tag: 'tree', + attrs: {}, + children: [ + {tag: 'field', attrs: {name: 'a'}}, + {tag: 'field', attrs: {name: 'b'}}, + {tag: 'field', attrs: {name: 'c'}} + ] + } + }}; + } + }); + } + }); + asyncTest('edition events', function () { + var ds = new instance.web.DataSetStatic(null, 'demo', null, [1]); + var o = { + counter: 0, + onEvent: function (e) { this.counter++; } + }; + var l = new instance.web.ListView(null, ds); + l.set_editable(true); + l.on('edit:before edit:after', o, o.onEvent); + l.appendTo($fix) + .always(start) + .pipe(function () { + ok(l.options.editable, "should be editable"); + equal(o.counter, 0, "should have seen no event yet"); + return l.startEdition(); + }) + .pipe(function () { + ok(l.editor.isEditing(), "should be editing"); + equal(o.counter, 2, "should have seen two edition events"); + }) + .fail(function (e) { ok(false, e && e.message); }); + }); + // TODO: test cancelling edition events }); From d968398d153a76cb36b5099ad312fef6cdb03c8d Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 5 Jul 2012 14:43:42 +0200 Subject: [PATCH 058/112] [ADD] events tests bzr revid: xmo@openerp.com-20120705124342-vm0pavyzqfuwa3k5 --- addons/web/static/test/list-editable.js | 39 +++++++++++++++++++++++-- addons/web/static/test/test.html | 3 ++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/addons/web/static/test/list-editable.js b/addons/web/static/test/list-editable.js index 5029f9a7643..5a03f263ffe 100644 --- a/addons/web/static/test/list-editable.js +++ b/addons/web/static/test/list-editable.js @@ -208,6 +208,14 @@ $(document).ready(function () { ] } }}; + }, + '/web/dataset/call_kw:read': function (params) { + return {result: [{ + id: 1, + a: 'foo', + b: 'bar', + c: 'baz' + }]}; } }); } @@ -218,10 +226,11 @@ $(document).ready(function () { counter: 0, onEvent: function (e) { this.counter++; } }; - var l = new instance.web.ListView(null, ds); + var l = new instance.web.ListView({}, ds); l.set_editable(true); l.on('edit:before edit:after', o, o.onEvent); l.appendTo($fix) + .pipe(l.proxy('reload_content')) .always(start) .pipe(function () { ok(l.options.editable, "should be editable"); @@ -234,5 +243,31 @@ $(document).ready(function () { }) .fail(function (e) { ok(false, e && e.message); }); }); - // TODO: test cancelling edition events + + asyncTest('edition events: cancelling', 3, function () { + var edit_after = false; + var ds = new instance.web.DataSetStatic(null, 'demo', null, [1]); + var l = new instance.web.ListView({}, ds); + l.set_editable(true); + l.on('edit:before', {}, function (e) { + e.cancel = true; + }); + l.on('edit:after', {}, function () { + edit_after = true; + }); + l.appendTo($fix) + .pipe(l.proxy('reload_content')) + .always(start) + .pipe(function () { + ok(l.options.editable, "should be editable"); + return l.startEdition(); + }) + // cancelling an event rejects the deferred + .pipe($.Deferred().reject(), function () { + ok(!l.editor.isEditing(), "should not be editing"); + ok(!edit_after, "should not have fired the edit:after event"); + return $.when(); + }) + .fail(function (e) { ok(false, e && e.message || e); }); + }); }); diff --git a/addons/web/static/test/test.html b/addons/web/static/test/test.html index 21daf20241a..520a7106b5b 100644 --- a/addons/web/static/test/test.html +++ b/addons/web/static/test/test.html @@ -41,6 +41,9 @@ +

    OpenERP web Test Suite

    From ee20d563570fcf3b3eabb90d82dd5f1c5164f1a9 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 5 Jul 2012 16:37:21 +0200 Subject: [PATCH 059/112] [FIX] Create new records as a bunch of empty (false) fields, otherwise Bad Things Happen ~/projects/tiny/web/current namely, if the list view fields have e.g. attributes associated, the computation of the domains blow up also, always create an editor in the listview (if the editable listview module has been installed), avoids blowing up 'safety' calls to #ensureSaved. An alternative would be to fix #ensureSaved not to blow up if there's no editor, but that means third parties which *know* there may be an editor in the list view can't easily hook up to it. Things will have to change anyway as currently toggling a list view from not-editable to editable after on_loaded has been called will not work correctly (it won't start the editor), which is shitty. bzr revid: xmo@openerp.com-20120705143721-4fiz64k7fka4052k --- addons/web/static/src/js/view_list_editable.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index 8f0a036b1b9..ab02ef49093 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -12,6 +12,8 @@ openerp.web.list_editable = function (instance) { var self = this; this._super.apply(this, arguments); + this.editor = new instance.web.list.Editor(this); + $(this.groups).bind({ 'edit': function (e, id, dataset) { self.do_edit(dataset.index, id, dataset); @@ -80,8 +82,6 @@ openerp.web.list_editable = function (instance) { this.options.editable = ! this.options.read_only && (data.arch.attrs.editable || this.options.editable); var result = this._super(data, grouped); if (this.options.editable) { - this.editor = new instance.web.list.Editor(this); - var editor_ready = this.editor.prependTo(this.$element) .then(this.proxy('setupEvents')); @@ -119,7 +119,12 @@ openerp.web.list_editable = function (instance) { startEdition: function (record) { var self = this; if (!record) { - record = new instance.web.list.Record(); + var attrs = {}; + _(this.columns).chain() + .filter(function (x) { return x.tag === 'field'}) + .pluck('name') + .each(function (field) { attrs[field] = false; }); + record = new instance.web.list.Record(attrs); this.records.add(record, { at: this.isPrependOnCreate() ? 0 : null}); } From cd59881b05bcfc6d27e2b97ad5edb617dd3fdbb7 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 9 Jul 2012 10:06:04 +0200 Subject: [PATCH 060/112] [FIX] Editor is not restartable, so don't restart it. Instead, create a new one at each on_loaded. Editor is not restartable because the formview wedges itself if its on_loaded is called more than once bzr revid: xmo@openerp.com-20120709080604-er1l6bn5eespue59 --- addons/web/static/src/js/view_list_editable.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index ab02ef49093..1285e48885f 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -78,10 +78,16 @@ openerp.web.list_editable = function (instance) { }, on_loaded: function (data, grouped) { var self = this; + if (this.editor) { + this.editor.destroy(); + } // tree/@editable takes priority on everything else if present. this.options.editable = ! this.options.read_only && (data.arch.attrs.editable || this.options.editable); var result = this._super(data, grouped); if (this.options.editable) { + // Editor is not restartable due to formview not being + // restartable + this.editor = new instance.web.list.Editor(this); var editor_ready = this.editor.prependTo(this.$element) .then(this.proxy('setupEvents')); From 853c5a70c0281d622c86919f85afd5ae9e4be5ee Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 9 Jul 2012 10:46:52 +0200 Subject: [PATCH 061/112] [ADD] 'false' id to newly created records, otherwise format_cell's button rendering blows up also fix various bits of code looking for an absence of @data-id, so that they look for a false @data-id to match the row of the new record instead bzr revid: xmo@openerp.com-20120709084652-rt1ffu2ea20scw53 --- addons/web/static/src/js/view_list.js | 11 +++-------- addons/web/static/src/js/view_list_editable.js | 10 +++------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js index dab9fcb7727..936af4de5e1 100644 --- a/addons/web/static/src/js/view_list.js +++ b/addons/web/static/src/js/view_list.js @@ -905,13 +905,8 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web. this.record_callbacks = { 'remove': function (event, record) { - var $row; - if (!record.get('id')) { - $row = self.$current.children(':not([data-id])'); - } else { - $row = self.$current.children( - '[data-id=' + record.get('id') + ']'); - } + var $row = self.$current.children( + '[data-id=' + record.get('id') + ']'); var index = $row.data('index'); $row.remove(); self.refresh_zebra(index); @@ -928,7 +923,7 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web. self.dataset.ids.splice( self.records.indexOf(record), 0, value); // Set id on new record - $row = self.$current.children('tr:not([data-id])'); + $row = self.$current.children('[data-id=false]'); } else { $row = self.$current.children( '[data-id=' + record.get('id') + ']'); diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index 1285e48885f..e66fdc1c917 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -125,7 +125,7 @@ openerp.web.list_editable = function (instance) { startEdition: function (record) { var self = this; if (!record) { - var attrs = {}; + var attrs = {id: false}; _(this.columns).chain() .filter(function (x) { return x.tag === 'field'}) .pluck('name') @@ -499,12 +499,8 @@ openerp.web.list_editable = function (instance) { * @return {jQuery|null} */ getRowFor: function (record) { - var id, $row; - if (id = record.get('id')) { - $row = this.$current.children('[data-id=' + id + ']'); - } else { - $row = this.$current.children(':not([data-id])'); - } + var id; + var $row = this.$current.children('[data-id=' + record.get('id') + ']'); if ($row.length) { return $row; } From d51c9a85903fc26bdc305cd9dce2f7cb2bc684ec Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 9 Jul 2012 11:24:01 +0200 Subject: [PATCH 062/112] [FIX] double-include of ids in the dataset, and double-save in some cases ~/projects/tiny/web/current on [change] of a listview record, if there was no id on the record (id=false) we need to add one. But depending how the creation/addition of the record was done, the id may already have been inserted e.g. by the formview. So first check if the record's id is already in the dataset, and add it if it's not. bzr revid: xmo@openerp.com-20120709092401-p1in6bvs5e2ptp5p --- addons/web/static/src/js/view_list.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js index 936af4de5e1..f25164d56e5 100644 --- a/addons/web/static/src/js/view_list.js +++ b/addons/web/static/src/js/view_list.js @@ -919,9 +919,12 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web. throw new Error("Setting 'id' attribute on existing record " + JSON.stringify(record.attributes)); } - // add record to dataset - self.dataset.ids.splice( - self.records.indexOf(record), 0, value); + if (!_.contains(self.dataset.ids, value)) { + // add record to dataset if not already in (added by + // the form view?) + self.dataset.ids.splice( + self.records.indexOf(record), 0, value); + } // Set id on new record $row = self.$current.children('[data-id=false]'); } else { From e9921d62df5c7811aeaa83225b6e5a1877ca4df0 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 9 Jul 2012 13:01:12 +0200 Subject: [PATCH 063/112] [FIX] ensures the o2m is correctly saved before activating action buttons, even when a row is being edited bzr revid: xmo@openerp.com-20120709110112-dgpm1jhb9o2lmweg --- addons/web/static/src/js/view_form.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 2620694b74b..14fa87e5be5 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -3243,11 +3243,17 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({ readonly: self.o2m.get("effective_readonly") }); }, - do_button_action: function (name, id, callback) { - var _super = _.bind(this._super, this); - - this.o2m.view.do_save().then(function () { - _super(name, id, callback); + /** + * Ensures the o2m is saved and committed to db before returning: executes + * the super-method (saves the current edition to the buffered dataset) + * then saves the parent form. + * + * @returns {jQuery.Deferred} + */ + ensureSaved: function () { + var parent_form = this.o2m.view; + return this._super().pipe(function () { + return parent_form.do_save(); }); } }); From 46e511d1f25f01da02db6fe1b8f2bcc73b44099e Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 9 Jul 2012 13:04:26 +0200 Subject: [PATCH 064/112] [FIX] o2m validation workflow for lists, to match new edition methods bzr revid: xmo@openerp.com-20120709110426-w6v512ff5dskmr3x --- addons/web/static/src/js/view_form.js | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 14fa87e5be5..93c6ea103cc 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -3156,11 +3156,8 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({ })); }, is_valid: function () { - var form; - // A list not being edited is always valid - if (!(form = this.first_edition_form())) { - return true; - } + var form = this.editor.form; + // If the form has not been modified, the view can only be valid // NB: is_dirty will also be set on defaults/onchanges/whatever? // oe_form_dirty seems to only be set on actual user actions @@ -3171,25 +3168,12 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({ // Otherwise validate internal form return _(form.fields).chain() .invoke(function () { - this._check_css_flag(); + this._check_css_flags(); return this.is_valid(); }) .all(_.identity) .value(); }, - first_edition_form: function () { - var get_form = function (group_or_list) { - if (group_or_list.edition) { - return group_or_list.edition_form; - } - return _(group_or_list.children).chain() - .map(get_form) - .compact() - .first() - .value(); - }; - return get_form(this.groups); - }, do_add_record: function () { if (this.options.editable) { this._super.apply(this, arguments); From 9568c463b3444db0bb3633e826c7a039f7e38802 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 10 Jul 2012 09:39:28 +0200 Subject: [PATCH 065/112] [FIX] conflicts between save-on-blur and action buttons in o2m bzr revid: xmo@openerp.com-20120710073928-riswpz7f9mddqjgf --- addons/web/static/src/js/view_form.js | 96 +++++++++---------- addons/web/static/src/js/view_list.js | 13 +++ .../web/static/src/js/view_list_editable.js | 16 +++- 3 files changed, 70 insertions(+), 55 deletions(-) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 93c6ea103cc..bb020f8db49 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -3105,6 +3105,7 @@ instance.web.form.One2ManyViewManager = instance.web.ViewManager.extend({ form: 'instance.web.form.One2ManyFormView', kanban: 'instance.web.form.One2ManyKanbanView', }); + this.__ignore_blur = false; }, switch_view: function(mode, unused) { if (mode !== 'form') { @@ -3154,6 +3155,15 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({ this._super(parent, dataset, view_id, _.extend(options || {}, { ListType: instance.web.form.One2ManyList })); + this.on('edit:before', this, this.proxy('_beforeEdit')); + this.on('save:before cancel:before', this, this.proxy('_beforeUnEdit')); + }, + start: function () { + var ret = this._super(); + this.$element + .off('mousedown.handleButtons') + .on('mousedown.handleButtons', 'table button', this.proxy('_buttonDown')); + return ret; }, is_valid: function () { var form = this.editor.form; @@ -3227,62 +3237,46 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({ readonly: self.o2m.get("effective_readonly") }); }, - /** - * Ensures the o2m is saved and committed to db before returning: executes - * the super-method (saves the current edition to the buffered dataset) - * then saves the parent form. - * - * @returns {jQuery.Deferred} - */ - ensureSaved: function () { + do_button_action: function () { var parent_form = this.o2m.view; - return this._super().pipe(function () { + var self = this, args = arguments; + return this.ensureSaved().pipe(function () { return parent_form.do_save(); - }); - } -}); -instance.web.form.One2ManyList = instance.web.ListView.List.extend({ - KEY_RETURN: 13, - // blurring caused by hitting the [Return] key, should skip the - // autosave-on-blur and let the handler for [Return] do its thing - __return_blur: false, - render_row_as_form: function () { - var self = this; - return this._super.apply(this, arguments).then(function () { - // Replace the "Save Row" button with "Cancel Edition" - self.edition_form.$element - .undelegate('button.oe-edit-row-save', 'click') - .delegate('button.oe-edit-row-save', 'click', function () { - self.cancel_pending_edition(); - }); - - // Overload execute_action on the edition form to perform a simple - // reload_record after the action is done, rather than fully - // reload the parent view (or something) - var _execute_action = self.edition_form.do_execute_action; - self.edition_form.do_execute_action = function (action, dataset, record_id, _callback) { - return _execute_action.call(this, action, dataset, record_id, function () { - self.view.reload_record( - self.view.records.get(record_id)); - }); - }; - - self.edition_form.on('blurred', null, function () { - if (self.__return_blur) { - delete self.__return_blur; - return; - } - if (!self.edition_form.widget_is_stopped) { - self.view.ensureSaved(); - } - }); + }).then(function () { + self.handleButton.apply(self, args); }); }, - on_row_keyup: function (e) { - if (e.which === this.KEY_RETURN) { - this.__return_blur = true; + + _beforeEdit: function () { + this.__ignore_blur = false; + this.editor.form.on('blurred', this, this._onFormBlur); + }, + _beforeUnEdit: function () { + this.editor.form.off('blurred', this, this._onFormBlur); + }, + _buttonDown: function () { + // If a button is clicked (usually some sort of action button), it's + // the button's responsibility to ensure the editable list is in the + // correct state -> ignore form blurring + this.__ignore_blur = true; + }, + /** + * Handles blurring of the nested form (saves the currently edited row), + * unless the flag to ignore the event is set to ``true`` + */ + _onFormBlur: function () { + if (this.__ignore_blur) { + this.__ignore_blur = false; + return; } - this._super(e); + this.saveEdition(); + }, + keyup_ENTER: function () { + // blurring caused by hitting the [Return] key, should skip the + // autosave-on-blur and let the handler for [Return] do its thing (save + // the current row *anyway*, then create a new one/edit the next one) + this.__ignore_blur = true; + this._super.apply(this, arguments); } }); diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js index f25164d56e5..7c82d2b173f 100644 --- a/addons/web/static/src/js/view_list.js +++ b/addons/web/static/src/js/view_list.js @@ -661,6 +661,19 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi * @param {Function} callback should be called after the action is executed, if non-null */ do_button_action: function (name, id, callback) { + this.handleButton(name, id, callback); + }, + /** + * Base handling of buttons, can be called when overriding do_button_action + * in order to bypass parent overrides. + * + * This method should not be overridden. + * + * @param {String} name action name + * @param {Object} id id of the record the action should be called on + * @param {Function} callback should be called after the action is executed, if non-null + */ + handleButton: function (name, id, callback) { var action = _.detect(this.columns, function (field) { return field.name === name; }); diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index e66fdc1c917..f759c274287 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -12,7 +12,7 @@ openerp.web.list_editable = function (instance) { var self = this; this._super.apply(this, arguments); - this.editor = new instance.web.list.Editor(this); + this.editor = this.makeEditor(); $(this.groups).bind({ 'edit': function (e, id, dataset) { @@ -87,7 +87,7 @@ openerp.web.list_editable = function (instance) { if (this.options.editable) { // Editor is not restartable due to formview not being // restartable - this.editor = new instance.web.list.Editor(this); + this.editor = this.makeEditor(); var editor_ready = this.editor.prependTo(this.$element) .then(this.proxy('setupEvents')); @@ -96,10 +96,18 @@ openerp.web.list_editable = function (instance) { return result; }, + /** + * Builds a new editor object + * + * @return {instance.web.list.Editor} + */ + makeEditor: function () { + return new instance.web.list.Editor(this); + }, do_button_action: function () { - var self = this, _super = this._super, args = arguments; + var self = this, args = arguments; this.ensureSaved().then(function () { - _super.apply(self, args); + self.handleButton.apply(self, args); }); }, /** From 611fc6cc4f8a18dbe1eaf6df258e2a8d2a9a75ad Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 10 Jul 2012 12:35:57 +0200 Subject: [PATCH 066/112] [FIX] handling of resize event during edition so form fields don't get all fubar bzr revid: xmo@openerp.com-20120710103557-584hsyats0fdzpo1 --- .../web/static/src/js/view_list_editable.js | 48 +++++++++++++++---- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index f759c274287..1fb99966eeb 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -13,6 +13,10 @@ openerp.web.list_editable = function (instance) { this._super.apply(this, arguments); this.editor = this.makeEditor(); + // Stores records of {field, cell}, allows for re-rendering fields + // depending on cell state during and after resize events + this.fields_for_resize = []; + instance.web.bus.on('resize', this, this.resizeFields); $(this.groups).bind({ 'edit': function (e, id, dataset) { @@ -27,6 +31,10 @@ openerp.web.list_editable = function (instance) { } }) }, + destroy: function () { + instance.web.bus.off('resize', this, this.resizeFields); + this._super(); + }, /** * Handles the activation of a record in editable mode (making a record * editable), called *after* the record has become editable. @@ -146,6 +154,7 @@ openerp.web.list_editable = function (instance) { var cells = this.getCellsFor($recordRow); return this.ensureSaved().pipe(function () { + self.fields_for_resize.splice(0, self.fields_for_resize.length); return self.withEvent('edit', { record: record.attributes, cancel: false @@ -158,17 +167,11 @@ openerp.web.list_editable = function (instance) { field.set({invisible: true}); return; } - var $cell = $(cell); - var position = $cell.position(); - field.$element.css({ - top: position.top, - left: position.left, - width: $cell.outerWidth(), - minHeight: $cell.outerHeight() - }); + self.fields_for_resize.push({field: field, cell: cell}); }).pipe(function () { $recordRow.addClass('oe_edition'); + self.resizeFields(); return record.attributes; }); }); @@ -181,6 +184,35 @@ openerp.web.list_editable = function (instance) { }); return cells; }, + /** + * If currently editing a row, resizes all registered form fields based + * on the corresponding row cell + */ + resizeFields: function () { + if (!this.editor.isEditing()) { return; } + for(var i=0, len=this.fields_for_resize.length; i Date: Tue, 10 Jul 2012 14:37:18 +0200 Subject: [PATCH 067/112] [ADD] successor method to list collections, to safely get the "next" record in a nested (groupby) context bzr revid: xmo@openerp.com-20120710123718-kiot6zzji07a4iao --- addons/web/static/src/js/view_list.js | 31 +++++++++-- .../web/static/src/js/view_list_editable.js | 8 +-- addons/web/static/test/list-utils.js | 51 ++++++++++++++++--- 3 files changed, 71 insertions(+), 19 deletions(-) diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js index 7c82d2b173f..2d1a06173cd 100644 --- a/addons/web/static/src/js/view_list.js +++ b/addons/web/static/src/js/view_list.js @@ -1849,7 +1849,7 @@ var Collection = instance.web.Class.extend(/** @lends Collection# */{ * @returns this */ remove: function (record) { - var index = _(this.records).indexOf(record); + var index = this.indexOf(record); if (index === -1) { _(this._proxies).each(function (proxy) { proxy.remove(record); @@ -1885,12 +1885,14 @@ var Collection = instance.web.Class.extend(/** @lends Collection# */{ // underscore-type methods find: function (callback) { - var record = null; + var record; for(var section in this._proxies) { - if (this._proxies.hasOwnProperty(section)) { - record = this._proxies[section].find(callback); + if (!this._proxies.hasOwnProperty(section)) { + continue + } + if ((record = this._proxies[section].find(callback))) { + return record; } - if (record) { return record; } } for(var i=0; i Date: Tue, 10 Jul 2012 15:12:05 +0200 Subject: [PATCH 068/112] [ADD] Collection#pred as well, while we're at it bzr revid: xmo@openerp.com-20120710131205-voi1nc5rkvewjr2f --- addons/web/static/src/js/view_list.js | 23 ++++++++++++++- addons/web/static/test/list-utils.js | 41 +++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js index 2d1a06173cd..27b39a40b22 100644 --- a/addons/web/static/src/js/view_list.js +++ b/addons/web/static/src/js/view_list.js @@ -1931,7 +1931,7 @@ var Collection = instance.web.Class.extend(/** @lends Collection# */{ var result; for(var section in this._proxies) { if (!this._proxies.hasOwnProperty(section)) { - continue + continue; } if ((result = this._proxies[section].succ(record, options))) { return result; @@ -1944,6 +1944,27 @@ var Collection = instance.web.Class.extend(/** @lends Collection# */{ return this.at(0); } return this.at(next_index); + }, + pred: function (record, options) { + options = options || {wraparound: false}; + + var result; + for (var section in this._proxies) { + if (!this._proxies.hasOwnProperty(section)) { + continue; + } + if ((result = this._proxies[section].pred(record, options))) { + return result; + } + } + + var index = this.indexOf(record); + if (index === -1) { return null; } + var next_index = index - 1; + if (options.wraparound && (next_index === -1)) { + return this.at(this.length - 1); + } + return this.at(next_index); } }); Collection.include(Events); diff --git a/addons/web/static/test/list-utils.js b/addons/web/static/test/list-utils.js index 18069943b2d..baec728528e 100644 --- a/addons/web/static/test/list-utils.js +++ b/addons/web/static/test/list-utils.js @@ -278,6 +278,9 @@ $(document).ready(function () { root.at(0).attributes, "should return index 0 as successor to last record if" + " wraparound is set"); + deepEqual(root.succ(root.at(2), {wraparound: true}).attributes, + root.at(3).attributes, + "wraparound should have no effect if not succ(last_record)"); }); test('successor', function () { var root = new openerp.web.list.Collection(); @@ -295,6 +298,44 @@ $(document).ready(function () { root.get(3).attributes, "should wraparound within a collection"); }); + test('degenerate-predecessor', function () { + var root = new openerp.web.list.Collection([ + {id: 1, value: 1}, + {id: 2, value: 2}, + {id: 3, value: 3}, + {id: 4, value: 5}, + {id: 5, value: 8}, + ]); + + deepEqual(root.pred(root.at(2)).attributes, + root.at(1).attributes, + "should return the record at (index - 1) from the pivot"); + equal(root.pred(root.at(0)), null, + "should return null as predecessor to first record"); + deepEqual(root.pred(root.at(0), {wraparound: true}).attributes, + root.at(4).attributes, + "should return last record as predecessor to first record" + + " if wraparound is set"); + deepEqual(root.pred(root.at(1), {wraparound: true}).attributes, + root.at(0).attributes, + "wraparound should have no effect if not pred(first_record)"); + }); + test('predecessor', function () { + var root = new openerp.web.list.Collection(); + root.proxy('first').add([{id: 1, value: 1}, {id: 2, value: 2}]); + root.proxy('second').add([{id: 3, value: 3}, {id: 4, value: 5}]); + root.proxy('third').add([{id: 5, value: 8}, {id: 6, value: 13}]); + + deepEqual(root.pred(root.get(4)).attributes, + root.get(3).attributes, + "should get predecessor"); + equal(root.pred(root.get(3)), + null, + "predecessor do not cross collections"); + deepEqual(root.pred(root.get(3), {wraparound: true}).attributes, + root.get(4).attributes, + "should wraparound within a collection"); + }); module('list-hofs', { setup: function () { From 46da3547cabfeb8e8ab3c501f2049c2046a9bcb3 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 10 Jul 2012 16:32:28 +0200 Subject: [PATCH 069/112] [IMP] move saving a row from per-row buttons to a single global button replacing [Create] bzr revid: xmo@openerp.com-20120710143228-mqfuaxw894x9khkc --- addons/web/static/src/css/base.css | 23 +++++------ addons/web/static/src/css/base.sass | 15 +++++--- addons/web/static/src/js/view_list.js | 2 +- .../web/static/src/js/view_list_editable.js | 38 +++++++------------ addons/web/static/src/xml/base.xml | 13 ++++--- 5 files changed, 41 insertions(+), 50 deletions(-) diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index 6061113642a..8a2020cba47 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -1999,6 +1999,15 @@ .openerp .oe_form .oe_form_field_many2many > .oe-listview .oe_list_pager_single_page { display: none; } +.openerp .oe_list_buttons .oe_list_save { + display: none; +} +.openerp .oe_list_buttons.oe_editing .oe_list_add, .openerp .oe_list_buttons.oe_editing .oe_alternative { + display: none; +} +.openerp .oe_list_buttons.oe_editing .oe_list_save { + display: inline-block; +} .openerp .oe-listview { position: relative; } @@ -2115,20 +2124,6 @@ .openerp .oe-listview-content .numeric input { text-align: right; } -.openerp .oe-listview-content tr.oe_edition th.oe-record-selector { - padding: 0 6px; - font-size: 1px; - letter-spacing: -1px; - color: transparent; -} -.openerp .oe-listview-content tr.oe_edition th.oe-record-selector:before { - font: 21px "mnmliconsRegular"; - content: "S"; - color: #404040; -} -.openerp .oe-listview-content tr.oe_edition th.oe-record-selector input { - display: none; -} .openerp .oe_trad_field.touched { border: 1px solid green !important; } diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index a55d1155859..9377c11e2c2 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -1587,6 +1587,15 @@ $colour4: #8a89ba display: none // }}} // ListView {{{ + .oe_list_buttons + .oe_list_save + display: none + &.oe_editing + .oe_list_add, .oe_alternative + display: none + .oe_list_save + display: inline-block + .oe-listview position: relative .oe_form .oe_form_field @@ -1666,12 +1675,6 @@ $colour4: #8a89ba width: 82px input text-align: right - - tr.oe_edition th.oe-record-selector - padding: 0 6px - @include text-to-icon("S") - input - display: none // }}} // Translation {{{ .oe_trad_field.touched diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js index 27b39a40b22..aa7ffdb66d4 100644 --- a/addons/web/static/src/js/view_list.js +++ b/addons/web/static/src/js/view_list.js @@ -285,7 +285,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi } this.$buttons.find('.oe_list_add') .click(this.proxy('do_add_record')) - .prop('disabled', grouped && this.options.editable); + .prop('disabled', grouped); this.$buttons.on('click', '.oe_list_button_import', function() { self.on_sidebar_import(); return false; diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index 61ee6698452..7bc12b325fc 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -29,7 +29,14 @@ openerp.web.list_editable = function (instance) { self.configure_pager(self.dataset); self.compute_aggregates(); } - }) + }); + + this.on('edit:after', this, function () { + self.$element.add(self.$buttons).addClass('oe_editing'); + }); + this.on('save:after cancel:after', this, function () { + self.$element.add(self.$buttons).removeClass('oe_editing'); + }); }, destroy: function () { instance.web.bus.off('resize', this, this.resizeFields); @@ -93,6 +100,10 @@ openerp.web.list_editable = function (instance) { this.options.editable = ! this.options.read_only && (data.arch.attrs.editable || this.options.editable); var result = this._super(data, grouped); if (this.options.editable) { + // FIXME: any hook available to ensure this is only done once? + this.$buttons + .off('click', 'button.oe_list_save') + .on('click', 'button.oe_list_save', this.proxy('saveEdition')); // Editor is not restartable due to formview not being // restartable this.editor = this.makeEditor(); @@ -498,33 +509,12 @@ openerp.web.list_editable = function (instance) { }); instance.web.ListView.List.include(/** @lends instance.web.ListView.List# */{ - init: function () { - var self = this; - this._super.apply(this, arguments); - var selection_handler = _.find(this.$_element.data('events').click, function (h) { - return h.selector === 'th.oe-record-selector'; - }).handler; - // TODO: cleaner way to do that? - this.$_element - .off('click', 'th.oe-record-selector') - .on('click', '.oe_edition th.oe-record-selector', function (e) { - e.stopImmediatePropagation(); - self.view.saveEdition(); - }) - .on('click', 'th.oe-record-selector', selection_handler); - }, row_clicked: function (event) { if (!this.options.editable) { return this._super.apply(this, arguments); } - this.edit_record($(event.currentTarget).data('id')); - }, - /** - * Edits record currently selected via dataset - */ - edit_record: function (record_id) { - return this.view.startEdition( - record_id ? this.records.get(record_id) : null); + var record_id = $(event.currentTarget).data('id'); + this.view.startEdition(record_id ? this.records.get(record_id) : null); }, /** * If a row mapping to the record (@data-id matching the record's id or diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 66a1c02e448..ec5f9c6d9de 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -619,7 +619,9 @@ - or Import + + or Import + @@ -663,10 +665,11 @@
    - - + + + + From 52723ef0525d2044c3df7f0359b601fd9326394a Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 10 Jul 2012 16:33:23 +0200 Subject: [PATCH 070/112] [FIX] monkey around keyboard handling to ensure we don't cancel a cancelled edition or any other such thing. maybe the form should be explicitly blurred when saved/cancelled? bzr revid: xmo@openerp.com-20120710143323-fye927s9k455mqls --- addons/web/static/src/js/view_list_editable.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index 7bc12b325fc..b225af4cb0c 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -365,6 +365,7 @@ openerp.web.list_editable = function (instance) { }); }, keyup_ENTER: function () { + if (!this.editor.isEditing()) { return; } var self = this; return this.saveEdition().pipe(function (saveInfo) { if (saveInfo.created) { @@ -375,6 +376,7 @@ openerp.web.list_editable = function (instance) { }); }, keyup_ESCAPE: function () { + if (!this.editor.isEditing()) { return; } return this.cancelEdition(); } }); From e0a62b4bed7b1c883650859d87af84aff021f51b Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 11 Jul 2012 10:11:16 +0200 Subject: [PATCH 071/112] [FIX] modifiers handling in list view, add class for readonly fields bzr revid: xmo@openerp.com-20120711081116-ao854p4ebsgyelp3 --- addons/web/static/src/js/view_list.js | 23 ++++++++++++++++------- addons/web/static/src/xml/base.xml | 7 ++++--- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js index aa7ffdb66d4..6a364d65f81 100644 --- a/addons/web/static/src/js/view_list.js +++ b/addons/web/static/src/js/view_list.js @@ -405,20 +405,27 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi if (column.modifiers) { var modifiers = JSON.parse(column.modifiers); column.modifiers_for = function (fields) { - if (!modifiers.invisible) { - return {}; + var out = {}; + + for (var attr in modifiers) { + if (!modifiers.hasOwnProperty(attr)) { continue; } + var modifier = modifiers[attr]; + out[attr] = _.isBoolean(modifier) + ? modifier + : domain_computer(modifier, fields); } - return { - 'invisible': domain_computer(modifiers.invisible, fields) - }; + + return out; }; if (modifiers['tree_invisible']) { column.invisible = '1'; } else { delete column.invisible; } + column.modifiers = modifiers; } else { column.modifiers_for = noop; + column.modifiers = {}; } return column; }; @@ -430,10 +437,12 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi if (grouped) { this.columns.unshift({ id: '_group', tag: '', string: _t("Group"), meta: true, - modifiers_for: function () { return {}; } + modifiers_for: function () { return {}; }, + modifiers: {} }, { id: '_count', tag: '', string: '#', meta: true, - modifiers_for: function () { return {}; } + modifiers_for: function () { return {}; }, + modifiers: {} }); } diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index ec5f9c6d9de..1a8c420fa18 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -641,6 +641,7 @@ + From af5cd045f29ac3b7b696f8a19d87d32318d00cc1 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 11 Jul 2012 10:59:26 +0200 Subject: [PATCH 072/112] [DOC] doc bzr revid: xmo@openerp.com-20120711085926-dnsdnqmrd1tvl2oo --- doc/list-view.rst | 129 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 124 insertions(+), 5 deletions(-) diff --git a/doc/list-view.rst b/doc/list-view.rst index 28f22ab7ad4..9e21fa38791 100644 --- a/doc/list-view.rst +++ b/doc/list-view.rst @@ -1,6 +1,79 @@ List View ========= +Style Hooks +----------- + +The list view provides a few style hook classes for re-styling of list views in +various situations: + +``.oe-listview`` + + The root element of the list view, styling rules should be rooted + on that class. + +``table.oe-listview-content`` + + The root table for the listview, accessory components may be + generated or added outside this section, this is the list view + "proper". + +``.oe_list_buttons`` + + The action buttons array for the list view, with its sub-elements + + ``.oe_list_add`` + + The default "Create"/"Add" button of the list view + + ``.oe_alternative`` + + The "alternative choice" for the list view, by default text + along the lines of "or import" with a link. + +``.oe-field-cell`` + + The cell (``td``) for a given field of the list view, cells which + are *not* fields (e.g. name of a group, or number of items in a + group) will not have this class. The field cell can be further + specified: + + ``.oe_number`` + + Numeric cell types (integer and float) + + ``.oe-button`` + + Action button (``button`` tag in the view) inside the cell + + ``.oe_readonly`` + + Readonly field cell + +``.oe-record-selector`` + + Selector cells + +Editable list view +++++++++++++++++++ + +The editable list view module adds a few supplementary style hook +classes, for edition situations: + +``.oe_editing`` + + Added to both ``.oe-listview`` and ``.oe_list_button`` (as the + buttons may be outside of the list view) when a row of the list is + currently being edited. + +``tr.oe_edition`` + + Class set on the row being edited itself. Note that the edition + form is *not* contained within the row, this allows for styling or + modifying the row while it's being edited separately. Mostly for + fields which can not be edited (e.g. read-only fields). + + Editable list view ------------------ @@ -30,7 +103,7 @@ view. ``options.read_only`` - worthless shit added by niv in the listview directly (wtf?) + force options.editable to false, or something? .. note:: can probably be replaced by cancelling ``edit:before`` @@ -172,6 +245,20 @@ view provides a number of dedicated events to its lifecycle. Invoked after a pending edition has been cancelled. +DOM events +++++++++++ + +The list view has grown hooks for the ``keyup`` event on its edition +form (during edition): any such event bubbling out of the edition form +will be forwarded to a method ``keyup_EVENTNAME``, where ``EVENTNAME`` +is the name of the key in ``$.ui.keyCode``. + +The method will also get the event object (originally passed to the +``keyup`` handler) as its sole parameter. + +The base editable list view has handlers for the ``ENTER`` and +``ESCAPE`` keys. + Editor ------ @@ -299,8 +386,40 @@ Changes from 6.1 :js:class:`~openerp.web.ListView.List` for editability has been drastically simplified, and most of the behavior has been moved to the list view itself. Only - :js:function:`~openerp.web.ListView.List.edit_record` remains from - the methods previously added, and only - :js:function:`~openerp.web.ListView.List.row_clicked` and - :js:function:`~openerp.web.ListView.List.init` are still + :js:func:`~openerp.web.ListView.List.row_clicked` is still overridden. + + * A new method ``getRowFor(record) -> jQuery(tr) | null`` has been + added to both ListView.List and ListView.Group, it can be called + from the list view to get the table row matching a record (if such + a row exists). + +* ``ListView#ensure_saved`` has been re-capitalized to + :js:func:`~openerp.web.ListView.ensureSaved` + +* :js:func:`~openerp.web.ListView.do_button_action`'s core behavior + has been split away to + :js:func:`~openerp.web.ListView.handleButton`. This allows bypassing + overrides of :js:func:`~openerp.web.ListView.do_button_action` in a + parent class. + + Ideally, :js:func:`~openerp.web.ListView.handleButton` should not be + overridden. + +* Modifiers handling has been improved (all modifiers information + should now be available through :js:func:`~Column.modifiers_for`, + not just ``invisible``) + +* Changed some handling of the list view's record: a record may now + have no id, and the listview will handle that correctly (for new + records being created) as well as correctly handle the ``id`` being + set. + +* Extended the internal collections structure of the list view with + `#find`_, `#succ`_ and `#pred`_. + +.. _#find: http://underscorejs.org/#find + +.. _#succ: http://hackage.haskell.org/packages/archive/base/latest/doc/html/Prelude.html#v:succ + +.. _#pred: http://hackage.haskell.org/packages/archive/base/latest/doc/html/Prelude.html#v:pred From c0e9d320f3b4fc53f914cd4af90d834f16aadfae Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 11 Jul 2012 12:17:40 +0200 Subject: [PATCH 073/112] [ADD] one more styling hook for the exact type of the field rendered in the cell bzr revid: xmo@openerp.com-20120711101740-8po45qujkny5gk6a --- addons/web/static/src/xml/base.xml | 2 +- doc/list-view.rst | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 1a8c420fa18..8eee2bcc6bb 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -656,7 +656,7 @@ diff --git a/doc/list-view.rst b/doc/list-view.rst index 9e21fa38791..475d5678b79 100644 --- a/doc/list-view.rst +++ b/doc/list-view.rst @@ -50,6 +50,12 @@ various situations: Readonly field cell + ``.oe_list_field_$type`` + + Additional class for the precise type of the cell, ``$type`` + is the field's @widget if there is one, otherwise it's the + field's type. + ``.oe-record-selector`` Selector cells From 3efc9c033a7f9b2a1d032951785ff5a94ecdbe6c Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 11 Jul 2012 13:10:27 +0200 Subject: [PATCH 074/112] [ADD] create new record when clicking on an empty row bzr revid: xmo@openerp.com-20120711111027-6vtyoxx17fw8dmvc --- addons/web/static/src/js/view_list.js | 2 +- addons/web/static/src/js/view_list_editable.js | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js index 6a364d65f81..92b633bf54d 100644 --- a/addons/web/static/src/js/view_list.js +++ b/addons/web/static/src/js/view_list.js @@ -1006,9 +1006,9 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web. e.stopPropagation(); }) .delegate('tr', 'click', function (e) { - e.stopPropagation(); var row_id = self.row_id(e.currentTarget); if (row_id !== undefined) { + e.stopPropagation(); if (!self.dataset.select_id(row_id)) { throw "Could not find id in dataset" } diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index b225af4cb0c..cbcd74cc31c 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -104,6 +104,13 @@ openerp.web.list_editable = function (instance) { this.$buttons .off('click', 'button.oe_list_save') .on('click', 'button.oe_list_save', this.proxy('saveEdition')); + this.$element + .off('click', 'tbody tr') + .on('click', 'tbody tr', function () { + if (!self.editor.isEditing()) { + self.startEdition(); + } + }); // Editor is not restartable due to formview not being // restartable this.editor = this.makeEditor(); From cd02216c28a0530f3d56369566645a6dbaefcfe4 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 11 Jul 2012 14:24:24 +0200 Subject: [PATCH 075/112] [FIX] selector for edit-on-click-on-empty-row bzr revid: xmo@openerp.com-20120711122424-355c6ed4vnmrr69n --- addons/web/static/src/js/view_list_editable.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index cbcd74cc31c..a78b0fbef97 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -105,8 +105,8 @@ openerp.web.list_editable = function (instance) { .off('click', 'button.oe_list_save') .on('click', 'button.oe_list_save', this.proxy('saveEdition')); this.$element - .off('click', 'tbody tr') - .on('click', 'tbody tr', function () { + .off('click', 'tbody td:not(.oe_list_field_cell)') + .on('click', 'tbody td:not(.oe_list_field_cell)', function () { if (!self.editor.isEditing()) { self.startEdition(); } From 9b570343a52669fb5206fdb58051d1f4febc031f Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 11 Jul 2012 15:18:04 +0200 Subject: [PATCH 076/112] [FIX] handling of clicking on a row for a record being created (and not existing yet) bzr revid: xmo@openerp.com-20120711131804-u8l2taeq2k1gc5v0 --- addons/web/static/src/js/view_list.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js index 4d2d1b4245f..2bbb9e87866 100644 --- a/addons/web/static/src/js/view_list.js +++ b/addons/web/static/src/js/view_list.js @@ -1013,10 +1013,10 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web. }) .delegate('tr', 'click', function (e) { var row_id = self.row_id(e.currentTarget); - if (row_id !== undefined) { + if (row_id) { e.stopPropagation(); if (!self.dataset.select_id(row_id)) { - throw "Could not find id in dataset" + throw new Error("Could not find id in dataset"); } self.row_clicked(e); } From 6b907bb4d0421bc1c408846dec7c2a57a778cca6 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 11 Jul 2012 16:14:02 +0200 Subject: [PATCH 077/112] [FIX] can't perform action on a o2m record which is not saved in db, so forbid it. Also yet-another-shitty-undoc-change to the way widgets and form view communicate bzr revid: xmo@openerp.com-20120711141402-1x7frd0v3vc3649e --- addons/web/static/src/js/view_form.js | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 0211b934a4e..e6e78a46ab8 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -3196,6 +3196,11 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({ })); this.on('edit:before', this, this.proxy('_beforeEdit')); this.on('save:before cancel:before', this, this.proxy('_beforeUnEdit')); + + this.records + .bind('add', this.proxy("changedRecords")) + .bind('edit', this.proxy("changedRecords")) + .bind('remove', this.proxy("changedRecords")); }, start: function () { var ret = this._super(); @@ -3204,6 +3209,9 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({ .on('mousedown.handleButtons', 'table button', this.proxy('_buttonDown')); return ret; }, + changedRecords: function () { + this.o2m.trigger_on_change(); + }, is_valid: function () { var form = this.editor.form; @@ -3276,13 +3284,19 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({ readonly: self.o2m.get("effective_readonly") }); }, - do_button_action: function () { + do_button_action: function (name, id, callback) { + if (!_.isNumber(id)) { + instance.webclient.notification.warn( + _t("Action Button"), + _t("The o2m record must be saved before an action can be used")); + return; + } var parent_form = this.o2m.view; - var self = this, args = arguments; - return this.ensureSaved().pipe(function () { + var self = this; + this.ensureSaved().pipe(function () { return parent_form.do_save(); }).then(function () { - self.handleButton.apply(self, args); + self.handleButton(name, id, callback); }); }, From 16559cd6aed2867a4d45621e2ccff0644957c06c Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 11 Jul 2012 16:20:48 +0200 Subject: [PATCH 078/112] [ADD] discard 'link-ish' to editable list view bzr revid: xmo@openerp.com-20120711142048-covvchmiscjlhji0 --- addons/web/static/src/css/base.css | 7 +++++-- addons/web/static/src/css/base.sass | 6 ++++-- addons/web/static/src/js/view_list_editable.js | 9 +++++++-- addons/web/static/src/xml/base.xml | 3 +++ 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index 7ea01bf3bee..6e5c6117023 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -2179,15 +2179,18 @@ .openerp .oe_form .oe_form_field_many2many > .oe_list .oe_list_pager_single_page { display: none; } -.openerp .oe_list_buttons .oe_list_save { +.openerp .oe_list_buttons .oe_list_save, .openerp .oe_list_buttons .oe_list_discard { display: none; } -.openerp .oe_list_buttons.oe_editing .oe_list_add, .openerp .oe_list_buttons.oe_editing .oe_alternative { +.openerp .oe_list_buttons.oe_editing .oe_list_add, .openerp .oe_list_buttons.oe_editing .oe_list_button_import { display: none; } .openerp .oe_list_buttons.oe_editing .oe_list_save { display: inline-block; } +.openerp .oe_list_buttons.oe_editing .oe_list_discard { + display: inline; +} .openerp .oe_list { position: relative; } diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index 0472aa34710..83153cc11d5 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -1723,13 +1723,15 @@ $sheet-max-width: 860px // }}} // ListView {{{ .oe_list_buttons - .oe_list_save + .oe_list_save, .oe_list_discard display: none &.oe_editing - .oe_list_add, .oe_alternative + .oe_list_add, .oe_list_button_import display: none .oe_list_save display: inline-block + .oe_list_discard + display: inline .oe_list position: relative diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index a78b0fbef97..93b7d38e3bb 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -102,8 +102,13 @@ openerp.web.list_editable = function (instance) { if (this.options.editable) { // FIXME: any hook available to ensure this is only done once? this.$buttons - .off('click', 'button.oe_list_save') - .on('click', 'button.oe_list_save', this.proxy('saveEdition')); + .off('click', '.oe_list_save') + .on('click', '.oe_list_save', this.proxy('saveEdition')) + .off('click', '.oe_list_discard') + .on('click', '.oe_list_discard', function (e) { + e.preventDefault(); + self.cancelEdition(); + }); this.$element .off('click', 'tbody td:not(.oe_list_field_cell)') .on('click', 'tbody td:not(.oe_list_field_cell)', function () { diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index a85e8817ad8..a9c8e15bcd7 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -671,6 +671,9 @@ + + discard + From ce11683e9240a56cf1ab3f27bf93c534528db9e6 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 11 Jul 2012 16:26:04 +0200 Subject: [PATCH 079/112] [FIX] re-enable delete button for o2m list items bzr revid: xmo@openerp.com-20120711142604-9jw3kikvqje8reu6 --- addons/web/static/src/js/view_form.js | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index e6e78a46ab8..be30abe5aba 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -2915,6 +2915,7 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({ selectable: self.multi_selection, sortable: false, import_enabled: false, + deletable: true }); if (self.get("effective_readonly")) { _.extend(view.options, { From 8812a84ba825ff09153065cdd7e1a939d57d5f68 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 11 Jul 2012 16:32:13 +0200 Subject: [PATCH 080/112] [FIX] don't let [Return] key hit in a text field bubble, or it validates editable list rows bzr revid: xmo@openerp.com-20120711143213-ug1u98evn6nxhi5r --- addons/web/static/src/js/view_form.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index be30abe5aba..d10990b1717 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -2250,6 +2250,11 @@ instance.web.form.FieldText = instance.web.form.AbstractField.extend(instance.we } else { this.$textarea.attr('disabled', 'disabled'); } + this.$element.keyup(function (e) { + if (e.which === $.ui.keyCode.ENTER) { + e.stopPropagation(); + } + }); this.setupFocus(this.$textarea); }, set_value: function(value_) { From 35b5732bba91544b99c63a2ef57c769758cd4f0c Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 11 Jul 2012 16:43:27 +0200 Subject: [PATCH 081/112] [IMP] keep 'save row' button hidden in o2m list bzr revid: xmo@openerp.com-20120711144327-gyhad79cmd730tb4 --- addons/web/static/src/css/base.css | 3 +++ addons/web/static/src/css/base.sass | 3 +++ 2 files changed, 6 insertions(+) diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index 6e5c6117023..2d27b380386 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -2176,6 +2176,9 @@ height: auto; line-height: 16px; } +.openerp .oe_form_field_one2many .oe_list_buttons.oe_editing .oe_list_save { + visibility: hidden; +} .openerp .oe_form .oe_form_field_many2many > .oe_list .oe_list_pager_single_page { display: none; } diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index 83153cc11d5..52b28b7da4f 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -1715,6 +1715,9 @@ $sheet-max-width: 860px li height: auto line-height: 16px + .oe_list_buttons.oe_editing .oe_list_save + // keep "save row" button hidden in o2m + visibility: hidden // }}} // FormView.many2many {{{ .oe_form .oe_form_field_many2many > .oe_list From 0bccd9be59b3cf258a9a2eea08c01a00992a0564 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 12 Jul 2012 13:55:52 +0200 Subject: [PATCH 082/112] [FIX] tests: update jquery link, fix broken searchview test following merge bzr revid: xmo@openerp.com-20120712115552-7adsu941o3wjtrmr --- addons/web/static/src/js/search.js | 1 + addons/web/static/test/search.js | 12 +++--------- addons/web/static/test/test.html | 2 +- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/addons/web/static/src/js/search.js b/addons/web/static/src/js/search.js index c8b6f797dba..504510ef133 100644 --- a/addons/web/static/src/js/search.js +++ b/addons/web/static/src/js/search.js @@ -1669,6 +1669,7 @@ instance.web.search.AddToDashboard = instance.web.Widget.extend({ return $.when(this.load_data(),this.data_loaded).pipe(this.proxy("render_data")); }, load_data:function(){ + if (!instance.webclient) { return $.Deferred().reject(); } var self = this,dashboard_menu = instance.webclient.menu.data.data.children; var ir_model_data = new instance.web.Model('ir.model.data',{},[['name','=','menu_reporting_dashboard']]).query(['res_id']); var map_data = function(result){ diff --git a/addons/web/static/test/search.js b/addons/web/static/test/search.js index 0b1ef7fa2b6..548eef612b5 100644 --- a/addons/web/static/test/search.js +++ b/addons/web/static/test/search.js @@ -1061,17 +1061,11 @@ $(document).ready(function () { module('saved_filters', { setup: function () { - instance = window.openerp.init([]); - window.openerp.web.corelib(instance); - window.openerp.web.coresetup(instance); - window.openerp.web.chrome(instance); - window.openerp.web.data(instance); - window.openerp.web.formats(instance); - window.openerp.web.search(instance); + instance = openerp.testing.instanceFor('search'); - instance.web.qweb.add_template(doc); + openerp.testing.loadTemplate(instance); - mockifyRPC(instance.connection); + openerp.testing.mockifyRPC(instance); } }); asyncTest('checkboxing', 6, function () { diff --git a/addons/web/static/test/test.html b/addons/web/static/test/test.html index 520a7106b5b..cab1586f240 100644 --- a/addons/web/static/test/test.html +++ b/addons/web/static/test/test.html @@ -13,7 +13,7 @@ - + From 0c872cf4694c25df89c9f6b24e32a08d475b393f Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 12 Jul 2012 14:06:57 +0200 Subject: [PATCH 083/112] [FIX] trailing comma in array literal creates a single undefined element in IE8 bzr revid: xmo@openerp.com-20120712120657-14j2izo6idppq5aq --- addons/web/static/test/list-utils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/web/static/test/list-utils.js b/addons/web/static/test/list-utils.js index baec728528e..6200ba788ce 100644 --- a/addons/web/static/test/list-utils.js +++ b/addons/web/static/test/list-utils.js @@ -266,7 +266,7 @@ $(document).ready(function () { {id: 2, value: 2}, {id: 3, value: 3}, {id: 4, value: 5}, - {id: 5, value: 8}, + {id: 5, value: 8} ]); deepEqual(root.succ(root.at(2)).attributes, @@ -304,7 +304,7 @@ $(document).ready(function () { {id: 2, value: 2}, {id: 3, value: 3}, {id: 4, value: 5}, - {id: 5, value: 8}, + {id: 5, value: 8} ]); deepEqual(root.pred(root.at(2)).attributes, From 75c2845fb5414a65a7366273eda3c1234d5d43b9 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 16 Jul 2012 13:04:30 +0200 Subject: [PATCH 084/112] [ADD] default_get support on new row creation bzr revid: xmo@openerp.com-20120716110430-9uv352m46d3mn1bx --- addons/web/static/src/css/base.css | 13 +++ addons/web/static/src/css/base.sass | 13 +++ addons/web/static/src/js/view_form.js | 21 +++-- .../web/static/src/js/view_list_editable.js | 16 ++-- addons/web/static/test/list-editable.js | 89 +++++++++++++++++-- 5 files changed, 136 insertions(+), 16 deletions(-) diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index 2d27b380386..97d2fcf57a7 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -2382,6 +2382,19 @@ .kitten-mode-activated > * { opacity: 0.7; } +.kitten-mode-activated .oe_footer a { + background-image: url(http://www.risacher.com/la-rache/zfiles/la-rache.png); + font-size: 1px; + letter-spacing: -1px; + color: transparent; + display: inline-block; + height: 15px; + width: 80px; + vertical-align: top; +} +.kitten-mode-activated .oe_footer a span { + display: none; +} div.ui-widget-overlay { background: black; diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index 52b28b7da4f..c5197743acd 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -1879,6 +1879,19 @@ $sheet-max-width: 860px background-attachment: fixed >* opacity: 0.70 + + .oe_footer a + background-image: url(http://www.risacher.com/la-rache/zfiles/la-rache.png) + font-size: 1px + letter-spacing: -1px + color: transparent + display: inline-block + height: 15px + width: 80px + vertical-align: top + + span + display: none // }}} div.ui-widget-overlay diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index d10990b1717..b090c1c18ec 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -336,6 +336,20 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM self.$element.add(self.$buttons).removeClass('oe_form_dirty'); }); }, + /** + * Loads and sets up the default values for the model as the current + * record + * + * @return {$.Deferred} + */ + load_defaults: function () { + var keys = _.keys(this.fields_view.fields); + if (keys.length) { + return this.dataset.default_get(keys) + .pipe(this.on_record_loaded); + } + return this.on_record_loaded({}); + }, on_form_changed: function() { this.trigger("view_content_has_changed"); }, @@ -608,12 +622,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM this.set({mode: "edit"}); return $.when(this.has_been_loaded).pipe(function() { if (self.can_be_discarded()) { - var keys = _.keys(self.fields_view.fields); - if (keys.length) { - return self.dataset.default_get(keys) - .pipe(self.on_record_loaded); - } - return self.on_record_loaded({}); + return self.load_defaults(); } }); }, diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index 93b7d38e3bb..bad3f939db2 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -163,7 +163,10 @@ openerp.web.list_editable = function (instance) { */ startEdition: function (record) { var self = this; - if (!record) { + var item = false; + if (record) { + item = record.attributes; + } else { var attrs = {id: false}; _(this.columns).chain() .filter(function (x) { return x.tag === 'field'}) @@ -182,7 +185,7 @@ openerp.web.list_editable = function (instance) { record: record.attributes, cancel: false }, function () { - return self.editor.edit(record.attributes, function (field_name, field) { + return self.editor.edit(item, function (field_name, field) { var cell = cells[field_name]; if (!cell || field.get('effective_readonly')) { // Readonly fields can just remain the list's, form's @@ -466,11 +469,14 @@ openerp.web.list_editable = function (instance) { // TODO: specify sequence of edit calls var self = this; var form = self.form; - record = _.extend({}, record); - return form.on_record_loaded(record).pipe(function () { + var loaded = record + ? form.on_record_loaded(_.extend({}, record)) + : form.load_defaults(); + + return loaded.pipe(function () { return form.do_show({reload: false}); }).pipe(function () { - self.record = record; + self.record = form.datarecord; _(form.fields).each(function (field, name) { configureField(name, field); }); diff --git a/addons/web/static/test/list-editable.js b/addons/web/static/test/list-editable.js index 5a03f263ffe..5b702c36f96 100644 --- a/addons/web/static/test/list-editable.js +++ b/addons/web/static/test/list-editable.js @@ -108,7 +108,7 @@ $(document).ready(function () { var counter = 0; e.appendTo($fix) .pipe(function () { - return e.edit(null, function () { + return e.edit({}, function () { ++counter; }); }) @@ -138,7 +138,7 @@ $(document).ready(function () { var counter = 0; e.appendTo($fix) .pipe(function () { - return e.edit(null, function () { + return e.edit({}, function () { ++counter; }); }) @@ -171,7 +171,7 @@ $(document).ready(function () { var warnings = 0; e.appendTo($fix) .pipe(function () { - return e.edit(null, function () { + return e.edit({}, function () { ++counter; }); }) @@ -186,6 +186,85 @@ $(document).ready(function () { }) }); + module('list-edition', { + setup: function () { + baseSetup(); + + var records = {}; + _.extend(instance.connection.responses, { + '/web/listview/load': function () { + return {result: { + type: 'tree', + fields: { + a: {type: 'char', string: "A"}, + b: {type: 'char', string: "B"}, + c: {type: 'char', string: "C"} + }, + arch: { + tag: 'tree', + attrs: {}, + children: [ + {tag: 'field', attrs: {name: 'a'}}, + {tag: 'field', attrs: {name: 'b'}}, + {tag: 'field', attrs: {name: 'c'}} + ] + } + }}; + }, + '/web/dataset/call_kw:create': function (params) { + records[42] = _.extend({}, params.params.args[0]); + return {result: 42}; + }, + '/web/dataset/call_kw:read': function (params) { + var id = params.params.args[0][0]; + if (id in records) { + return {result: [records[id]]}; + } + return {result: []}; + } + }) + } + }); + asyncTest('newrecord', 6, function () { + var got_defaults = false; + instance.connection.responses['/web/dataset/call_kw:default_get'] = function (params) { + var fields = params.params.args[0]; + deepEqual( + fields, ['a', 'b', 'c'], + "should ask defaults for all fields"); + got_defaults = true; + return {result: { + a: "qux", + b: "quux" + }}; + }; + + var ds = new instance.web.DataSetStatic(null, 'demo', null, [1]); + var l = new instance.web.ListView({}, ds); + l.set_editable(true); + + l.appendTo($fix) + .pipe(l.proxy('reload_content')) + .pipe(function () { + return l.startEdition(); + }) + .always(start) + .pipe(function () { + ok(got_defaults, "should have fetched default values for form"); + return l.saveEdition(); + }) + .pipe(function (result) { + ok(result.created, "should yield newly created record"); + equal(result.record.get('a'), "qux", + "should have used default values"); + equal(result.record.get('b'), "quux", + "should have used default values"); + ok(!result.record.get('c'), + "should have no value if there was no default"); + }) + .fail(function (e) { ok(false, e && e.message || e); }); + }); + module('list-edition-events', { setup: function () { baseSetup(); @@ -220,7 +299,7 @@ $(document).ready(function () { }); } }); - asyncTest('edition events', function () { + asyncTest('edition events', 4, function () { var ds = new instance.web.DataSetStatic(null, 'demo', null, [1]); var o = { counter: 0, @@ -235,7 +314,7 @@ $(document).ready(function () { .pipe(function () { ok(l.options.editable, "should be editable"); equal(o.counter, 0, "should have seen no event yet"); - return l.startEdition(); + return l.startEdition(l.records.get(1)); }) .pipe(function () { ok(l.editor.isEditing(), "should be editing"); From c3368bf2bbd779b1dd9e2bbc202cf85845803030 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 17 Jul 2012 15:02:51 +0200 Subject: [PATCH 085/112] [FIX] use native DOM methods to programmatically set focus on an element MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit jQuery breaks the expected ordering of triggered consequential events to a focusing (a blur event on the source field, a focus event on the destination one), which in turn breaks the expectations of the auto-saving o2m, making the o2m field attempt to auto-save immediately after having saved a row (via the [Return] key) e.g. create a new row in a list o2m, press [Return], the widget creates a new empty row and immediately tries to save it resulting — depending on the view's setup — in either an empty row being saved or a bunch of fields being marked as invalid even though the user hasn't done anything yet. Bypass jquery to avoid this issue. jQuery tracker ref: http://bugs.jquery.com/ticket/12094 bzr revid: xmo@openerp.com-20120717130251-q2cjoj6rkxh0bvdq --- addons/web/static/src/js/view_form.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index b090c1c18ec..98d50546c52 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -1916,7 +1916,7 @@ instance.web.form.AbstractField = instance.web.form.FormWidget.extend(instance.w */ delay_focus: function($elem) { setTimeout(function() { - $elem.focus(); + $elem[0].focus(); }, 50); }, /** From 01e6e3ec079c3221d14903a4a6aa066e0b53dafb Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 17 Jul 2012 15:23:08 +0200 Subject: [PATCH 086/112] [IMP] DOWNCASE ALL THE THINGS apparently and contrary to what I remembered, we're still supposed to use underscore_separated method names, not camelCase bzr revid: xmo@openerp.com-20120717132308-wvpldtprt33heee3 --- addons/web/static/src/js/view_form.js | 34 ++--- addons/web/static/src/js/view_list.js | 4 +- .../web/static/src/js/view_list_editable.js | 120 +++++++++--------- addons/web/static/test/list-editable.js | 36 +++--- doc/list-view.rst | 29 ++--- 5 files changed, 110 insertions(+), 113 deletions(-) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 98d50546c52..948b3a73913 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -3122,7 +3122,7 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({ } return res; } else if (this.viewmanager.active_view === "list") { - var res = $.when(view.ensureSaved()); + var res = $.when(view.ensure_saved()); if (!res.isResolved() && !res.isRejected()) { console.warn("Asynchronous get_value() is not supported in list view."); } @@ -3209,22 +3209,22 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({ this._super(parent, dataset, view_id, _.extend(options || {}, { ListType: instance.web.form.One2ManyList })); - this.on('edit:before', this, this.proxy('_beforeEdit')); - this.on('save:before cancel:before', this, this.proxy('_beforeUnEdit')); + this.on('edit:before', this, this.proxy('_before_edit')); + this.on('save:before cancel:before', this, this.proxy('_before_unedit')); this.records - .bind('add', this.proxy("changedRecords")) - .bind('edit', this.proxy("changedRecords")) - .bind('remove', this.proxy("changedRecords")); + .bind('add', this.proxy("changed_records")) + .bind('edit', this.proxy("changed_records")) + .bind('remove', this.proxy("changed_records")); }, start: function () { var ret = this._super(); this.$element .off('mousedown.handleButtons') - .on('mousedown.handleButtons', 'table button', this.proxy('_buttonDown')); + .on('mousedown.handleButtons', 'table button', this.proxy('_button_down')); return ret; }, - changedRecords: function () { + changed_records: function () { this.o2m.trigger_on_change(); }, is_valid: function () { @@ -3308,21 +3308,21 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({ } var parent_form = this.o2m.view; var self = this; - this.ensureSaved().pipe(function () { + this.ensure_saved().pipe(function () { return parent_form.do_save(); }).then(function () { - self.handleButton(name, id, callback); + self.handle_button(name, id, callback); }); }, - _beforeEdit: function () { + _before_edit: function () { this.__ignore_blur = false; - this.editor.form.on('blurred', this, this._onFormBlur); + this.editor.form.on('blurred', this, this._on_form_blur); }, - _beforeUnEdit: function () { - this.editor.form.off('blurred', this, this._onFormBlur); + _before_unedit: function () { + this.editor.form.off('blurred', this, this._on_form_blur); }, - _buttonDown: function () { + _button_down: function () { // If a button is clicked (usually some sort of action button), it's // the button's responsibility to ensure the editable list is in the // correct state -> ignore form blurring @@ -3332,12 +3332,12 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({ * Handles blurring of the nested form (saves the currently edited row), * unless the flag to ignore the event is set to ``true`` */ - _onFormBlur: function () { + _on_form_blur: function () { if (this.__ignore_blur) { this.__ignore_blur = false; return; } - this.saveEdition(); + this.save_edition(); }, keyup_ENTER: function () { // blurring caused by hitting the [Return] key, should skip the diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js index 2bbb9e87866..82c9d1be12b 100644 --- a/addons/web/static/src/js/view_list.js +++ b/addons/web/static/src/js/view_list.js @@ -676,7 +676,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi * @param {Function} callback should be called after the action is executed, if non-null */ do_button_action: function (name, id, callback) { - this.handleButton(name, id, callback); + this.handle_button(name, id, callback); }, /** * Base handling of buttons, can be called when overriding do_button_action @@ -688,7 +688,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi * @param {Object} id id of the record the action should be called on * @param {Function} callback should be called after the action is executed, if non-null */ - handleButton: function (name, id, callback) { + handle_button: function (name, id, callback) { var action = _.detect(this.columns, function (field) { return field.name === name; }); diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index bad3f939db2..fa39329fc81 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -12,11 +12,11 @@ openerp.web.list_editable = function (instance) { var self = this; this._super.apply(this, arguments); - this.editor = this.makeEditor(); + this.editor = this.make_editor(); // Stores records of {field, cell}, allows for re-rendering fields // depending on cell state during and after resize events this.fields_for_resize = []; - instance.web.bus.on('resize', this, this.resizeFields); + instance.web.bus.on('resize', this, this.resize_fields); $(this.groups).bind({ 'edit': function (e, id, dataset) { @@ -39,7 +39,7 @@ openerp.web.list_editable = function (instance) { }); }, destroy: function () { - instance.web.bus.off('resize', this, this.resizeFields); + instance.web.bus.off('resize', this, this.resize_fields); this._super(); }, /** @@ -86,7 +86,7 @@ openerp.web.list_editable = function (instance) { if (this.options.editable) { this.$element.find('table:first').show(); this.$element.find('.oe_view_nocontent').remove(); - this.startEdition(); + this.start_edition(); } else { this._super(); } @@ -103,24 +103,24 @@ openerp.web.list_editable = function (instance) { // FIXME: any hook available to ensure this is only done once? this.$buttons .off('click', '.oe_list_save') - .on('click', '.oe_list_save', this.proxy('saveEdition')) + .on('click', '.oe_list_save', this.proxy('save_edition')) .off('click', '.oe_list_discard') .on('click', '.oe_list_discard', function (e) { e.preventDefault(); - self.cancelEdition(); + self.cancel_edition(); }); this.$element .off('click', 'tbody td:not(.oe_list_field_cell)') .on('click', 'tbody td:not(.oe_list_field_cell)', function () { - if (!self.editor.isEditing()) { - self.startEdition(); + if (!self.editor.is_editing()) { + self.start_edition(); } }); // Editor is not restartable due to formview not being // restartable - this.editor = this.makeEditor(); + this.editor = this.make_editor(); var editor_ready = this.editor.prependTo(this.$element) - .then(this.proxy('setupEvents')); + .then(this.proxy('setup_events')); return $.when(result, editor_ready); } @@ -132,13 +132,13 @@ openerp.web.list_editable = function (instance) { * * @return {instance.web.list.Editor} */ - makeEditor: function () { + make_editor: function () { return new instance.web.list.Editor(this); }, do_button_action: function () { var self = this, args = arguments; - this.ensureSaved().then(function () { - self.handleButton.apply(self, args); + this.ensure_saved().then(function () { + self.handle_button.apply(self, args); }); }, /** @@ -149,11 +149,11 @@ openerp.web.list_editable = function (instance) { * * @returns {$.Deferred} */ - ensureSaved: function () { - if (!this.editor.isEditing()) { + ensure_saved: function () { + if (!this.editor.is_editing()) { return $.when(); } - return this.saveEdition(); + return this.save_edition(); }, /** * Set up the edition of a record of the list view "inline" @@ -161,7 +161,7 @@ openerp.web.list_editable = function (instance) { * @param {instance.web.list.Record} [record] record to edit, leave empty to create a new record * @return {jQuery.Deferred} */ - startEdition: function (record) { + start_edition: function (record) { var self = this; var item = false; if (record) { @@ -174,14 +174,14 @@ openerp.web.list_editable = function (instance) { .each(function (field) { attrs[field] = false; }); record = new instance.web.list.Record(attrs); this.records.add(record, { - at: this.isPrependOnCreate() ? 0 : null}); + at: this.prepends_on_create() ? 0 : null}); } - var $recordRow = this.groups.getRowFor(record); - var cells = this.getCellsFor($recordRow); + var $recordRow = this.groups.get_row_for(record); + var cells = this.get_cells_for($recordRow); - return this.ensureSaved().pipe(function () { + return this.ensure_saved().pipe(function () { self.fields_for_resize.splice(0, self.fields_for_resize.length); - return self.withEvent('edit', { + return self.with_event('edit', { record: record.attributes, cancel: false }, function () { @@ -197,13 +197,13 @@ openerp.web.list_editable = function (instance) { self.fields_for_resize.push({field: field, cell: cell}); }).pipe(function () { $recordRow.addClass('oe_edition'); - self.resizeFields(); + self.resize_fields(); return record.attributes; }); }); }); }, - getCellsFor: function ($row) { + get_cells_for: function ($row) { var cells = {}; $row.children('td').each(function (index, el) { cells[el.getAttribute('data-field')] = el @@ -214,11 +214,11 @@ openerp.web.list_editable = function (instance) { * If currently editing a row, resizes all registered form fields based * on the corresponding row cell */ - resizeFields: function () { - if (!this.editor.isEditing()) { return; } + resize_fields: function () { + if (!this.editor.is_editing()) { return; } for(var i=0, len=this.fields_for_resize.length; i jQuery(tr) | null`` has been + * A new method ``get_row_for(record) -> jQuery(tr) | null`` has been added to both ListView.List and ListView.Group, it can be called from the list view to get the table row matching a record (if such a row exists). -* ``ListView#ensure_saved`` has been re-capitalized to - :js:func:`~openerp.web.ListView.ensureSaved` - * :js:func:`~openerp.web.ListView.do_button_action`'s core behavior has been split away to - :js:func:`~openerp.web.ListView.handleButton`. This allows bypassing + :js:func:`~openerp.web.ListView.handle_button`. This allows bypassing overrides of :js:func:`~openerp.web.ListView.do_button_action` in a parent class. - Ideally, :js:func:`~openerp.web.ListView.handleButton` should not be + Ideally, :js:func:`~openerp.web.ListView.handle_button` should not be overridden. * Modifiers handling has been improved (all modifiers information From 4c82865bfdcaa3d0509f4f393121963fdf309577 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Wed, 18 Jul 2012 00:24:57 +0200 Subject: [PATCH 087/112] [IMP] etherpad bzr revid: fp@openerp.com-20120717222457-f652anzrzharlwkn --- addons/pad/static/src/css/etherpad.css | 9 +++------ addons/pad/static/src/js/pad.js | 2 +- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/addons/pad/static/src/css/etherpad.css b/addons/pad/static/src/css/etherpad.css index fd804c95472..8a867f42402 100644 --- a/addons/pad/static/src/css/etherpad.css +++ b/addons/pad/static/src/css/etherpad.css @@ -1,8 +1,6 @@ .etherpad_head{ - width: 99.7%; + width: 100%; display: block; - border-radius: 3px 3px 0px 0px; - background: -webkit-linear-gradient( #F7F7F7, #F1F1F1 80%); padding-left:3px; cursor:pointer; } @@ -18,7 +16,7 @@ left: 0px; top: 15px; width: 100%; - height: 98% !important; + height: 100% !important; z-index:10001 !important; background-color:white; } @@ -31,9 +29,8 @@ } .etherpad_readonly{ - width: 97.2%; + width: 100%; height: 450px; - padding:7px 7px 10px 10px; overflow:auto; } .etherpad_readonly ul,.etherpad_readonly ol{ diff --git a/addons/pad/static/src/js/pad.js b/addons/pad/static/src/js/pad.js index 3f13df45d48..5749f4d7523 100644 --- a/addons/pad/static/src/js/pad.js +++ b/addons/pad/static/src/js/pad.js @@ -4,7 +4,7 @@ instance.web.form.FieldEtherpad = instance.web.form.AbstractField.extend(_.exten template: 'FieldEtherpad', initialize_content: function() { this.$textarea = undefined; - this.$element.find('span').text(this.field.string); + this.$element.find('span').text('Fullscreen'); this.$element.find('span').click(_.bind(function(ev){ this.$element.find('span').toggleClass('etherpad_zoom_head'); var iszoom = this.$element.find('span').hasClass('etherpad_zoom_head'); From d17e291031e2b997b97be30a05df04bb027be92e Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Wed, 18 Jul 2012 00:56:36 +0200 Subject: [PATCH 088/112] [IMP] etherpad fullscreen bzr revid: fp@openerp.com-20120717225636-bqudwmo6cve50fjn --- addons/pad/static/src/css/etherpad.css | 26 ++++++++++++++------------ addons/pad/static/src/js/pad.js | 12 ++++-------- addons/pad/static/src/xml/pad.xml | 9 ++++++--- 3 files changed, 24 insertions(+), 23 deletions(-) diff --git a/addons/pad/static/src/css/etherpad.css b/addons/pad/static/src/css/etherpad.css index 8a867f42402..a0240afcab7 100644 --- a/addons/pad/static/src/css/etherpad.css +++ b/addons/pad/static/src/css/etherpad.css @@ -1,29 +1,32 @@ -.etherpad_head{ +.oe_etherpad_head{ width: 100%; display: block; padding-left:3px; cursor:pointer; } -.etherpad_zoom_head{ - position: fixed; - left: 0px; - top: 0px; - width: 100%; - height: 15px; +.oe_etherpad_fullscreen div.oe_etherpad_head .oe_normal { + display: none; } -.etherpad_zoom{ +.oe_etherpad_normal div.oe_etherpad_head .oe_fullscreen { + display: none; +} +.oe_etherpad_fullscreen { position: fixed; + top: 0px; left: 0px; - top: 15px; width: 100%; - height: 100% !important; + height: 100%; + background-color: white; z-index:10001 !important; - background-color:white; } .etherpad_default{ width: 100%; height: 450px; } +.oe_etherpad_fullscreen .etherpad_default{ + height: 100% +} + .etherpad_body{ overflow:hidden; } @@ -50,7 +53,6 @@ list-style-type: none !important; } - .etherpad_readonly{ font-family: arial, sans-serif; font-size: 13px; diff --git a/addons/pad/static/src/js/pad.js b/addons/pad/static/src/js/pad.js index 5749f4d7523..5736d42bbab 100644 --- a/addons/pad/static/src/js/pad.js +++ b/addons/pad/static/src/js/pad.js @@ -4,13 +4,9 @@ instance.web.form.FieldEtherpad = instance.web.form.AbstractField.extend(_.exten template: 'FieldEtherpad', initialize_content: function() { this.$textarea = undefined; - this.$element.find('span').text('Fullscreen'); - this.$element.find('span').click(_.bind(function(ev){ - this.$element.find('span').toggleClass('etherpad_zoom_head'); - var iszoom = this.$element.find('span').hasClass('etherpad_zoom_head'); - this.$element.find('span').text((iszoom?'Back to Task':this.field.string)); - this.$element.find('div').toggleClass('etherpad_zoom'); - $("body").toggleClass('etherpad_body'); + this.$element.find('div.oe_etherpad_head').click(_.bind(function(ev){ + this.$element.toggleClass('oe_etherpad_fullscreen').toggleClass('oe_etherpad_normal'); + },this)); }, set_value: function(value_) { @@ -21,7 +17,7 @@ instance.web.form.FieldEtherpad = instance.web.form.AbstractField.extend(_.exten var show_value = instance.web.format_value(this.get('value'), this, ''); if (!this.get("effective_readonly")) { var pad_username = this.session.username; - this.$element.find('div').html(''); + this.$element.find('div.oe_etherpad_default').html(''); } else { if(this.get('value') != false) { diff --git a/addons/pad/static/src/xml/pad.xml b/addons/pad/static/src/xml/pad.xml index 334064e88a1..27b210b3cf2 100644 --- a/addons/pad/static/src/xml/pad.xml +++ b/addons/pad/static/src/xml/pad.xml @@ -5,9 +5,12 @@ -
    - -
    +
    +
    + Fullscreen + Return to Record +
    +
    From d708c4ae0cfe7062573d43c74284c1e4a292b325 Mon Sep 17 00:00:00 2001 From: Fabien Pinckaers Date: Wed, 18 Jul 2012 01:00:06 +0200 Subject: [PATCH 089/112] imp bzr revid: fp@openerp.com-20120717230006-dgs6r4f2pm73ancp --- addons/pad_project/project_task.py | 5 ++++- addons/pad_project/project_task.xml | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/addons/pad_project/project_task.py b/addons/pad_project/project_task.py index 96e1ef86e4d..1095b753e9f 100644 --- a/addons/pad_project/project_task.py +++ b/addons/pad_project/project_task.py @@ -5,7 +5,10 @@ from osv import fields, osv class task(osv.osv): _name = "project.task" _inherit = ["project.task",'pad.common'] - _pad_url = 'description' + _pad_url = 'description_pad' + _columns = { + 'description_pad': fields.char('PAD Description', size=250) + } _defaults = { _pad_url: lambda self, cr, uid, context: self.pad_generate_url(cr, uid, self._name, context), } diff --git a/addons/pad_project/project_task.xml b/addons/pad_project/project_task.xml index da9a9b2c3a1..122bcc546fa 100644 --- a/addons/pad_project/project_task.xml +++ b/addons/pad_project/project_task.xml @@ -7,7 +7,7 @@ - From 71ede66dce1b89ce3c2c9e1ff93c1e1cae2da397 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 18 Jul 2012 12:17:44 +0200 Subject: [PATCH 090/112] [FIX] handling of @onwrite extraction was broken: using _.bind/3 will partially apply the function (first argument) on top of binding it to a context (second argument), but the partial application will be from the *left* (which is pretty much the only one making sense, short of kwargs). handle_onwrite_record took its arguments the wrong way around: the partially applied one was specified on the right and the "actually applied" one was on the left, so it used the wrong argument and ended up blowing up the rest of the code. bzr revid: xmo@openerp.com-20120718101744-bbbshq60x2kqhob6 --- addons/web/static/src/js/view_list_editable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index fa39329fc81..baff7085e0e 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -352,7 +352,7 @@ openerp.web.list_editable = function (instance) { _.bind(self.handle_onwrite_record, self, source_record))); }); }, - handle_onwrite_record: function (id, source_record) { + handle_onwrite_record: function (source_record, id) { var record = this.records.get(id); if (!record) { // insert after the source record From 0f054b57c18073b396e45dda15cb5f23da3815a3 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 18 Jul 2012 14:43:59 +0200 Subject: [PATCH 091/112] [IMP] add ability to change row using up/down key while editing list * Allow asserting state of record being edited (creating or modifying) through Editor#is_editing * Improve setup_events to also dispatch keydown events bzr revid: xmo@openerp.com-20120718124359-q0udajwbuhzpqjmi --- .../web/static/src/js/view_list_editable.js | 77 +++++++++++++++++-- doc/list-view.rst | 13 +++- 2 files changed, 82 insertions(+), 8 deletions(-) diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index baff7085e0e..d8a1e26dfa6 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -368,19 +368,19 @@ openerp.web.list_editable = function (instance) { }, setup_events: function () { var self = this; - this.editor.$element.on('keyup', function (e) { + this.editor.$element.on('keyup keydown', function (e) { + if (!self.editor.is_editing()) { return; } var key = _($.ui.keyCode).chain() .map(function (v, k) { return {name: k, code: v}; }) .find(function (o) { return o.code === e.which; }) .value(); if (!key) { return; } - var method = 'keyup_' + key.name; + var method = e.type + '_' + key.name; if (!(method in self)) { return; } self[method](e); }); }, keyup_ENTER: function () { - if (!this.editor.is_editing()) { return; } var self = this; return this.save_edition().pipe(function (saveInfo) { if (saveInfo.created) { @@ -391,8 +391,58 @@ openerp.web.list_editable = function (instance) { }); }, keyup_ESCAPE: function () { - if (!this.editor.is_editing()) { return; } return this.cancel_edition(); + }, + _text_selection_range: function (el) { + if (el.selectionStart !== undefined) { + return { + start: el.selectionStart, + end: el.selectionEnd + }; + } else if(document.body.createTextRange) { + throw new Error("Implement text range handling for MSIE"); + var sel = document.body.createTextRange(); + if (sel.parentElement() === el) { + + } + } + }, + _text_cursor: function (el) { + var selection = this._text_selection_range(el); + if (selection.start !== selection.end) { + return null; + } + return selection.start; + }, + keydown_UP: function (e) { + if (!this.editor.is_editing('edit')) { return; } + // FIXME: assumes editable widgets are input-type elements + var index = this._text_cursor(e.target); + // If selecting or not at the start of the input + if (index === null || index !== 0) { return; } + + var self = this; + e.preventDefault(); + return this.save_edition().pipe(function (saveInfo) { + // Should not happen when creating, ignore saveInfo.created + return self.start_edition( + self.records.pred(saveInfo.record, {wraparound: true})); + }); + }, + keydown_DOWN: function (e) { + if (!this.editor.is_editing('edit')) { return; } + // FIXME: assumes editable widgets are input-type elements + var index = this._text_cursor(e.target); + // If selecting or not at the end of the input + if (index === null || index !== e.target.value.length) { return; } + + var self = this; + e.preventDefault(); + return this.save_edition().pipe(function (saveInfo) { + // Should not happen when creating, ignore saveInfo.created + return self.start_edition( + self.records.succ(saveInfo.record, {wraparound: true})); + }); } }); @@ -462,8 +512,23 @@ openerp.web.list_editable = function (instance) { return edition_view; }, - is_editing: function () { - return !!this.record; + /** + * + * @param {String} [state] either ``new`` or ``edit`` + * @return {Boolean} + */ + is_editing: function (state) { + if (!this.record) { + return false; + } + switch(state) { + case null: case undefined: + return true; + case 'new': return !this.record.id; + case 'edit': return !!this.record.id; + } + throw new Error("is_editing's state filter must be either `new` or" + + " `edit` if provided"); }, edit: function (record, configureField) { // TODO: specify sequence of edit calls diff --git a/doc/list-view.rst b/doc/list-view.rst index 4030df2edd0..ebb5f0ffe4d 100644 --- a/doc/list-view.rst +++ b/doc/list-view.rst @@ -287,11 +287,20 @@ formview, delegating instead to its :type parent: :js:class:`~openerp.web.Widget` :param EditorOptions options: - .. js:function:: openerp.web.list.Editor.is_editing + .. js:function:: openerp.web.list.Editor.is_editing([record_state]) Indicates whether the editor is currently in the process of - providing edition for a field. + providing edition for a record. + Can be filtered by the state of the record being edited + (whether it's a record being *created* or a record being + *altered*), in which case it asserts both that an edition is + underway and that the record being edited respectively does + not yet exist in the database or already exists there. + + :param record_state: state of the record being edited. + Either ``"new"`` or ``"edit"``. + :type record_state: String :rtype: Boolean .. js:function:: openerp.web.list.Editor.edit(record, configureField) From 65ac59f42a9501b2ba3129e3229e6b46bd1f5596 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 18 Jul 2012 15:07:51 +0200 Subject: [PATCH 092/112] [ADD] when tabbing from the last field in an edition row, go to next edition as if we'd hit [return] extract common code for 'save current edition and move on to next edition' to a private method bzr revid: xmo@openerp.com-20120718130751-g8htb78n54z1b9hv --- .../web/static/src/js/view_list_editable.js | 53 ++++++++++++------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index d8a1e26dfa6..58925a11ba1 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -380,16 +380,29 @@ openerp.web.list_editable = function (instance) { self[method](e); }); }, - keyup_ENTER: function () { + /** + * Saves the current record, and goes to the next one (creation or + * edition) + * + * @private + * @param {String} [next_record='succ'] method to call on the records collection to get the next record to edit + * @return {*} + */ + _next: function (next_record) { + next_record = next_record || 'succ'; var self = this; return this.save_edition().pipe(function (saveInfo) { if (saveInfo.created) { return self.start_edition(); } return self.start_edition( - self.records.succ(saveInfo.record, {wraparound: true})); + self.records[next_record]( + saveInfo.record, {wraparound: true})); }); }, + keyup_ENTER: function () { + return this._next(); + }, keyup_ESCAPE: function () { return this.cancel_edition(); }, @@ -415,34 +428,38 @@ openerp.web.list_editable = function (instance) { return selection.start; }, keydown_UP: function (e) { - if (!this.editor.is_editing('edit')) { return; } + if (!this.editor.is_editing('edit')) { return $.when(); } // FIXME: assumes editable widgets are input-type elements var index = this._text_cursor(e.target); // If selecting or not at the start of the input - if (index === null || index !== 0) { return; } + if (index === null || index !== 0) { return $.when(); } - var self = this; e.preventDefault(); - return this.save_edition().pipe(function (saveInfo) { - // Should not happen when creating, ignore saveInfo.created - return self.start_edition( - self.records.pred(saveInfo.record, {wraparound: true})); - }); + return this._next('pred'); }, keydown_DOWN: function (e) { - if (!this.editor.is_editing('edit')) { return; } + if (!this.editor.is_editing('edit')) { return $.when(); } // FIXME: assumes editable widgets are input-type elements var index = this._text_cursor(e.target); // If selecting or not at the end of the input - if (index === null || index !== e.target.value.length) { return; } + if (index === null || index !== e.target.value.length) { return $.when(); } - var self = this; e.preventDefault(); - return this.save_edition().pipe(function (saveInfo) { - // Should not happen when creating, ignore saveInfo.created - return self.start_edition( - self.records.succ(saveInfo.record, {wraparound: true})); - }); + return this._next(); + }, + keydown_TAB: function (e) { + var form = this.editor.form; + var last_field = _(form.fields_order).chain() + .map(function (name) { return form.fields[name]; }) + .filter(function (field) { return field.$element.is(':visible'); }) + .last() + .value(); + // tabbed from last field in form + if (last_field && last_field.$element.has(e.target).length) { + e.preventDefault(); + return this._next(); + } + return $.when(); } }); From 85a8641e2c672aa3801e49df39296335b9567368 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 18 Jul 2012 15:31:15 +0200 Subject: [PATCH 093/112] [FIX] cancel edition when deleting a record, so we're not left with a phantom editor for the deleted record on top of an other row bzr revid: xmo@openerp.com-20120718133115-8cn8wqf1ra8pjrnv --- addons/web/static/src/js/view_list_editable.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index 58925a11ba1..052a471f0f3 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -31,6 +31,12 @@ openerp.web.list_editable = function (instance) { } }); + this.records.bind('remove', function () { + if (self.editor.is_editing()) { + self.cancel_edition(); + } + }); + this.on('edit:after', this, function () { self.$element.add(self.$buttons).addClass('oe_editing'); }); @@ -282,7 +288,12 @@ openerp.web.list_editable = function (instance) { }, function () { return this.editor.cancel().pipe(function (attrs) { if (attrs.id) { - return self.reload_record(self.records.get(attrs.id)); + var record = self.records.get(attrs.id); + if (!record) { + // Record removed by third party during edition + return + } + return self.reload_record(record); } var to_delete = self.records.find(function (r) { return !r.get('id'); From f19e5ff9bcc8cbee1d8e389c5a59d5e75591730a Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 18 Jul 2012 15:37:12 +0200 Subject: [PATCH 094/112] [IMP] don't collapse newlines in list cells bzr revid: xmo@openerp.com-20120718133712-0pbdahv62rf0nslc --- addons/web/static/src/css/base.css | 1 + addons/web/static/src/css/base.sass | 1 + addons/web/static/src/xml/base.xml | 5 ++--- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index 97d2fcf57a7..f92d5cbf1c2 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -2256,6 +2256,7 @@ } .openerp .oe_list_content > tbody > tr > td.oe_list_field_cell { padding: 3px 6px; + white-space: pre-line; } .openerp .oe_list_content > tbody > tr > td, .openerp .oe_list_content > tbody > tr > th { line-height: 18px; diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index c5197743acd..2ad33c8ca62 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -1784,6 +1784,7 @@ $sheet-max-width: 860px border-top: 1px solid #ddd > td.oe_list_field_cell padding: 3px 6px + white-space: pre-line > td, > th line-height: 18px > button diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index a9c8e15bcd7..6d0d4a9ea7d 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -657,9 +657,8 @@
    + t-att-data-field="column.id" + >
    - +
    - -
    @@ -652,10 +653,10 @@ - + + - - From 9096f2c45c3c9c12486eed2dd273aeb03441cb68 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 18 Jul 2012 15:46:07 +0200 Subject: [PATCH 095/112] [ADD] reimplement o2m's cancel-on-blur when no alteration has been done by the user to the row being edited (new or not) bzr revid: xmo@openerp.com-20120718134607-bukj6c5lysmqeer3 --- addons/web/static/src/js/view_form.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 948b3a73913..92faa3d7d15 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -3331,13 +3331,20 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({ /** * Handles blurring of the nested form (saves the currently edited row), * unless the flag to ignore the event is set to ``true`` + * + * Makes the internal form go away */ _on_form_blur: function () { if (this.__ignore_blur) { this.__ignore_blur = false; return; } - this.save_edition(); + // FIXME: why isn't there an API for this? + if (this.editor.form.$element.hasClass('oe_form_dirty')) { + this.save_edition(); + return; + } + this.cancel_edition(); }, keyup_ENTER: function () { // blurring caused by hitting the [Return] key, should skip the From 03dd20254e2c62b8290f9b329bde256e92fcc3cd Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Wed, 18 Jul 2012 16:01:31 +0200 Subject: [PATCH 096/112] [IMP] use 'Save' as label of the button to save an editable list row bzr revid: xmo@openerp.com-20120718140131-eop88n524hwvrguw --- addons/web/static/src/xml/base.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 6d0d4a9ea7d..94b76c170ea 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -668,7 +668,7 @@ + type="button">Save discard From 06104ba553702b16878d52c5b8ab089355216a5e Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 19 Jul 2012 10:23:52 +0200 Subject: [PATCH 097/112] [FIX] shitty undoc flag so saving a form doesn't forget/miss a row being edited bzr revid: xmo@openerp.com-20120719082352-pqq02u03fo09rcwe --- addons/web/static/src/js/view_form.js | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 92faa3d7d15..9e3db4f1b05 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -3236,6 +3236,7 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({ if (!form.$element.is('.oe_form_dirty')) { return true; } + this.o2m._dirty_flag = true; // Otherwise validate internal form return _(form.fields).chain() From ac5dc58367d9bed5a576baaa7b803a67b5d00d07 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Thu, 19 Jul 2012 11:57:42 +0200 Subject: [PATCH 098/112] [IMP] focus the clicked field when starting a row edition by clicking on a cell bzr revid: xmo@openerp.com-20120719095742-lps9xjsrvhn5fnlh --- .../web/static/src/js/view_list_editable.js | 54 +++++++++++++------ doc/list-view.rst | 22 +++++++- 2 files changed, 58 insertions(+), 18 deletions(-) diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index 052a471f0f3..56df125c98b 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -165,9 +165,11 @@ openerp.web.list_editable = function (instance) { * Set up the edition of a record of the list view "inline" * * @param {instance.web.list.Record} [record] record to edit, leave empty to create a new record + * @param {Object} [options] + * @param {String} [options.focus_field] field to focus at start of edition * @return {jQuery.Deferred} */ - start_edition: function (record) { + start_edition: function (record, options) { var self = this; var item = false; if (record) { @@ -194,14 +196,14 @@ openerp.web.list_editable = function (instance) { return self.editor.edit(item, function (field_name, field) { var cell = cells[field_name]; if (!cell || field.get('effective_readonly')) { - // Readonly fields can just remain the list's, form's - // usually don't have backgrounds &al + // Readonly fields can just remain the list's, + // form's usually don't have backgrounds &al field.set({invisible: true}); return; } self.fields_for_resize.push({field: field, cell: cell}); - }).pipe(function () { + }, options).pipe(function () { $recordRow.addClass('oe_edition'); self.resize_fields(); return record.attributes; @@ -558,7 +560,33 @@ openerp.web.list_editable = function (instance) { throw new Error("is_editing's state filter must be either `new` or" + " `edit` if provided"); }, - edit: function (record, configureField) { + _focus_setup: function (focus_field) { + var form = this.form; + + var field; + // If a field to focus was specified + if (focus_field + // Is actually in the form + && (field = form.fields[focus_field]) + // And is visible + && field.$element.is(':visible')) { + // focus it + field.focus(); + return; + } + + _(form.fields_order).detect(function (name) { + // look for first visible field in fields_order, focus it + var field = form.fields[name]; + if (!field.$element.is(':visible')) { + return false; + } + field.focus(); + // Stop as soon as a field got focused + return true; + }); + }, + edit: function (record, configureField, options) { // TODO: specify sequence of edit calls var self = this; var form = self.form; @@ -573,16 +601,7 @@ openerp.web.list_editable = function (instance) { _(form.fields).each(function (field, name) { configureField(name, field); }); - // TODO: actually focus clicked field (if editable) - _(form.fields_order).detect(function (name) { - // look for first visible field in fields_order, focus it - var field = form.fields[name]; - if (!field.$element.is(':visible')) { - return false; - } - field.focus(); - return true; - }); + self._focus_setup(options && options.focus_field); return form; }); }, @@ -627,7 +646,10 @@ openerp.web.list_editable = function (instance) { return this._super.apply(this, arguments); } var record_id = $(event.currentTarget).data('id'); - this.view.start_edition(record_id ? this.records.get(record_id) : null); + this.view.start_edition( + record_id ? this.records.get(record_id) : null, { + focus_field: $(event.target).data('field') + }); }, /** * If a row mapping to the record (@data-id matching the record's id or diff --git a/doc/list-view.rst b/doc/list-view.rst index ebb5f0ffe4d..0d67c27f963 100644 --- a/doc/list-view.rst +++ b/doc/list-view.rst @@ -131,7 +131,7 @@ Interaction Methods rejected if a pending edition could not be saved (e.g. validation failure) -.. js:function:: openerp.web.ListView.start_edition([record]) +.. js:function:: openerp.web.ListView.start_edition([record][, options]) Starts editing the provided record inline, through an overlay form view of editable fields in the record. @@ -142,7 +142,9 @@ Interaction Methods This method resolves any pending edition when invoked, before starting a new edition. + :param record: record to edit, or null to create a new record :type record: :js:class:`~openerp.web.list.Record` + :param EditOptions options: :returns: delegate to the form used for the edition .. js:function:: openerp.web.ListView.save_edition @@ -303,7 +305,7 @@ formview, delegating instead to its :type record_state: String :rtype: Boolean - .. js:function:: openerp.web.list.Editor.edit(record, configureField) + .. js:function:: openerp.web.list.Editor.edit(record, configureField[, options]) Loads the provided record into the internal form view and displays the form view. @@ -320,6 +322,7 @@ formview, delegating instead to its method do some last-minute configuration of form fields. :type configureField: Function + :param EditOptions options: :returns: jQuery delegate to the form object .. js:function:: openerp.web.list.Editor.save @@ -391,6 +394,21 @@ formview, delegating instead to its dataset (instead of appended) :rtype: Boolean + +.. js:class:: EditOptions + + Options object optionally passed into a method starting an edition + to configure its setup and behavior + + .. js:attribute:: focus_field + + Name of the field to set focus on after setting up the edition + of the record. + + If this option is not provided, or the requested field can not + be focused (invisible, readonly or not in the view), the first + visible non-readonly field is focused. + Changes from 6.1 ---------------- From a79139048d496d51cdb23f3087252229b099371a Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Sat, 21 Jul 2012 21:25:51 +0200 Subject: [PATCH 099/112] [FIX] pad everything, remove unused api bzr revid: al@openerp.com-20120721192551-q6nbqn4xxau0e64x --- addons/pad/LICENSE | 15 -- addons/pad/etherpad.py | 254 ---------------------------- addons/pad/pad.py | 35 +--- addons/pad/res_company.py | 10 +- addons/pad/res_company.xml | 3 +- addons/pad/static/src/js/pad.js | 65 +++---- addons/pad/static/src/xml/pad.xml | 12 +- addons/pad_project/__openerp__.py | 2 +- addons/pad_project/project_task.py | 6 +- addons/pad_project/project_task.xml | 5 +- 10 files changed, 55 insertions(+), 352 deletions(-) delete mode 100644 addons/pad/LICENSE delete mode 100644 addons/pad/etherpad.py diff --git a/addons/pad/LICENSE b/addons/pad/LICENSE deleted file mode 100644 index ff64be448e9..00000000000 --- a/addons/pad/LICENSE +++ /dev/null @@ -1,15 +0,0 @@ -OpenERP, Open Source Management Solution -Copyright © 2010-2011 OpenERP SA (). - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as -published by the Free Software Foundation, either version 3 of the -License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public Lice -along with this program. If not, see . diff --git a/addons/pad/etherpad.py b/addons/pad/etherpad.py deleted file mode 100644 index 547720c1cc8..00000000000 --- a/addons/pad/etherpad.py +++ /dev/null @@ -1,254 +0,0 @@ -#!/usr/bin/env python -"""Module to talk to EtherpadLite API.""" - -import json -import urllib -import urllib2 - - -class EtherpadLiteClient: - """Client to talk to EtherpadLite API.""" - API_VERSION = 1 # TODO probably 1.1 sometime soon - - CODE_OK = 0 - CODE_INVALID_PARAMETERS = 1 - CODE_INTERNAL_ERROR = 2 - CODE_INVALID_FUNCTION = 3 - CODE_INVALID_API_KEY = 4 - TIMEOUT = 20 - - apiKey = "" - baseUrl = "" - - def __init__(self, apiKey=None, baseUrl=None): - if apiKey: - self.apiKey = apiKey - - if baseUrl: - self.baseUrl = baseUrl - - def call(self, function, arguments=None): - """Create a dictionary of all parameters""" - url = '%s/%d/%s' % (self.baseUrl, self.API_VERSION, function) - - params = arguments or {} - params.update({'apikey': self.apiKey}) - data = urllib.urlencode(params, True) - - try: - opener = urllib2.build_opener() - request = urllib2.Request(url=url, data=data) - response = opener.open(request, timeout=self.TIMEOUT) - result = response.read() - response.close() - except urllib2.HTTPError: - raise - - result = json.loads(result) - if result is None: - raise ValueError("JSON response could not be decoded") - - return self.handleResult(result) - - def handleResult(self, result): - """Handle API call result""" - if 'code' not in result: - raise Exception("API response has no code") - if 'message' not in result: - raise Exception("API response has no message") - - if 'data' not in result: - result['data'] = None - - if result['code'] == self.CODE_OK: - return result['data'] - elif result['code'] == self.CODE_INVALID_PARAMETERS or result['code'] == self.CODE_INVALID_API_KEY: - raise ValueError(result['message']) - elif result['code'] == self.CODE_INTERNAL_ERROR: - raise Exception(result['message']) - elif result['code'] == self.CODE_INVALID_FUNCTION: - raise Exception(result['message']) - else: - raise Exception("An unexpected error occurred whilst handling the response") - - # GROUPS - # Pads can belong to a group. There will always be public pads that do not belong to a group (or we give this group the id 0) - - def createGroup(self): - """creates a new group""" - return self.call("createGroup") - - def createGroupIfNotExistsFor(self, groupMapper): - """this functions helps you to map your application group ids to etherpad lite group ids""" - return self.call("createGroupIfNotExistsFor", { - "groupMapper": groupMapper - }) - - def deleteGroup(self, groupID): - """deletes a group""" - return self.call("deleteGroup", { - "groupID": groupID - }) - - def listPads(self, groupID): - """returns all pads of this group""" - return self.call("listPads", { - "groupID": groupID - }) - - def createGroupPad(self, groupID, padName, text=''): - """creates a new pad in this group""" - params = { - "groupID": groupID, - "padName": padName, - } - if text: - params['text'] = text - return self.call("createGroupPad", params) - - # AUTHORS - # Theses authors are bind to the attributes the users choose (color and name). - - def createAuthor(self, name=''): - """creates a new author""" - params = {} - if name: - params['name'] = name - return self.call("createAuthor", params) - - def createAuthorIfNotExistsFor(self, authorMapper, name=''): - """this functions helps you to map your application author ids to etherpad lite author ids""" - params = { - 'authorMapper': authorMapper - } - if name: - params['name'] = name - return self.call("createAuthorIfNotExistsFor", params) - - # SESSIONS - # Sessions can be created between a group and a author. This allows - # an author to access more than one group. The sessionID will be set as - # a cookie to the client and is valid until a certain date. - - def createSession(self, groupID, authorID, validUntil): - """creates a new session""" - return self.call("createSession", { - "groupID": groupID, - "authorID": authorID, - "validUntil": validUntil - }) - - def deleteSession(self, sessionID): - """deletes a session""" - return self.call("deleteSession", { - "sessionID": sessionID - }) - - def getSessionInfo(self, sessionID): - """returns informations about a session""" - return self.call("getSessionInfo", { - "sessionID": sessionID - }) - - def listSessionsOfGroup(self, groupID): - """returns all sessions of a group""" - return self.call("listSessionsOfGroup", { - "groupID": groupID - }) - - def listSessionsOfAuthor(self, authorID): - """returns all sessions of an author""" - return self.call("listSessionsOfAuthor", { - "authorID": authorID - }) - - # PAD CONTENT - # Pad content can be updated and retrieved through the API - - def getText(self, padID, rev=None): - """returns the text of a pad""" - params = {"padID": padID} - if rev is not None: - params['rev'] = rev - return self.call("getText", params) - - # introduced with pull request merge - def getHtml(self, padID, rev=None): - """returns the html of a pad""" - params = {"padID": padID} - if rev is not None: - params['rev'] = rev - return self.call("getHTML", params) - - def setText(self, padID, text): - """sets the text of a pad""" - return self.call("setText", { - "padID": padID, - "text": text - }) - - def setHtml(self, padID, html): - """sets the text of a pad from html""" - return self.call("setHTML", { - "padID": padID, - "html": html - }) - - # PAD - # Group pads are normal pads, but with the name schema - # GROUPID$PADNAME. A security manager controls access of them and its - # forbidden for normal pads to include a in the name. - - def createPad(self, padID, text=''): - """creates a new pad""" - params = { - "padID": padID, - } - if text: - params['text'] = text - return self.call("createPad", params) - - def getRevisionsCount(self, padID): - """returns the number of revisions of this pad""" - return self.call("getRevisionsCount", { - "padID": padID - }) - - def deletePad(self, padID): - """deletes a pad""" - return self.call("deletePad", { - "padID": padID - }) - - def getReadOnlyID(self, padID): - """returns the read only link of a pad""" - return self.call("getReadOnlyID", { - "padID": padID - }) - - def setPublicStatus(self, padID, publicStatus): - """sets a boolean for the public status of a pad""" - return self.call("setPublicStatus", { - "padID": padID, - "publicStatus": publicStatus - }) - - def getPublicStatus(self, padID): - """return true of false""" - return self.call("getPublicStatus", { - "padID": padID - }) - - def setPassword(self, padID, password): - """returns ok or a error message""" - return self.call("setPassword", { - "padID": padID, - "password": password - }) - - def isPasswordProtected(self, padID): - """returns true or false""" - return self.call("isPasswordProtected", { - "padID": padID - }) - diff --git a/addons/pad/pad.py b/addons/pad/pad.py index c7e448767bc..40569fcd589 100644 --- a/addons/pad/pad.py +++ b/addons/pad/pad.py @@ -2,52 +2,29 @@ from osv import fields, osv import random import string -from etherpad import EtherpadLiteClient import urllib2 from tools.translate import _ - class pad_common(osv.osv_memory): _name = 'pad.common' - _pad_url = None # name of the field for the etherpad - def pad_generate_url(self, cr, uid, model, context=None): - pad_url_template = self._pad_url_template(cr, uid, context) + _pad_fields = [] + def pad_generate_url(self, cr, uid, context=None): + pad_url_template = self.pool.get('res.users').browse(cr,uid, uid, context).company_id.pad_url_template s = string.ascii_uppercase + string.digits salt = ''.join([s[random.randint(0, len(s) - 1)] for i in range(8)]) template_vars = { 'db' : cr.dbname, - 'model' : model, + 'model' : self._name, 'salt' : salt, } url = pad_url_template % template_vars - api_key = self._pad_api_key(cr, uid, context) - if api_key: - urls = url.split('/') - api_url = '/'.join(urls[:3]) + "/api" - pad_id = urls[-1] - ep_client = EtherpadLiteClient(api_key, api_url) - try: - ep_client.createPad(pad_id," ") - except ValueError as strerror: - raise osv.except_osv(_('Configuration Error !'),_("Etherpad Have Wrong API Key.")) - except urllib2.HTTPError as e: - raise osv.except_osv(_('Configuration Error !'),_("Etherpad Have Wrong API URL.")) - except urllib2.URLError as e: - raise osv.except_osv(_('Configuration Error !'),_("Etherpad Have Wrong Pad URL Template.")) return url - def _pad_api_key(self, cr, uid, context=None): - return self.pool.get('res.users').browse(cr,uid, uid, context).company_id.etherpad_api_key - - def _pad_url_template(self, cr, uid, context=None): - return self.pool.get('res.users').browse(cr,uid, uid, context).company_id.pad_url_template - def copy(self, cr, uid, id, default=None, context=None): if not default: default = {} - default.update({ - self._pad_url:self.pad_generate_url(cr, uid, self._name), - }) + update = [(field,self.pad_generate_url(cr, uid, context)) for field in self._pad_fields] + default.update(update) return super(pad_common, self).copy(cr, uid, id, default, context) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/pad/res_company.py b/addons/pad/res_company.py index 4ce27fd4d22..bd955cd0481 100644 --- a/addons/pad/res_company.py +++ b/addons/pad/res_company.py @@ -1,19 +1,15 @@ # -*- coding: utf-8 -*- from osv import fields, osv -PAD_TEMPLATE = 'http://beta.etherpad.org/p/%(db).10s-%(model)s-%(salt)s' -PAD_API_KEY = 'EtherpadFTW' +DEFAULT_PAD_TEMPLATE = 'http://beta.etherpad.org/p/%(db)s-%(model)s-%(salt)s' class company_pad(osv.osv): _inherit = 'res.company' _columns = { - 'pad_url_template': fields.char('Pad URL Template', size=128, required=True, - help="Template used to generate pad URL."), - 'etherpad_api_key': fields.char('Pad API Key', size=128), + 'pad_url_template': fields.char('Pad URL Template', size=128, required=True, help="Template used to generate pad URL."), } _defaults = { - 'pad_url_template': PAD_TEMPLATE, - 'etherpad_api_key': PAD_API_KEY, + 'pad_url_template': DEFAULT_PAD_TEMPLATE, } diff --git a/addons/pad/res_company.xml b/addons/pad/res_company.xml index 96c3c004bef..12f14b0f5bd 100644 --- a/addons/pad/res_company.xml +++ b/addons/pad/res_company.xml @@ -8,8 +8,7 @@ - - + diff --git a/addons/pad/static/src/js/pad.js b/addons/pad/static/src/js/pad.js index 5736d42bbab..6384eb72da8 100644 --- a/addons/pad/static/src/js/pad.js +++ b/addons/pad/static/src/js/pad.js @@ -1,37 +1,40 @@ openerp.pad = function(instance) { -instance.web.form.FieldEtherpad = instance.web.form.AbstractField.extend(_.extend({}, instance.web.form.ReinitializeFieldMixin, { - template: 'FieldEtherpad', +instance.web.form.FieldPad = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, { + template: 'FieldPad', initialize_content: function() { - this.$textarea = undefined; - this.$element.find('div.oe_etherpad_head').click(_.bind(function(ev){ - this.$element.toggleClass('oe_etherpad_fullscreen').toggleClass('oe_etherpad_normal'); - - },this)); - }, - set_value: function(value_) { - this._super(value_); - this.render_value(); - }, - render_value: function() { - var show_value = instance.web.format_value(this.get('value'), this, ''); - if (!this.get("effective_readonly")) { + var self = this; + this.$textarea = undefined; + this.$element.find('div.oe_etherpad_head').click(function(ev) { + self.$element.toggleClass('oe_etherpad_fullscreen').toggleClass('oe_etherpad_normal'); + }); + }, + set_value: function(value_) { + this._super(value_); + this.render_value(); + }, + render_value: function() { + var self = this; + var value = this.get('value'); + if(value !== false) { + var url = value.split('\n')[0]; + if (!this.get("effective_readonly")) { var pad_username = this.session.username; - this.$element.find('div.oe_etherpad_default').html(''); + var code = ''; + this.$element.find('div.oe_etherpad_default').html(code); } else { - if(this.get('value') != false) - { - var self = this; - if(show_value.split('\n')[0] != '') - $.get(show_value.split('\n')[0]+'/export/html') - .success(function(data) { self.$element.html('
    '+data+'
    '); }) - .error(function() { self.$element.text('Unable to load pad'); }); - } + $.get(url+'/export/html').success(function(data) { + self.$element.html('
    '+data+'
    '); + }).error(function() { + self.$element.text('Unable to load pad'); + }); } - }, - })); - - instance.web.form.widgets = instance.web.form.widgets.extend({ - 'etherpad': 'instance.web.form.FieldEtherpad', - }); -}; + } + }, +}); + +instance.web.form.widgets = instance.web.form.widgets.extend({ + 'pad': 'instance.web.form.FieldPad', +}); + +}; diff --git a/addons/pad/static/src/xml/pad.xml b/addons/pad/static/src/xml/pad.xml index 27b210b3cf2..e128fb183fb 100644 --- a/addons/pad/static/src/xml/pad.xml +++ b/addons/pad/static/src/xml/pad.xml @@ -3,14 +3,14 @@ --> - +
    -
    - Fullscreen - Return to Record -
    -
    +
    + Fullscreen + Return to Record +
    +
    diff --git a/addons/pad_project/__openerp__.py b/addons/pad_project/__openerp__.py index 61ebfa8f17f..18c7d3191d3 100644 --- a/addons/pad_project/__openerp__.py +++ b/addons/pad_project/__openerp__.py @@ -20,7 +20,7 @@ ############################################################################## { - 'name': 'Specifications on PADs', + 'name': 'Pad on tasks', 'version': '1.0', "category": "Project Management", 'description': """ diff --git a/addons/pad_project/project_task.py b/addons/pad_project/project_task.py index 1095b753e9f..46c2c54cd46 100644 --- a/addons/pad_project/project_task.py +++ b/addons/pad_project/project_task.py @@ -5,10 +5,10 @@ from osv import fields, osv class task(osv.osv): _name = "project.task" _inherit = ["project.task",'pad.common'] - _pad_url = 'description_pad' + _pad_fields = ['description_pad'] _columns = { - 'description_pad': fields.char('PAD Description', size=250) + 'description_pad': fields.char('Description PAD', size=250) } _defaults = { - _pad_url: lambda self, cr, uid, context: self.pad_generate_url(cr, uid, self._name, context), + 'description_pad': lambda self, cr, uid, context: self.pad_generate_url(cr, uid, context), } diff --git a/addons/pad_project/project_task.xml b/addons/pad_project/project_task.xml index 122bcc546fa..d18f43a92f4 100644 --- a/addons/pad_project/project_task.xml +++ b/addons/pad_project/project_task.xml @@ -7,10 +7,7 @@ - + From ad4b42d6e099cf5557f39facf4e37cb883d26da3 Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Sat, 21 Jul 2012 21:34:59 +0200 Subject: [PATCH 100/112] [FIX] pad fix bzr ignore bzr revid: al@openerp.com-20120721193459-gxs3fs21nfel9enr --- addons/.bzrignore | 4 ---- 1 file changed, 4 deletions(-) diff --git a/addons/.bzrignore b/addons/.bzrignore index 5fee3e96f5d..8d98f9debde 100644 --- a/addons/.bzrignore +++ b/addons/.bzrignore @@ -1,5 +1 @@ .* -pad/__init__.pyc -pad/ir_attachment.pyc -pad/res_company.pyc -web_etherpad From c53deab9878bfa5e5e44dc1165f7982a5b8f6e55 Mon Sep 17 00:00:00 2001 From: Launchpad Translations on behalf of openerp <> Date: Mon, 23 Jul 2012 05:21:34 +0000 Subject: [PATCH 101/112] Launchpad automatic translations update. bzr revid: launchpad_translations_on_behalf_of_openerp-20120723052134-owmld5p3oqmn3nol --- addons/web_dashboard/i18n/uk.po | 63 +++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 addons/web_dashboard/i18n/uk.po diff --git a/addons/web_dashboard/i18n/uk.po b/addons/web_dashboard/i18n/uk.po new file mode 100644 index 00000000000..fd4df4a2141 --- /dev/null +++ b/addons/web_dashboard/i18n/uk.po @@ -0,0 +1,63 @@ +# Ukrainian translation for openerp-web +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openerp-web package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openerp-web\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2012-07-02 09:06+0200\n" +"PO-Revision-Date: 2012-07-22 09:32+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: Ukrainian \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2012-07-23 05:21+0000\n" +"X-Generator: Launchpad (build 15654)\n" + +#. openerp-web +#: addons/web_dashboard/static/src/js/dashboard.js:60 +msgid "Edit Layout" +msgstr "" + +#. openerp-web +#: addons/web_dashboard/static/src/js/dashboard.js:106 +msgid "Are you sure you want to remove this item ?" +msgstr "" + +#. openerp-web +#: addons/web_dashboard/static/src/xml/web_dashboard.xml:4 +msgid "Reset Layout.." +msgstr "" + +#. openerp-web +#: addons/web_dashboard/static/src/xml/web_dashboard.xml:6 +msgid "Reset" +msgstr "" + +#. openerp-web +#: addons/web_dashboard/static/src/xml/web_dashboard.xml:8 +msgid "Change Layout.." +msgstr "" + +#. openerp-web +#: addons/web_dashboard/static/src/xml/web_dashboard.xml:10 +msgid "Change Layout" +msgstr "" + +#. openerp-web +#: addons/web_dashboard/static/src/xml/web_dashboard.xml:27 +msgid " " +msgstr "" + +#. openerp-web +#: addons/web_dashboard/static/src/xml/web_dashboard.xml:28 +msgid "Create" +msgstr "" + +#. openerp-web +#: addons/web_dashboard/static/src/xml/web_dashboard.xml:39 +msgid "Choose dashboard layout" +msgstr "" From 071dce2cb94536c408b94f57967d223f94eb776c Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Mon, 23 Jul 2012 15:49:14 +0200 Subject: [PATCH 102/112] [FIX] problem with wizards bzr revid: nicolas.vanhoren@openerp.com-20120723134914-4rrmeysncsancwfq --- addons/web/static/src/js/chrome.js | 9 +++++++++ addons/web/static/src/js/view_form.js | 6 +----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/addons/web/static/src/js/chrome.js b/addons/web/static/src/js/chrome.js index 40cff06e66a..38bcfef3e05 100644 --- a/addons/web/static/src/js/chrome.js +++ b/addons/web/static/src/js/chrome.js @@ -135,8 +135,12 @@ instance.web.Dialog = instance.web.Widget.extend({ this.$element.dialog('close'); }, on_close: function() { + if (this.__tmp_dialog_destroying) + return; if (this.dialog_options.destroy_on_close) { + this.__tmp_dialog_closing = true; this.destroy(); + this.__tmp_dialog_closing = undefined; } }, on_resized: function() { @@ -145,6 +149,11 @@ instance.web.Dialog = instance.web.Widget.extend({ _.each(this.getChildren(), function(el) { el.destroy(); }); + if (! this.__tmp_dialog_closing) { + this.__tmp_dialog_destroying = true; + this.close(); + this.__tmp_dialog_destroying = undefined; + } if (! this.isDestroyed()) { this.$element.dialog('destroy'); } diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index d6ed85fedad..b75d0886387 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -1682,11 +1682,7 @@ instance.web.form.WidgetButton = instance.web.form.FormWidget.extend({ on_confirmed: function() { var self = this; - var context = this.node.attrs.context; - if (context && context.__ref) { - context = new instance.web.CompoundContext(context); - context.set_eval_context(this._build_eval_context()); - } + var context = this.build_context(); return this.view.do_execute_action( _.extend({}, this.node.attrs, {context: context}), From c5db4ca91d34b9fb222785eda0b407eaf00cefce Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 23 Jul 2012 15:49:31 +0200 Subject: [PATCH 103/112] [IMP] field.char: make string and size optional * Increases coherence with other fields (default string is 'unknown') * Char fields defaut to unbounded, bounds are optional bzr revid: xmo@openerp.com-20120723134931-b5moeenk0db5m2ss --- openerp/osv/fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py index 2a6d0d6e3eb..3117793dcab 100644 --- a/openerp/osv/fields.py +++ b/openerp/osv/fields.py @@ -181,7 +181,7 @@ class reference(_column): class char(_column): _type = 'char' - def __init__(self, string, size, **args): + def __init__(self, string="unknown", size=None, **args): _column.__init__(self, string=string, size=size, **args) self._symbol_set = (self._symbol_c, self._symbol_set_char) From 8bdd7300905341957579bea7c0557dbff99a051d Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 23 Jul 2012 15:50:22 +0200 Subject: [PATCH 104/112] [REM] orm: pointless character replacement of quotes by spaces in field strings bzr revid: xmo@openerp.com-20120723135022-9lh5yyk0au5xag3l --- openerp/osv/orm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 78487e7641c..18a8ec59982 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -758,7 +758,7 @@ class BaseModel(object): 'model_id': model_id, 'model': self._name, 'name': k, - 'field_description': f.string.replace("'", " "), + 'field_description': f.string, 'ttype': f._type, 'relation': f._obj or '', 'view_load': (f.view_load and 1) or 0, From 3b5890d79b8286f564b5cf3dd8166efaa7a5f3b3 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 23 Jul 2012 15:51:13 +0200 Subject: [PATCH 105/112] [REM] redundant code: a field of type one2many will always have _type one2many bzr revid: xmo@openerp.com-20120723135113-6x3s5lmljk8cdpf0 --- openerp/osv/orm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openerp/osv/orm.py b/openerp/osv/orm.py index 18a8ec59982..a62561fc95d 100644 --- a/openerp/osv/orm.py +++ b/openerp/osv/orm.py @@ -767,7 +767,7 @@ class BaseModel(object): 'required': (f.required and 1) or 0, 'selectable': (f.selectable and 1) or 0, 'translate': (f.translate and 1) or 0, - 'relation_field': (f._type=='one2many' and isinstance(f, fields.one2many)) and f._fields_id or '', + 'relation_field': f._fields_id if isinstance(f, fields.one2many) else '', 'serialization_field_id': None, } if getattr(f, 'serialization_field', None): From 8b0f8a09927254e0cb7e7e1ae1deb83ff2d78b34 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 23 Jul 2012 15:52:15 +0200 Subject: [PATCH 106/112] [IMP] replace equality None test by identity test bzr revid: xmo@openerp.com-20120723135215-7n63uh4ikulsdc6r --- openerp/osv/fields.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py index 3117793dcab..6c74c05bd5a 100644 --- a/openerp/osv/fields.py +++ b/openerp/osv/fields.py @@ -49,7 +49,7 @@ import simplejson _logger = logging.getLogger(__name__) def _symbol_set(symb): - if symb == None or symb == False: + if symb is None or symb == False: return None elif isinstance(symb, unicode): return symb.encode('utf-8') @@ -191,7 +191,7 @@ class char(_column): # * we need to remove the "symb==False" from the next line BUT # for now too many things rely on this broken behavior # * the symb==None test should be common to all data types - if symb == None or symb == False: + if symb is None or symb == False: return None # we need to convert the string to a unicode object to be able From bef7ef96200769e8700aa969b097f05cce02d191 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Mon, 23 Jul 2012 15:59:37 +0200 Subject: [PATCH 107/112] [IMP] Modified loading message bzr revid: nicolas.vanhoren@openerp.com-20120723135937-3tuwpzdhn3311mg1 --- addons/web/static/src/js/chrome.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/addons/web/static/src/js/chrome.js b/addons/web/static/src/js/chrome.js index 38bcfef3e05..047a4a80e0d 100644 --- a/addons/web/static/src/js/chrome.js +++ b/addons/web/static/src/js/chrome.js @@ -263,7 +263,11 @@ instance.web.Loading = instance.web.Widget.extend({ this.count += increment; if (this.count > 0) { - this.$element.text(_.str.sprintf( _t("Loading (%d)"), this.count)); + if (instance.connection.debug) { + this.$element.text(_.str.sprintf( _t("Loading (%d)"), this.count)); + } else { + this.$element.text(_t("Loading")); + } this.$element.show(); this.getParent().$element.addClass('oe_wait'); } else { From ce02c0887a836897d190519c7b8e5330af16ecc4 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Mon, 23 Jul 2012 16:05:02 +0200 Subject: [PATCH 108/112] [IMP] coerce 'falsy' char sizes to None Otherwise users using fields.char(size=False) will see their content disappear without warning, not cool bzr revid: xmo@openerp.com-20120723140502-nrtgw0o47ewj25qa --- openerp/osv/fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openerp/osv/fields.py b/openerp/osv/fields.py index 6c74c05bd5a..b7f47fe6549 100644 --- a/openerp/osv/fields.py +++ b/openerp/osv/fields.py @@ -182,7 +182,7 @@ class char(_column): _type = 'char' def __init__(self, string="unknown", size=None, **args): - _column.__init__(self, string=string, size=size, **args) + _column.__init__(self, string=string, size=size or None, **args) self._symbol_set = (self._symbol_c, self._symbol_set_char) # takes a string (encoded in utf8) and returns a string (encoded in utf8) From 50a5ccebe700abc0d58b20b10ac3edbbb873dd53 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Mon, 23 Jul 2012 17:11:13 +0200 Subject: [PATCH 109/112] [FIX] problem with translate dialog bzr revid: nicolas.vanhoren@openerp.com-20120723151113-nvh8l72duewcotvx --- addons/web/static/src/js/view_form.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index b75d0886387..3f12bac7843 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -945,6 +945,9 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM is_create_mode: function() { return !this.datarecord.id; }, + open_translate_dialog: function(field) { + return this._super(field); + }, }); /** From 8218e1cc8051f38ef8d38d73b74f536a9c59dbb2 Mon Sep 17 00:00:00 2001 From: niv-openerp Date: Mon, 23 Jul 2012 17:22:24 +0200 Subject: [PATCH 110/112] [FIX] another problem with translation wizard bzr revid: nicolas.vanhoren@openerp.com-20120723152224-pmll06c6j3hk1hjd --- addons/web/static/src/js/views.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/addons/web/static/src/js/views.js b/addons/web/static/src/js/views.js index bd102d867de..319dfc9ca6d 100644 --- a/addons/web/static/src/js/views.js +++ b/addons/web/static/src/js/views.js @@ -1074,11 +1074,13 @@ instance.web.TranslateDialog = instance.web.Dialog.extend({ if (self.view.translatable_fields && self.view.translatable_fields.length) { self.do_load_fields_values(function() { sup.call(self); + // desactivated because it created an exception, plus it does not seem very useful + /* if (field) { var $field_input = self.$element.find('tr[data-field="' + field.name + '"] td:nth-child(2) *:first-child'); self.$element.scrollTo($field_input); $field_input.focus(); - } + }*/ }); } else { sup.call(self); From ce7580492bcb63b6782677d7dff96f6bd0d9d8b6 Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Mon, 23 Jul 2012 17:30:39 +0200 Subject: [PATCH 111/112] [IMP] m2o no tabindex=-! for open button will be handled by a keyboard shortcut bzr revid: al@openerp.com-20120723153039-3xstwq2a1pcc67gh --- addons/web/static/src/xml/base.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 55280f6badb..54bf221fc26 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -956,7 +956,7 @@
    - / + /
    Date: Mon, 23 Jul 2012 18:22:23 +0200 Subject: [PATCH 112/112] [FIX] hide topbar on login page bzr revid: chs@openerp.com-20120723162223-amclassz6dzneayq --- addons/web/static/src/js/chrome.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/addons/web/static/src/js/chrome.js b/addons/web/static/src/js/chrome.js index 047a4a80e0d..71010ef6d74 100644 --- a/addons/web/static/src/js/chrome.js +++ b/addons/web/static/src/js/chrome.js @@ -970,10 +970,12 @@ instance.web.WebClient = instance.web.Client.extend({ }, show_login: function() { var self = this; + self.$('.oe_topbar').hide(); self.login.appendTo(self.$element); }, show_application: function() { var self = this; + self.$('.oe_topbar').show(); self.login.$element.hide(); self.menu = new instance.web.Menu(self); self.menu.replace(this.$element.find('.oe_menu_placeholder'));