diff options
Diffstat (limited to 'addons/web/static/src')
30 files changed, 7955 insertions, 5704 deletions
diff --git a/addons/web/static/src/css/Makefile b/addons/web/static/src/css/Makefile new file mode 100644 index 00000000000..eca044497ef --- /dev/null +++ b/addons/web/static/src/css/Makefile @@ -0,0 +1,3 @@ +base.css: base.sass + sass -t expanded base.sass base.css + diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index b0a2b7945b9..854946cdf97 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -1,37 +1,428 @@ -.openerp2 { +@charset "utf-8"; +@font-face { + font-family: "mnmliconsRegular"; + src: url("/web/static/src/font/mnmliconsv21-webfont.eot") format("eot"); + src: url("/web/static/src/font/mnmliconsv21-webfont.woff") format("woff"); + src: url("/web/static/src/font/mnmliconsv21-webfont.ttf") format("truetype"); + src: url("/web/static/src/font/mnmliconsv21-webfont.svg") format("svg") active; + font-weight: normal; + font-style: normal; +} + +.openerp.openerp-web-client-container { + height: 100%; + position: relative; +} + +.openerp { padding: 0; margin: 0; font-family: "Lucida Grande", Helvetica, Verdana, Arial, sans-serif; color: #4c4c4c; font-size: 13px; background: white; - position: relative; + /* http://www.quirksmode.org/dom/inputfile.html + * http://stackoverflow.com/questions/2855589/replace-input-type-file-by-an-image + */ } -.openerp2 a { +.openerp a { text-decoration: none; } -.openerp2 .oe_webclient { - position: absolute; +.openerp table { + padding: 0; + font-size: 13px; + border-collapse: collapse; +} +.openerp thead { + font-weight: bold; + background-color: #f0f0f0; +} +.openerp thead th { + border-right: 1px dotted #afafb6; +} +.openerp thead th:last-child { + border-right: none; +} +.openerp th, .openerp td { + padding: 0; + line-height: 18px; + text-align: left; +} +.openerp th { + font-weight: bold; + vertical-align: middle; +} +.openerp td { + vertical-align: top; +} +.openerp .zebra tbody tr:nth-child(odd) td { + background-color: #f0f0fa; + background-color: #f0f0fa; + background-image: -webkit-gradient(linear, left top, left bottom, from(#f0f0fa), to(#eeeef6)); + background-image: -webkit-linear-gradient(top, #f0f0fa, #eeeef6); + background-image: -moz-linear-gradient(top, #f0f0fa, #eeeef6); + background-image: -ms-linear-gradient(top, #f0f0fa, #eeeef6); + background-image: -o-linear-gradient(top, #f0f0fa, #eeeef6); + background-image: linear-gradient(to bottom, #f0f0fa, #eeeef6); +} +.openerp .zebra tbody tr:hover td { + background-color: #eeeeee; + background-image: -webkit-gradient(linear, left top, left bottom, from(#eeeeee), to(#dedede)); + background-image: -webkit-linear-gradient(top, #eeeeee, #dedede); + background-image: -moz-linear-gradient(top, #eeeeee, #dedede); + background-image: -ms-linear-gradient(top, #eeeeee, #dedede); + background-image: -o-linear-gradient(top, #eeeeee, #dedede); + background-image: linear-gradient(to bottom, #eeeeee, #dedede); +} +.openerp .numeric { + text-align: right; + width: 82px; +} +.openerp .numeric input { + text-align: right; +} +.openerp ul, .openerp li, .openerp ol { + margin: 0; + padding: 0; +} +.openerp li { + list-style-type: none; +} +.openerp .oe_i { + font-family: "mnmliconsRegular" !important; + font-size: 21px; + font-weight: 300 !important; +} +.openerp .oe_left { + float: left; + margin-right: 8px; +} +.openerp .oe_right { + float: right; + margin-left: 8px; +} +.openerp .oe_clear { + clear: both; +} +.openerp .oe_wait { + cursor: wait; +} +.openerp a.button:link, .openerp a.button:visited, .openerp button, .openerp input[type='submit'] { + display: inline-block; + border: 1px solid #ababab; + color: #404040; + margin: 0; + padding: 4px 12px; + font-size: 13px; + text-align: center; + background-color: #efefef; + background-image: -webkit-gradient(linear, left top, left bottom, from(#efefef), to(#d8d8d8)); + background-image: -webkit-linear-gradient(top, #efefef, #d8d8d8); + background-image: -moz-linear-gradient(top, #efefef, #d8d8d8); + background-image: -ms-linear-gradient(top, #efefef, #d8d8d8); + background-image: -o-linear-gradient(top, #efefef, #d8d8d8); + background-image: linear-gradient(to bottom, #efefef, #d8d8d8); + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; + -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(255, 255, 255, 0.8) inset; + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(255, 255, 255, 0.8) inset; + -box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(255, 255, 255, 0.8) inset; + text-shadow: 0 1px 1px rgba(255, 255, 255, 0.5); + -webkit-font-smoothing: antialiased; + outline: none; +} +.openerp a.button:hover, .openerp button:hover, .openerp input[type='submit']:hover { + background-color: #f6f6f6; + background-image: -webkit-gradient(linear, left top, left bottom, from(#f6f6f6), to(#e3e3e3)); + background-image: -webkit-linear-gradient(top, #f6f6f6, #e3e3e3); + background-image: -moz-linear-gradient(top, #f6f6f6, #e3e3e3); + background-image: -ms-linear-gradient(top, #f6f6f6, #e3e3e3); + background-image: -o-linear-gradient(top, #f6f6f6, #e3e3e3); + background-image: linear-gradient(to bottom, #f6f6f6, #e3e3e3); + cursor: pointer; +} +.openerp a.button:focus, .openerp button:focus, .openerp input[type='submit']:focus { + border: 1px solid #80bfff; + background-color: #f6f6f6; + background-image: -webkit-gradient(linear, left top, left bottom, from(#f6f6f6), to(#e3e3e3)); + background-image: -webkit-linear-gradient(top, #f6f6f6, #e3e3e3); + background-image: -moz-linear-gradient(top, #f6f6f6, #e3e3e3); + background-image: -ms-linear-gradient(top, #f6f6f6, #e3e3e3); + background-image: -o-linear-gradient(top, #f6f6f6, #e3e3e3); + background-image: linear-gradient(to bottom, #f6f6f6, #e3e3e3); + -moz-box-shadow: 0 0 3px #80bfff, 0 1px 1px rgba(255, 255, 255, 0.8) inset; + -webkit-box-shadow: 0 0 3px #80bfff, 0 1px 1px rgba(255, 255, 255, 0.8) inset; + -box-shadow: 0 0 3px #80bfff, 0 1px 1px rgba(255, 255, 255, 0.8) inset; +} +.openerp a.button:active, .openerp a.button.active, .openerp button:active, .openerp button.active, .openerp input[type='submit']:active, .openerp input[type='submit'].active { + background: #e3e3e3; + background: -moz-linear-gradient(top, #e3e3e3, #f6f6f6) #1b468f; + background: -webkit-gradient(linear, left top, left bottom, from(#e3e3e3), to(#f6f6f6)) #1b468f; + background: linear-gradient(top, #e3e3e3, #f6f6f6) #1b468f; + -moz-box-shadow: none; + -webkit-box-shadow: none; + -box-shadow: none; +} +.openerp a.button.disabled, .openerp button:disabled, .openerp input[type='submit']:disabled { + background: #efefef !important; + border: 1px solid #d1d1d1 !important; + -moz-box-shadow: none !important; + -webkit-box-shadow: none !important; + -box-shadow: none !important; + color: #aaaaaa !important; + cursor: default; + text-shadow: 0 1px 1px white !important; +} +.openerp button.primary { + color: white; + background-color: #dc5f59; + background-image: -webkit-gradient(linear, left top, left bottom, from(#dc5f59), to(#b33630)); + background-image: -webkit-linear-gradient(top, #dc5f59, #b33630); + background-image: -moz-linear-gradient(top, #dc5f59, #b33630); + background-image: -ms-linear-gradient(top, #dc5f59, #b33630); + background-image: -o-linear-gradient(top, #dc5f59, #b33630); + background-image: linear-gradient(to bottom, #dc5f59, #b33630); + -moz-box-shadow: none; + -webkit-box-shadow: none; + -box-shadow: none; +} +.openerp button.primary:hover { + background-color: #df6b66; + background-image: -webkit-gradient(linear, left top, left bottom, from(#df6b66), to(#bf3a33)); + background-image: -webkit-linear-gradient(top, #df6b66, #bf3a33); + background-image: -moz-linear-gradient(top, #df6b66, #bf3a33); + background-image: -ms-linear-gradient(top, #df6b66, #bf3a33); + background-image: -o-linear-gradient(top, #df6b66, #bf3a33); + background-image: linear-gradient(to bottom, #df6b66, #bf3a33); + -moz-box-shadow: 0 0 1px rgba(0, 0, 0, 0.2); + -webkit-box-shadow: 0 0 1px rgba(0, 0, 0, 0.2); + -box-shadow: 0 0 1px rgba(0, 0, 0, 0.2); +} +.openerp button.primary:active { + background-color: #b33630; + background-image: -webkit-gradient(linear, left top, left bottom, from(#b33630), to(#dc5f59)); + background-image: -webkit-linear-gradient(top, #b33630, #dc5f59); + background-image: -moz-linear-gradient(top, #b33630, #dc5f59); + background-image: -ms-linear-gradient(top, #b33630, #dc5f59); + background-image: -o-linear-gradient(top, #b33630, #dc5f59); + background-image: linear-gradient(to bottom, #b33630, #dc5f59); + -moz-box-shadow: none; + -webkit-box-shadow: none; + -box-shadow: none; +} +.openerp button.following { + color: white; + width: 120px; + background-color: #8a89ba; + background-image: -webkit-gradient(linear, left top, left bottom, from(#8a89ba), to(#807fb4)); + background-image: -webkit-linear-gradient(top, #8a89ba, #807fb4); + background-image: -moz-linear-gradient(top, #8a89ba, #807fb4); + background-image: -ms-linear-gradient(top, #8a89ba, #807fb4); + background-image: -o-linear-gradient(top, #8a89ba, #807fb4); + background-image: linear-gradient(to bottom, #8a89ba, #807fb4); +} +.openerp button.unfollow { + display: none; + color: white; + width: 120px; + background-color: #dc5f59; + background-image: -webkit-gradient(linear, left top, left bottom, from(#dc5f59), to(#b33630)); + background-image: -webkit-linear-gradient(top, #dc5f59, #b33630); + background-image: -moz-linear-gradient(top, #dc5f59, #b33630); + background-image: -ms-linear-gradient(top, #dc5f59, #b33630); + background-image: -o-linear-gradient(top, #dc5f59, #b33630); + background-image: linear-gradient(to bottom, #dc5f59, #b33630); +} +.openerp .oe_loading { + display: none; + z-index: 100; + position: fixed; top: 0; - bottom: 0; + right: 50%; + padding: 4px 12px; + background: #a61300; + color: white; + text-align: center; + border: 1px solid #990000; + border-top: none; + -moz-border-radius-bottomright: 8px; + -moz-border-radius-bottomleft: 8px; + border-bottom-right-radius: 8px; + border-bottom-left-radius: 8px; +} +.openerp .oe_notification { + z-index: 1050; +} +.openerp .oe_login { + background: url("/web/static/src/img/pattern.png") repeat; + text-align: center; + font-size: 14px; + height: 100%; +} +.openerp .oe_login li { + list-style-type: none; + padding-bottom: 4px; +} +.openerp .oe_login button { + float: right; + display: inline-block; + cursor: pointer; + padding: 6px 16px; + border: 1px solid #222222; + color: white; + margin: 0; + background-color: #b92020; + background-image: -webkit-gradient(linear, left top, left bottom, from(#b92020), to(#600606)); + background-image: -webkit-linear-gradient(top, #b92020, #600606); + background-image: -moz-linear-gradient(top, #b92020, #600606); + background-image: -ms-linear-gradient(top, #b92020, #600606); + background-image: -o-linear-gradient(top, #b92020, #600606); + background-image: linear-gradient(to bottom, #b92020, #600606); + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + border-radius: 4px; + -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(155, 155, 155, 0.4) inset; + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(155, 155, 155, 0.4) inset; + -box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(155, 155, 155, 0.4) inset; +} +.openerp .oe_login input, .openerp .oe_login select { + width: 252px; + font-family: "Lucida Grande", Helvetica, Verdana, Arial; + border: 1px solid #999999; + background: whitesmoke; + -moz-box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.3); + -webkit-box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.3); + -box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.3); + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; +} +.openerp .oe_login input { + margin-bottom: 9px; + padding: 5px 6px; +} +.openerp .oe_login select { + padding: 1px; +} +.openerp .oe_login .oe_login_dbpane { + position: fixed; + top: 0; + right: 8px; + padding: 5px 10px; + color: #eeeeee; + border: solid 1px #333333; + background: #1e1e1e; + background: rgba(30, 30, 30, 0.94); + -moz-border-radius: 0 0 8px 8px; + -webkit-border-radius: 0 0 8px 8px; + border-radius: 0 0 8px 8px; +} +.openerp .oe_login .oe_login_dbpane input { + padding: 2px 4px; + margin: 4px 0; +} +.openerp .oe_login .oe_login_bottom { + position: absolute; + top: 50%; left: 0; right: 0; + bottom: 0; + text-shadow: 0 1px 1px #999999; + background-color: #b41616; + background-image: -webkit-gradient(linear, left top, left bottom, from(#b41616), to(#600606)); + background-image: -webkit-linear-gradient(top, #b41616, #600606); + background-image: -moz-linear-gradient(top, #b41616, #600606); + background-image: -ms-linear-gradient(top, #b41616, #600606); + background-image: -o-linear-gradient(top, #b41616, #600606); + background-image: linear-gradient(to bottom, #b41616, #600606); } -.openerp2 .oe_webclient .oe_application { +.openerp .oe_login .oe_login_pane { position: absolute; - top: 32px; - bottom: 0; - left: 206px; - right: 0; + top: 50%; + left: 50%; + margin: -160px -166px; + border: solid 1px #333333; + background: #1e1e1e; + background: rgba(30, 30, 30, 0.94); + padding: 22px 32px; + color: #eeeeee; + text-align: left; + -moz-border-radius: 8px; + -webkit-border-radius: 8px; + border-radius: 8px; + -moz-box-shadow: 0 0 18px rgba(0, 0, 0, 0.9); + -webkit-box-shadow: 0 0 18px rgba(0, 0, 0, 0.9); + -box-shadow: 0 0 18px rgba(0, 0, 0, 0.9); +} +.openerp .oe_login .oe_login_pane h2 { + margin-top: 0; + font-size: 18px; +} +.openerp .oe_login .oe_login_logo { + position: absolute; + top: -70px; + left: 0; + width: 100%; + margin: 0 auto; + text-align: center; +} +.openerp .oe_login .oe_login_footer { + position: absolute; + bottom: -40px; + left: 0; + width: 100%; + text-align: center; +} +.openerp .oe_login .oe_login_footer a { + color: #eeeeee; + margin: 0 8px; +} +.openerp .oe_login .oe_login_footer a:hover { + text-decoration: underline; +} +.openerp .oe_login .oe_login_footer span { + font-weight: bold; + font-size: 16px; +} +.openerp .oe_login .oe_login_error_message { + display: none; + background-color: #b41616; + color: #eeeeee; + padding: 14px 18px; + margin-top: 15px; + text-align: center; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + border-radius: 4px; + -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.8); + -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.8); + -box-shadow: 0 1px 4px rgba(0, 0, 0, 0.8); +} +.openerp .oe_login_invalid .oe_login_error_message { + display: inline-block; +} +.openerp .oe_database_manager { + background: white; + color: black; + text-align: left; +} +.openerp .oe_database_manager .oe_database_manager_menu { + color: black; } -.openerp2 .oe_content_full_screen .oe_application { +.openerp .oe_webclient { + width: 100%; + height: 100%; + border-spacing: 0px; +} +.openerp .oe_content_full_screen .oe_application { top: 0; left: 0; } -.openerp2 .oe_content_full_screen .topbar, .openerp2 .oe_content_full_screen .leftbar { +.openerp .oe_content_full_screen .oe_topbar, .openerp .oe_content_full_screen .oe_leftbar { display: none; } -.openerp2 .oe_topbar { +.openerp .oe_topbar { width: 100%; height: 31px; border-top: solid 1px #d3d3d3; @@ -44,17 +435,17 @@ background-image: -o-linear-gradient(top, #646060, #262626); background-image: linear-gradient(to bottom, #646060, #262626); } -.openerp2 .oe_topbar .oe_systray { +.openerp .oe_topbar .oe_systray { float: right; } -.openerp2 .oe_topbar .oe_systray > div { +.openerp .oe_topbar .oe_systray > div { float: left; padding: 0 4px 0 4px; } -.openerp2 .oe_topbar .oe_topbar_item li { +.openerp .oe_topbar .oe_topbar_item li { float: left; } -.openerp2 .oe_topbar .oe_topbar_item li a { +.openerp .oe_topbar .oe_topbar_item li a { display: block; padding: 5px 10px 7px; line-height: 20px; @@ -63,14 +454,14 @@ vertical-align: top; text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); } -.openerp2 .oe_topbar .oe_topbar_item li a:hover { +.openerp .oe_topbar .oe_topbar_item li a:hover { background: #303030; color: white; -moz-box-shadow: 0 1px 2px rgba(255, 255, 255, 0.3) inset; -webkit-box-shadow: 0 1px 2px rgba(255, 255, 255, 0.3) inset; -box-shadow: 0 1px 2px rgba(255, 255, 255, 0.3) inset; } -.openerp2 .oe_topbar .oe_topbar_item .oe_active { +.openerp .oe_topbar .oe_topbar_item .oe_active { background: #303030; font-weight: bold; color: white; @@ -78,7 +469,7 @@ -webkit-box-shadow: 0 1px 2px rgba(255, 255, 255, 0.3) inset; -box-shadow: 0 1px 2px rgba(255, 255, 255, 0.3) inset; } -.openerp2 .oe_topbar .oe_topbar_avatar { +.openerp .oe_topbar .oe_topbar_avatar { width: 24px; height: 24px; margin: -2px 2px 0 0; @@ -86,60 +477,134 @@ -webkit-border-radius: 4px; border-radius: 4px; } -.openerp2 .oe_topbar .oe_topbar_avatar { +.openerp .oe_topbar .oe_topbar_avatar { vertical-align: top; } -.openerp2 .oe_leftbar { - width: 205px; - height: 100%; +.openerp .oe_topbar .oe_dropdown_toggle:after { + width: 0; + height: 0; + display: inline-block; + content: "&darr"; + text-indent: -99999px; + vertical-align: top; + margin-top: 8px; + margin-left: 4px; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 4px solid white; + filter: alpha(opacity=50); + opacity: 0.5; +} +.openerp .oe_leftbar { + width: 220px; background: #f0eeee; border-right: 1px solid #afafb6; - overflow: auto; text-shadow: 0 1px 1px white; } -.openerp2 .oe_leftbar .oe_footer { - position: absolute; - width: 205px; - text-align: center; - bottom: 8px; -} -.openerp2 a.oe_logo { +.openerp a.oe_logo { + width: 220px; display: block; text-align: center; height: 70px; line-height: 70px; } -.openerp2 a.oe_logo img { +.openerp a.oe_logo img { height: 40px; width: 157px; margin: 14px 0; } -.openerp2 .oe_footer { - position: absolute; - width: 205px; +.openerp .oe_footer { + position: fixed; + bottom: 0; + padding: 4px 0; + background: #f0eeee; + width: 220px; text-align: center; - bottom: 8px; } -.openerp2 .oe_footer a { +.openerp .oe_footer a { font-weight: 800; font-family: serif; font-size: 16px; color: black; } -.openerp2 .oe_footer a span { +.openerp .oe_footer a span { color: #c81010; font-style: italic; } -.openerp2 .oe_menu { - float: left; +.openerp .oe_user_menu { + float: right; padding: 0; margin: 0; } -.openerp2 .oe_menu li { +.openerp .oe_user_menu li { list-style-type: none; float: left; } -.openerp2 .oe_menu a { +.openerp .oe_user_menu .oe_dropdown { + position: relative; +} +.openerp .oe_user_menu .oe_dropdown_options { + float: left; + background: #333333; + background: rgba(37, 37, 37, 0.9); + display: none; + position: absolute; + top: 32px; + right: -1px; + border: 0; + z-index: 900; + margin-left: 0; + margin-right: 0; + padding: 6px 0; + zoom: 1; + border-color: #999999; + border-color: rgba(0, 0, 0, 0.2); + border-style: solid; + border-width: 0 1px 1px; + -moz-border-radius: 0 0 6px 6px; + -webkit-border-radius: 0 0 6px 6px; + border-radius: 0 0 6px 6px; + -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); + -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); + -box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); + -webkit-background-clip: padding-box; + -moz-background-clip: padding-box; + background-clip: padding-box; +} +.openerp .oe_user_menu .oe_dropdown_options li { + float: none; + display: block; + background-color: none; +} +.openerp .oe_user_menu .oe_dropdown_options li a { + display: block; + padding: 4px 15px; + clear: both; + font-weight: normal; + line-height: 18px; + color: #eeeeee; +} +.openerp .oe_user_menu .oe_dropdown_options li a:hover { + background-color: #292929; + background-image: -webkit-gradient(linear, left top, left bottom, from(#292929), to(#191919)); + background-image: -webkit-linear-gradient(top, #292929, #191919); + background-image: -moz-linear-gradient(top, #292929, #191919); + background-image: -ms-linear-gradient(top, #292929, #191919); + background-image: -o-linear-gradient(top, #292929, #191919); + background-image: linear-gradient(to bottom, #292929, #191919); + -moz-box-shadow: none; + -webkit-box-shadow: none; + -box-shadow: none; +} +.openerp .oe_menu { + float: left; + padding: 0; + margin: 0; +} +.openerp .oe_menu li { + float: left; +} +.openerp .oe_menu a { display: block; padding: 5px 10px 7px; line-height: 20px; @@ -148,14 +613,14 @@ vertical-align: top; text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); } -.openerp2 .oe_menu a:hover { +.openerp .oe_menu a:hover { background: #303030; color: white; -moz-box-shadow: 0 1px 2px rgba(255, 255, 255, 0.3) inset; -webkit-box-shadow: 0 1px 2px rgba(255, 255, 255, 0.3) inset; -box-shadow: 0 1px 2px rgba(255, 255, 255, 0.3) inset; } -.openerp2 .oe_menu .oe_active { +.openerp .oe_menu .oe_active { background: #303030; font-weight: bold; color: white; @@ -163,10 +628,10 @@ -webkit-box-shadow: 0 1px 2px rgba(255, 255, 255, 0.3) inset; -box-shadow: 0 1px 2px rgba(255, 255, 255, 0.3) inset; } -.openerp2 .oe_menu_more_container { +.openerp .oe_menu_more_container { position: relative; } -.openerp2 .oe_menu_more_container .oe_menu_more { +.openerp .oe_menu_more_container .oe_menu_more { position: absolute; padding: 0; background-color: #646060; @@ -175,34 +640,33 @@ border-bottom-left-radius: 5px; border-bottom-right-radius: 5px; } -.openerp2 .oe_menu_more_container .oe_menu_more li { +.openerp .oe_menu_more_container .oe_menu_more li { float: none; } -.openerp2 .oe_menu_more_container .oe_menu_more li a { +.openerp .oe_menu_more_container .oe_menu_more li a { white-space: nowrap; } -.openerp2 .oe_secondary_menu_section { +.openerp .oe_secondary_menu_section { font-weight: bold; margin-left: 8px; color: #8a89ba; } -.openerp2 .oe_secondary_submenu { +.openerp .oe_secondary_submenu { padding: 2px 0 8px 0; margin: 0; width: 100%; display: inline-block; } -.openerp2 .oe_secondary_submenu li { +.openerp .oe_secondary_submenu li { position: relative; padding: 1px 0 1px 16px; - list-style-type: none; } -.openerp2 .oe_secondary_submenu li a { +.openerp .oe_secondary_submenu li a { display: block; color: #4c4c4c; padding: 2px 4px 2px 0; } -.openerp2 .oe_secondary_submenu li .oe_menu_label { +.openerp .oe_secondary_submenu li .oe_menu_label { position: absolute; top: 1px; right: 1px; @@ -220,7 +684,20 @@ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.2); -box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.2); } -.openerp2 .oe_secondary_submenu .oe_active { +.openerp .oe_secondary_submenu .oe_menu_counter { + float: right; + background: #8a89ba; + color: #eeeeee; + font-size: 12px; + border: 1px solid lightgray; + padding: 0px 4px 0px 4px; + text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); + margin: 0px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + border-radius: 4px; +} +.openerp .oe_secondary_submenu .oe_active { background: #8a89ba; border-top: 1px solid lightGray; border-bottom: 1px solid lightGray; @@ -229,10 +706,10 @@ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.2); -box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.2); } -.openerp2 .oe_secondary_submenu .oe_active a { +.openerp .oe_secondary_submenu .oe_active a { color: white; } -.openerp2 .oe_secondary_submenu .oe_active .oe_menu_label { +.openerp .oe_secondary_submenu .oe_active .oe_menu_label { background: #eeeeee; color: #8a89ba; text-shadow: 0 1px 1px white; @@ -240,7 +717,11 @@ -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); -box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); } -.openerp2 .oe_secondary_submenu .oe_menu_toggler:before { +.openerp .oe_secondary_submenu .oe_active .oe_menu_counter { + background: #eeeeee; + color: #8a89ba; +} +.openerp .oe_secondary_submenu .oe_menu_toggler:before { width: 0; height: 0; display: inline-block; @@ -256,7 +737,7 @@ filter: alpha(opacity=50); opacity: 0.5; } -.openerp2 .oe_secondary_submenu .oe_menu_opened:before { +.openerp .oe_secondary_submenu .oe_menu_opened:before { margin-top: 6px; margin-left: -12px; margin-right: 4px; @@ -264,83 +745,1075 @@ border-right: 4px solid transparent; border-top: 4px solid #4c4c4c; } -.openerp2 .oe_dropdown { +.openerp .oe_application { + width: 100%; +} +.openerp .oe_application a { + color: #8a89ba; +} +.openerp .oe_application a:hover { + text-decoration: underline; +} +.openerp .oe-view-manager-header { + border-top: 1px solid #cacaca; + border-bottom: 1px solid #cacaca; + background-color: #fcfcfc; + background-image: -webkit-gradient(linear, left top, left bottom, from(#fcfcfc), to(#dedede)); + background-image: -webkit-linear-gradient(top, #fcfcfc, #dedede); + background-image: -moz-linear-gradient(top, #fcfcfc, #dedede); + background-image: -ms-linear-gradient(top, #fcfcfc, #dedede); + background-image: -o-linear-gradient(top, #fcfcfc, #dedede); + background-image: linear-gradient(to bottom, #fcfcfc, #dedede); + -moz-box-shadow: 0 1px 0 rgba(255, 255, 255, 0.4), 0 0 9px rgba(0, 0, 0, 0.1); + -webkit-box-shadow: 0 1px 0 rgba(255, 255, 255, 0.4), 0 0 9px rgba(0, 0, 0, 0.1); + -box-shadow: 0 1px 0 rgba(255, 255, 255, 0.4), 0 0 9px rgba(0, 0, 0, 0.1); +} +.openerp .oe-view-manager-header h2 { + float: left; + font-size: 18px; + margin: 1px 0; +} +.openerp .oe-view-manager-header h2 a { + color: #8a89ba; +} +.openerp .oe-view-manager-header .buttons { + padding-right: 8px; +} +.openerp .oe-view-manager-header .buttons li { + float: left; + margin-right: 12px; +} +.openerp .oe-view-manager-header .filter { + vertical-align: top; +} +.openerp .oe-view-manager-header .filter li { + height: 24px; + line-height: 24px; + padding: 0 8px; +} +.openerp .oe-view-manager-header .header-row { + height: 26px; + line-height: 26px; + margin: 8px; + clear: both; + text-shadow: 0 1px 1px white; +} +.openerp .oe-view-manager-header .header-row .dropdown-toggle:after { + margin-top: 7px; + margin-left: 6px; + border-top-color: #404040; +} +.openerp .oe-view-manager-header .oe_vm_switch li { + padding-left: 1px; + text-align: center; + width: 24px; + height: 24px; + line-height: 24px; +} +.openerp .oe-view-manager-header .pagination li { + height: 24px; + line-height: 24px; + padding: 0 8px; +} +.openerp .oe-view-manager-header .button-group { + display: inline-block; + border: 1px solid #ababab; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + border-radius: 5px; +} +.openerp .oe-view-manager-header .button-group li { + float: left; + border-right: 1px solid #ababab; +} +.openerp .oe-view-manager-header .button-group li:last-child { + border: none; +} +.openerp .oe-view-manager-header .button-group a { + color: #4c4c4c; +} +.openerp .oe-view-manager-header .button-group a:hover { + text-decoration: none; +} +.openerp .oe-view-manager-header .button-group .active { + width: 100%; + background: #999999; + -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3) inset; + -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3) inset; + -box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3) inset; + color: white; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4); +} +.openerp .oe_view_manager_header2 { + width: 100%; + border-top: 1px solid #cacaca; + border-bottom: 1px solid #cacaca; + background-color: #fcfcfc; + background-image: -webkit-gradient(linear, left top, left bottom, from(#fcfcfc), to(#dedede)); + background-image: -webkit-linear-gradient(top, #fcfcfc, #dedede); + background-image: -moz-linear-gradient(top, #fcfcfc, #dedede); + background-image: -ms-linear-gradient(top, #fcfcfc, #dedede); + background-image: -o-linear-gradient(top, #fcfcfc, #dedede); + background-image: linear-gradient(to bottom, #fcfcfc, #dedede); + -moz-box-shadow: 0 1px 0 rgba(255, 255, 255, 0.4), 0 0 9px rgba(0, 0, 0, 0.1); + -webkit-box-shadow: 0 1px 0 rgba(255, 255, 255, 0.4), 0 0 9px rgba(0, 0, 0, 0.1); + -box-shadow: 0 1px 0 rgba(255, 255, 255, 0.4), 0 0 9px rgba(0, 0, 0, 0.1); +} +.openerp .oe_view_manager_header2 h2 { + float: left; + font-size: 18px; + margin: 1px 0; +} +.openerp .oe_view_manager_header2 h2 a { + color: #8a89ba; +} +.openerp .oe_view_manager_header2 td { + padding: 4px 8px; + line-height: 26px; +} +.openerp .oe_view_manager_header2 .oe_button_group { + display: inline-block; + border: 1px solid #ababab; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + border-radius: 5px; +} +.openerp .oe_view_manager_header2 .oe_button_group li { + float: left; + border-right: 1px solid #ababab; +} +.openerp .oe_view_manager_header2 .oe_button_group li:last-child { + border: none; +} +.openerp .oe_view_manager_header2 .oe_button_group a { + color: #4c4c4c; +} +.openerp .oe_view_manager_header2 .oe_button_group a:hover { + text-decoration: none; +} +.openerp .oe_view_manager_header2 .oe_button_group .active { + background: #999999; + -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3) inset; + -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3) inset; + -box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3) inset; +} +.openerp .oe_view_manager_header2 .oe_button_group .active a { + color: white; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4); +} +.openerp .oe_view_manager_header2 .oe_view_manager_buttons { + white-space: nowrap; +} +.openerp .oe_view_manager_body h4 { + margin: 8px 0; +} +.openerp .oe_pager_value { + float: left; + margin-right: 8px; +} +.openerp .oe_pager_group { + float: left; + height: 24px; + line-height: 24px; + display: inline-block; + border: 1px solid #ababab; + cursor: pointer; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + border-radius: 5px; +} +.openerp .oe_pager_group li { + height: 24px; + line-height: 24px; + padding: 0; + float: left; + border-right: 1px solid #ababab; +} +.openerp .oe_pager_group li:last-child { + border: none; +} +.openerp .oe_pager_group a { + color: #4c4c4c; + padding: 0 8px; +} +.openerp .oe_pager_group a:hover { + text-decoration: none; +} +.openerp .oe_pager_group .active { + background: #999999; + -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3) inset; + -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3) inset; + -box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3) inset; +} +.openerp .oe_pager_group .active a { + color: white; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4); +} +.openerp .oe_view_manager_switch li { + padding-left: 1px; + text-align: center; + width: 24px; + height: 24px; + line-height: 24px; +} +.openerp .oe_view_manager_switch .oe_vm_switch_list:after, .openerp .oe_view_manager_switch .oe_vm_switch_tree:after { + content: "ö"; +} +.openerp .oe_view_manager_switch .oe_vm_switch_graph:after { + content: "ó"; +} +.openerp .oe_view_manager_switch .oe_vm_switch_gantt:after { + content: "y"; +} +.openerp .oe_view_manager_switch .oe_vm_switch_calendar:after { + content: "b"; +} +.openerp .oe_view_manager_switch .oe_vm_switch_kanban:after { + content: "ó"; +} +.openerp .oe_view_manager_switch .oe_vm_switch_diagram:after { + content: "}"; +} +.openerp .oe_form_dropdown_section { position: relative; + display: inline-block; + margin-right: 4px; } -.openerp2 .oe_dropdown_toggle:after { +.openerp .oe_dropdown_toggle:after { width: 0; height: 0; display: inline-block; content: "&darr"; text-indent: -99999px; vertical-align: top; - margin-top: 8px; - margin-left: 4px; border-left: 4px solid transparent; border-right: 4px solid transparent; border-top: 4px solid white; filter: alpha(opacity=50); opacity: 0.5; + margin-top: 7px; + margin-left: 6px; + border-top-color: #404040; } -.openerp2 .oe_user_menu { - float: right; - padding: 0; - margin: 0; -} -.openerp2 .oe_user_menu li { - list-style-type: none; - float: left; -} -.openerp2 .oe_user_menu .oe_dropdown_options { - float: left; - background: #333333; - background: rgba(37, 37, 37, 0.9); +.openerp .oe_dropdown_menu { display: none; position: absolute; - top: 32px; - right: -1px; - border: 0; + top: 28px; + left: 0px; + padding: 8px; + border: 1px solid #afafb6; + width: 120px; + overflow-x: hidden; z-index: 900; - margin-left: 0; - margin-right: 0; - padding: 6px 0; - zoom: 1; - border-color: #999999; - border-color: rgba(0, 0, 0, 0.2); - border-style: solid; - border-width: 0 1px 1px; - -moz-border-radius: 0 0 6px 6px; - -webkit-border-radius: 0 0 6px 6px; - border-radius: 0 0 6px 6px; + text-align: left; + background: white; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); -box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); - -webkit-background-clip: padding-box; - -moz-background-clip: padding-box; - background-clip: padding-box; } -.openerp2 .oe_user_menu .oe_dropdown_options li { +.openerp .oe_dropdown_menu li { + list-style-type: none; float: none; display: block; background-color: none; } -.openerp2 .oe_user_menu .oe_dropdown_options li a { +.openerp .oe_dropdown_menu li a { display: block; - padding: 4px 15px; + padding: 3px 6px; clear: both; font-weight: normal; + line-height: 14px; + color: #4c4c4c; + text-decoration: none; +} +.openerp .oe_dropdown_menu li a:hover { + text-decoration: none; + background-color: #f0f0fa; + background-image: -webkit-gradient(linear, left top, left bottom, from(#f0f0fa), to(#eeeef6)); + background-image: -webkit-linear-gradient(top, #f0f0fa, #eeeef6); + background-image: -moz-linear-gradient(top, #f0f0fa, #eeeef6); + background-image: -ms-linear-gradient(top, #f0f0fa, #eeeef6); + background-image: -o-linear-gradient(top, #f0f0fa, #eeeef6); + background-image: linear-gradient(to bottom, #f0f0fa, #eeeef6); + -moz-box-shadow: none; + -webkit-box-shadow: none; + -box-shadow: none; +} +.openerp .oe_sidebar { + white-space: nowrap; +} +.openerp .oe_searchview { + position: relative; + float: right; +} +.openerp .oe_searchview .VS-search .VS-search-box { + min-height: 0; + border-width: 1px; + padding: 0; + width: 410px; +} +.openerp .oe_searchview .VS-search .VS-icon { + top: 5px; +} +.openerp .oe_searchview .VS-search .VS-icon-search { + left: 5px; +} +.openerp .oe_searchview .VS-search .VS-search-inner { + margin: 0 40px 0 17px; + font-size: 13px; +} +.openerp .oe_searchview .VS-search .VS-search-inner input { + font-size: inherit; + line-height: inherit; + height: auto; + padding: 0; +} +.openerp .oe_searchview .VS-search .VS-search-inner .VS-input-width-tester { + font-size: inherit; + padding: 0; +} +.openerp .oe_searchview .VS-search .VS-search-inner .search_input { + margin-left: 0; + height: 22px; +} +.openerp .oe_searchview .VS-search .VS-search-inner .search_input input, .openerp .oe_searchview .VS-search .VS-search-inner .search_input .VS-input-width-tester { + padding: 0 2px; + height: inherit; + margin: 0; +} +.openerp .oe_searchview .VS-search .VS-search-inner .search_facet { + padding: 0; + margin: 1px 0; + border: 1px solid #afafb6; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; + background: #f0f0fa; + height: 18px; +} +.openerp .oe_searchview .VS-search .VS-search-inner .search_facet.is_selected { + border-color: #a6a6fe; + -moz-box-shadow: 0 0 3px 1px #a6a6fe; + -webkit-box-shadow: 0 0 3px 1px #a6a6fe; + -box-shadow: 0 0 3px 1px #a6a6fe; +} +.openerp .oe_searchview .VS-search .VS-search-inner .search_facet.is_selected .category { + margin-left: 0; +} +.openerp .oe_searchview .VS-search .VS-search-inner .search_facet.is_selected .VS-icon-cancel { + filter: alpha(opacity=100); + opacity: 1; + background-position: center 0; +} +.openerp .oe_searchview .VS-search .VS-search-inner .search_facet .category { + padding: 0 4px 0 14px; + color: white; + background: #8786b7; + text-shadow: 0 1px 1px rgba(0, 0, 0, 0.4); + font-weight: normal; + text-transform: none; + height: 18px; line-height: 18px; - color: #eeeeee; + font-size: inherit; } -.openerp2 .oe_user_menu .oe_dropdown_options li a:hover { - background-color: #292929; - background-image: -webkit-gradient(linear, left top, left bottom, from(#292929), to(#191919)); - background-image: -webkit-linear-gradient(top, #292929, #191919); - background-image: -moz-linear-gradient(top, #292929, #191919); - background-image: -ms-linear-gradient(top, #292929, #191919); - background-image: -o-linear-gradient(top, #292929, #191919); - background-image: linear-gradient(to bottom, #292929, #191919); +.openerp .oe_searchview .VS-search .VS-search-inner .search_facet .search_facet_input_container { + border-left: 1px solid #afafb6; + height: 18px; + line-height: 18px; + padding: 0 4px; + cursor: pointer; +} +.openerp .oe_searchview .VS-search .VS-search-inner .search_facet .search_facet_remove { + left: 1px; + top: 3px; +} +.openerp .oe_searchview .VS-search .VS-icon-cancel { + right: 24px; +} +.openerp .oe_searchview .VS-search .oe_vs_unfold_drawer { + position: absolute; + top: 0; + right: 0; + height: 100%; + line-height: 23px; + padding: 0 7px 0 4px; + color: #cccccc; + cursor: pointer; +} +.openerp .oe_searchview .VS-search .oe_vs_unfold_drawer:before { + content: "▾"; +} +.openerp .oe_searchview.oe_searchview_open_drawer .oe_searchview_drawer { + display: block; +} +.openerp .oe_searchview .oe_searchview_drawer { + position: absolute; + z-index: 1; + top: 100%; + right: 0; + background-color: white; + width: 410px; + display: none; + border: 1px solid #cccccc; + text-align: left; +} +.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_filters { + display: table; + width: 100%; +} +.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_filters > div { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + display: table-cell; + width: 50%; +} +.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_filters h3 { + margin: 2px 4px 2px 8px; + color: #8786b7; +} +.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_filters ul { + margin: 0 12px 3px; + padding: 0; + list-style: none; +} +.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_filters li { + list-style: none; + padding: 3px 6px; + height: 14px; + color: inherit; + cursor: pointer; +} +.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_filters li:hover { + background-color: #f0f0fa; +} +.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_advanced h4 { + border-top: 1px solid #cccccc; + margin: 5px 0 3px; + cursor: pointer; +} +.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_advanced h4:before { + content: "▸ "; +} +.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_advanced div { + display: none; +} +.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_advanced button.oe_add_condition:before { + content: "⊞ "; +} +.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_advanced button.oe_apply:before { + content: "⚡ "; +} +.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_advanced ul { + list-style: none; + padding: 0; +} +.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_advanced li { + list-style: none; + margin: 0; +} +.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_advanced.oe_opened h4:before { + content: "▾ "; +} +.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_advanced.oe_opened div { + display: block; +} +.openerp .oe_view_topbar { + border-bottom: 1px solid #cacaca; + background-color: #fcfcfc; + background-image: -webkit-gradient(linear, left top, left bottom, from(#fcfcfc), to(#dedede)); + background-image: -webkit-linear-gradient(top, #fcfcfc, #dedede); + background-image: -moz-linear-gradient(top, #fcfcfc, #dedede); + background-image: -ms-linear-gradient(top, #fcfcfc, #dedede); + background-image: -o-linear-gradient(top, #fcfcfc, #dedede); + background-image: linear-gradient(to bottom, #fcfcfc, #dedede); + padding: 0 8px; + line-height: 30px; +} +.openerp .oe_formview { + background: white; +} +.openerp .oe_form_header { + padding: 8px; +} +.openerp .oe_form_invalid input, .openerp .oe_form_invalid select, .openerp .oe_form_invalid textarea { + background-color: #ff6666 !important; + border: 1px solid #dd0000 !important; +} +.openerp .oe_form_button_save_dirty { + color: white; + background: #dc5f59; + background: -moz-linear-gradient(#dc5f59, #b33630); + background: -webkit-gradient(linear, left top, left bottom, from(#dc5f59), to(#b33630)); + background: -webkit-linear-gradient(#dc5f59, #b33630); -moz-box-shadow: none; -webkit-box-shadow: none; -box-shadow: none; + font-weight: bold; +} +.openerp .oe_form_button_save_dirty:hover { + background: #ed6f6a; +} +.openerp .oe_form_topbar { + border-bottom: 1px solid #cacaca; + background-color: #fcfcfc; + background-image: -webkit-gradient(linear, left top, left bottom, from(#fcfcfc), to(#dedede)); + background-image: -webkit-linear-gradient(top, #fcfcfc, #dedede); + background-image: -moz-linear-gradient(top, #fcfcfc, #dedede); + background-image: -ms-linear-gradient(top, #fcfcfc, #dedede); + background-image: -o-linear-gradient(top, #fcfcfc, #dedede); + background-image: linear-gradient(to bottom, #fcfcfc, #dedede); + padding: 0 8px; + line-height: 30px; +} +.openerp .oe_form_topbar button { + font-size: 12px; + height: 24px; + line-height: 24px; + vertical-align: top; + padding: 0 10px; + margin: 3px 0; +} +.openerp .oe_form_topbar button span.i { + line-height: 20px; + height: 24px; +} +.openerp .oe_form_topbar ul { + height: 30px; + padding: 0; + margin: 0; + text-shadow: 0 1px 1px white; + border-left: 1px solid #cacaca; + border-right: 1px solid #cacaca; +} +.openerp .oe_form_topbar ul li { + padding: 0; + margin: 0; + float: left; + vertical-align: top; + border-right: 1px solid #cacaca; + height: 30px; + padding: 0 12px; +} +.openerp .oe_form_topbar ul li:first-child { + border-left: 1px solid #cacaca; +} +.openerp .oe_form_topbar ul li a { + color: #4c4c4c; +} +.openerp .oe_form_topbar ul li a:hover { + color: black; +} +.openerp .oe_form_topbar .oe_form_steps img { + margin: 0 8px; + vertical-align: top; +} +.openerp .oe_form_topbar .oe_form_steps li { + border-right: none; + padding: 0; +} +.openerp .oe_form_topbar .oe_form_steps li:first-child { + margin-left: 12px; + border-left: none; +} +.openerp .oe_form_topbar .oe_form_steps li:last-child { + margin-right: 12px; +} +.openerp .oe_form_topbar .oe_form_steps_active { + font-weight: bold; + color: #b33630; +} +.openerp .oe_application .oe_form_sheetbg { + background: url(/web/static/src/img/form_sheetbg.png); + padding: 8px; + border-bottom: 1px solid #dddddd; +} +.openerp .oe_application .oe_form_sheet_width { + min-width: 650px; + max-width: 980px; + margin: 0 auto; +} +.openerp .oe_application .oe_form_sheet { + padding: 8px 8px 8px 8px; + background: white; + min-height: 420px; + border: 1px solid #afafb6; + -moz-box-shadow: 0 0 10px rgba(0, 0, 0, 0.3); + -webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.3); + -box-shadow: 0 0 10px rgba(0, 0, 0, 0.3); +} +.openerp .oe_form_autolayout .oe_button { + height: 22px; +} +.openerp .oe_form_autolayout .oe_form_group_cell { + line-height: 18px; + padding: 2px; + height: 25px; +} +.openerp .oe_form_autolayout .oe_form_group_cell .oe_form_button { + width: 100%; + white-space: nowrap; +} +.openerp .oe_form_autolayout .oe_form_group_cell > .oe_form_field_many2one, +.openerp .oe_form_autolayout .oe_form_group_cell > .oe_form_field_many2one input, +.openerp .oe_form_autolayout .oe_form_group_cell > .oe_form_field_char input, +.openerp .oe_form_autolayout .oe_form_group_cell > .oe_form_field_text textarea, +.openerp .oe_form_autolayout .oe_form_group_cell > .oe_form_field_email, +.openerp .oe_form_autolayout .oe_form_group_cell > .oe_form_field_email input, +.openerp .oe_form_autolayout .oe_form_group_cell > .oe_form_field_url, +.openerp .oe_form_autolayout .oe_form_group_cell > .oe_form_field_url input, +.openerp .oe_form_autolayout .oe_form_group_cell > .oe_form_field_date input, +.openerp .oe_form_autolayout .oe_form_group_cell > .oe_form_field_float input, +.openerp .oe_form_autolayout .oe_form_group_cell > .oe_form_field_selection select, +.openerp .oe_form_autolayout .oe_form_group_cell > .oe_datepicker_root { + width: 100%; +} +.openerp .oe_form_autolayout .oe_form_button.oe_button { + color: #4c4c4c; + white-space: nowrap; + min-width: 100%; + width: 100%; + height: 22px; +} +.openerp .oe_form_autolayout .oe_form_button.oe_button span { + position: relative; + vertical-align: top; +} +.openerp .oe_form_autolayout .oe_form_group_cell.oe_form_group_nested { + padding: 0; +} +.openerp .oe_form label { + font-weight: bold; +} +.openerp .oe_form_label_help[for], .openerp .oe_form_label[for] { + white-space: nowrap; +} +.openerp .oe_form textarea { + resize: vertical; +} +.openerp .oe_form input[type="text"], +.openerp .oe_form input[type="password"], +.openerp .oe_form input[type="file"], +.openerp .oe_form select { + height: 22px; + padding-top: 2px; +} +.openerp .oe_form input[type="text"], +.openerp .oe_form input[type="password"], +.openerp .oe_form input[type="file"], +.openerp .oe_form select, +.openerp .oe_form textarea { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + -ms-box-sizing: border-box; + box-sizing: border-box; + padding: 0 2px; + margin: 0 2px; + border: 1px solid #999999; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; + background: white; + min-width: 90px; + color: #1f1f1f; +} +.openerp .oe_form input[readonly], +.openerp .oe_form select[readonly], +.openerp .oe_form textarea[readonly], +.openerp .oe_form input[disabled], +.openerp .oe_form select[disabled], +.openerp .oe_form textarea[disabled] { + background: #e5e5e5 !important; + color: #666666; +} +.openerp .oe_form .oe_form_field_many2one input, +.openerp .oe_form .oe_form_field_binary input, +.openerp .oe_form .oe_form_field_binary input, +.openerp .oe_form .oe_form_field_email input, +.openerp .oe_form .oe_form_field_url input { + border-right: none; + -webkit-border-top-right-radius: 0px; + -webkit-border-bottom-right-radius: 0px; + -moz-border-radius-topright: 0px; + -moz-border-radius-bottomright: 0px; + border-top-right-radius: 0px; + border-bottom-right-radius: 0px; +} +.openerp .oe_form .oe_form_field_email button img, +.openerp .oe_form .oe_form_field_url button img { + vertical-align: top; +} +.openerp .oe_form .oe_form_field_translatable, +.openerp .oe_form .oe_form_field_many2one, +.openerp .oe_form .oe_form_field_date, +.openerp .oe_form .oe_form_field_datetime { + white-space: nowrap; +} +.openerp .oe_form .oe_form_field_boolean { + padding-top: 4px; +} +.openerp .oe_form .oe_form_field_datetime input { + min-width: 11em; +} +.openerp .oe_form .oe_datepicker_container { + display: none; +} +.openerp .oe_form .oe_datepicker_root { + display: inline-block; +} +.openerp .oe_form .oe_form_required input, .openerp .oe_form .oe_form_required select, .openerp .oe_form .oe_form_required textarea { + background-color: #d2d2ff !important; +} +.openerp .oe_form .oe_form_invalid input, .openerp .oe_form .oe_form_invalid select, .openerp .oe_form .oe_form_invalid textarea { + background-color: #ff6666 !important; + border: 1px solid #dd0000 !important; +} +.openerp .oe_form .oe_button.oe_field_button { + -webkit-border-top-left-radius: 0px; + -webkit-border-bottom-left-radius: 0px; + -moz-border-radius-topleft: 0px; + -moz-border-radius-bottomleft: 0px; + border-top-left-radius: 0px; + border-bottom-left-radius: 0px; + margin-right: -1px; + height: 22px; +} +.openerp .oe_form .oe_input_icon { + cursor: pointer; + margin: 3px 0 0 -21px; + vertical-align: top; +} +.openerp .oe_form .oe_input_icon_disabled { + position: absolute; + cursor: default; + opacity: 0.5; + filter: alpha(opacity=50); + right: 5px; + top: 3px; +} +.openerp .oe_form .oe-binary-file-set { + overflow: hidden; + position: relative; + width: 45px; + height: 30px; +} +.openerp .oe_form input.oe-binary-file { + z-index: 0; + line-height: 0; + font-size: 12px; + position: absolute; + top: 1px; + right: 10px; + opacity: 0; + filter: alpha(opacity=0); + -ms-filter: "alpha(opacity=0)"; + margin: 0; + padding: 0; +} +.openerp .oe_horizontal_separator { + font-weight: bold; + border: 0 solid #666666; + border-bottom-width: 1px; + margin: 3px 4px 3px 1px; + height: 17px; +} +.openerp .oe_horizontal_separator:empty { + height: 5px; +} +.openerp .oe_vertical_separator { + border-left: 1px solid #666666; + padding: 0 4px 0 4px; +} +.openerp .oe_form_notebook { + margin: 8px 0; + padding: 0 8px; + list-style: none; + zoom: 1; +} +.openerp .oe_form_notebook:before, .openerp .oe_form_notebook:after { + display: table; + content: ""; + zoom: 1; +} +.openerp .oe_form_notebook:after { + clear: both; +} +.openerp .oe_form_notebook > li { + float: left; +} +.openerp .oe_form_notebook > li > a { + display: block; + color: #4c4c4c; +} +.openerp .oe_form_notebook { + border-color: #dddddd; + border-style: solid; + border-width: 0 0 1px; +} +.openerp .oe_form_notebook > li { + position: relative; + margin-bottom: -1px; +} +.openerp .oe_form_notebook > li > a { + padding: 0 12px; + margin-right: 2px; + line-height: 30px; + border: 1px solid transparent; + -moz-border-radius: 4px 4px 0 0; + -webkit-border-radius: 4px 4px 0 0; + border-radius: 4px 4px 0 0; +} +.openerp .oe_form_notebook > li > a:hover { + text-decoration: none; + background-color: #eeeeee; + border-color: #eeeeee #eeeeee #dddddd; +} +.openerp .oe_form_notebook > li.ui-state-active > a, .openerp .oe_form_notebook > li.ui-state-active > a:hover { + background-color: white; + border: 1px solid #dddddd; + border-bottom-color: transparent; + cursor: default; +} +.openerp .ui-tabs-hide { + display: none; +} +.openerp .oe_form .oe_form_field_progressbar.ui-progressbar { + height: 22px; + font-size: 10px; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + -ms-box-sizing: border-box; + box-sizing: border-box; + border: 1px solid #999999; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; + background: white; + min-width: 90px; +} +.openerp .oe_form .oe_form_field_progressbar.ui-progressbar span { + position: absolute; + margin-left: 10px; + font-weight: bold; +} +.openerp .oe_form .oe_form_field_progressbar.ui-progressbar .ui-widget-header { + background: #cccccc url(/web/static/lib/jquery.ui/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png) 50% 50% repeat-x; +} +.openerp .oe_form .oe_view_manager_header2 td { + padding: 0px 8px; + line-height: 16px; +} +.openerp .oe_form .oe_view_manager_header2 td .oe_i { + font-size: 13px; +} +.openerp .oe_form .oe_view_manager_header2 td .oe_pager_group { + height: auto; + line-height: 16px; +} +.openerp .oe_form .oe_view_manager_header2 td .oe_pager_group li { + height: auto; + line-height: 16px; +} +.openerp .oe_listview_nocontent > img { + float: left; + margin-right: 1.5em; +} +.openerp .oe_listview_nocontent > div { + overflow: hidden; + padding: 6px; + font-size: 125%; +} +.openerp .oe-listview-content { + width: 100%; +} +.openerp .oe-listview-content thead, .openerp .oe-listview-content tfoot { + border-bottom: 1px solid #cacaca; + background: #eeeeee; +} +.openerp .oe-listview-content tfoot { + border-bottom: 1px solid #cacaca; + font-weight: bold; +} +.openerp .oe-listview-content tbody { + cursor: pointer; + background: white; +} +.openerp .oe-listview-content thead { + vertical-align: top; +} +.openerp .oe-listview-content td.oe_list_checkbox:first-child, .openerp .oe-listview-content th.oe_list_checkbox:first-child { + width: 17px; +} +.openerp .oe-listview-content td.oe_list_checkbox:first-child:after, .openerp .oe-listview-content th.oe_list_checkbox:first-child:after { + border-width: 0; +} +.openerp .oe-listview-content tbody tr:nth-child(odd) td, .openerp .oe-listview-content tbody tr:nth-child(odd) th { + background-color: #f0f0fa; + background-color: #f0f0fa; + background-image: -webkit-gradient(linear, left top, left bottom, from(#f0f0fa), to(#eeeef6)); + background-image: -webkit-linear-gradient(top, #f0f0fa, #eeeef6); + background-image: -moz-linear-gradient(top, #f0f0fa, #eeeef6); + background-image: -ms-linear-gradient(top, #f0f0fa, #eeeef6); + background-image: -o-linear-gradient(top, #f0f0fa, #eeeef6); + background-image: linear-gradient(to bottom, #f0f0fa, #eeeef6); +} +.openerp .oe-listview-content tbody tr:hover td, .openerp .oe-listview-content tbody tr:hover th { + background-color: #eeeeee; + background-image: -webkit-gradient(linear, left top, left bottom, from(#eeeeee), to(#dedede)); + background-image: -webkit-linear-gradient(top, #eeeeee, #dedede); + background-image: -moz-linear-gradient(top, #eeeeee, #dedede); + background-image: -ms-linear-gradient(top, #eeeeee, #dedede); + background-image: -o-linear-gradient(top, #eeeeee, #dedede); + background-image: linear-gradient(to bottom, #eeeeee, #dedede); +} +.openerp .oe-listview-content td, .openerp .oe-listview-content th { + padding: 3px 6px; + line-height: 18px; +} +.openerp .oe-listview-content td button, .openerp .oe-listview-content th button { + border: none; + background: transparent; + padding: 0; + -moz-box-shadow: none; + -webkit-box-shadow: none; + -box-shadow: none; +} +.openerp .oe-listview-content .numeric { + text-align: right; + width: 82px; +} +.openerp .oe-listview-content .numeric input { + text-align: right; +} +.openerp .oe-listview-content table .sortdown:after, .openerp .oe-listview-content table .sortdown:hover:after { + visibility: visible; + filter: alpha(opacity=60); + opacity: 0.6; +} +.openerp .oe-listview-content table .sortup:after { + border-bottom: none; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 4px solid black; + visibility: visible; + -moz-box-shadow: none; + -webkit-box-shadow: none; + -box-shadow: none; + filter: alpha(opacity=60); + opacity: 0.6; +} +.openerp .oe_kanban_group_title { + margin: 1px 1px 4px; + font-size: 15px; + font-weight: bold; + text-shadow: 0 1px 0 white; +} +.openerp .oe_kanban_column, .openerp .oe_kanban_group_header { + width: 240px; + vertical-align: top; + padding: 6px 7px 6px 6px; + background: #f0eeee; + border-left: 1px solid #f0f8f8; + border-right: 1px solid #b9b9b9; +} +.openerp .oe_kanban_record { + position: relative; + display: block; + min-height: 50px; + margin: 6px 0; + display: block; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + border-radius: 4px; +} +.openerp .oe_kanban_record:last-child { + margin-bottom: 0; +} +.openerp .oe_kanban_record .oe_kanban_title { + font-weight: bold; + margin: 2px 4px; +} +.openerp .oe_kanban_gravatar { + width: 20px; + height: 20px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; + -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); + -box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); +} +.openerp .oe_kanban_avatar_toto { + height: 40px; + width: 40px; + border: 1px solid; + border-color: #e5e5e5 #dbdbdb #d2d2d2; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; + -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); + -box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); +} +.openerp .oe_trad_field.touched { + border: 1px solid green !important; +} +.openerp.oe_tooltip { + font-size: 12px; +} +.openerp.oe_tooltip .oe_tooltip_string { + color: #ffdd55; + font-weight: bold; + font-size: 13px; +} +.openerp.oe_tooltip .oe_tooltip_help { + white-space: pre-wrap; +} +.openerp.oe_tooltip .oe_tooltip_technical { + padding: 0 0 4px 0; + margin: 5px 0 0 15px; +} +.openerp.oe_tooltip .oe_tooltip_technical li { + list-style: circle; +} +.openerp.oe_tooltip .oe_tooltip_technical_title { + font-weight: bold; +} +.openerp .oe_layout_debugging .oe_form_group { + outline: 2px dashed red; +} +.openerp .oe_layout_debugging .oe_form_group_cell { + outline: 1px solid blue; +} +.openerp .oe_debug_view { + float: left; +} +.openerp .oe_debug_view_log { + font-size: 95%; +} +.openerp .oe_debug_view_log label { + display: block; + width: 49%; + text-align: right; + float: left; + font-weight: bold; + color: #000099; +} +.openerp .oe_debug_view_log span { + display: block; + width: 49%; + float: right; + color: #333333; +} + +.openerp .oe_form_field_many2one td:first-child { + position: relative; +} +.openerp .oe_form_field_many2one span.oe-m2o-drop-down-button { + position: absolute; + top: 3px; + right: 2px; } diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index c31f501c499..98caeafbb29 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -1,9 +1,21 @@ +@charset "utf-8" + // Variables {{{ +$section-title-color: #8786b7 +$facets-border: #afafb6 +$facets-border-selected: #a6a6fe $colour4: #8a89ba -//$colour4: #d14836 // }}} - // Mixins {{{ +@font-face + font-family: 'mnmliconsRegular' + src: url('/web/static/src/font/mnmliconsv21-webfont.eot') format('eot') + src: url('/web/static/src/font/mnmliconsv21-webfont.woff') format('woff') + src: url('/web/static/src/font/mnmliconsv21-webfont.ttf') format('truetype') + src: url('/web/static/src/font/mnmliconsv21-webfont.svg') format('svg') active + font-weight: normal + font-style: normal + @mixin vertical-gradient($startColor: #555, $endColor: #333) background-color: $startColor background-image: -webkit-gradient(linear, left top, left bottom, from($startColor), to($endColor)) /* Saf4+, Chrome */ @@ -46,48 +58,295 @@ $colour4: #8a89ba -webkit-background-clip: $clip -moz-background-clip: $clip background-clip: $clip + +@mixin unscrew-box + -moz-box-sizing: border-box + -webkit-box-sizing: border-box + box-sizing: border-box + // }}} -.openerp2 - // Common styles {{{ +.openerp.openerp-web-client-container + height: 100% + position: relative + +.openerp + // Global style {{{ padding: 0 margin: 0 font-family: "Lucida Grande", Helvetica, Verdana, Arial, sans-serif color: #4c4c4c font-size: 13px background: white - position: relative - + // }}} + // Tag reset {{{ a text-decoration: none + table + padding: 0 + font-size: 13px + border-collapse: collapse + thead + font-weight: bold + background-color: #f0f0f0 + th + border-right: 1px dotted #afafb6 + &:last-child + border-right: none + th, td + padding: 0 + line-height: 18px + text-align: left + th + font-weight: bold + vertical-align: middle + td + vertical-align: top + .zebra tbody tr:nth-child(odd) td + background-color: #f0f0fa + @include vertical-gradient(#f0f0fa, #eeeef6) + .zebra tbody tr:hover td + @include vertical-gradient(#eee, #dedede) + .numeric + text-align: right + width: 82px + input + text-align: right + ul, li, ol + margin: 0 + padding: 0 + li + list-style-type: none // }}} + // Generic classes {{{ + .oe_i + font-family: "mnmliconsRegular" !important + font-size: 21px + font-weight: 300 !important + .oe_left + float: left + margin-right: 8px + .oe_right + float: right + margin-left: 8px + .oe_clear + clear: both + .oe_wait + cursor: wait + // }}} + // Button style {{{ + a.button:link, a.button:visited, button, input[type='submit'] + display: inline-block + border: 1px solid #ababab + color: #404040 + margin: 0 + padding: 4px 12px + font-size: 13px + text-align: center + @include vertical-gradient(#efefef, #d8d8d8) + @include radius(3px) + @include box-shadow((0 1px 2px rgba(0, 0, 0, .1), 0 1px 1px rgba(255, 255, 255, .8) inset)) + text-shadow: 0 1px 1px rgba(255, 255, 255, .5) + -webkit-font-smoothing: antialiased + outline: none - // WebClient {{{ - .oe_webclient - position: absolute - top: 0 - bottom: 0 - left: 0 - right: 0 + a.button:hover, button:hover, input[type='submit']:hover + @include vertical-gradient(#f6f6f6, #e3e3e3) + cursor: pointer - .oe_application + a.button:focus, button:focus, input[type='submit']:focus + border: 1px solid #80bfff + @include vertical-gradient(#f6f6f6, #e3e3e3) + @include box-shadow((0 0 3px #80bfff, 0 1px 1px rgba(255, 255, 255, .8) inset)) + + a.button:active, a.button.active, button:active, button.active, input[type='submit']:active, input[type='submit'].active + background: #e3e3e3 + background: -moz-linear-gradient(top, #e3e3e3, #f6f6f6) #1b468f + background: -webkit-gradient(linear, left top, left bottom, from(#e3e3e3), to(#f6f6f6)) #1b468f + background: linear-gradient(top, #e3e3e3, #f6f6f6) #1b468f + @include box-shadow(none) + + a.button.disabled, button:disabled, input[type='submit']:disabled + background: #efefef !important + border: 1px solid #d1d1d1 !important + @include box-shadow(none !important) + color: #aaa !important + cursor: default + text-shadow: 0 1px 1px #fff !important + + button.primary + color: white + @include vertical-gradient(#dc5f59, #b33630) + @include box-shadow(none) + + button.primary:hover + @include vertical-gradient(lighten(#dc5f59, 3%), lighten(#b33630, 3%)) + @include box-shadow(0 0 1px rgba(0,0,0,0.2)) + + button.primary:active + @include vertical-gradient(#b33630, #dc5f59) + @include box-shadow(none) + + button.following + color: white + width: 120px + @include vertical-gradient($colour4, darken($colour4, 3%)) + + button.unfollow + display: none + color: white + width: 120px + @include vertical-gradient(#dc5f59, #b33630) + + // }}} + // Loading {{{ + .oe_loading + display: none + z-index: 100 + position: fixed + top: 0 + right: 50% + padding: 4px 12px + background: #A61300 + color: white + text-align: center + border: 1px solid #900 + border-top: none + -moz-border-radius-bottomright: 8px + -moz-border-radius-bottomleft: 8px + border-bottom-right-radius: 8px + border-bottom-left-radius: 8px + // }}} + // Notification {{{ + .oe_notification + z-index: 1050 + // }}} + // Login {{{ + .oe_login + background: url("/web/static/src/img/pattern.png") repeat + text-align: center + font-size: 14px + height: 100% + li + list-style-type: none + padding-bottom: 4px + button + float: right + display: inline-block + cursor: pointer + padding: 6px 16px + border: 1px solid #222 + color: white + margin: 0 + @include vertical-gradient(#b92020, #600606) + @include radius(4px) + @include box-shadow((0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(155, 155, 155, 0.4) inset)) + input, select + width: 252px + font-family: "Lucida Grande", Helvetica, Verdana, Arial + border: 1px solid #999999 + background: whitesmoke + @include box-shadow(inset 0 1px 4px rgba(0, 0, 0, 0.3)) + @include radius(3px) + input + margin-bottom: 9px + padding: 5px 6px + select + padding: 1px + .oe_login_dbpane + position: fixed + top: 0 + right: 8px + padding: 5px 10px + color: #eee + border: solid 1px #333 + background: #1e1e1e + background: rgba(30,30,30,0.94) + @include radius(0 0 8px 8px) + input + padding: 2px 4px + margin: 4px 0 + .oe_login_bottom position: absolute - top: 32px - bottom: 0 - left: 206px + top: 50% + left: 0 right: 0 + bottom: 0 + text-shadow: 0 1px 1px #999999 + @include vertical-gradient(#b41616, #600606) + .oe_login_pane + position: absolute + top: 50% + left: 50% + margin: -160px -166px + border: solid 1px #333333 + background: #1e1e1e + background: rgba(30,30,30,0.94) + padding: 22px 32px + color: #eee + text-align: left + @include radius(8px) + @include box-shadow(0 0 18px rgba(0, 0, 0, 0.9)) + h2 + margin-top: 0 + font-size: 18px + .oe_login_logo + position: absolute + top: -70px + left: 0 + width: 100% + margin: 0 auto + text-align: center + .oe_login_footer + position: absolute + bottom: -40px + left: 0 + width: 100% + text-align: center + a + color: #eee + margin: 0 8px + &:hover + text-decoration: underline + span + font-weight: bold + font-size: 16px + .oe_login_error_message + display: none + background-color: #b41616 + color: #eee + padding: 14px 18px + margin-top: 15px + text-align: center + @include radius(4px) + @include box-shadow(0 1px 4px rgba(0, 0, 0, 0.8)) + .oe_login_invalid + .oe_login_error_message + display: inline-block // }}} - - // Fullscreen mode {{{ + // DatabaseManager {{{ + .oe_database_manager + background: #fff + color: #000 + text-align: left + .oe_database_manager_menu + color: #000 + // }}} + // WebClient {{{ + .oe_webclient + width: 100% + height: 100% + border-spacing: 0px + // }}} + // WebClient.fullscreen {{{ .oe_content_full_screen .oe_application top: 0 left: 0 - .topbar, .leftbar + .oe_topbar, .oe_leftbar display: none - // }} - - // Topbar {{{ + // }}} + // WebClient.topbar {{{ .oe_topbar width: 100% height: 31px @@ -129,23 +388,28 @@ $colour4: #8a89ba @include radius(4px) .oe_topbar_avatar vertical-align: top + .oe_dropdown_toggle:after + width: 0 + height: 0 + display: inline-block + content: "&darr" + text-indent: -99999px + vertical-align: top + margin-top: 8px + margin-left: 4px + border-left: 4px solid transparent + border-right: 4px solid transparent + border-top: 4px solid white + @include opacity(0.5) // }}} - - // Leftbar {{{ + // Webclient.leftbar {{{ .oe_leftbar - width: 205px - height: 100% + width: 220px background: #f0eeee border-right: 1px solid #afafb6 - overflow: auto text-shadow: 0 1px 1px white - - .oe_footer - position: absolute - width: 205px - text-align: center - bottom: 8px a.oe_logo + width: 220px display: block text-align: center height: 70px @@ -155,10 +419,12 @@ $colour4: #8a89ba width: 157px margin: 14px 0 .oe_footer - position: absolute - width: 205px + position: fixed + bottom: 0 + padding: 4px 0 + background: #f0eeee + width: 220px text-align: center - bottom: 8px a font-weight: 800 font-family: serif @@ -168,14 +434,58 @@ $colour4: #8a89ba color: #c81010 font-style: italic // }}} - + // UserMenu {{{ + .oe_user_menu + float: right + padding: 0 + margin: 0 + li + list-style-type: none + float: left + .oe_dropdown + position: relative + .oe_dropdown_options + float: left + background: #333 + background: rgba(37,37,37,0.9) + display: none + position: absolute + top: 32px + right: -1px + border: 0 + z-index: 900 + margin-left: 0 + margin-right: 0 + padding: 6px 0 + zoom: 1 + border-color: #999 + border-color: rgba(0, 0, 0, 0.2) + border-style: solid + border-width: 0 1px 1px + @include radius(0 0 6px 6px) + @include box-shadow(0 1px 4px rgba(0,0,0,0.3)) + @include background-clip() + li + float: none + display: block + background-color: none + a + display: block + padding: 4px 15px + clear: both + font-weight: normal + line-height: 18px + color: #eee + &:hover + @include vertical-gradient(#292929, #191919) + @include box-shadow(none) + // }}} // Menu {{{ .oe_menu float: left padding: 0 margin: 0 li - list-style-type: none float: left a display: block @@ -220,7 +530,6 @@ $colour4: #8a89ba li position: relative padding: 1px 0 1px 16px - list-style-type: none a display: block color: #4c4c4c @@ -238,6 +547,16 @@ $colour4: #8a89ba text-shadow: 0 1px 1px rgba(0,0,0,0.2) @include radius(4px) @include box-shadow(inset 0 1px 1px rgba(0, 0, 0, 0.2)) + .oe_menu_counter + float: right + background: #8a89ba + color: #eee + font-size: 12px + border: 1px solid lightgray + padding: 0px 4px 0px 4px + text-shadow: 0 1px 1px rgba(0,0,0,0.2) + margin: 0px + @include radius(4px) .oe_active background: $colour4 border-top: 1px solid lightGray @@ -251,6 +570,9 @@ $colour4: #8a89ba color: $colour4 text-shadow: 0 1px 1px white @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.2)) + .oe_menu_counter + background: #eee + color: #8a89ba .oe_menu_toggler:before width: 0 height: 0 @@ -273,11 +595,180 @@ $colour4: #8a89ba border-right: 4px solid transparent border-top: 4px solid #4c4c4c // }}} - - // UserMenu {{{ - .oe_dropdown + // ActionManager {{{ + .oe_application + width: 100% + a + color: $colour4 + &:hover + text-decoration: underline + // }}} + // Content Header MIT {{{ + .oe-view-manager-header + border-top: 1px solid #cacaca + border-bottom: 1px solid #cacaca + @include vertical-gradient(#fcfcfc, #dedede) + @include box-shadow((0 1px 0 rgba(255,255,255,0.4), 0 0 9px rgba(0,0,0,0.1))) + h2 + float: left + font-size: 18px + margin: 1px 0 + a + color: $colour4 + .buttons + padding-right: 8px + li + float: left + margin-right: 12px + .filter + vertical-align: top + li + height: 24px + line-height: 24px + padding: 0 8px + .header-row + height: 26px + line-height: 26px + margin: 8px + clear: both + text-shadow: 0 1px 1px white + .dropdown-toggle:after + margin-top: 7px + margin-left: 6px + border-top-color: #404040 + .oe_vm_switch + li + padding-left: 1px + text-align: center + width: 24px + height: 24px + line-height: 24px + .pagination + li + height: 24px + line-height: 24px + padding: 0 8px + .button-group + display: inline-block + border: 1px solid #ababab + @include radius(5px) + li + float: left + border-right: 1px solid #ababab + &:last-child + border: none + a + color: #4c4c4c + &:hover + text-decoration: none + .active + width: 100% + background: #999 + @include box-shadow(0 1px 4px rgba(0,0,0,0.3) inset) + color: #fff + text-shadow: 0 1px 2px rgba(0,0,0,0.4) + // }}} + // ViewManager.header {{{ + .oe_view_manager_header2 + width: 100% + border-top: 1px solid #cacaca + border-bottom: 1px solid #cacaca + @include vertical-gradient(#fcfcfc, #dedede) + @include box-shadow((0 1px 0 rgba(255,255,255,0.4), 0 0 9px rgba(0,0,0,0.1))) + h2 + float: left + font-size: 18px + margin: 1px 0 + a + color: $colour4 + td + padding: 4px 8px + line-height: 26px + .oe_button_group + display: inline-block + border: 1px solid #ababab + @include radius(5px) + li + float: left + border-right: 1px solid #ababab + &:last-child + border: none + a + color: #4c4c4c + &:hover + text-decoration: none + .active + background: #999 + @include box-shadow(0 1px 4px rgba(0,0,0,0.3) inset) + a + color: #fff + text-shadow: 0 1px 2px rgba(0,0,0,0.4) + .oe_view_manager_buttons + white-space: nowrap + // }}} + // ViewManager.body {{{ + .oe_view_manager_body + h4 + margin: 8px 0 + // }}} + // ViewManager.pager {{{ + .oe_pager_value + float: left + margin-right: 8px + .oe_pager_group + float: left + height: 24px + line-height: 24px + display: inline-block + border: 1px solid #ababab + cursor: pointer + @include radius(5px) + li + height: 24px + line-height: 24px + padding: 0 + float: left + border-right: 1px solid #ababab + &:last-child + border: none + a + color: #4c4c4c + padding: 0 8px + &:hover + text-decoration: none + .active + background: #999 + @include box-shadow(0 1px 4px rgba(0,0,0,0.3) inset) + a + color: #fff + text-shadow: 0 1px 2px rgba(0,0,0,0.4) + // }}} + // ViewManager.switches {{{ + .oe_view_manager_switch + li + padding-left: 1px + text-align: center + width: 24px + height: 24px + line-height: 24px + .oe_vm_switch_list:after, .oe_vm_switch_tree:after + content: "ö" + .oe_vm_switch_graph:after + content: "ó" + .oe_vm_switch_gantt:after + content: "y" + .oe_vm_switch_calendar:after + content: "b" + .oe_vm_switch_kanban:after + content: "ó" + .oe_vm_switch_diagram:after + content: "}" + // }}} + // ViewManager.sidebar {{{ + .oe_form_dropdown_section position: relative - + display: inline-block + margin-right: 4px .oe_dropdown_toggle:after width: 0 height: 0 @@ -285,61 +776,752 @@ $colour4: #8a89ba content: "&darr" text-indent: -99999px vertical-align: top - margin-top: 8px - margin-left: 4px border-left: 4px solid transparent border-right: 4px solid transparent border-top: 4px solid white @include opacity(0.5) - - .oe_user_menu - float: right - padding: 0 - margin: 0 + margin-top: 7px + margin-left: 6px + border-top-color: #404040 + .oe_dropdown_menu + display: none + position: absolute + top: 28px + left: 0px + padding: 8px + border: 1px solid #afafb6 + width: 120px + overflow-x: hidden + z-index: 900 + text-align: left + background: white + @include radius(3px) + @include box-shadow(0 1px 4px rgba(0,0,0,0.3)) li list-style-type: none - float: left + float: none + display: block + background-color: none + a + display: block + padding: 3px 6px + clear: both + font-weight: normal + line-height: 14px + color: #4c4c4c + text-decoration: none + &:hover + text-decoration: none + @include vertical-gradient(#f0f0fa, #eeeef6) + @include box-shadow(none) + .oe_sidebar + white-space: nowrap + // }}} + // SearchView xmo {{{ + .oe_searchview + position: relative + float: right + .VS-search + .VS-search-box + min-height: 0 + border-width: 1px + padding: 0 + width: 410px + //border: 1px solid #ababab + //margin-top: 0 + //margin-bottom: 0 + //padding: 4px 19px + //font-size: 13px + //background: url('../img/search.png') no-repeat 5px + //background-color: white + //@include radius(13px) + //@include box-shadow(0 1px 2px rgba(0,0,0,0.2) inset) + .VS-icon + top: 5px - .oe_dropdown_options - float: left - background: #333 - background: rgba(37,37,37,0.9) + .VS-icon-search + left: 5px + + .VS-search-inner + margin: 0 40px 0 17px + font-size: 13px + + input + font-size: inherit + line-height: inherit + height: auto + padding: 0 + + .VS-input-width-tester + font-size: inherit + padding: 0 + + .search_input + margin-left: 0 + height: 22px + input, .VS-input-width-tester + padding: 0 2px + height: inherit + margin: 0 + + .search_facet + padding: 0 + margin: 1px 0 + border: 1px solid $facets-border + @include radius(3px) + background: #f0f0fa + height: 18px + + &.is_selected + border-color: $facets-border-selected + @include box-shadow(0 0 3px 1px $facets-border-selected) + + .category + margin-left: 0 + .VS-icon-cancel + // don't change the icon on selection + @include opacity(1.0) + background-position: center 0 + + .category + padding: 0 4px 0 14px + color: white + background: #8786b7 + text-shadow: 0 1px 1px rgba(0, 0, 0, 0.4) + font-weight: normal + text-transform: none + height: 18px + line-height: 18px + font-size: inherit + + .search_facet_input_container + border-left: 1px solid $facets-border + height: 18px + line-height: 18px + padding: 0 4px + cursor: pointer + + .search_facet_remove + left: 1px + top: 3px + + .VS-icon-cancel + right: 24px + + .oe_vs_unfold_drawer + position: absolute + top: 0 + right: 0 + height: 100% + line-height: 23px + padding: 0 7px 0 4px + color: #ccc + cursor: pointer + &:before + content: "▾" + + &.oe_searchview_open_drawer + .oe_searchview_drawer + display: block + + .oe_searchview_drawer + position: absolute + z-index: 1 + top: 100% + right: 0 + background-color: white + width: 410px + display: none + border: 1px solid #ccc + text-align: left + + .oe_searchview_filters + display: table + width: 100% + + > div + @include unscrew-box + display: table-cell + width: 50% + + h3 + margin: 2px 4px 2px 8px + color: $section-title-color + + ul + margin: 0 12px 3px + padding: 0 + list-style: none + + li + list-style: none + padding: 3px 6px + height: 14px + color: inherit + cursor: pointer + + &:hover + background-color: #f0f0fa + + .oe_searchview_advanced + h4 + border-top: 1px solid #ccc + margin: 5px 0 3px + cursor: pointer + + h4:before + content: "▸ " + + div + display: none + + button.oe_add_condition:before + content: "⊞ " + button.oe_apply:before + content: "⚡ " + + ul + list-style: none + padding: 0 + li + list-style: none + margin: 0 + + &.oe_opened + h4:before + content: "▾ " + div + display: block + // }}} + // View.topbar {{{ + .oe_view_topbar + border-bottom: 1px solid #cacaca + @include vertical-gradient(#fcfcfc, #dedede) + padding: 0 8px + line-height: 30px + // }}} + // FormView.body {{{ + .oe_formview + background: white + .oe_form_header + padding: 8px + .oe_form_invalid + input, select, textarea + background-color: #F66 !important + border: 1px solid #D00 !important + .oe_form_button_save_dirty + color: white + background: #dc5f59 + background: -moz-linear-gradient(#dc5f59, #b33630) + background: -webkit-gradient(linear, left top, left bottom, from(#dc5f59), to(#b33630)) + background: -webkit-linear-gradient(#dc5f59, #b33630) + -moz-box-shadow: none + -webkit-box-shadow: none + -box-shadow: none + font-weight: bold + &:hover + background: #ED6F6A + // }}} + // FormView.customdivs {{{ + .oe_form_topbar + border-bottom: 1px solid #cacaca + @include vertical-gradient(#fcfcfc, #dedede) + padding: 0 8px + line-height: 30px + button + font-size: 12px + height: 24px + line-height: 24px + vertical-align: top + padding: 0 10px + margin: 3px 0 + span.i + line-height: 20px + height: 24px + ul + height: 30px + padding: 0 + margin: 0 + text-shadow: 0 1px 1px white + border-left: 1px solid #cacaca + border-right: 1px solid #cacaca + li + padding: 0 + margin: 0 + float: left + vertical-align: top + border-right: 1px solid #cacaca + height: 30px + padding: 0 12px + &:first-child + border-left: 1px solid #cacaca + a + color: #4c4c4c + &:hover + color: black + .oe_form_steps + img + margin: 0 8px + vertical-align: top + li + border-right: none + padding: 0 + &:first-child + margin-left: 12px + border-left: none + &:last-child + margin-right: 12px + .oe_form_steps_active + font-weight: bold + color: #b33630 + .oe_application + .oe_form_sheetbg + background: url(/web/static/src/img/form_sheetbg.png) + padding: 8px + border-bottom: 1px solid #ddd + .oe_form_sheet_width + min-width: 650px + max-width: 980px + margin: 0 auto + .oe_form_sheet + padding: 8px 8px 8px 8px + background: white + min-height: 420px + border: 1px solid #afafb6 + @include box-shadow(0 0 10px rgba(0,0,0,0.3)) + //.avatar + // max-height: 52px + //.contacts + // li + // float: left + // width: 33% + // margin-bottom: 4px + // .avatar + // height: 40px + // width: 40px + // border: 1px solid + // border-color: #e5e5e5 #dbdbdb #d2d2d2 + // .data + // padding: 2px + // line-height: 18px + // }}} + // FormView.autolayout {{{ + .oe_form_autolayout + .oe_button + height: 22px + .oe_form_group_cell + line-height: 18px + padding: 2px + height: 25px + .oe_form_button + width: 100% + white-space: nowrap + .oe_form_group_cell > .oe_form_field_many2one, + .oe_form_group_cell > .oe_form_field_many2one input, + .oe_form_group_cell > .oe_form_field_char input, + .oe_form_group_cell > .oe_form_field_text textarea, + .oe_form_group_cell > .oe_form_field_email, + .oe_form_group_cell > .oe_form_field_email input, + .oe_form_group_cell > .oe_form_field_url, + .oe_form_group_cell > .oe_form_field_url input, + .oe_form_group_cell > .oe_form_field_date input, + .oe_form_group_cell > .oe_form_field_float input, + .oe_form_group_cell > .oe_form_field_selection select, + .oe_form_group_cell > .oe_datepicker_root + width: 100% + .oe_form_button.oe_button + color: #4c4c4c + white-space: nowrap + min-width: 100% + width: 100% + height: 22px + span + position: relative + vertical-align: top + .oe_form_group_cell.oe_form_group_nested + padding: 0 + // }}} + // FormView.label {{{ + .oe_form + label + font-weight: bold + .oe_form_label_help[for], .oe_form_label[for] + white-space: nowrap + // }}} + // FormView.fields {{{ + .oe_form + textarea + resize: vertical + input[type="text"], + input[type="password"], + input[type="file"], + select + height: 22px + padding-top: 2px + input[type="text"], + input[type="password"], + input[type="file"], + select, + textarea + -moz-box-sizing: border-box + -webkit-box-sizing: border-box + -ms-box-sizing: border-box + box-sizing: border-box + padding: 0 2px + margin: 0 2px + border: 1px solid #999 + -moz-border-radius: 3px + -webkit-border-radius: 3px + border-radius: 3px + background: white + min-width: 90px + color: #1f1f1f + input[readonly], + select[readonly], + textarea[readonly], + input[disabled], + select[disabled], + textarea[disabled] + background: #E5E5E5 !important + color: #666 + .oe_form_field_many2one input, + .oe_form_field_binary input, + .oe_form_field_binary input, + .oe_form_field_email input, + .oe_form_field_url input + border-right: none + -webkit-border-top-right-radius: 0px + -webkit-border-bottom-right-radius: 0px + -moz-border-radius-topright: 0px + -moz-border-radius-bottomright: 0px + border-top-right-radius: 0px + border-bottom-right-radius: 0px + .oe_form_field_email button img, + .oe_form_field_url button img + vertical-align: top + .oe_form_field_translatable, + .oe_form_field_many2one, + .oe_form_field_date, + .oe_form_field_datetime + white-space: nowrap + .oe_form_field_boolean + padding-top: 4px + .oe_form_field_datetime input + min-width: 11em + .oe_datepicker_container display: none + .oe_datepicker_root + display: inline-block + .oe_form_required + input, select, textarea + background-color: #D2D2FF !important + .oe_form_invalid + input, select, textarea + background-color: #F66 !important + border: 1px solid #D00 !important + // }}} + // FormView.buttons {{{ + .oe_form + .oe_button.oe_field_button + -webkit-border-top-left-radius: 0px + -webkit-border-bottom-left-radius: 0px + -moz-border-radius-topleft: 0px + -moz-border-radius-bottomleft: 0px + border-top-left-radius: 0px + border-bottom-left-radius: 0px + margin-right: -1px + height: 22px + .oe_input_icon + cursor: pointer + margin: 3px 0 0 -21px + vertical-align: top + .oe_input_icon_disabled position: absolute - top: 32px - right: -1px - border: 0 - z-index: 900 - margin-left: 0 - margin-right: 0 - padding: 6px 0 - zoom: 1 - border-color: #999 - border-color: rgba(0, 0, 0, 0.2) - border-style: solid - border-width: 0 1px 1px - @include radius(0 0 6px 6px) - @include box-shadow(0 1px 4px rgba(0,0,0,0.3)) - @include background-clip() + cursor: default + opacity: 0.5 + filter: alpha(opacity=50) + right: 5px + top: 3px + // }}} + // FormView.binaryfile {{{ + /* http://www.quirksmode.org/dom/inputfile.html + * http://stackoverflow.com/questions/2855589/replace-input-type-file-by-an-image + */ + .oe_form + .oe-binary-file-set + overflow: hidden + position: relative + width: 45px + height: 30px + input.oe-binary-file + z-index: 0 + line-height: 0 + font-size: 12px + position: absolute + // Should be adjusted for all browsers + top: 1px + right: 10px + opacity: 0 + filter: alpha(opacity = 0) + -ms-filter: "alpha(opacity=0)" + margin: 0 + padding: 0 + // }}} + // FormView.separator {{{ + .oe_horizontal_separator + font-weight: bold + border: 0 solid #666 + border-bottom-width: 1px + margin: 3px 4px 3px 1px + height: 17px + .oe_horizontal_separator:empty + height: 5px + .oe_vertical_separator + border-left: 1px solid #666 + padding: 0 4px 0 4px + // }}} + // FormView.notebook {{{ + .oe_form_notebook + margin: 8px 0 + padding: 0 8px + list-style: none + zoom: 1 + .oe_form_notebook:before, .oe_form_notebook:after + display: table + content: "" + zoom: 1 + .oe_form_notebook:after + clear: both + .oe_form_notebook > li + float: left + .oe_form_notebook > li > a + display: block + color: #4c4c4c + .oe_form_notebook + border-color: #ddd + border-style: solid + border-width: 0 0 1px + .oe_form_notebook > li + position: relative + margin-bottom: -1px + .oe_form_notebook > li > a + padding: 0 12px + margin-right: 2px + line-height: 30px + border: 1px solid transparent + @include radius(4px 4px 0 0) + .oe_form_notebook > li > a:hover + text-decoration: none + background-color: #eee + border-color: #eee #eee #ddd + .oe_form_notebook > li.ui-state-active > a, .oe_form_notebook > li.ui-state-active > a:hover + background-color: #ffffff + border: 1px solid #ddd + border-bottom-color: transparent + cursor: default + .ui-tabs-hide + display: none + + // }}} + // FormView.progressbar {{{ + .oe_form + .oe_form_field_progressbar.ui-progressbar + height: 22px + font-size: 10px + -moz-box-sizing: border-box + -webkit-box-sizing: border-box + -ms-box-sizing: border-box + box-sizing: border-box + border: 1px solid #999 + -moz-border-radius: 3px + -webkit-border-radius: 3px + border-radius: 3px + background: white + min-width: 90px + + span + position: absolute + margin-left: 10px + font-weight: bold + + .ui-widget-header + background: #cccccc url(/web/static/lib/jquery.ui/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png) 50% 50% repeat-x + // }}} + // FormView.viewmanager {{{ + .oe_form + .oe_view_manager_header2 + td + padding: 0px 8px + line-height: 16px + .oe_i + font-size: 13px + .oe_pager_group + height: auto + line-height: 16px + li + height: auto + line-height: 16px + + // }}} + // ListView {{{ + .oe_listview_nocontent + > img + float: left + margin-right: 1.5em + > div + // don't encroach on my arrow + overflow: hidden + padding: 6px + font-size: 125% + .oe-listview-content + width: 100% + thead, tfoot + border-bottom: 1px solid #cacaca + background: #eee + tfoot + border-bottom: 1px solid #cacaca + font-weight: bold + tbody + cursor: pointer + background: white + thead + vertical-align: top + td.oe_list_checkbox:first-child, th.oe_list_checkbox:first-child + width: 17px + &:after + border-width: 0 + tbody tr:nth-child(odd) td, tbody tr:nth-child(odd) th + background-color: #f0f0fa + @include vertical-gradient(#f0f0fa, #eeeef6) + tbody tr:hover td, tbody tr:hover th + @include vertical-gradient(#eee, #dedede) + td, th + padding: 3px 6px + line-height: 18px + button + border: none + background: transparent + padding: 0 + @include box-shadow(none) + .numeric + text-align: right + width: 82px + input + text-align: right + //thead th:after + //content: "" + //float: right + //margin-top: 7px + //border-width: 0 4px 4px + //border-style: solid + //border-color: #000 transparent + //visibility: hidden + table .sortdown:after, table .sortdown:hover:after + visibility: visible + @include opacity(0.6) + table .sortup:after + border-bottom: none + border-left: 4px solid transparent + border-right: 4px solid transparent + border-top: 4px solid #000 + visibility: visible + @include box-shadow(none) + @include opacity(0.6) + // }}} + // KanbanView {{{ + .oe_kanban_group_title + margin: 1px 1px 4px + font-size: 15px + font-weight: bold + text-shadow: 0 1px 0 white + .oe_kanban_column, .oe_kanban_group_header + width: 240px + vertical-align: top + padding: 6px 7px 6px 6px + background: #f0eeee + border-left: 1px solid #f0f8f8 + border-right: 1px solid #b9b9b9 + .oe_kanban_record + position: relative + display: block + min-height: 50px + margin: 6px 0 + display: block + @include radius(4px) + &:last-child + margin-bottom: 0 + .oe_kanban_title + font-weight: bold + margin: 2px 4px + .oe_kanban_gravatar + width: 20px + height: 20px + @include radius(3px) + @include box-shadow(0 1px 2px rgba(0,0,0,0.2)) + .oe_kanban_avatar_toto + height: 40px + width: 40px + border: 1px solid + border-color: #e5e5e5 #dbdbdb #d2d2d2 + @include radius(3px) + @include box-shadow(0 1px 2px rgba(0,0,0,0.2)) + // }}} + // Translation {{{ + .oe_trad_field.touched + border: 1px solid green !important + // }}} + // Tooltips {{{ + &.oe_tooltip + font-size: 12px + .oe_tooltip_string + color: #FD5 + font-weight: bold + font-size: 13px + .oe_tooltip_help + white-space: pre-wrap + .oe_tooltip_technical + padding: 0 0 4px 0 + margin: 5px 0 0 15px li - float: none - display: block - background-color: none - a - display: block - padding: 4px 15px - clear: both - font-weight: normal - line-height: 18px - color: #eee - &:hover - @include vertical-gradient(#292929, #191919) - @include box-shadow(none) + list-style: circle + .oe_tooltip_technical_title + font-weight: bold // }}} + // Debugging stuff {{{ + .oe_layout_debugging + .oe_form_group + outline: 2px dashed red + .oe_form_group_cell + outline: 1px solid blue -.openerp - // Transitional overrides for old styles {{{ + .oe_debug_view + float: left + .oe_debug_view_log + font-size: 95% + .oe_debug_view_log label + display: block + width: 49% + text-align: right + float: left + font-weight: bold + color: #009 + .oe_debug_view_log span + display: block + width: 49% + float: right + color: #333 // }}} +// Transitional overrides for old styles {{{ +.openerp + .oe_form_field_many2one + td:first-child + position: relative + span.oe-m2o-drop-down-button + position: absolute + top: 3px + right: 2px +// }}} + // au BufWritePost,FileWritePost *.sass :!sass --style expanded --line-numbers <afile> > "%:p:r.css" // vim:tabstop=4:shiftwidth=4:softtabstop=4:fdm=marker: diff --git a/addons/web/static/src/css/base_old.css b/addons/web/static/src/css/base_old.css index 2b6f6fbf639..8f333fae761 100644 --- a/addons/web/static/src/css/base_old.css +++ b/addons/web/static/src/css/base_old.css @@ -1,7 +1,6 @@ .openerp { padding: 0; margin: 0; - height: 100%; font-size: 80%; font-family: Ubuntu, Helvetica, sans-serif; } @@ -14,10 +13,9 @@ .openerp .view-manager-main-content { width: 100%; - padding: 0 8px 8px 8px; } -.openerp .oe_form_frame_cell .view-manager-main-content { +.openerp .oe_form_group_cell .view-manager-main-content { padding: 0; } @@ -56,216 +54,6 @@ display: none; } -/* Loading */ -.loading { - cursor: wait; -} -.openerp .loading { - display: none; - z-index: 100; - position: fixed; - top: 0; - right: 50%; - padding: 4px 12px; - background: #A61300; - color: white; - text-align: center; - border: 1px solid #900; - border-top: none; - -moz-border-radius-bottomright: 8px; - -moz-border-radius-bottomleft: 8px; - border-bottom-right-radius: 8px; - border-bottom-left-radius: 8px; -} -.openerp .oe_notification { - z-index: 1050; - display: none; -} -.openerp .oe_notification * { - color: white; -} - -/* Login page */ - -.login { - padding: 0; - margin: 0; - font-family: "Lucida Grande", Helvetica, Verdana, Arial; - background: url("/web/static/src/img/pattern.png") repeat; - color: #eee; - font-size: 14px; - height: 100%; -} - -.login ul, ol { - padding: 0; - margin: 0; -} - -.login li { - list-style-type: none; - padding-bottom: 4px; -} - -.login a { - color: #eee; - text-decoration: none; -} - -.login button { - float: right; - display: inline-block; - cursor: pointer; - padding: 6px 16px; - font-size: 13px; - font-family: "Lucida Grande", Helvetica, Verdana, Arial; - border: 1px solid #222222; - color: white; - margin: 0; - background: #600606; - background: -moz-linear-gradient(#b92020, #600606); - background: -webkit-gradient(linear, left top, left bottom, from(#b92020), to(#600606)); - background: -ms-linear-gradient(top, #b92020, #600606); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#b92020', endColorstr='#600606',GradientType=0 ); - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - border-radius: 4px; - -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(155, 155, 155, 0.4) inset; - -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(155, 155, 155, 0.4) inset; - -o-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(155, 155, 155, 0.4) inset; - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(155, 155, 155, 0.4) inset; -} - -.login input, .login select { - width: 252px; - font-size: 14px; - font-family: "Lucida Grande", Helvetica, Verdana, Arial; - border: 1px solid #999999; - background: whitesmoke; - -moz-box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.3); - -webkit-box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.3); - -box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.3); - -moz-border-radius: 3px; - -webkit-border-radius: 3px; - border-radius: 3px; -} - -.login input { - margin-bottom: 9px; - padding: 5px 6px; -} - -.login select { - padding: 1px; -} - -.login .dbpane { - position: fixed; - top: 0; - right: 8px; - padding: 5px 10px; - color: #eee; - border: solid 1px #333; - background: #1e1e1e; - background: rgba(30,30,30,0.94); - -moz-border-radius: 0 0 8px 8px; - -webkit-border-radius: 0 0 8px 8px; - border-radius: 0 0 8px 8px; -} - -.login .bottom { - position: absolute; - top: 50%; - left: 0; - right: 0; - bottom: 0; - text-shadow: 0 1px 1px #999999; - background: #600606; - background: -moz-linear-gradient(#b41616, #600606); - background: -webkit-gradient(linear, left top, left bottom, from(#b41616), to(#600606)); - background: -ms-linear-gradient(top, #b41616, #600606); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#b41616', endColorstr='#600606',GradientType=0 ); -} - -.login .pane { - position: absolute; - top: 50%; - left: 50%; - margin: -160px -166px; - border: solid 1px #333333; - background: #1e1e1e; - background: rgba(30,30,30,0.94); - padding: 22px 32px; - text-align: left; - -moz-border-radius: 8px; - -webkit-border-radius: 8px; - border-radius: 8px; - -moz-box-shadow: 0 0 18px rgba(0, 0, 0, 0.9); - -webkit-box-shadow: 0 0 18px rgba(0, 0, 0, 0.9); - -box-shadow: 0 0 18px rgba(0, 0, 0, 0.9); -} - -.login .pane h2 { - margin-top: 0; - font-size: 18px; -} - -.login #logo { - position: absolute; - top: -70px; - left: 0; - width: 100%; - margin: 0 auto; - text-align: center; -} - -.login .footer { - position: absolute; - bottom: -40px; - left: 0; - width: 100%; - text-align: center; -} - -.login .footer a { - font-size: 13px; - margin: 0 8px; -} - -.login .footer a:hover { - text-decoration: underline; -} - -.login .openerp { - font-weight: bold; - font-family: serif; - font-size: 16px; -} - -.openerp .login { - text-align: center; -} - -.openerp .login .login_error_message { - display: none; - background-color: #b41616; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - border-radius: 4px; - -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.8); - -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.8); - -box-shadow: 0 1px 4px rgba(0, 0, 0, 0.8); - color: #eee; - font-size: 14px; - padding: 14px 18px; - margin-top: 15px; - text-align: center; -} - -.openerp .login.login_invalid .login_error_message { - display: inline-block; -} - /* Database */ @@ -404,201 +192,6 @@ label.error { padding-bottom:4px; } -/* View Manager */ -.openerp .oe_vm_switch { - float: right; -} -.openerp .oe-view-manager-header .oe_view_title { - font-size:150%; - padding:2px 0 0 0; -} - -/* SearchView */ -.openerp .oe_searchview_field > div { - position: relative; - white-space: nowrap; -} -.openerp .oe_searchview_field .oe_input_icon { - top: auto; - bottom: 3px; -} - -.openerp .filter_label, .openerp .filter_icon { - background: #F0F0F0; - border: 1px solid #999; - background: -moz-linear-gradient(top, #F0F0F0 0%, #C0C0C0 100%); - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#F0F0F0), color-stop(100%,#C0C0C0)); - filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#F0F0F0', endColorstr='#C0C0C0',GradientType=0 ); -} -.openerp .filter_label:hover, .openerp .filter_icon:hover { - background: #F0F0F0; - background: -moz-linear-gradient(top, #F0F0F0 0%, #A1A7CE 100%); - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#F0F0F0), color-stop(100%,#A1A7CE)); - filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#F0F0F0', endColorstr='#A1A7CE',GradientType=0 ); -} -.openerp .filter_label:active, .openerp .filter_icon:active { - background: #aaa; - background: -moz-linear-gradient(top, #999999 0%, #EEEEEE 100%); - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#999999), color-stop(100%,#EEEEEE)); - filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#999999', endColorstr='#EEEEEE',GradientType=0 ); -} -.openerp .filter_label.enabled, .openerp .filter_icon.enabled { - background: #aaa; - filter: none; - -moz-box-shadow: none; - -webkit-box-shadow: none; - -o-box-shadow: none; - box-shadow: none; -} -.openerp .filter_icon { - height: 22px; - padding: 1px 2px 0 2px; - margin: 0; - vertical-align: bottom; -} -.openerp .filter_label { - font-weight: bold; - text-transform: uppercase; - text-shadow: #EEE 0 1px 0; - color: #4C4C4C; - white-space: nowrap; - min-height: 40px; - min-width: 75px; - padding: 2px 4px; - margin: 0; -} -.openerp .filter_label_group { - padding-right: 0.4em; - white-space: nowrap; -} - -.openerp .filter_label_group button { - -webkit-border-radius: 0; - -moz-border-radius: 0; - border-radius: 0; - border-right: none; -} -.openerp .filter_label_group button:first-child { - -webkit-border-top-left-radius: 7px; - -webkit-border-bottom-left-radius: 7px; - -moz-border-radius-topleft: 7px; - -moz-border-radius-bottomleft: 7px; - border-top-left-radius: 7px; - border-bottom-left-radius: 7px; - border-right: none; -} -.openerp .filter_label_group button:last-child { - -webkit-border-top-right-radius: 7px; - -webkit-border-bottom-right-radius: 7px; - -moz-border-radius-topright: 7px; - -moz-border-radius-bottomright: 7px; - border-top-right-radius: 7px; - border-bottom-right-radius: 7px; - border-right: 1px solid #999; -} -.openerp .filter_label_group button.filter_icon img { - padding: 1px 8px 0 8px; -} -.openerp .filter_label_group button.filter_icon:first-child { - border-left: solid 1px #999; - margin-left: -7px; - -webkit-border-top-left-radius: 0; - -webkit-border-bottom-left-radius: 0; - -moz-border-radius-topleft: 0; - -moz-border-radius-bottomleft: 0; - border-top-left-radius: 0; - border-bottom-left-radius: 0; -} - -.openerp .searchview_group_string { - display: block; - color: #7D7979; - font-weight: bold; - padding: 2px 0 2px 10px; - text-decoration: none; -} -.openerp .searchview_group_string:hover { - background-color: #ccc; -} -.openerp .searchview_group.folded .searchview_group_string { - background: url("/web/static/src/img/ui/group-folded.png") no-repeat scroll 0 50%; -} -.openerp .searchview_group.folded .searchview_group_content { - display: none; -} -.openerp .searchview_group.expanded .searchview_group_string { - background: url("/web/static/src/img/ui/group-expanded.png") no-repeat scroll 0 50%; -} -.openerp .searchview_group.expanded .searchview_group_content { - display: block; - padding-bottom:3px; -} - -.openerp .searchview_group_content .oe_label, .openerp .searchview_group_content .oe_label_help { - font-weight: bold; - color: #4c4c4c; -} - -.openerp .oe-searchview-render-line .oe_label, .openerp .oe-searchview-render-line .oe_label_help { - font-weight: bold; - font-size: 80%; - white-space: nowrap; -} - -.openerp .searchview_extended_group { - padding: 3px; - margin: 2px; -} - -.openerp .searchview_extended_group .oe_adv_filters_and { - border-bottom: 1px solid #8E8E8E; - text-align: center; - margin-top: -10px; -} -.openerp .searchview_extended_group .oe_adv_filters_and span { - background: #F0EEEE; - position: relative; - top: 0.5em; - padding: 0 1em 0 1em; - color: #8E8E8E; -} - -.openerp .searchview_extended_group.last_group .oe_adv_filters_and { - display: none; -} - -.openerp .oe_search-view-buttons { - padding: 2px 0 10px 0; - vertical-align:middle; -} -.openerp .oe_search-view-filters-management { - float: right; -} -.openerp .oe_search-view-filters-management, .openerp .oe_search-view-custom-filter-btn { - float:right; -} - -.openerp .searchview_extended_add_proposition span { - font-size: 0.9em; - background: url(/web/static/src/img/icons/gtk-add.png) repeat-y; - padding-left: 18px; -} - -.openerp .searchview_extended_delete_group { - float:right; - display: none; -} - -.openerp .searchview_extended_delete_prop { - text-decoration: none; -} - -.openerp .searchview_extended_delete_group span, -.openerp .searchview_extended_delete_prop span { - font-size: 0.9em; - background: url(/web/static/src/img/icons/gtk-close.png) repeat-y; - padding-left: 18px; -} /* List */ .openerp table.oe-listview-content { clear: right; @@ -818,91 +411,19 @@ label.error { border-bottom-right-radius: 4px; } -/* Notebook */ -.openerp .oe_form_notebook { - padding: 0; - background: none; - border-width: 0; -} -.openerp .oe_form_notebook .ui-tabs-panel { - padding: 4px; - -moz-border-radius-topright: 4px; - -webkit-border-top-right-radius: 4px; - border-top-right-radius: 4px; -} -.openerp .oe_form_notebook ul.ui-tabs-nav { - padding-left: 0; - background: transparent; - border-width: 0; - border-radius: 0; - -moz-border-radius: 0; - -webkit-border-radius: 0; - line-height: 0.8em; - font-size: 95%; - color: #555; -} -.openerp .oe_form_notebook ul.ui-tabs-nav li { - font-weight: bold; -} -.openerp .oe_form_notebook .ui-tabs-panel { - background: #f9f9f9; - border-width: 1px; -} -.openerp .oe_form_notebook .ui-tabs-selected { - background: #f9f9f9; -} -/* Unedit Form */ -.openerp .field_char, -.openerp .field_date, -.openerp .field_float, -.openerp .field_selection, -.openerp a.oe_form_uri { - vertical-align: middle; - padding-top: 3px; - font-size: 90%; - color: #222; -} -.openerp a.oe_form_uri { - color: #9A0404; - line-height: 12px; -} - - - /* Form */ -.openerp .oe_form_button_save_dirty { - display: none; -} -.openerp .oe_form_dirty > .oe_form_header > .oe_form_buttons > .oe_form_button_save { - color: white; - background: #dc5f59; - background: -moz-linear-gradient(#dc5f59, #b33630); - background: -webkit-gradient(linear, left top, left bottom, from(#dc5f59), to(#b33630)); - background: -webkit-linear-gradient(#dc5f59, #b33630); - -moz-box-shadow: none; - -webkit-box-shadow: none; - -box-shadow: none; - font-weight: bold; -} -.openerp .oe_form_frame_cell input[type="checkbox"] { +.openerp .oe_form_group_cell input[type="checkbox"] { margin-top: 3px; vertical-align: middle; } -.openerp .oe_form_frame_cell .input[type="text"] { +.openerp .oe_form_group_cell .input[type="text"] { padding-bottom: 1px; } -.openerp table.oe_frame td { +.openerp table.oe_form_group td { color: #4c4c4c; } -.openerp td.oe_form_frame_cell { - padding: 2px; - position: relative; -} -.openerp .oe_frame.oe_forms { - clear: both; -} -.openerp table.oe_frame { +.openerp table.oe_form_group { color: #4c4c4c; } .openerp fieldset.oe_group_box { @@ -916,22 +437,6 @@ label.error { .openerp fieldset.oe_group_box legend { font-weight: bold; } -.openerp td.oe_form_frame_cell { - padding: 2px; - position: relative; -} -.openerp td.oe_form_field_translatable, -.openerp td.oe_form_field_many2one, -.openerp td.oe_form_field_date, -.openerp td.oe_form_field_datetime { - white-space: nowrap; -} -.openerp td.oe_form_field_boolean { - padding-top: 4px; -} -.openerp td.oe_form_frame_cell.oe_form_group { - padding: 0; -} .openerp .required.error { border: 1px solid #900; } @@ -949,66 +454,6 @@ label.error { margin-right: 3px; } - -.openerp label.oe_label_help, .openerp label.oe_label, -.openerp .oe_form_paragraph, -.openerp .oe_form_field_statusbar, -.openerp .oe_forms input[type="text"], -.openerp .oe_forms input[type="password"], -.openerp .oe_forms input[type="file"], -.openerp .oe_forms select, -.openerp .oe_forms .oe_button, -.openerp .oe_forms textarea { - font-size: 85%; -} - -.openerp label.oe_label_help, .openerp label.oe_label { - display: block; - color: #4c4c4c; - font-weight: normal; -} -.openerp label.oe_label_help { - cursor: help; -} -.openerp .oe_form_frame_cell .oe_label, .openerp .oe_form_frame_cell .oe_label_help { - font-weight: normal; -} -.openerp #tiptip_content { - font-size: 12px; -} -.openerp .oe_tooltip_string { - color: #FD5; - font-weight: bold; - font-size: 13px; -} -.openerp .oe_tooltip_help { - white-space: pre-wrap; -} -.openerp .oe_tooltip_technical { - padding: 0 0 4px 0; - margin: 5px 0 0 15px; - list-style: circle; -} -.openerp .oe_tooltip_technical_title { - font-weight: bold; -} - -.openerp .oe_forms label.oe_label, .openerp .oe_forms label.oe_label_help { - margin: 3px 0 0 3px; - white-space: nowrap; -} -.openerp .oe_forms .searchview_group_content label.oe_label, .openerp .searchview_group_content .oe_forms label.oe_label_help { /* making a distinction between labels in search view and other labels */ - margin: 3px 0 0 3px; -} - -.openerp label.oe_label_help span { - font-size: 80%; - color: darkgreen; - vertical-align:top; - position: relative; - top: -4px; - padding: 0 2px; -} .openerp .oe_align_left { text-align: left; } @@ -1018,12 +463,12 @@ label.error { .openerp .oe_align_center { text-align: center; } -.openerp .oe_forms .oe_form_paragraph { +.openerp .oe_form .oe_form_paragraph { margin: 3px 0 0 0; white-space: normal; } -.openerp .oe_forms .oe_form_paragraph.oe_multilines { +.openerp .oe_form .oe_form_paragraph.oe_multilines { white-space: pre; } @@ -1033,13 +478,13 @@ label.error { margin: 0 0.5em 0 0; } -.openerp .oe_forms .oe-listview th.oe-sortable .ui-icon, -.openerp .oe_forms .oe-listview th.oe-sortable .ui-icon { +.openerp .oe_form .oe-listview th.oe-sortable .ui-icon, +.openerp .oe_form .oe-listview th.oe-sortable .ui-icon { height: 100%; margin-top: -9px; } -.openerp table.oe_frame .oe-listview-content td { +.openerp table.oe_form_group .oe-listview-content td { color: inherit; } @@ -1047,19 +492,19 @@ label.error { .openerp .oe_form_readonly { } -.openerp .oe_form_readonly .oe_form_frame_cell .field_text, +.openerp .oe_form_readonly .oe_form_group_cell .field_text, .openerp .oe_form_readonly .field_char, .openerp .oe_form_readonly .field_int, .openerp .oe_form_readonly .field_float, .openerp .oe_form_readonly .field_email, .openerp .oe_form_readonly .field_date, .openerp .oe_form_readonly .field_selection, -.openerp .oe_forms_readonly .oe_form_field_many2one { +.openerp .oe_form_readonly .oe_form_field_many2one { padding: 3px 2px 2px 2px; background-color: white; height: 17px; } -.openerp .oe_form_readonly .oe_form_frame_cell .field_text { +.openerp .oe_form_readonly .oe_form_group_cell .field_text { height: auto; } .openerp .oe_form_readonly .field_datetime { @@ -1084,211 +529,34 @@ label.error { .openerp .oe_form_readonly .oe_form_field_text_html div.field_text_html { white-space: pre-wrap; } -.openerp .oe_form_readonly .oe_form_frame_cell .field_text { +.openerp .oe_form_readonly .oe_form_group_cell .field_text { min-height:100px; } /* Inputs */ -.openerp .oe_forms input[type="text"], -.openerp .oe_forms input[type="password"], -.openerp .oe_forms input[type="file"], -.openerp .oe_forms select, -.openerp .oe_forms textarea { - -moz-box-sizing: border-box; - -webkit-box-sizing: border-box; - -ms-box-sizing: border-box; - box-sizing: border-box; - padding: 0 2px; - margin: 0 2px; - border: 1px solid #999; - -moz-border-radius: 3px; - -webkit-border-radius: 3px; - border-radius: 3px; - background: white; - min-width: 90px; - color: #1f1f1f; -} - -.openerp .oe_forms input.field_many2one, -.openerp .oe_forms input.field_binary, -.openerp .oe_forms input.field_binary, -.openerp .oe_forms input.field_email, -.openerp .oe_forms input.field_url { - border-right: none; - -webkit-border-top-right-radius: 0px; - -webkit-border-bottom-right-radius: 0px; - -moz-border-radius-topright: 0px; - -moz-border-radius-bottomright: 0px; - border-top-right-radius: 0px; - border-bottom-right-radius: 0px; -} -.openerp .oe_button.oe_field_button { - -webkit-border-top-left-radius: 0px; - -webkit-border-bottom-left-radius: 0px; - -moz-border-radius-topleft: 0px; - -moz-border-radius-bottomleft: 0px; - border-top-left-radius: 0px; - border-bottom-left-radius: 0px; - margin-right:-1px; - height: 22px; -} - -.openerp .oe_form_field_email button img, -.openerp .oe_form_field_url button img { - vertical-align: top; -} /* vertically recentering filter management select tag */ .openerp select.oe_search-view-filters-management { margin-top:2px; } -.openerp .oe_forms select{ - padding-top: 2px; -} -.openerp .oe_forms input[readonly], -.openerp .oe_forms select[readonly], -.openerp .oe_forms textarea[readonly], -.openerp .oe_forms input[disabled], -.openerp .oe_forms select[disabled], -.openerp .oe_forms textarea[disabled]{ - background: #E5E5E5 !important; - color: #666; -} -.openerp .oe_forms textarea { - resize:vertical; -} -.openerp .oe_forms input[type="text"], -.openerp .oe_forms input[type="password"], -.openerp .oe_forms input[type="file"], -.openerp .oe_forms select, -.openerp .oe_forms .oe_button { - height: 22px; -} -.openerp .oe_forms input.field_datetime { - min-width: 11em; -} -.openerp .oe_forms .oe_form_button .oe_button { - color: #4c4c4c; - white-space: nowrap; - min-width: 100%; - width: 100%; -} @-moz-document url-prefix() { /* Strange firefox behaviour on width: 100% + white-space: nowrap */ - .openerp .oe_forms .oe_form_button .oe_button { + .openerp .oe_form .oe_form_button .oe_button { width: auto; } } /* IE Hack - for IE < 9 * Avoids buttons overflow * */ -.openerp .oe_forms .oe_form_button .oe_button { +.openerp .oe_form .oe_form_button .oe_button { min-width: auto\9; } -.openerp .oe_forms .button { - height: 22px; -} -.openerp .oe_forms .oe_button span { - position: relative; - vertical-align: top; -} -.openerp .oe_input_icon { - cursor: pointer; - margin: 3px 0 0 -21px; - vertical-align: top; -} -.openerp .oe_datepicker_container { - display: none; -} -.openerp .oe_datepicker_root { - display: inline-block; -} -.openerp .oe_form_frame_cell .oe_datepicker_root { - width: 100%; -} -.openerp .oe_input_icon_disabled { - position: absolute; - cursor: default; - opacity: 0.5; - filter:alpha(opacity=50); - right: 5px; - top: 3px; -} -.openerp .oe_trad_field.touched { - border: 1px solid green !important; -} - -/* http://www.quirksmode.org/dom/inputfile.html - * http://stackoverflow.com/questions/2855589/replace-input-type-file-by-an-image - */ -.openerp .oe-binary-file-set { - overflow: hidden; - position: relative; -} -.openerp input.oe-binary-file { - z-index: 0; - line-height: 0; - font-size: 12px; - position: absolute; - /* Should be adjusted for all browsers */ - top: 1px; - right: 10px; - opacity: 0; - filter: alpha(opacity = 0); - -ms-filter: "alpha(opacity=0)"; - margin: 0; - padding:0; -} -/* Widgets */ -.openerp .separator { - border: 0 solid #666; -} -.openerp .separator.horizontal { - font-weight: bold; - border-bottom-width: 1px; - margin: 3px 4px 3px 1px; - height: 17px; - font-size: 95%; -} -.openerp .separator.horizontal:empty { - height: 5px; -} -.openerp .oe_form_frame_cell.oe_form_separator_vertical { - border-left: 1px solid #666; -} -.openerp td.required input, .openerp td.required select, .openerp td.required textarea { - background-color: #D2D2FF !important; -} -.openerp td.invalid input, .openerp td.invalid select, .openerp td.invalid textarea { - background-color: #F66 !important; - border: 1px solid #D00 !important; -} -.openerp div.oe-progressbar span { - position: absolute; - margin-left: 10px; - margin-top: 5px; - font-weight: bold; -} /* jQuery UI override */ .openerp .ui-widget { font-size: 1em; } -.openerp .oe_form_field_progressbar .ui-progressbar { - height: 22px; - font-size: 10px; - -moz-box-sizing: border-box; - -webkit-box-sizing: border-box; - -ms-box-sizing: border-box; - box-sizing: border-box; - border: 1px solid #999; - -moz-border-radius: 3px; - -webkit-border-radius: 3px; - border-radius: 3px; - background: white; - min-width: 90px; -} .openerp tbody.ui-widget-content { margin-bottom: 10px; border-spacing: 4px; @@ -1296,10 +564,6 @@ label.error { .openerp .ui-widget-header { background: white none; } -/* progress bars */ -.openerp .ui-progressbar .ui-widget-header { - background: #cccccc url(/web/static/lib/jquery.ui/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png) 50% 50% repeat-x; -} /* Sidebar */ .openerp .view-manager-main-table { @@ -1318,7 +582,7 @@ label.error { background: url("/web/static/src/img/sep-a.gif") 0 100% repeat-x; margin:6px 0 6px 2px; } -.openerp .oe_form_frame_cell .oe-view-manager-header { /* Trick: remove the background when element is in a formular */ +.openerp .oe_form_group_cell .oe-view-manager-header { /* Trick: remove the background when element is in a formular */ background: none; } @@ -1354,45 +618,6 @@ label.error { text-decoration: underline; color: inherit; } -.openerp .oe-view-manager-logs { - clear: both; - background: #fff; - margin: 0.25em 0; - font-size: 85%; - color: #4C4C4C; - position: relative; - overflow: hidden; -} -.openerp .oe-view-manager-logs ul { - margin: 0; - padding: 0 10px; - list-style: none; -} -.openerp .oe-view-manager-logs li:before { - content: '\2192 '; -} -.openerp .oe-view-manager-logs a { - text-decoration: none; - color: inherit; -} -/* only display first three log items of a folded logs list */ -.openerp .oe-view-manager-logs.oe-folded li:nth-child(n+4) { - display: none; -} -/* display link to more logs if there are more logs to view and the logview is - currently folded */ -.openerp .oe-view-manager-logs a.oe-more-logs { - display: none; -} -.openerp .oe-view-manager-logs.oe-folded.oe-has-more a.oe-more-logs { - display: block; -} -.openerp .oe-view-manager-logs a.oe-remove-everything { - position: absolute; - top: 0; - right: 0; - cursor: pointer; -} .openerp .view-manager-main-sidebar { width: 180px; @@ -1810,228 +1035,6 @@ ul.oe-arrow-list li.oe-arrow-list-selected .oe-arrow-list-after { margin: 0 20px; } -.openerp .oe_vm_switch { - margin:2px 0 0 0; -} - -.openerp .oe_vm_switch_form, -.openerp .oe_vm_switch_page, -.openerp .oe_vm_switch_tree, -.openerp .oe_vm_switch_list, -.openerp .oe_vm_switch_graph, -.openerp .oe_vm_switch_gantt, -.openerp .oe_vm_switch_calendar, -.openerp .oe_vm_switch_kanban, -.openerp .oe_vm_switch_diagram { - background: url("/web/static/src/img/views-icons-a.png") repeat-x scroll left top transparent; - overflow: hidden; - width: 22px; - height: 21px; - border: none; - background-position: 0px 0px; -} - -.openerp .oe_vm_switch_form span, -.openerp .oe_vm_switch_page span, -.openerp .oe_vm_switch_tree span, -.openerp .oe_vm_switch_list span, -.openerp .oe_vm_switch_graph span, -.openerp .oe_vm_switch_gantt span, -.openerp .oe_vm_switch_calendar span, -.openerp .oe_vm_switch_kanban span, -.openerp .oe_vm_switch_diagram span { - display: none; -} - -.openerp .oe_vm_switch_list { - background-position: 0px 0px; -} -.openerp .oe_vm_switch_list:active, -.openerp .oe_vm_switch_list:hover, -.openerp .oe_vm_switch_list:focus, -.openerp .oe_vm_switch_list[disabled="disabled"] { - background-position: 0px -21px; -} - -.openerp .oe_vm_switch_tree { - background-position: 0px 0px; -} -.openerp .oe_vm_switch_tree:active, -.openerp .oe_vm_switch_tree:hover, -.openerp .oe_vm_switch_tree:focus, -.openerp .oe_vm_switch_tree[disabled="disabled"] { - background-position: 0px -21px; -} - -.openerp .oe_vm_switch_form { - background-position: -22px 0px; -} -.openerp .oe_vm_switch_form:active, -.openerp .oe_vm_switch_form:hover, -.openerp .oe_vm_switch_form:focus, -.openerp .oe_vm_switch_form[disabled="disabled"] { - background-position: -22px -21px; -} - -.openerp .oe_vm_switch_page { - background-position: -22px 0px; -} -.openerp .oe_vm_switch_page:active, -.openerp .oe_vm_switch_page:hover, -.openerp .oe_vm_switch_page:focus, -.openerp .oe_vm_switch_page[disabled="disabled"] { - background-position: -22px -21px; -} -.openerp .oe_vm_switch_graph { - background-position: -44px 0px; -} -.openerp .oe_vm_switch_graph:active, -.openerp .oe_vm_switch_graph:hover, -.openerp .oe_vm_switch_graph:focus, -.openerp .oe_vm_switch_graph[disabled="disabled"] { - background-position: -44px -21px; -} - -.openerp .oe_vm_switch_gantt { - background-position: -66px 0px; -} -.openerp .oe_vm_switch_gantt:active, -.openerp .oe_vm_switch_gantt:hover, -.openerp .oe_vm_switch_gantt:focus, -.openerp .oe_vm_switch_gantt[disabled="disabled"] { - background-position: -66px -21px; -} - -.openerp .oe_vm_switch_calendar { - background-position: -88px 0px; -} -.openerp .oe_vm_switch_calendar:active, -.openerp .oe_vm_switch_calendar:hover, -.openerp .oe_vm_switch_calendar:focus, -.openerp .oe_vm_switch_calendar[disabled="disabled"] { - background-position: -88px -21px; -} -.openerp .oe_vm_switch_kanban { - background-position: -110px 0px; -} -.openerp .oe_vm_switch_kanban:active, -.openerp .oe_vm_switch_kanban:hover, -.openerp .oe_vm_switch_kanban:focus, -.openerp .oe_vm_switch_kanban[disabled="disabled"] { - background-position: -110px -21px; -} - -.openerp .oe_vm_switch_diagram { - background-position: 0px 0px; -} -.openerp .oe_vm_switch_diagram:active, -.openerp .oe_vm_switch_diagram:hover, -.openerp .oe_vm_switch_diagram:focus, -.openerp .oe_vm_switch_diagram[disabled="disabled"] { - background-position: 0px -21px; -} - -/* Buttons */ -.openerp .oe_button:link, -.openerp .oe_button:visited, -.openerp .oe_button { - display: inline-block; - border: 1px solid #ababab; - color: #404040; - font-size: 12px; - padding: 3px 10px; - text-align: center; - -o-background-size: 100% 100%; - -moz-background-size: 100% 100%; - -webkit-background-size: auto auto !important; - background-size: 100% 100%; - background: #d8d8d8 none; - background: none, -webkit-gradient(linear, left top, left bottom, from(#efefef), to(#d8d8d8)); - background: none, -webkit-linear-gradient(#efefef, #d8d8d8); - background: none, -moz-linear-gradient(#efefef, #d8d8d8); - background: none, -o-linear-gradient(top, #efefef, #d8d8d8); - background: none, -khtml-gradient(linear, left top, left bottom, from(#efefef), to(#d8d8d8)); - background: -ms-linear-gradient(top, #efefef, #d8d8d8); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#efefef', endColorstr='#d8d8d8',GradientType=0 ); - -moz-border-radius: 3px; - -webkit-border-radius: 3px; - -o-border-radius: 3px; - -ms-border-radius: 3px; - border-radius: 3px; - -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(255, 255, 255, 0.8) inset; - -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(255, 255, 255, 0.8) inset; - -o-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(255, 255, 255, 0.8) inset; - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(255, 255, 255, 0.8) inset; - text-shadow: 0 1px 1px rgba(255, 255, 255, 0.5); - -webkit-font-smoothing: antialiased; - outline: none; -} - -.openerp .oe_button:hover { - -o-background-size: 100% 100%; - -moz-background-size: 100% 100%; - -webkit-background-size: auto auto !important; - background-size: 100% 100%; - background: #e3e3e3 none; - background: none, -webkit-gradient(linear, left top, left bottom, from(#f6f6f6), to(#e3e3e3)); - background: none, -webkit-linear-gradient(#f6f6f6, #e3e3e3); - background: none, -moz-linear-gradient(#f6f6f6, #e3e3e3); - background: none, -o-linear-gradient(top, #f6f6f6, #e3e3e3); - background: none, -khtml-gradient(linear, left top, left bottom, from(#f6f6f6), to(#e3e3e3)); - background: -ms-linear-gradient(top, #f6f6f6, #e3e3e3); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f6f6f6', endColorstr='#e3e3e3',GradientType=0 ); - cursor: pointer; -} - -.openerp .oe_button:focus { - border: 1px solid #80bfff; - -o-background-size: 100% 100%; - -moz-background-size: 100% 100%; - -webkit-background-size: auto auto !important; - background-size: 100% 100%; - background: #e3e3e3, none; - background: none, -webkit-gradient(linear, left top, left bottom, from(#f6f6f6), to(#e3e3e3)); - background: none, -webkit-linear-gradient(#f6f6f6, #e3e3e3); - background: none, -moz-linear-gradient(#f6f6f6, #e3e3e3); - background: none, -o-linear-gradient(top, #f6f6f6, #e3e3e3); - background: none, -khtml-gradient(linear, left top, left bottom, from(#f6f6f6), to(#e3e3e3)); - background: -ms-linear-gradient(top, #f6f6f6, #e3e3e3); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f6f6f6', endColorstr='#e3e3e3',GradientType=0 ); - -moz-box-shadow: 0 0 3px #80bfff, 0 1px 1px rgba(255, 255, 255, 0.8) inset; - -webkit-box-shadow: 0 0 3px #80bfff, 0 1px 1px rgba(255, 255, 255, 0.8) inset; - -o-box-shadow: 0 0 3px #80bfff, 0 1px 1px rgba(255, 255, 255, 0.8) inset; - box-shadow: 0 0 3px #80bfff, 0 1px 1px rgba(255, 255, 255, 0.8) inset; -} - -.openerp .oe_button:active, -.openerp .oe_button.active { - background: #e3e3e3; - background: -moz-linear-gradient(top, #e3e3e3, #f6f6f6) #1b468f; - background: -webkit-gradient(linear, left top, left bottom, from(#e3e3e3), to(#f6f6f6)) #1b468f; - background: linear-gradient(top, #e3e3e3, #f6f6f6) #1b468f; - background: -ms-linear-gradient(top, #e3e3e3, #f6f6f6); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#e3e3e3', endColorstr='#f6f6f6',GradientType=0 ); - -moz-box-shadow: none, 0 0 0 transparent; - -webkit-box-shadow: none, 0 0 0 transparent; - -o-box-shadow: none, 0 0 0 transparent; - box-shadow: none, 0 0 0 transparent; -} - -.openerp .oe_button.disabled, -.openerp .oe_button:disabled { - background: #efefef !important; - border: 1px solid #d1d1d1 !important; - font-size: 12px; - padding: 3px 10px; - -moz-box-shadow: none !important, 0 0 0 transparent; - -webkit-box-shadow: none !important, 0 0 0 transparent; - -o-box-shadow: none !important, 0 0 0 transparent; - box-shadow: none !important, 0 0 0 transparent; - color: #aaaaaa !important; - cursor: default; - text-shadow: 0 1px 1px white !important; -} - .openerp select.oe_search-view-filters-management { font-style: oblique; color: #999999; @@ -2043,24 +1046,6 @@ ul.oe-arrow-list li.oe-arrow-list-selected .oe-arrow-list-after { color: black; } -/* Debug stuff */ -.openerp .oe_debug_view_log { - font-size: 95%; -} -.openerp .oe_debug_view_log label { - display: block; - width: 49%; - text-align: right; - float: left; - font-weight: bold; - color: #009; -} -.openerp .oe_debug_view_log span { - display: block; - width: 49%; - float: right; - color: #333; -} /* Internet Explorer Fix */ a img { diff --git a/addons/web/static/src/font/mnmliconsv21-webfont.eot b/addons/web/static/src/font/mnmliconsv21-webfont.eot Binary files differnew file mode 100755 index 00000000000..433ebb82e4b --- /dev/null +++ b/addons/web/static/src/font/mnmliconsv21-webfont.eot diff --git a/addons/web/static/src/font/mnmliconsv21-webfont.svg b/addons/web/static/src/font/mnmliconsv21-webfont.svg new file mode 100755 index 00000000000..34b20ff992f --- /dev/null +++ b/addons/web/static/src/font/mnmliconsv21-webfont.svg @@ -0,0 +1,149 @@ +<?xml version="1.0" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" > +<svg xmlns="http://www.w3.org/2000/svg"> +<metadata> +This is a custom SVG webfont generated by Font Squirrel. +Copyright : MIT License +Designer : Carlos Elas +Foundry : Carlos Elias +Foundry URL : httpcarloseliascl +</metadata> +<defs> +<font id="webfontfL60xSgA" horiz-adv-x="1536" > +<font-face units-per-em="2048" ascent="1638" descent="-410" /> +<missing-glyph horiz-adv-x="512" /> +<glyph horiz-adv-x="0" /> +<glyph horiz-adv-x="0" /> +<glyph unicode="
" horiz-adv-x="512" /> +<glyph unicode=" " horiz-adv-x="512" /> +<glyph unicode="	" horiz-adv-x="512" /> +<glyph unicode=" " horiz-adv-x="512" /> +<glyph unicode="!" horiz-adv-x="1556" d="M1302 840q30 -10 52 -31t35 -47t16 -55t-7 -59q-12 -34 -36 -58t-54 -38q-26 -52 -52 -114t-38 -96q-10 -32 -23 -80l-27 -98q32 -38 32 -92q0 -64 -44 -108t-108 -44q-34 0 -65 15t-51 41q-30 2 -62 6t-63 6t-59 4t-46 2q-36 0 -97 -5t-115 -9q-28 -38 -72 -52 q-60 -20 -116 8t-76 86q-14 40 -6 78t32 68l-23 90q-15 60 -31 112q-12 34 -34 86l-44 100q-40 12 -74 50q-40 50 -33 112t55 102q66 52 146 26l33 21q21 13 44 30l47 34q24 17 44 35q34 28 86 76l94 88q4 50 32 84q40 48 102 54t112 -34q28 -22 41 -51t15 -63l91 -89 q49 -47 77 -71q28 -22 77 -57l95 -65q46 18 98 2zM1160 814h-2h2zM1148 804l-4 -4zM1138 792l-4 -4zM1128 780q-4 -4 -4 -6q0 2 4 6zM1118 764q0 -2 -1 -2t-1 -2q0 2 1 2t1 2zM156 552h2h-2zM138 552h2h-2zM214 558h-6h6zM174 552h-2h2zM226 562l4 4zM242 570q0 2 2 4 q-2 -2 -2 -4zM260 584l12 12zM522 64v4v-4zM500 -6v2v-2zM506 10q2 2 4 2q-2 0 -4 -2zM514 26v2v-2zM518 104v-6v6zM520 86q2 -2 2 -6q0 4 -2 6zM920 152q36 52 90 66q22 48 41 97t31 83q18 52 32 112t22 90q-16 20 -25 44t-9 52l-88 84q-48 44 -82 72q-42 34 -93 70t-79 52 q-56 -20 -108 6l-93 -66q-47 -34 -75 -56q-30 -22 -78 -70l-90 -88q0 -50 -32 -92l25 -101q13 -51 23 -81q6 -18 17 -44l23 -54l26 -58l26 -56q48 -20 76 -66q0 2 -2 2q54 -4 114 -7t100 -3q54 0 116 4t92 8zM514 120q-4 12 -8 16q4 -4 8 -16zM1110 744q-2 -6 -2 -16 q0 10 2 16zM1106 722q-2 -4 -2 -6q0 2 2 6zM1132 198v2v-2zM1118 206l-2 2zM1104 212q-2 2 -6 2q4 0 6 -2zM1088 218q-2 2 -6 2q4 0 6 -2zM1072 222h-6h6z" /> +<glyph unicode=""" horiz-adv-x="1526" d="M16 338q0 70 26 132t72 108t108 72t132 26h18q10 60 40 111t74 89t100 59t120 21q70 0 131 -26t107 -72t73 -107t27 -129q0 -12 -2 -21t-2 -17q12 4 24 4h22q60 0 114 -23t93 -63t62 -93t23 -113t-23 -114t-62 -93t-93 -62t-114 -23h-22h-684q-6 0 -13 -2t-13 -2 q-70 0 -132 27t-108 73t-72 107t-26 131z" /> +<glyph unicode="#" horiz-adv-x="1520" d="M1286 216v-10q0 -44 -16 -81t-44 -65t-65 -44t-79 -16h-802q-42 0 -80 16t-66 44t-44 65t-16 81v12l114 -68l490 -10l494 10zM1352 592v-206l-66 -66v-80l-116 -68l-484 -10h-8l-488 10l-116 68v72l-76 74v206l76 74v90q0 42 16 79t44 66t66 45t80 16h130l102 104h118v86 q4 34 16 58q4 4 6 10t6 10q6 8 22 20q8 4 28 12q10 2 19 3t19 1h56q16 26 46 26q22 0 38 -16t16 -38t-16 -38t-38 -16q-30 0 -46 26h-56q-12 0 -28 -8q-10 -2 -20 -16t-10 -34v-86h114l102 -104h178q42 0 79 -16t65 -45t44 -66t16 -79v-100zM318 284q22 0 37 15t15 37 t-15 39t-37 17t-39 -17t-17 -39t17 -37t39 -15zM1024 284q22 0 37 15t15 37t-15 39t-37 17t-39 -17t-17 -39t17 -37t39 -15z" /> +<glyph unicode="$" horiz-adv-x="1460" d="M642 1288q132 0 249 -51t204 -138t138 -204t51 -249q0 -134 -51 -251t-138 -204t-204 -138t-249 -51q-134 0 -252 51t-205 138t-137 204t-50 251q0 132 50 249t137 204t205 138t252 51zM642 1222q-14 0 -25 -1t-25 -1q-4 -2 -11 -2t-13 -2h-10l-46 -8q-24 -4 -46 -12 q58 4 117 0t113 -32q6 -22 27 -26t44 -5t40 -6t15 -31l4 -4q-2 -2 -1 -6t3 2q8 -8 18 -20t16 -22t4 -16t-18 -4q-22 4 -31 -12t-8 -39t11 -44t26 -29q14 -8 25 -5t15 11t-1 18t-19 18q-2 20 11 17t26 -17t20 -32t-9 -28q-4 -2 -6 -4v2q0 -2 -2 -2q-18 -12 -23 -33t-6 -44 l-2 -47q-1 -24 -7 -44q-12 -28 -1 -37t27 -4t30 20t8 37q-8 20 4 27t28 11t28 8t4 18q24 -12 36 -4t14 25t-4 37t-16 32q6 18 13 19t13 -7t8 -21t-4 -23q2 -20 16 -27t26 -11q2 -2 4 -2q12 -4 16 -14t-6 -32q-16 -48 -57 -57t-81 1q-20 -8 -33 -23t-22 -33t-18 -38t-19 -36 q-12 -22 -21 -46t-12 -49t4 -48t27 -41q18 -18 38 -21t40 4t39 20l37 25q30 4 47 -10t22 -36t-1 -47t-20 -43q-2 -6 -2 -14q64 76 101 171t37 203q0 34 -6 64q-12 -6 -27 5t-23 23t-6 18t24 -12q20 0 26 10q-8 52 -30 104q-6 10 -16 16q2 6 1 12t-1 10v2q-36 72 -89 132 t-120 102t-146 66t-163 24zM398 1146q-34 -28 -73 -64l-78 -75q-39 -39 -72 -81t-53 -84q-18 -52 -19 -105t3 -109q16 -52 49 -98t61 -94q18 -24 15 -49t-5 -49v-2q0 -16 3 -30t15 -28q24 -32 54 -59t46 -67v-2q30 -18 61 -32t65 -26q18 2 35 7t35 9v2q28 10 54 24t48 30 q18 24 13 43t-23 33t-43 19t-43 -1q-44 28 -81 67t-85 61q-22 18 -52 25t-58 15t-47 24t-19 54q4 34 -17 55t-43 37q-8 26 8 44t40 23t45 -6t21 -41q16 0 28 13t24 28l25 30q13 15 31 19q10 8 26 10t29 4t20 7t-3 23q10 8 26 14t29 14t18 19t-9 31q-4 20 -18 35t-30 19 t-31 -3t-23 -31q-22 -6 -30 11t-4 39t20 38t40 10q16 0 24 5t8 14t-6 17t-16 14q0 2 -2 2q-4 2 -16 2q-22 12 -17 33t14 43t11 41t-28 29zM570 1056q-2 0 -3 -1t-3 -1q-10 -2 -14 -9t-2 -13t10 -7t20 11q6 4 8 11t-8 7q-4 2 -8 2zM616 1046q-16 -2 -29 -9t-21 -19t-8 -26 t14 -28q10 -12 6 -30l-8 -35q-4 -17 -1 -32t27 -21q8 20 24 39t29 38t20 41t1 46q-4 22 -21 30t-33 6zM496 978q-6 0 -8 -2q6 -12 4 -24t-5 -24t-3 -24t10 -22q12 6 20 22t8 32t-6 29t-20 13zM830 842q-6 0 -6 -5t12 -15q-10 -22 6 -24t26 14q2 12 -13 21t-25 9zM494 752 q-6 -2 -7 -6t15 -14q2 0 9 1t1 -7q22 14 8 21t-26 5zM372 704q-6 0 -12 -2q2 0 2 -2h2q6 -2 9 1t-1 3zM254 542q-20 -4 12 -24q4 -4 11 -9t15 -1q4 12 -3 20t-17 12q-2 2 -10 2h-8z" /> +<glyph unicode="%" horiz-adv-x="1478" d="M180 1142q32 0 53 -22t21 -52v-146h-146v146q0 30 21 52t51 22zM290 886q30 0 52 -22t22 -54v-72q0 -32 -22 -53t-52 -21h-220q-30 0 -51 21t-21 53v72q0 32 21 54t51 22h220zM254 628v-548q0 -32 -21 -53t-53 -21q-30 0 -51 21t-21 53v548h146zM1104 1142q30 0 52 -22 t22 -52v-146h-148v146q0 30 22 52t52 22zM1214 886q30 0 51 -22t21 -54v-72q0 -32 -21 -53t-51 -21h-220q-30 0 -51 21t-21 53v72q0 32 21 54t51 22h220zM1178 628v-548q0 -32 -22 -53t-52 -21t-52 21t-22 53v548h148zM632 6q-30 0 -52 21t-22 53v146h146v-146 q0 -32 -21 -53t-51 -21zM522 262q-32 0 -53 21t-21 53v72q0 30 21 52t53 22h220q30 0 51 -22t21 -52v-72q0 -32 -21 -53t-51 -21h-220zM558 518v550q0 30 22 52t52 22t51 -22t21 -52v-550h-146z" /> +<glyph unicode="&" d="M1200 276q54 -14 71 -34t11 -44t-30 -48t-54 -46t-60 -38l-46 -24q-26 -12 -83 -25t-125 -25t-135 -20t-111 -8t-112 9t-136 21t-124 25t-76 23q-12 4 -38 20t-56 38t-56 47t-34 49t7 45t69 35q80 20 121 68t59 114t20 142t2 154q0 76 38 128t84 84q52 36 122 58 q-2 44 10 80q10 30 34 56t70 26t70 -26t34 -56q10 -36 10 -80q68 -22 122 -58q46 -32 84 -84t38 -128q0 -78 2 -154t20 -142t59 -114t119 -68zM660 88q92 0 175 11t146 26t99 33t36 32q0 12 -27 23t-74 21t-112 16t-139 10q0 -42 -29 -71t-71 -29t-71 29t-29 71 q-76 -4 -140 -10t-111 -16t-74 -21t-27 -23q0 -14 35 -32t95 -33t142 -26t176 -11z" /> +<glyph unicode="'" horiz-adv-x="1252" d="M1188 2h-1186l206 776h140q-32 42 -50 91t-18 105q0 66 25 123t67 99t100 67t124 25t123 -25t100 -67t68 -99t25 -123q0 -108 -66 -196h138zM596 1086q-46 0 -80 -33t-34 -79q0 -48 34 -82t80 -34q48 0 81 34t33 82q0 46 -33 79t-81 33z" /> +<glyph unicode="(" horiz-adv-x="1198" d="M34 506q-18 22 -27 50t-9 57t9 55t27 44l528 432h58q58 0 92 -22t34 -78v-106q0 -20 6 -36t42 -16h306q36 0 68 -31t32 -69v-354q0 -40 -32 -70t-68 -30h-306q-36 0 -42 -14t-6 -34v-100q0 -60 -34 -95t-92 -35q-10 6 -18 12q-8 4 -18 8t-22 4z" /> +<glyph unicode=")" horiz-adv-x="1198" d="M1158 698q18 -22 27 -50t9 -57t-9 -55t-27 -44l-528 -432h-58q-58 0 -92 22t-34 78v106q0 20 -6 36t-42 16h-306q-36 0 -68 31t-32 69v354q0 40 32 70t68 30h306q36 0 42 14t6 34v100q0 60 34 95t92 35l16 -12q8 -4 19 -8t23 -4z" /> +<glyph unicode="*" horiz-adv-x="1638" d="M1532 1222q16 0 28 -12t12 -30v-1080q0 -16 -12 -28t-28 -12h-1492q-16 0 -28 12t-12 28v1080q0 18 12 30t28 12h1492zM588 750q10 10 8 20q4 14 -8 22l-94 94q-4 2 -5 4t-3 2l-206 206q-20 16 -40 0l-96 -96q-6 -8 -6 -20t6 -18l192 -194l-192 -190q-6 -8 -6 -20t6 -20 l96 -96q20 -16 40 0l206 208q4 0 8 4zM1302 448q0 16 -12 28t-28 12h-582q-16 0 -28 -12t-12 -28v-90q0 -16 12 -29t28 -13h582q16 0 28 13t12 29v90z" /> +<glyph unicode="+" horiz-adv-x="1340" d="M1010 1280q62 0 105 -20t69 -51t38 -72t12 -83v-840q0 -42 -12 -81t-38 -71t-69 -51t-105 -19h-786q-42 0 -81 19t-71 51t-51 71t-19 81v840q0 42 19 83t51 72t71 51t81 20h786zM1010 720h-280v334h-226v-334h-280v-226h280v-280h226v280h280v226z" /> +<glyph unicode="," horiz-adv-x="1328" d="M1072 566l146 -146q28 -28 42 -65t14 -75t-14 -74t-42 -62l-72 -72q-28 -28 -64 -42t-73 -14t-74 14t-65 42l-146 146zM622 1016l358 -360l-348 -346l-358 358zM384 628q16 -16 36 -16t36 16q14 12 14 33t-14 35q-16 16 -36 15t-36 -17q-14 -12 -14 -33t14 -33zM662 490 q-12 14 -33 14t-35 -14q-16 -16 -16 -36t16 -34q14 -16 35 -16t33 16q16 14 16 34t-16 36zM768 594q-16 14 -36 14t-36 -14q-14 -16 -14 -37t14 -37q16 -12 36 -12t36 14q14 16 14 36t-14 36zM558 594q-14 14 -34 14t-34 -14q-16 -16 -16 -37t16 -37q12 -12 33 -12t35 12 q16 16 16 37t-16 37zM870 694q-14 16 -34 17t-36 -15q-14 -14 -14 -35t14 -33q16 -16 36 -16t34 16q16 12 16 33t-16 33zM662 696q-12 16 -33 16t-35 -16q-16 -14 -16 -35t16 -33q14 -16 35 -16t33 16q14 12 14 33t-14 35zM768 800q-16 16 -36 16t-36 -16q-14 -14 -14 -35 t12 -33q16 -16 37 -16t37 16q14 14 14 34t-14 34zM558 800q-14 16 -34 16t-34 -16q-16 -14 -16 -34t16 -32q14 -16 34 -16t34 16q16 12 16 32t-16 34zM662 834q16 16 16 36t-16 36q-12 14 -33 14t-35 -14q-16 -16 -16 -36t16 -36q14 -14 35 -14t33 14zM144 1218q26 28 62 42 t74 14t75 -15t67 -43l108 -112l-344 -344l-114 108q-28 30 -42 66t-14 74t14 74t42 64z" /> +<glyph unicode="-" horiz-adv-x="1364" d="M1064 1288q48 0 90 -18t73 -50t49 -75t18 -93v-822q0 -48 -18 -91t-49 -75t-73 -51t-90 -19h-828q-48 0 -91 19t-75 51t-51 75t-19 91v822q0 50 19 93t51 75t75 50t91 18h828zM1064 756h-296h-532v-230h532h296v230z" /> +<glyph unicode="." horiz-adv-x="1072" d="M0 1184q0 44 31 75t77 31h642q46 0 77 -31t31 -75v-1074q0 -44 -31 -75t-77 -31h-642q-46 0 -77 31t-31 75v1074zM194 1074q-24 0 -42 -18t-18 -42v-692q0 -26 18 -43t42 -17h470q24 0 42 17t18 43v692q0 24 -18 42t-42 18h-470zM362 144q0 -28 20 -48t48 -20t47 20 t19 48t-19 47t-47 19t-48 -19t-20 -47zM542 1158q0 28 -28 28h-170q-10 0 -19 -8t-9 -20q0 -10 9 -19t19 -9h170q12 0 20 9t8 19z" /> +<glyph unicode="/" horiz-adv-x="1224" d="M904 274q16 -14 16 -34t-16 -36q-14 -14 -34 -14t-34 14l-92 92q-4 -4 -9 -10t-9 -10q-22 -22 -45 -37t-47 -25l-2 -88q-2 -12 -9 -27t-17 -25l-62 -64q-16 -16 -36 -16t-34 16q-16 16 -16 36t16 34l42 42q10 12 12 22l2 44q-44 -4 -89 0t-87 30q-2 0 -4 2q-4 2 -8 6 l-54 54l-4 4l-54 54q-6 2 -6 8l-4 4q-24 44 -27 89t-1 87l-44 -2q-14 0 -22 -12l-42 -42q-16 -14 -35 -14t-35 14q-16 16 -16 35t16 35l62 62q10 10 25 17t27 7l88 4q24 52 64 92l9 9q5 5 11 9l-92 92q-14 14 -14 34t14 36q14 14 34 14t36 -14l48 -50v2l66 -66q14 4 27 7 l29 7l4 120q0 14 9 30t19 26l64 64q14 14 34 14t34 -14q16 -16 16 -36t-16 -34l-44 -46q-12 -12 -14 -22l-2 -76q34 10 69 28t69 52q38 40 91 46t99 -22l-64 -64q-12 -12 -13 -28t11 -26q0 -2 2 -4q12 -10 28 -10t26 10l66 68q28 -46 22 -99t-46 -93q-34 -34 -52 -68 t-30 -70l78 4q8 0 20 12l48 46q14 14 34 14t34 -14q16 -16 16 -36t-16 -34l-62 -64q-10 -10 -27 -18t-29 -8l-120 -6q-4 -14 -7 -27l-7 -29l64 -64z" /> +<glyph unicode="0" horiz-adv-x="1114" d="M128 1250q30 8 60 3t56 -21t45 -42t23 -56q4 -48 5 -97t1 -97q124 0 246 2t244 -4q46 -8 83 -41t43 -81q8 -30 2 -61t-23 -57t-43 -45t-56 -23q-126 -10 -250 -7t-250 1q2 -46 2 -91t6 -91q8 -44 41 -79t79 -39q94 -8 187 -5t187 -5q40 -6 69 -34t43 -65t9 -77t-33 -70 q-14 -26 -37 -40t-49 -20t-54 -7t-54 -1q-96 2 -193 -1t-189 25q-70 20 -130 64t-103 102t-68 127t-25 143q-4 150 -3 300t1 300q-4 32 3 64t24 58t43 45t58 23z" /> +<glyph unicode="1" horiz-adv-x="1198" d="M874 774q-20 -10 -28 -26t-9 -34t4 -36t11 -36q6 -16 19 -33t21 -33q12 -28 14 -59t-2 -63q-4 -28 -18 -52t-34 -45t-42 -40l-42 -35q-24 -18 -48 -26t-44 -16t-34 -23t-18 -49t-6 -48t-3 -18t-1 -6t-2 -14q0 -2 16 -4t37 -4l41 -4q20 -2 28 -6t21 -12t24 -17t19 -18 t8 -11q0 -4 -45 -5t-100 -1t-102 1t-53 5q-6 2 2 13t19 23t21 24t10 16t-1 23l-2 40q-1 21 -5 40t-10 23q-14 10 -42 16t-58 16q-44 14 -98 11t-100 -3q-70 0 -141 -12t-141 -12l-40 60q62 8 114 16l78 11q36 5 36 9t-24 14t-52 20q-34 12 -74 28l64 16q24 6 42 10t14 2 q58 22 123 30t123 32q16 6 52 32t76 60t75 68t47 56l22 43l19 37q9 18 17 37t14 45l15 65q7 31 39 49q28 18 64 18t68 -8q24 -6 53 -31t56 -56t46 -60t23 -43q2 -4 -23 -5t-55 -1t-58 1t-32 -1q-4 0 -8 -4z" /> +<glyph unicode="2" horiz-adv-x="1460" d="M1282 1282v-1280h-1282v1280h1282zM1106 1190h-174q-70 -2 -115 -22t-71 -52t-36 -76t-10 -94v-110h-116v-196h116v-548h232v548h152l22 196h-174v110q0 26 12 42t42 16h120v186z" /> +<glyph unicode="3" d="M1024 976q78 0 146 -29t119 -80t81 -118t30 -143q0 -78 -30 -147t-81 -120t-119 -81t-146 -30q-102 0 -188 52t-132 138q-52 -86 -137 -138t-189 -52q-78 0 -146 30t-119 81t-80 120t-29 147q0 76 29 143t80 118t119 80t146 29q104 0 189 -51t137 -135q46 84 132 135 t188 51zM1218 410q82 76 82 196q0 110 -82 192q-36 36 -86 57t-108 21q-56 0 -105 -21t-85 -57q-40 -40 -61 -89t-21 -103q0 -58 21 -109t61 -87q74 -80 190 -80q58 0 108 20t86 60z" /> +<glyph unicode="4" horiz-adv-x="1466" d="M0 1284h1286v-1286h-1286v1286zM1170 114v528h-526v526h-526v-528h526v-526h526z" /> +<glyph unicode="5" horiz-adv-x="1240" d="M314 1072q0 44 28 66q24 26 72 26q42 0 66 -26q26 -24 26 -68v-186q0 -22 -5 -40t-21 -30q-26 -26 -70 -26q-42 0 -68 26q-28 22 -28 72v186zM382 880q0 -16 8 -24q4 -4 20 -4t20 4q10 10 10 24v194q0 14 -10 20q-8 8 -20 8q-6 0 -10 -2t-10 -6q-8 -6 -8 -20v-194z M156 988l-90 294h76l48 -196h6l46 196h76l-86 -282v-202h-76v190zM610 794q-26 0 -34 14q-6 10 -10 20t-4 28v298h68v-276q0 -8 4 -16q2 -6 16 -6q6 0 18 6q8 12 18 18v274h68v-356h-68v36l-18 -18q-8 -8 -18 -14q-22 -8 -40 -8zM1082 524q4 -50 6 -101t2 -101 q0 -54 -2 -104t-6 -102q0 -66 -47 -111t-111 -45q-94 -4 -188 -7t-192 -3q-100 0 -194 3t-190 7q-64 0 -111 45t-47 111q-4 52 -6 102t-2 104q0 50 2 101t6 101q0 32 13 61t34 51t50 35t61 13q188 6 384 6q194 0 380 -6q32 0 61 -13t50 -35t34 -51t13 -61zM232 500h76v68 h-226v-68h76v-416h74v416zM500 442h-70v-270q-12 -16 -20 -22q-10 -6 -16 -6q-12 0 -16 4q-4 8 -4 20v274h-66v-298q0 -32 12 -50q12 -14 34 -14q20 0 40 10q10 4 19 14t17 20v-40h70v358zM754 356q0 44 -18 68q-16 22 -46 22q-16 0 -30 -8q-18 -6 -30 -26v156h-68v-484h68 v30q6 -10 14 -16t16 -10q14 -8 32 -8q30 0 46 20q8 12 12 25t4 33v198zM998 346q0 54 -22 78q-28 28 -72 28t-72 -28q-12 -16 -20 -35t-8 -43v-160q0 -50 26 -82q22 -28 68 -28q52 0 76 24q24 30 24 86v28h-68v-26q0 -16 -1 -27t-5 -15q-8 -8 -24 -8q-10 0 -22 8 q-6 12 -6 42v66h126v92zM900 388q18 0 24 -6q6 -16 6 -36v-36h-58v36q0 20 6 36q8 6 22 6zM660 390q12 0 20 -8q4 -8 4 -28v-184q0 -24 -4 -26q-8 -8 -20 -8q-4 0 -16 6q-8 0 -14 10v224q4 4 14 12q4 0 8 1t8 1z" /> +<glyph unicode="6" horiz-adv-x="1442" d="M1214 802q-12 -178 -248 -482q-250 -320 -420 -320q-102 0 -178 196l-48 178l-50 178q-54 196 -116 196q-16 0 -96 -56l-58 74q46 38 89 78l89 80q58 52 104 78t74 30q140 12 176 -194q18 -112 30 -180t16 -96q42 -184 92 -184q36 0 114 118q74 122 82 182 q10 104 -82 104q-22 0 -45 -5t-47 -15q92 290 344 284q186 -8 178 -244z" /> +<glyph unicode="7" horiz-adv-x="1466" d="M1174 1284q46 0 78 -32t32 -78v-1066q0 -46 -32 -78t-78 -32h-1066q-46 0 -78 32t-32 78v1066q0 46 32 78t78 32h1066zM952 352q-74 -50 -152 -50q-46 0 -76 22q-24 16 -36 38q-8 24 -8 112v260h240v160h-240v256h-144q-12 -78 -36 -130q-14 -26 -31 -48t-39 -38 q-42 -38 -100 -56v-144h110v-354q0 -68 16 -106t54 -68q18 -16 40 -29t50 -23q54 -16 122 -16q60 0 112 12q28 4 57 16t61 28v158z" /> +<glyph unicode="8" horiz-adv-x="1454" d="M682 702q0 2 2 2v-2h-2zM1180 1282q38 0 66 -26t28 -66v-1100q0 -38 -28 -65t-66 -27h-1088q-40 0 -67 27t-27 65v1100q0 40 27 66t67 26h1088zM382 788h-190v-580h190v580zM288 866q50 0 79 29t29 73q0 40 -29 69t-79 29q-48 0 -78 -29t-30 -69q0 -44 29 -73t77 -29h2z M1078 540q0 134 -63 198t-159 64q-38 0 -66 -10t-48 -24t-34 -32l-24 -32v84h-194q0 -10 1 -41t1 -74v-94v-103q0 -122 -2 -268h194v324v24q0 12 4 22q10 26 35 49t65 23q54 0 76 -37t22 -95v-310h192v332z" /> +<glyph unicode="9" horiz-adv-x="1310" d="M1162 514q24 -40 38 -85t14 -95q0 -70 -27 -132t-73 -108t-108 -73t-134 -27q-50 0 -94 14t-84 38q-40 -8 -88 -8q-118 0 -220 44t-179 121t-121 179t-44 220q0 48 8 86q-24 40 -38 85t-14 95q0 72 27 134t73 108t108 73t132 27q50 0 95 -14t85 -38q38 8 88 8 q116 0 219 -44t180 -121t121 -180t44 -219q0 -50 -8 -88zM874 344q16 28 26 56t10 62q0 52 -22 90q-16 36 -56 62q-40 24 -90 42q-26 8 -55 15l-61 15l-44 10q-18 4 -30 8q-20 6 -44 16q-22 12 -32 28q-12 12 -12 34q0 34 36 56q30 22 94 22t92 -20q14 -10 26 -26t22 -34 q14 -30 30 -42q18 -14 42 -14q32 0 50 22q20 20 20 48q0 14 -4 28t-10 30q-16 26 -52 56q-32 26 -86 44q-52 16 -120 16q-90 0 -152 -24q-68 -28 -102 -70q-32 -44 -32 -106q0 -64 30 -102q38 -44 90 -66q54 -24 138 -42q28 -8 53 -13t43 -11q40 -14 58 -34q24 -22 24 -54 q0 -42 -44 -70q-42 -32 -108 -32q-48 0 -80 16q-28 16 -46 36q-8 10 -15 25l-15 31q-14 30 -30 44t-42 14q-32 0 -52 -18q-20 -24 -20 -46q0 -24 8 -47t24 -47q32 -46 86 -76q76 -40 186 -40q92 0 164 30q34 14 59 34t45 44z" /> +<glyph unicode=":" horiz-adv-x="1382" d="M1002 390l128 -16l-14 -112l-128 16zM1028 462l-2 504l218 -26l-122 -490zM466 956q108 0 194 -28t148 -77t95 -114t33 -139t-34 -139t-96 -113t-149 -76t-191 -28t-190 28t-148 76t-96 113t-34 139t33 139t94 114t148 77t193 28zM754 710q-8 0 -23 1t-32 1h-33h-28h-33 h-37h-32h-20l4 -28q6 0 34 -3t40 -7q-2 -8 -18 -23l-35 -32q-19 -17 -37 -31t-22 -20q-16 22 -54 70t-68 94q6 2 18 3t26 1t26 1t16 3l4 20q-4 0 -22 1t-40 1h-45h-37q-46 -4 -86 -1t-46 -1v-24q6 0 18 -2t25 -3t23 -3t14 -4q12 -10 38 -38t52 -60t46 -59t22 -33 q2 -10 2 -21v-17v-16v-37q0 -9 -4 -13q-10 -10 -39 -10h-39l-6 -26h26q18 0 37 1t36 1h27h144v26h-17h-27q-14 0 -27 1t-15 1q-2 10 -4 26t-2 28v20q0 20 4 36q2 10 26 32t52 45t53 41t31 20l34 7q24 5 32 5z" /> +<glyph unicode=";" horiz-adv-x="1204" d="M594 628q22 -12 36 -35t14 -51q0 -26 -14 -48t-36 -36v-64h-98v64q-22 14 -36 36t-14 48q0 28 14 51t36 35v212h98v-212zM978 870q52 -68 82 -151t30 -177q0 -112 -43 -211t-117 -173t-174 -117t-212 -43t-211 43t-173 117t-117 173t-43 211q0 108 38 202t105 167 t158 119t195 56v102h-50v98h198v-98h-50v-102q78 -8 149 -36t129 -72l110 108l106 -106zM544 98q92 0 173 35t142 95t96 141t35 173t-35 173t-96 142t-142 96t-173 35t-173 -35t-141 -96t-95 -142t-35 -173t35 -173t95 -141t141 -95t173 -35z" /> +<glyph unicode="<" horiz-adv-x="1198" d="M614 1290q78 0 145 -29t118 -80t80 -119t29 -146q0 -50 -18 -138t-47 -190t-68 -207t-81 -190t-85 -139t-79 -54q-44 0 -89 54t-85 139t-75 190t-61 207t-41 189t-15 139q0 78 29 146t80 119t118 80t145 29zM614 828q58 0 100 42t42 100t-42 100t-100 42t-100 -42 t-42 -100t42 -100t100 -42z" /> +<glyph unicode="=" horiz-adv-x="1400" d="M1270 930q34 0 59 -25t25 -61v-650q0 -34 -25 -58t-59 -24h-1186q-34 0 -58 24t-24 58v650q0 36 24 61t58 25h252l54 102q0 12 9 23t25 11h510q12 0 22 -11t10 -23l52 -102h252zM676 192q70 0 132 27t108 73t73 107t27 131t-27 132t-73 108t-108 73t-132 27t-131 -27 t-107 -73t-73 -108t-27 -132t27 -131t73 -107t107 -73t131 -27zM886 530q0 -42 -17 -80t-45 -67t-66 -45t-82 -16q-42 0 -80 16t-67 45t-45 67t-16 80q0 44 16 82t45 66t67 45t80 17q44 0 82 -17t66 -45t45 -66t17 -82z" /> +<glyph unicode=">" horiz-adv-x="1204" d="M146 16q-24 -22 -57 -22t-59 22q-22 26 -22 59t22 57l1028 1024q24 22 58 22t58 -22q22 -24 22 -58t-22 -58zM530 12q-22 -22 -56 -22t-58 22q-24 24 -24 58t24 58l650 652q26 24 59 24t57 -24q22 -22 22 -56t-22 -58zM940 30q-24 -22 -58 -22t-56 22q-22 26 -22 59 t22 57l228 228q24 24 58 24t58 -24q22 -22 22 -56t-22 -56z" /> +<glyph unicode="?" horiz-adv-x="1400" d="M646 1284q132 0 249 -50t204 -137t137 -205t50 -252q0 -132 -50 -249t-137 -204t-204 -137t-249 -50t-250 50t-205 137t-138 204t-51 249q0 134 51 252t138 205t205 137t250 50zM650 258q6 42 -14 76q-24 32 -64 42q-44 10 -76 -14q-38 -22 -44 -62q-10 -44 14 -76 q24 -36 64 -44q44 -10 76 14q36 24 44 64zM978 776q18 80 -12 152q-26 70 -90 118q-56 46 -144 64q-42 10 -94 6q-50 -2 -102 -22q-26 -10 -46 -23t-38 -29q-36 -36 -44 -82q-8 -40 6 -70q18 -30 52 -38q12 -4 25 -1t23 7q20 12 54 36q16 12 31 22t31 16q26 10 64 4 q44 -8 68 -44q28 -34 18 -74q-12 -52 -42 -66q-26 -16 -82 -22q-30 -2 -52 -3t-34 -5q-24 -6 -30 -36l-26 -128q-8 -38 12 -68q18 -28 60 -38q38 -8 72 10q30 20 38 58l10 46q106 2 180 56q72 56 92 154z" /> +<glyph unicode="@" d="M1368 948q28 0 47 -18t19 -46v-818q0 -28 -19 -47t-47 -19h-1304q-28 0 -47 19t-19 47v818q0 28 19 46t47 18h1304zM1366 806q16 14 20 34t-10 36q-14 20 -36 22t-38 -8l-584 -446l-588 438q-20 12 -40 8t-34 -20q-14 -18 -10 -39t20 -33l620 -458q12 -12 32 -12 q18 0 30 12z" /> +<glyph unicode="A" horiz-adv-x="882" d="M178 1260q12 10 27 10q9 0 19 -4q22 -10 28 -32l88 -264q10 -16 0 -36q-8 -20 -24 -26l-44 -20q-22 -10 -30 -30t2 -42l32 -92l28 -79q14 -41 28 -69q12 -28 31 -63l35 -67l42 -76q8 -22 29 -30t43 2l44 20q16 10 38 0q20 -10 26 -24l138 -242q7 -12 7 -24q0 -10 -5 -20 q-12 -22 -32 -30h-6q-48 -12 -112 -20q-17 -2 -33 -2q-45 0 -85 16q-36 14 -75 49t-77 86t-74 114t-66 131l-36 76q-4 4 -4 5t-2 5l-9 21q-9 21 -19 47t-20 47t-10 23q-30 68 -52 137t-34 131q-13 68 -13 122q0 48 13 82q10 26 29 51t41 46t46 38l44 31z" /> +<glyph unicode="B" horiz-adv-x="1466" d="M1234 742q18 0 34 -16t16 -34v-100q0 -22 -16 -36t-34 -14h-60q-20 0 -39 -13t-27 -33l-34 -84q-8 -20 -5 -42t19 -38l40 -40q16 -16 16 -35t-16 -35l-70 -70q-16 -16 -35 -16t-35 16l-40 40q-16 16 -38 19t-42 -5l-80 -34q-22 -8 -36 -27t-14 -39v-60q0 -18 -14 -34 t-36 -16h-100q-18 0 -31 16t-13 34v60q0 20 -16 39t-34 27l-80 34q-20 8 -42 5t-38 -19l-40 -40q-16 -16 -38 -16t-32 16l-70 70q-16 16 -16 35t16 35l40 40q16 16 18 38t-8 42l-30 84q-8 20 -27 33t-39 13h-60q-18 0 -34 14t-16 36v100q0 18 16 34t34 16l60 -6q20 0 39 16 t27 34l30 80q10 20 8 42t-18 38l-40 46q-16 10 -16 32t16 38l70 64q10 16 32 16t38 -16l40 -40q16 -16 38 -18t42 8l80 36q18 4 34 22t16 38v60q0 22 13 36t31 14h100q50 0 50 -50v-60q0 -20 14 -38t36 -22l80 -36q20 -10 42 -8t38 18l40 40q16 16 35 16t35 -16l70 -64 q16 -16 16 -38t-16 -32l-40 -46q-16 -16 -19 -38t5 -42l34 -80q8 -18 27 -31t39 -13h60zM888 642q0 50 -19 95t-53 78t-80 52t-98 19q-50 0 -95 -19t-78 -52t-52 -78t-19 -95q0 -52 19 -98t52 -80t78 -53t95 -19q52 0 98 19t80 53t53 80t19 98z" /> +<glyph unicode="C" horiz-adv-x="1574" d="M1224 1280q38 0 71 -14t58 -39t39 -58t14 -71v-612q0 -74 -53 -127t-129 -53h-672v-318l-356 318h-14q-38 0 -72 14t-59 39t-39 58t-14 69v612q0 38 14 71t39 58t59 39t72 14h1042z" /> +<glyph unicode="D" horiz-adv-x="1466" d="M820 470v414q0 34 -24 58t-58 24h-12q-34 0 -59 -24t-25 -58v-414q0 -34 25 -58t59 -24h12q34 0 58 24t24 58zM820 214q0 38 -27 63t-63 25q-38 0 -64 -25t-26 -63q0 -40 26 -66t64 -26q36 0 63 26t27 66zM1358 -2h-1258q-58 0 -79 38q-9 16 -9 34q0 26 18 56l628 1086 q28 54 71 54t75 -54l626 -1086q19 -31 19 -58q0 -17 -8 -32q-21 -38 -83 -38z" /> +<glyph unicode="E" horiz-adv-x="1502" d="M1152 842l-322 326l102 106q22 18 53 18t53 -18l220 -220q22 -22 22 -53t-22 -53zM-4 48l58 310q0 30 22 56l678 676l324 -326l-676 -676q-6 -6 -22 -14t-22 -12l-322 -58q-5 -1 -9 -1q-14 0 -23 10t-9 25q0 4 1 10z" /> +<glyph unicode="F" horiz-adv-x="1514" d="M1114 1416l4 -96l-88 34l-88 -34l6 96l-62 74l94 24l50 78l50 -78l92 -24zM770 1512l8 -142l-130 50l-132 -50l10 142l-92 108l138 36l76 118l76 -118l134 -36zM1240 1062l-178 178q-18 18 -42 18t-44 -18l-262 -260h4q17 0 56 -26q44 -30 98 -82q52 -52 82 -97 q24 -36 24 -52q0 -4 -2 -7l264 260q18 20 18 44t-18 42zM812 814q-56 54 -101 85q-34 23 -48 23q-5 0 -7 -2l-4 -2l-620 -620v-4l-12 -10q-16 -18 -16 -42t16 -42l180 -178q16 -18 41 -18t41 18l60 56h-4l580 578q2 2 2 7q0 14 -22 50q-30 47 -86 101z" /> +<glyph unicode="G" d="M642 1288q134 0 252 -51t205 -140t138 -207t51 -252q0 -132 -51 -250t-138 -206t-205 -139t-252 -51t-252 51t-207 139t-140 206t-51 250q0 134 51 252t140 207t207 140t252 51zM642 202q90 0 169 35t138 94t94 138t35 169t-35 170t-94 140t-138 94t-169 34t-170 -34 t-140 -94t-94 -140t-34 -170t34 -169t94 -138t140 -94t170 -35zM878 638q0 -48 -19 -91t-51 -75t-75 -51t-91 -19q-50 0 -93 19t-76 51t-52 75t-19 91q0 50 19 93t52 76t76 52t93 19q48 0 91 -19t75 -52t51 -76t19 -93z" /> +<glyph unicode="H" horiz-adv-x="1300" d="M1198 1282q36 0 63 -27t27 -63l-46 -466q-4 -26 -18 -40q-22 -22 -52 -22t-50 22l-52 50q-26 20 -52 0l-216 -220q-22 -22 -51 -22t-51 22l-178 178q-22 22 -22 52t22 52l218 216q10 10 10 25t-10 25l-50 52q-22 22 -22 50t22 50q14 18 40 22zM892 416q10 6 14 8l102 104 v-476q0 -22 -16 -38t-40 -16h-900q-22 0 -38 16t-16 38v900q0 24 16 40t38 16h488l-108 -108l-8 -8h-272q-16 0 -27 -11t-11 -27v-702q0 -18 11 -28t27 -10h704q16 0 26 10t10 28v264z" /> +<glyph unicode="I" horiz-adv-x="1204" d="M100 96q-50 50 -75 112t-25 127t25 127t75 112q70 70 161 93q41 10 82 10q50 0 99 -15l418 418l202 -22l22 -200l-158 -40l-22 -140l-150 -32l-18 -136l-72 -72q16 -50 16 -101q0 -40 -10 -80q-22 -91 -92 -161q-50 -50 -112 -75t-127 -25t-127 25t-112 75zM332 330 q-34 34 -83 34t-83 -34t-34 -82t34 -82t83 -34t83 34t34 82t-34 82z" /> +<glyph unicode="J" horiz-adv-x="1460" d="M638 1288q134 0 251 -51t205 -139t139 -205t51 -251t-51 -251t-139 -205t-205 -139t-251 -51t-251 51t-205 139t-139 205t-51 251t51 251t139 205t205 139t251 51zM638 1124q-30 0 -51 -20t-21 -52v-306q-54 -34 -54 -104q0 -54 36 -93t90 -39t92 39t38 93q0 34 -15 61 t-39 43v306q0 32 -23 52t-53 20zM810 574q-28 -76 -104 -104l104 -104q22 -24 52 -24t52 24q22 22 22 53t-22 51z" /> +<glyph unicode="K" horiz-adv-x="1370" d="M442 156l-282 282v402l282 284h402l284 -284v-402l-284 -282h-402zM878 74l332 332v468l-332 332h-468l-332 -332v-468l332 -332h468zM348 26l-318 318q-12 14 -20 33t-8 35v454q0 16 8 36t20 32l318 320q14 10 34 18t34 8h454q16 0 36 -8t32 -18l316 -320q12 -12 22 -32 t10 -36v-454q0 -16 -10 -35t-22 -33l-316 -318q-12 -12 -32 -20t-36 -8h-454q-14 0 -34 8t-34 20z" /> +<glyph unicode="L" horiz-adv-x="1442" d="M642 968q52 80 121 120t141 46q14 1 27 1q58 0 114 -21q69 -26 123 -77t87 -123t33 -160q0 -90 -36 -169t-91 -146t-123 -123l-130 -104l-106 -81l-71 -56l-50 -41q-21 -18 -39 -38q-20 20 -42 38l-52 41l-72 55l-104 82l-129 105q-67 57 -123 123t-92 143t-36 167 q0 88 33 161t87 125t124 78q57 21 115 21q13 0 27 -1q72 -6 141 -46t123 -120z" /> +<glyph unicode="M" horiz-adv-x="1484" d="M1060 444l26 -444l-416 162l-418 -162l26 444l-282 348l432 114l242 378l244 -378l432 -114z" /> +<glyph unicode="N" d="M744 726q48 0 83 -36t35 -88q0 -50 -35 -86t-83 -36q-52 0 -89 36t-37 86q0 52 37 88t89 36zM744 1094q76 0 148 -21t138 -56t124 -79t108 -90q116 -110 214 -246q-98 -138 -214 -246q-50 -46 -108 -90t-124 -78t-138 -55t-148 -21t-148 21t-138 55t-125 78t-109 90 q-118 108 -216 246q98 136 216 246q50 46 109 90t125 79t138 56t148 21zM744 298q62 0 118 24t98 65t66 96t24 119t-24 120t-66 97t-98 65t-118 24q-66 0 -122 -24t-97 -65t-65 -97t-24 -120t24 -119t65 -96t97 -65t122 -24z" /> +<glyph unicode="O" horiz-adv-x="858" d="M276 -2v152q-80 2 -151 22t-117 46l48 184q50 -28 117 -49t145 -21q70 0 111 26t41 72t-39 77t-129 61q-128 44 -207 109t-79 179q0 108 70 184t198 98v152h156v-140q78 -4 134 -18t96 -34l-48 -180q-30 16 -86 36t-142 20q-76 0 -105 -28t-29 -62q0 -38 44 -68t146 -68 q142 -48 203 -118t61 -178q0 -52 -18 -100t-54 -86t-88 -65t-120 -39v-164h-158z" /> +<glyph unicode="P" horiz-adv-x="1460" d="M832 670q-16 -10 -36 -10q-26 0 -43 17t-17 39v104q-60 -2 -113 -25t-93 -63t-63 -93t-23 -113q0 -62 23 -116t63 -94t94 -63t116 -23q54 0 102 18t87 50t65 76t36 96q4 20 25 38t53 18h140q22 0 37 -17t15 -39v-14q-14 -104 -63 -194t-124 -157t-170 -105t-203 -38 q-118 0 -220 44t-179 121t-121 179t-44 220q0 116 44 218t120 178t178 120t218 44v104q0 26 17 41t43 15q10 0 19 -1t17 -7l306 -230q27 -28 27 -55.5t-27 -54.5z" /> +<glyph unicode="Q" horiz-adv-x="1258" d="M204 402q16 -20 12 -46t-28 -40q-20 -16 -46 -11t-42 23q-46 70 -72 148t-26 162v27q0 13 4 27v10q0 6 3 14t3 18v4q4 8 4 13t4 9q0 8 2 17t2 15q4 4 5 8t5 10q0 16 8 32q6 6 6 8v2q12 18 18 40l2 2v2q20 34 43 65t51 59q8 8 16 15t20 15q4 4 6 10q8 4 15 9l15 11h2 q136 94 306 94v108l252 -170l-252 -176v112q-76 0 -140 -26t-116 -66q-2 0 -3 -2t-5 -2l-13 -13q-5 -5 -13 -11v-4q-22 -22 -38 -42t-32 -48h-2q-6 -8 -8 -17t-6 -15v-4q-8 -20 -14 -32v-10q-4 -6 -7 -13t-3 -15t-4 -12q0 -20 -4 -26v-16v-32q-1 -11 -1 -22q0 -52 16 -103 q19 -61 55 -115zM542 -4l-258 170l258 174v-110q70 0 137 25t121 67v4q40 30 69 68t45 86q4 8 7 17l5 15q0 2 4 10q0 4 3 11t3 13t2 8t2 6q0 8 2 14t2 14v8v38q1 12 1 23q0 115 -73 217q-14 22 -9 47t29 37q16 16 36 16q5 0 10 -1q26 -5 42 -25q96 -142 96 -310v-10v-19 q0 -11 -4 -21v-18q0 -8 -2 -13t-2 -13v-10q-6 -6 -6 -20q-4 -6 -4 -14t-4 -16q-4 -4 -4 -12q-4 -10 -7 -21t-7 -21l-4 -6q-50 -118 -150 -198l-6 -4q-6 -8 -13 -12t-19 -12l-2 -4q-64 -46 -141 -69t-159 -23v-106z" /> +<glyph unicode="R" horiz-adv-x="1328" d="M1248 914q24 -24 24 -57t-24 -57l-322 -268q-18 -12 -40 -12q-28 0 -46 18t-18 46v64q0 14 -10 23t-22 9h-194q-28 0 -47 18t-19 46v226q0 28 19 46t47 18h194q12 0 22 10t10 24v64q0 26 18 45t46 19q24 0 40 -14zM350 666q12 14 36 14q28 0 46 -19t18 -45v-66 q0 -12 10 -22t24 -10h194q26 0 45 -18t19 -46v-226q0 -28 -19 -46t-45 -18h-194q-14 0 -24 -9t-10 -23v-64q0 -28 -18 -46t-46 -18q-24 0 -36 12l-326 268q-24 24 -24 57t24 57z" /> +<glyph unicode="S" d="M1184 1288q42 0 72 -30t30 -74v-1082q0 -42 -30 -72t-72 -30h-1084q-42 0 -72 30t-30 72v1082q0 44 30 74t72 30h1084zM244 836q0 -28 17 -45t43 -17h674q28 0 45 17t17 45v388v2h-796v-2v-388zM1140 594q0 20 -15 34t-37 14h-894q-22 0 -36 -14t-14 -34v-446 q0 -20 14 -34t36 -14h894q22 0 37 14t15 34v446zM496 868q0 -10 -8 -19t-20 -9h-106q-10 0 -19 9t-9 19v280q0 12 9 20t19 8h106q28 0 28 -28v-280zM1082 530q0 -24 -26 -24h-830q-26 0 -26 24q0 26 26 26h830q26 0 26 -26zM1082 376q0 -26 -26 -26h-830q-26 0 -26 26t26 26 h830q26 0 26 -26zM1082 212q0 -10 -8 -17t-18 -7h-830q-10 0 -18 7t-8 17t8 19t18 9h830q10 0 18 -9t8 -19z" /> +<glyph unicode="T" horiz-adv-x="1358" d="M1026 840q40 38 40 93t-40 95q-38 38 -92 38t-92 -38q-40 -40 -40 -95t40 -93q38 -40 92 -40t92 40zM36 588l640 636q16 18 37 24t43 4q6 2 20 2l400 -42h6q34 0 56 -20q19 -18 19 -45q0 -7 -1 -15l14 -368q-2 -8 -7 -21t-11 -26t-13 -25t-13 -18l-640 -638 q-28 -28 -65 -28t-65 28l-418 418q-28 28 -28 67t26 67z" /> +<glyph unicode="U" horiz-adv-x="1400" d="M874 492q-12 -24 -5 -53t19 -59l24 -60q12 -30 11 -54t-20 -40t-67 -18q-9 -1 -18 -1q-51 0 -105 20q-63 23 -122 57t-107 72t-74 62h-2q-4 2 -8 6t-9 8t-9 10l-4 2q-22 22 -56 61t-65 88t-55 106t-24 115v11q0 39 13 57q15 20 39 22t54 -8t61 -19t61 -13t52 10 q32 20 70 57t69 77t51 79t20 65q0 24 8 47t27 37t50 15h3q30 0 72 -19q76 -32 129 -67t83 -57q2 -2 5 -4t5 -4q2 -4 5 -6t5 -4q26 -28 64 -78t78 -120q24 -49 24 -84q0 -26 -11 -44q-13 -20 -35 -30t-48 -10q-24 -2 -61 -25t-75 -58t-71 -75t-51 -74zM-6 0l252 466 q14 -18 28 -38l27 -36q15 -20 31 -36t37 -33t41 -31q22 -16 48 -30z" /> +<glyph unicode="V" horiz-adv-x="1418" d="M638 0q-132 0 -249 51t-204 138t-138 204t-51 249t51 249t138 204t204 138t249 51t249 -51t204 -138t138 -204t51 -249t-51 -249t-138 -204t-204 -138t-249 -51zM196 642q0 -92 35 -172t95 -140t140 -95t172 -35q130 0 238 68l-612 612q-68 -108 -68 -238zM1080 642 q0 92 -34 172t-94 140t-141 95t-173 35q-130 0 -238 -68l612 -612q68 108 68 238z" /> +<glyph unicode="W" horiz-adv-x="1132" d="M846 800q24 0 40 -18t16 -42v-684q0 -24 -16 -41t-40 -17h-788q-24 0 -40 17t-16 41v684q0 24 16 42t40 18h42v124q0 72 28 136t76 112t112 76t136 28q74 0 139 -28t113 -76t76 -112t28 -136v-124h38zM678 800v124q0 46 -18 87t-49 72t-72 49t-87 18t-87 -18t-72 -49 t-49 -72t-18 -87v-124h452z" /> +<glyph unicode="X" horiz-adv-x="1484" d="M-8 58v688q0 26 17 42t43 16h624v124q0 48 -18 89t-49 72t-72 49t-89 18t-90 -18t-74 -50l-114 -12l54 -102q-4 -10 -4 -22v-24v-34h-130v34q0 74 28 139t77 114t114 77t139 28t139 -28t114 -77t77 -114t28 -139v-124h38q22 0 39 -16t17 -42v-688q0 -26 -17 -43t-39 -17 h-792q-26 0 -43 17t-17 43z" /> +<glyph unicode="Y" d="M1138 1282l310 -770v-514h-1446v564l294 720h842zM984 506l294 6l-224 594h-652l-218 -572h282v-284h518v256z" /> +<glyph unicode="Z" d="M1452 516v-516h-1452v566l298 722h844zM1284 516l-228 596h-654l-216 -574h282v-286h518v258zM772 488q-18 -16 -45 -16t-43 16l-184 224v24q0 54 42 54h46q8 0 15 3t7 19v130q0 16 13 29t29 13h152q16 0 29 -13t13 -29v-130q0 -16 6 -19t14 -3h42q56 0 56 -54 q-4 -2 -6 -6q-4 -8 -4 -18z" /> +<glyph unicode="[" horiz-adv-x="1364" d="M1000 626q22 -22 22 -52t-22 -52l-486 -486q-22 -22 -51 -22t-51 22l-370 372q-22 22 -22 52t22 52l486 486q22 22 52 22t52 -22l6 -8l242 244q26 26 62 26t62 -26l238 -238q26 -26 26 -62t-26 -62l-244 -242zM992 914q-12 -14 -12 -32t12 -30l20 -22q30 -30 64 0l20 22 q14 12 14 30t-14 32l-20 22q-34 26 -64 0zM840 1068q-30 -34 0 -66l20 -18q12 -14 31 -14t31 14l22 18q26 32 0 66l-22 18q-12 14 -31 14t-31 -14zM912 542q12 12 12 29t-12 29l-308 310q-12 12 -29 12t-29 -12l-424 -424q-12 -12 -12 -29t12 -29l310 -310q12 -12 29 -12 t29 12z" /> +<glyph unicode="\" horiz-adv-x="1448" d="M638 1286q132 0 248 -51t203 -138t138 -203t51 -248q0 -134 -51 -251t-138 -204t-203 -138t-248 -51q-134 0 -250 51t-203 138t-137 204t-50 251q0 132 50 248t137 203t203 138t250 51zM638 152q102 0 191 39t157 106t107 157t39 192t-39 191t-107 155t-157 105t-191 39 t-192 -39t-157 -105t-106 -155t-39 -191t39 -192t106 -157t157 -106t192 -39zM536 748l380 176l-174 -384l-382 -176zM568 714l-140 -278l282 138z" /> +<glyph unicode="]" horiz-adv-x="1102" d="M774 758q8 -24 32 -41t54 -31l59 -27q29 -13 45 -31t14 -42t-36 -60q-40 -44 -103 -71t-129 -42t-126 -20t-96 -7l-2 -2q-4 -2 -10 -2h-24l-4 2q-32 0 -83 5t-108 18t-113 39t-98 68q-34 34 -37 59t13 42t45 31l58 29q29 15 53 32t30 43q10 36 12 88t-3 103t-17 93 t-30 60t-27 41t-5 46t25 45t67 38q76 28 139 39t101 15q2 0 6 1t6 1q4 -2 8 -1t6 1q38 -4 101 -13t139 -33q46 -16 69 -38t28 -45t-5 -46t-28 -41q-16 -18 -27 -60t-14 -93t0 -103t15 -90zM474 -204l-138 512l46 -8q20 -4 44 -7t46 -7q22 -2 50 1t52 7l56 12z" /> +<glyph unicode="^" horiz-adv-x="1016" d="M730 452q18 -6 25 -22t1 -34q-4 -16 -18 -20l-606 -368q-16 -12 -36 -2q-16 4 -24 20t-2 32q4 6 8 14l200 278l-242 94q-18 6 -25 23t-1 33l8 12q4 6 10 8l348 212l-236 90q-18 6 -25 23t-1 33q8 14 20 22l604 368q20 8 38 2q16 -6 24 -23t0 -33q-4 -2 -4 -5t-2 -5 l-200 -280l240 -94q18 -6 26 -23t0 -33q-2 -14 -18 -22l-344 -208z" /> +<glyph unicode="_" horiz-adv-x="1398" d="M954 82q-46 52 -46 132q0 44 17 83t46 68t68 46t83 17q60 0 112 -34q-44 -102 -116 -181t-164 -131zM640 1282q132 0 249 -51t204 -138t138 -204t51 -249q0 -52 -8 -101t-22 -95q-32 16 -63 25t-67 9q-56 0 -105 -21t-85 -57t-57 -84t-21 -102q0 -46 14 -84t38 -72 q-126 -58 -266 -58q-142 0 -256 56q-18 6 -44 20q-4 2 -6 4t-6 4v-2q-96 52 -168 131t-112 181l4 4q-4 2 -8 10l-7 18q-3 8 -5 18h-2q-30 92 -30 196q0 132 50 249t137 204t204 138t249 51zM272 884q10 20 4 41t-28 31q-16 10 -38 6t-32 -26q-10 -18 -6 -39t26 -33 q18 -10 40 -6t34 26zM640 1206q-22 0 -38 -17t-16 -39t16 -37t38 -15q24 0 38 15t14 37t-14 39t-38 17zM428 1040q18 12 24 34t-6 38q-12 20 -33 26t-41 -4t-23 -32t7 -40q6 -22 28 -27t44 5zM674 510q16 24 40 75t44 105t29 98t-5 52q-12 8 -48 -17t-79 -64t-82 -80 t-53 -65q-32 -46 -32 -94t34 -70q32 -22 77 -4t75 64zM924 1062q12 18 8 40t-26 32q-18 10 -39 4t-33 -26q-10 -16 -6 -38t26 -34q18 -10 39 -5t31 27zM1082 864q20 12 26 33t-4 39q-10 22 -32 26t-40 -6q-22 -10 -28 -31t4 -41q12 -22 33 -26t41 6z" /> +<glyph unicode="`" horiz-adv-x="512" d="M0 768v512h256v-512h-256z" /> +<glyph unicode="a" horiz-adv-x="1144" d="M904 970l-82 -930q0 -16 -12 -28t-28 -12h-574q-16 0 -28 12t-12 28l-82 930h818zM966 1136q22 -14 22 -30v-54q0 -22 -22 -22h-334h-280h-334q-20 0 -20 22v54q0 16 20 30q20 6 38 14q16 6 31 13t21 13q4 6 11 20t13 30q8 18 12 38q4 6 9 15t19 9h180h280h180 q14 0 19 -9t9 -15q4 -20 12 -38q6 -16 13 -30t13 -20t20 -13t30 -13q18 -8 38 -14zM622 1158q0 20 -20 20h-218q-20 0 -20 -20v-14q0 -22 20 -22h218q20 0 20 22v14z" /> +<glyph unicode="b" horiz-adv-x="1200" d="M142 694q0 14 14 14h4q8 0 14 -6l34 -58l38 58q2 6 12 6h2q6 0 12 -4t6 -10v-104q0 -6 -6 -12t-12 -6q-4 0 -9 6t-5 12v66l-24 -40q-8 -8 -14 -8t-12 8l-26 38v-66q0 -4 -4 -10t-10 -6t-10 6t-4 10v106zM286 592l46 108q8 12 18 12h2q14 0 18 -12l46 -108q0 -2 2 -2l2 -2 q0 -4 -6 -10t-10 -6q-6 0 -8 4t-6 8l-8 20h-62l-8 -20q-4 -12 -14 -12q-4 0 -8 6t-4 10v4zM370 630l-20 46l-18 -46h38zM428 694q0 14 14 14h46q28 0 38 -12q12 -12 12 -30q0 -34 -26 -42l20 -26q6 -6 6 -10q0 -16 -14 -16q-8 0 -10 3t-6 7l-28 36h-24v-28q0 -6 -3 -12 t-11 -6q-6 0 -10 6t-4 12v104zM486 644q22 0 22 18q0 20 -22 20h-30v-38h30zM514 436l-28 -8h-14q-14 0 -25 9t-11 23q0 30 26 34l52 14q12 4 20 7t18 3q18 0 29 -12t11 -28v-292q0 -18 -11 -30t-29 -12q-16 0 -27 12t-11 30v250zM682 206l136 236h-138q-16 0 -26 10t-10 26 t10 25t26 9h196q34 0 34 -34q0 -22 -10 -42l-146 -262q-16 -30 -36 -30q-18 0 -31 11t-13 27q0 4 3 12t5 12zM362 1026q0 -40 -27 -67t-67 -27t-66 27t-26 67v160q0 40 26 68t66 28t67 -28t27 -68v-160zM916 1026q0 -40 -26 -67t-66 -27t-68 27t-28 67v160q0 40 28 68t68 28 t66 -28t26 -68v-160zM1018 1094h-34v-90q0 -34 -13 -63t-35 -50t-51 -34t-61 -13q-68 0 -113 47t-45 113v90h-10h-226h-2v-90q0 -66 -47 -113t-113 -47q-64 0 -111 47t-47 113v90h-32q-34 0 -56 -23t-22 -55v-938q0 -34 22 -56t56 -22h940q30 0 53 22t23 56v938q0 32 -23 55 t-53 23zM994 170q0 -24 -18 -41t-42 -17h-772q-24 0 -42 17t-18 41v532q0 24 18 41t42 17h772q24 0 42 -17t18 -41v-532z" /> +<glyph unicode="c" horiz-adv-x="1466" d="M544 644q0 -18 1 -29t5 -20t14 -19l28 -28q36 -42 92 -44h7q52 0 93 26q6 0 12 6t6 12l230 222l58 58q44 42 44 104t-44 106t-106 44t-104 -44l-20 -18q-52 22 -106 23h-6q-51 0 -100 -15l144 144q44 44 99 65t113 21t113 -21t99 -65l8 -8q44 -44 65 -98t21 -109 t-21 -109t-65 -98l-192 -192l-144 -144q-58 -58 -138 -78q-38 -10 -76 -10q-41 0 -82 12q-30 8 -64 28t-62 48v10q-28 28 -36 44t-22 52zM792 626q0 36 -6 53t-42 53t-92 41q-8 1 -17 1q-46 0 -83 -24q-6 -8 -12 -10t-6 -8l-230 -222l-58 -66q-44 -44 -44 -106t44 -106 t106 -44t104 44l20 20q52 -16 106 -19h13q47 0 93 19l-144 -154q-44 -40 -99 -60t-113 -20t-113 20t-99 60l-8 18q-44 40 -66 93t-22 110t22 112t66 99l192 192l144 144q58 58 138 73q32 6 65 6q47 0 93 -13q30 -6 64 -25t62 -41v-10q14 -14 22 -26t14 -24t10 -25t12 -31z " /> +<glyph unicode="d" horiz-adv-x="1072" d="M906 778v-676q0 -42 -30 -72t-72 -30h-638q-44 0 -74 30t-30 72v676h844zM336 626q0 46 -44 46q-40 0 -40 -46v-464q0 -18 11 -32t29 -14q20 0 32 14t12 32v464zM534 626q0 46 -44 46q-16 0 -30 -12t-14 -34v-464q0 -18 14 -32t30 -14q20 0 32 14t12 32v464zM718 626 q0 22 -14 34t-30 12q-44 0 -44 -46v-464q0 -18 12 -32t32 -14q16 0 30 14t14 32v464zM968 864h-966v162q0 24 17 43t43 19h162v162q0 14 9 24t23 10h440q16 0 25 -10t9 -24v-162h178q24 0 42 -19t18 -43v-162zM642 1152q0 14 -10 23t-24 9h-262q-14 0 -24 -9t-10 -23v-64 h330v64z" /> +<glyph unicode="e" horiz-adv-x="1430" d="M264 180q56 4 93 35q33 28 33 76v11q-4 54 -45 79q-35 22 -80 22q-7 0 -15 -1q-54 -4 -91 -34q-33 -27 -33 -75v-11q4 -54 44 -80q35 -23 80 -23q7 0 14 1zM228 754q54 4 90 35q32 28 32 74v11q-4 56 -44 80q-34 21 -79 21q-7 0 -15 -1q-56 -4 -93 -33q-33 -26 -33 -74 v-11q4 -56 45 -81q35 -22 82 -22q7 0 15 1zM204 1058q10 1 20 1q77 0 136 -42q66 -47 76 -137q2 -46 -16 -78t-46 -64l142 -66l512 348q44 25 89 25l31 -3q60 -12 124 -50l-612 -388l658 -302q-58 -46 -116 -65q-26 -8 -52 -8q-33 0 -68 13l-556 276l-130 -84q34 -26 53 -53 t23 -73q1 -11 1 -21q0 -77 -53 -126q-60 -55 -148 -61q-13 -1 -26 -1q-75 0 -132 40q-66 47 -72 137q-1 8 -1 17q0 79 53 128q58 55 146 61l150 104l-170 86q-86 0 -149 46t-67 132q-1 8 -1 17q0 79 53 129q58 56 148 62z" /> +<glyph unicode="f" horiz-adv-x="1258" d="M966 1288q46 0 77 -31t31 -77v-1070q0 -46 -31 -77t-77 -31h-856q-46 0 -77 31t-31 77v1070q0 46 31 77t77 31h856zM862 592h-218v374h-212v-374h-214l322 -320z" /> +<glyph unicode="g" horiz-adv-x="864" d="M686 1084v-738q0 -44 -15 -104t-54 -114t-105 -91t-168 -37t-169 37t-106 91t-55 114t-16 104v688q0 4 4 40t28 80q62 126 214 126q76 0 131 -32t83 -94q24 -44 28 -80t4 -40v-638q0 -22 -8 -49t-25 -49t-45 -37t-68 -15q-42 0 -70 15t-46 37t-26 49t-8 49v442 q0 22 14 36t36 14t36 -14t14 -36v-442q0 -16 10 -33t40 -17q28 0 37 17t9 33v638q0 8 -3 33t-17 50t-44 45t-82 20q-88 0 -128 -70q-8 -16 -11 -30l-5 -24q-2 -12 -2 -24v-688q0 -10 5 -50t28 -83t73 -78t140 -35t140 37t74 82t28 84t4 43v738q0 22 14 36t32 14q22 0 36 -14 t14 -36z" /> +<glyph unicode="h" horiz-adv-x="1460" d="M342 170q0 -68 -50 -118t-122 -50q-36 0 -68 14t-55 37t-37 53t-14 64q0 72 51 123t123 51t122 -51t50 -123zM840 12h-228q0 120 -46 229t-132 195q-86 90 -195 136t-229 46v228q170 -4 321 -71t263 -179t178 -263t68 -321zM1282 12h-228q-2 216 -84 406t-224 332 t-331 225t-405 85v224q174 -2 336 -48t302 -130t256 -200t199 -257t130 -302t49 -335z" /> +<glyph unicode="i" horiz-adv-x="1412" d="M644 1284q132 0 249 -50t204 -137t137 -205t50 -252q0 -132 -50 -249t-137 -204t-204 -137t-249 -50t-250 50t-205 137t-138 204t-51 249q0 134 51 252t138 205t205 137t250 50zM766 814q0 62 -70 62q-32 0 -84 -34q-28 -20 -54 -42t-54 -54q-52 -58 -52 -70v-16 q2 -4 8 -5t10 -1q8 0 36 36q76 90 106 90q10 0 10 -16q0 -10 -5 -27t-19 -43l-162 -326q-14 -34 -22 -60t-8 -46q0 -64 72 -64q36 0 84 30q24 22 51 44t57 50q28 30 40 50t12 24q0 12 -2 16q-8 10 -16 10q-4 0 -46 -44q-78 -88 -96 -88q-10 0 -10 18q0 10 5 27t19 45 l162 326q28 62 28 108zM854 1022q0 34 -28 62q-24 24 -58 24q-16 0 -32 -6t-26 -18q-24 -24 -24 -62q0 -32 24 -56q10 -12 26 -17t32 -5q36 0 58 22q28 28 28 56z" /> +<glyph unicode="j" d="M288 1038v236h804l6 -236h-810zM1334 198h-162v372h-958v-372h-156q-28 0 -45 19t-17 43v640q0 28 17 49t45 21h1276q28 0 45 -21t17 -49v-640q0 -24 -17 -43t-45 -19zM444 4l-150 150v342h804v-492h-654z" /> +<glyph unicode="k" horiz-adv-x="1072" d="M-4 58v1160q0 26 17 42t41 16h456l396 -392v-826q0 -24 -17 -41t-41 -17h-794q-24 0 -41 17t-17 41z" /> +<glyph unicode="l" horiz-adv-x="1112" d="M1002 646v-630q0 -18 -14 -30t-32 -12h-598q-20 0 -33 12t-13 30v884q0 16 13 29t33 13h344zM272 1034q-22 0 -38 -17t-16 -41v-372h-182q-14 0 -23 10t-9 24v614q0 10 9 19t23 9h240l208 -208v-38h-212z" /> +<glyph unicode="m" horiz-adv-x="1126" d="M-4 58v1160q0 26 17 42t41 16h456l396 -392v-826q0 -24 -17 -41t-41 -17h-794q-24 0 -41 17t-17 41zM448 148q74 0 141 28t116 78t78 115t29 141q0 74 -29 141t-78 116t-116 78t-141 29q-76 0 -141 -29t-115 -78t-78 -116t-28 -141q0 -76 28 -141t78 -115t115 -78 t141 -28zM198 510q0 38 11 71t29 63l344 -344q-30 -18 -63 -29t-71 -11q-52 0 -97 20t-79 54t-54 79t-20 97zM698 510q0 -76 -38 -134l-346 346q58 38 134 38q52 0 98 -20t80 -54t53 -79t19 -97z" /> +<glyph unicode="n" d="M650 206l-276 276l276 278q12 10 12 28t-12 28l-136 138q-15 13 -30 13l-30 -13l-296 -298q-4 0 -5 -2t-5 -4l-138 -136q-14 -14 -10 -32q-4 -16 10 -30l138 -136q6 -6 10 -6l296 -300q15 -12 30 -12l30 12l136 140q12 10 12 28t-12 28zM808 206l276 276l-276 278 q-10 10 -10 28t10 28l138 138q14 14 28.5 14t29.5 -14l296 -298q4 0 5 -2t5 -4l138 -136q14 -14 10 -32q4 -16 -10 -30l-138 -136q-6 -6 -10 -6l-296 -300q-15 -12 -30 -12l-30 12l-136 140q-10 10 -10 28t10 28z" /> +<glyph unicode="o" horiz-adv-x="1340" d="M1098 1280q74 0 126 -52t52 -126q0 -76 -50 -126v-2l-154 -156q20 -18 29 -45t9 -56t-9 -56t-29 -47l-154 -154l-104 102l-510 -514l-208 -48q-40 0 -70 30t-30 70l48 208l512 514l-100 100l154 154q20 20 47 29t56 9t56 -9t45 -29l156 154q26 26 59 38t69 12z" /> +<glyph unicode="p" horiz-adv-x="1466" d="M802 1238l40 -79q22 -45 47 -87t51 -76t50 -42q14 8 24 18q4 60 44 98t90 44q8 1 16 1q41 0 81 -26q47 -31 63 -109q6 -32 6 -55q0 -16 -3 -29q-7 -30 -26 -47t-47 -23t-59 -10t-60 -10t-51 -22q34 -76 73 -152l75 -148q-26 -16 -60 -39l-70 -46q-36 -23 -73 -42t-69 -25 q-10 4 -12 14t-6 14q4 26 4 50q0 39 -10 73q-16 55 -52 80q-24 16 -55 16q-16 0 -34 -4q-53 -13 -117 -81q-36 -38 -39 -69t13 -58t44 -48l54 -39l45 -31q19 -13 15 -23l-129 -77q-63 -37 -127 -79l-91 142l-91 142q-6 -4 -16 -7t-18 -5q-14 -48 -51 -92t-83 -59 q-17 -6 -34 -6q-29 0 -57 16q-45 25 -75 113q-14 42 -14 72q0 21 7 37q17 37 54 55t85 22t88 8q4 2 9 6t9 8l-80 150l-80 148l136 81l136 83q16 -20 21 -46t6 -54l2 -56q1 -28 11 -49t34 -34q21 -11 58 -11h12q44 0 84 26t64 65q26 42 26 90q0 42 -28 79q-18 10 -38 16 l-38 11q-18 5 -31 11t-17 18q-10 26 -3 26t7 8l132 77z" /> +<glyph unicode="q" horiz-adv-x="1620" d="M744 1280q154 0 290 -14t237 -38t159 -57t58 -71v-4q0 -44 -36 -94t-91 -103t-123 -109l-131 -109q-63 -53 -114 -104t-73 -95v-392q0 -38 -52 -63t-124 -25t-124 25t-52 63v392q-22 44 -72 95t-113 104l-131 109q-68 56 -124 109t-92 103t-36 94v4q0 38 58 71t159 57 t237 38t290 14zM744 1204q-118 0 -222 -8t-182 -22t-123 -32t-45 -38q0 -22 45 -39t123 -31t182 -21t222 -7t222 7t182 21t123 31t45 39q0 20 -45 38t-123 32t-182 22t-222 8z" /> +<glyph unicode="r" horiz-adv-x="1442" d="M1254 184q30 -32 30 -76t-30 -76q-16 -16 -37 -24t-39 -8q-46 0 -78 32l-308 308q-2 4 -2 6t-4 6q-62 -40 -132 -62t-146 -22q-106 0 -198 40t-160 109t-108 162t-40 197q0 106 40 199t108 162t160 109t198 40t198 -40t161 -109t109 -162t40 -199q0 -76 -21 -146 t-59 -128q4 -4 5 -6t5 -6zM508 428q72 0 136 28t111 75t74 110t27 135t-27 135t-74 110t-111 75t-136 28t-135 -28t-111 -75t-75 -110t-27 -135t27 -135t75 -110t111 -75t135 -28z" /> +<glyph unicode="s" horiz-adv-x="1152" d="M672 840q102 -150 182 -282l67 -116q33 -58 59 -112t42 -98t16 -74q0 -58 -25 -89t-53 -47q-36 -18 -78 -20h-368h-364q-44 2 -78 20q-30 16 -54 47t-24 89q0 30 15 74t41 98t59 112l67 116q80 132 184 282v346h-22q-20 0 -34 13t-14 35q0 18 14 31t34 13h358 q20 0 33 -13t13 -31q0 -22 -13 -35t-33 -13h-24v-346z" /> +<glyph unicode="t" horiz-adv-x="1466" d="M1308 1022q30 0 52 -23t22 -53v-876q0 -32 -22 -52t-52 -20h-1232q-32 0 -54 20t-22 52v876q0 30 22 53t54 23h226v218q0 16 12 29t28 13h698q18 0 30 -13t12 -29v-218h226zM412 1022h560v96q0 16 -13 28t-29 12h-478q-16 0 -28 -12t-12 -28v-96z" /> +<glyph unicode="u" d="M1360 234h-984q-28 0 -47 20t-19 46l-82 614l-204 176q-24 18 -24 48q0 28 21 47t45 19q26 0 44 -14h4l200 -172l1152 -94q28 0 49 -19t21 -43l-108 -562q0 -26 -20 -46t-48 -20zM1332 0q-36 0 -63 28q-24 24 -24 58q0 5 1 10q0 34 28 59t62 25q36 -4 61 -29t25 -65 q0 -36 -27 -61t-63 -25zM386 0q-36 0 -64 27t-28 63t28 63t64 27q34 0 62 -27t28 -63t-28 -63t-62 -27z" /> +<glyph unicode="v" horiz-adv-x="1346" d="M762 538v-300l160 -128v-108l-268 108l-268 -108v108l160 128v300l-482 -162v108l482 322v374q2 30 14 54q10 20 32 37t62 17t62 -17t32 -37q12 -24 14 -54v-374l480 -322v-108z" /> +<glyph unicode="w" horiz-adv-x="1172" d="M1004 984q-2 -2 -2 -6v-148q2 -6 2 -20q-2 -2 -2 -4v-2v-2q-6 -42 -45 -81t-113 -71q-76 -34 -169 -50t-191 -16h-21q-85 0 -166 14q-91 16 -163 50q-72 32 -107 73q-31 37 -31 74q0 4 1 8.5t1 8.5v146q-2 6 -2 20q2 6 2 18q18 -76 136 -128q72 -34 163 -49t187 -15 q98 0 191 17t169 49q70 30 108 67t48 77q4 -20 4 -30zM1004 674q-2 0 -2 -4v-150q2 -4 2 -18q-2 -2 -2 -8v-2q-6 -40 -45 -79t-113 -71q-76 -34 -169 -51t-191 -17h-20q-85 0 -167 15q-91 17 -163 49q-72 34 -107 75q-31 37 -31 74q0 4 1 8.5t1 8.5v144q-2 8 -2 22q2 4 2 8 v8q18 -72 136 -128q72 -32 163 -48t187 -16q98 2 191 18t169 50q70 30 108 67t48 77q2 -8 3 -15t1 -17zM1004 356q-2 -2 -2 -8v-146q2 -8 2 -20q-2 -2 -2 -6v-4q-6 -40 -45 -79t-113 -69q-76 -34 -169 -51t-191 -19q-96 0 -187 15t-163 49q-138 63 -138 153q0 3 1 7t1 6v146 q-2 6 -2 20q2 6 2 18q10 -34 43 -68t93 -60q72 -34 163 -49t187 -15q98 0 191 17t169 51q70 30 108 67t48 75q4 -20 4 -30zM134 962q-118 56 -136 128q10 40 49 77t107 67q76 34 170 50t192 18q96 0 188 -15t162 -49q118 -54 134 -128q-20 -82 -156 -142q-76 -34 -169 -51 t-191 -17h-21q-85 0 -166 14q-91 16 -163 48z" /> +<glyph unicode="x" horiz-adv-x="1358" d="M794 364h11q101 0 171 -36q74 -38 126 -96t86 -124l60 -116h-628h-628l60 116q34 66 85 124t124 96q69 36 168 36h11q34 0 54 23t20 55q0 10 -15 29t-31 37q-20 22 -44 46q-36 38 -64 85t-46 98t-27 102t-9 97q0 70 26 132t72 109t108 74t134 27h2q70 0 132 -27t108 -74 t73 -109t27 -132q0 -46 -9 -97t-27 -102t-45 -98t-63 -85q-26 -24 -46 -46q-16 -18 -30 -37t-14 -29q0 -32 20 -55t48 -23z" /> +<glyph unicode="y" horiz-adv-x="1298" d="M630 844v-210h426q14 0 14 -12v-288q0 -12 -14 -12h-426v-304q0 -20 -20 -20h-102q-20 0 -20 20v304h-300v-2q-18 -18 -36 2l-140 136q-10 10 -10 21t8 17l142 144q8 4 18 4t18 -8v-2h300v210h-384q-14 0 -14 12v288q0 12 14 12h384v108q0 18 20 18h102q20 0 20 -18v-108 h340v4q22 18 40 0l138 -142q9 -7 9 -16q0 -10 -9 -22l-138 -138q-8 -8 -19 -8t-19 8v2h-342z" /> +<glyph unicode="z" horiz-adv-x="1072" d="M724 942l142 -228q-74 6 -152 14q-66 6 -143 11t-147 5q-102 0 -175 -11t-107 -17v-694q0 -20 -20 -20h-100q-20 0 -20 20v1246q0 20 20 20h100q20 0 20 -20v-88q34 8 107 20t175 12q70 0 147 -6t143 -12l152 -16z" /> +<glyph unicode="{" horiz-adv-x="638" d="M452 8l-226 226l-226 -226v1268h452v-1268z" /> +<glyph unicode="|" horiz-adv-x="822" d="M540 1266q36 -10 58 -38t24 -64q38 8 71 -6t53 -48q18 -34 13 -70t-31 -62q32 -22 46 -55t4 -69q-10 -38 -38 -60t-66 -24q10 -34 -4 -69t-48 -53q-34 -20 -69 -15t-63 29q-22 -30 -55 -45t-69 -5t-59 40t-25 66q-34 -8 -69 6t-53 48q-20 32 -15 69t29 63q-30 20 -44 53 t-4 73q10 36 39 57t65 23q-8 36 6 70t48 54q32 20 69 14t63 -30q20 32 54 45t70 3zM492 1086q-60 16 -114 -15t-70 -91q-16 -62 15 -115t93 -69q30 -8 59 -3t54 19t43 37t26 53q16 60 -15 114t-91 70zM254 610q2 -36 24 -66t60 -40q36 -10 68 5t54 45q16 -16 35 -22t39 -8 l-144 -538l-80 139q-28 49 -59 60t-80 -16t-143 -87l144 540q42 -22 82 -12z" /> +<glyph unicode="}" horiz-adv-x="1406" d="M662 -52q-134 0 -250 56l250 532l92 -226l37 -92l36 -88l32 -78q15 -36 25 -58q-104 -46 -222 -46zM338 120q-74 36 -136 90t-106 123t-69 150t-25 169q0 70 17 136t47 124l522 -260zM932 188q-10 22 -24 57l-31 77l-36 88l-37 92l-92 224l-522 264q36 72 90 131t121 102 t146 67t165 24q120 0 227 -46t186 -126t125 -187t46 -229q0 -92 -27 -175t-75 -153t-115 -124t-147 -86z" /> +<glyph unicode="~" d="M1328 686q32 -34 34 -80t-30 -74h-8v-484q0 -22 -16 -38t-38 -16h-238v454q0 32 -23 55t-53 23h-224q-32 0 -54 -23t-22 -55v-454h-546q-20 0 -37 16t-17 38v478h-8q-32 34 -34 79t30 73l568 572q34 28 79 27t75 -35z" /> +<glyph unicode="­" horiz-adv-x="1364" d="M1064 1288q48 0 90 -18t73 -50t49 -75t18 -93v-822q0 -48 -18 -91t-49 -75t-73 -51t-90 -19h-828q-48 0 -91 19t-75 51t-51 75t-19 91v822q0 50 19 93t51 75t75 50t91 18h828zM1064 756h-296h-532v-230h532h296v230z" /> +<glyph unicode="É" horiz-adv-x="1502" /> +<glyph unicode="Ñ" horiz-adv-x="1030" d="M718 1178q-22 -116 4 -224t61 -212t59 -205t-4 -203q-22 -80 -73 -142t-118 -99t-147 -47t-162 12t-144 72t-101 117t-50 145t11 158q18 68 58 124t90 105t107 95t110 97t98 109t73 132l4 14q8 28 31 41t51 5q26 -8 39 -31t7 -49zM418 744q26 4 44 24t16 48q-4 28 -25 46 t-49 14t-45 -24t-15 -48t24 -45t50 -15zM306 594q16 12 20 34t-8 40q-16 22 -39 28t-47 -10q-28 -16 -50 -56t-36 -90q-12 -52 -9 -102t19 -74q14 -24 37 -29t45 7q16 10 24 32t0 40q-16 46 -5 95t49 85z" /> +<glyph unicode="á" horiz-adv-x="1394" d="M252 152q18 22 18 50t-18 50q-22 18 -52 18t-48 -18q-22 -22 -22 -50t22 -50q18 -22 48 -22t52 22zM64 294l18 18l500 500q-32 92 -10 187t92 165q62 62 141 88t159 12l-200 -200l66 -234l234 -66l200 200q10 -80 -14 -158t-86 -136q-70 -76 -167 -98t-185 10l-500 -500 l-18 -18q-48 -48 -114 -48t-116 48q-48 50 -48 118t48 112z" /> +<glyph unicode="é" horiz-adv-x="1376" d="M626 1022q-80 0 -149 -30t-120 -81t-81 -120t-30 -149q0 -82 30 -152t81 -121t120 -81t149 -30t151 30t122 81t81 121t30 152q0 80 -30 149t-81 120t-122 81t-151 30zM198 580l-168 14q-10 0 -20 12t-10 36q0 22 10 33t20 11l168 16v-122zM1054 702l170 -16q46 0 46 -44 q0 -14 -12 -31t-34 -17l-170 -14q14 22 14 61t-14 61zM688 214l-14 -170q0 -12 -10 -29t-20 -17q-24 0 -36 17t-12 29l-30 170q12 -6 30 -10t37 -4t34 4t21 10zM920 1008l104 92q0 12 17 12t31 -12q10 -12 10 -32t-10 -30l-92 -106q-12 22 -31 43t-29 33zM368 304l-108 -90 q-22 -28 -44 0q-14 10 -14 29t14 31l74 108q26 -56 78 -78zM994 382l78 -108q20 -24 0 -46q0 -10 -18 -10t-30 10l-104 90q22 0 43 26zM290 932l-92 106q-24 22 0 46q26 22 48 0l104 -92q-6 0 -16 -7t-20 -17t-17 -20t-7 -16zM566 1070l16 168q0 22 17 34t27 12q48 0 48 -46 l14 -168q-22 10 -61 10t-61 -10z" /> +<glyph unicode="í" horiz-adv-x="1400" d="M638 1272q134 0 251 -49t203 -135t135 -203t49 -251t-49 -250t-135 -202t-203 -135t-251 -49t-250 49t-202 135t-135 202t-49 250t49 251t135 203t202 135t250 49zM638 100q114 0 212 41t170 112t113 169t41 212t-41 212t-113 170t-170 113t-212 41v-1070z" /> +<glyph unicode="ñ" d="M1222 972q22 0 37 -17t15 -39v-232q0 -22 -15 -37t-37 -15h-948q-22 0 -37 15t-15 37v232q0 22 15 39t37 17h948zM408 710q40 0 66 26t26 64q0 40 -26 67t-66 27q-38 0 -65 -27t-27 -67q0 -38 27 -64t65 -26zM882 740q-6 14 -8 29t-2 31q0 40 10 62h-268l8 -29 q4 -15 4 -33q0 -16 -4 -31l-8 -29h268zM1088 710q40 0 66 26t26 64q0 40 -26 67t-66 27q-38 0 -65 -27t-27 -67q0 -38 27 -64t65 -26zM1362 1172q52 0 87 -36t35 -86v-868q0 -52 -35 -87t-87 -35h-1236q-52 0 -88 35t-36 87v868q0 50 36 86t88 36h1236zM240 130q18 0 30 13 t12 31t-12 30t-30 12q-16 0 -28 -12t-12 -30t12 -31t28 -13zM368 242q18 0 30 12t12 30q0 16 -12 28t-30 12t-30 -12t-12 -28q0 -18 12 -30t30 -12zM1140 242q16 0 28 12t12 30q0 16 -12 28t-28 12q-18 0 -30 -12t-12 -28q0 -18 12 -30t30 -12zM1266 130q18 0 30 13t12 31 t-12 30t-30 12q-16 0 -28 -12t-12 -30t12 -31t28 -13zM1396 1020q0 22 -15 39t-39 17h-1190q-22 0 -37 -17t-15 -39v-568q0 -22 15 -37t37 -15h1190q24 0 39 15t15 37v568z" /> +<glyph unicode="ó" horiz-adv-x="1418" d="M304 1284q30 0 53 -22t23 -52v-226q0 -32 -23 -54t-53 -22h-226q-32 0 -55 22t-23 54v226q0 30 23 52t55 22h226zM756 1284q32 0 53 -22t21 -52v-226q0 -32 -21 -54t-53 -22h-228q-30 0 -52 22t-22 54v226q0 30 22 52t52 22h228zM1210 1284q32 0 53 -22t21 -52v-226 q0 -32 -21 -54t-53 -22h-226q-32 0 -54 22t-22 54v226q0 30 22 52t54 22h226zM304 830q30 0 53 -21t23 -53v-228q0 -30 -23 -52t-53 -22h-226q-32 0 -55 22t-23 52v228q0 32 23 53t55 21h226zM756 830q32 0 53 -21t21 -53v-228q0 -30 -21 -52t-53 -22h-228q-30 0 -52 22 t-22 52v228q0 32 22 53t52 21h228zM1210 830q32 0 53 -21t21 -53v-228q0 -30 -21 -52t-53 -22h-226q-32 0 -54 22t-22 52v228q0 32 22 53t54 21h226zM304 380q30 0 53 -23t23 -55v-228q0 -28 -23 -51t-53 -23h-226q-32 0 -55 23t-23 51v228q0 32 23 55t55 23h226zM756 380 q32 0 53 -23t21 -55v-228q0 -28 -21 -51t-53 -23h-228q-30 0 -52 23t-22 51v228q0 32 22 55t52 23h228zM1210 380q32 0 53 -23t21 -55v-228q0 -28 -21 -51t-53 -23h-226q-32 0 -54 23t-22 51v228q0 32 22 55t54 23h226z" /> +<glyph unicode="ö" horiz-adv-x="1412" d="M1214 1282q32 0 53 -22t21 -54v-226q0 -32 -21 -53t-53 -21h-114h-112h-906q-32 0 -53 21t-21 53v226q0 32 21 54t53 22h906h112h114zM1214 828q32 0 53 -21t21 -51v-228q0 -32 -21 -53t-53 -21h-114h-112h-906q-32 0 -53 21t-21 53v228q0 30 21 51t53 21h906h112h114z M1214 378q32 0 53 -23t21 -55v-224q0 -30 -21 -53t-53 -23h-114h-112h-906q-32 0 -53 23t-21 53v224q0 32 21 55t53 23h906h112h114z" /> +<glyph unicode="ú" horiz-adv-x="1370" d="M1248 698q24 -24 24 -57t-24 -57l-552 -552q-26 -26 -58 -26t-58 26l-554 552q-22 24 -22 57t22 57l554 552q26 24 58 24t58 -24zM720 1104q10 8 10 21t-10 23l-62 60q-22 20 -42 0l-60 -60q-10 -10 -10 -23t10 -21l60 -60q10 -10 21 -10t21 10zM558 944q20 20 0 42 l-58 60q-24 24 -44 0l-58 -60q-12 -10 -12 -21t12 -21l58 -62q20 -20 44 0zM404 786q22 22 0 46l-62 56q-22 20 -42 0l-60 -56q-22 -24 0 -46l60 -58q10 -10 21 -10t21 10zM240 624q20 20 0 42l-58 62q-10 10 -23 10t-23 -10l-58 -62q-12 -10 -12 -21t12 -21l58 -60 q10 -10 23 -10t23 10zM1196 624q10 10 10 23t-10 23l-56 58q-10 10 -23 10t-23 -10l-58 -58q-12 -10 -12 -23t12 -23l58 -58q10 -10 23 -10t23 10zM1036 466q10 8 10 20t-10 22l-58 62q-10 6 -23 6t-23 -6l-58 -62q-10 -10 -10 -22t10 -20l58 -60q10 -10 23 -10t23 10z M880 308q10 10 10 23t-10 23l-60 58q-22 20 -42 0l-62 -58q-10 -10 -10 -23t10 -23l62 -58q10 -10 21 -10t21 10zM716 146q20 24 0 44l-58 60q-10 10 -22 10t-22 -10l-58 -60q-20 -20 0 -44l58 -60q10 -10 22 -10t22 10zM738 442q10 10 10 23t-10 23l-258 258q-10 10 -23 10 t-23 -10l-226 -226q-20 -20 0 -44l260 -260q10 -10 22 -10t22 10zM1060 764q8 10 8 23t-8 19l-262 262q-22 20 -42 0l-224 -224q-10 -10 -10 -23t10 -23l258 -258q10 -10 23 -10t23 10z" /> +<glyph unicode=" " horiz-adv-x="886" /> +<glyph unicode=" " horiz-adv-x="1774" /> +<glyph unicode=" " horiz-adv-x="886" /> +<glyph unicode=" " horiz-adv-x="1774" /> +<glyph unicode=" " horiz-adv-x="590" /> +<glyph unicode=" " horiz-adv-x="442" /> +<glyph unicode=" " horiz-adv-x="294" /> +<glyph unicode=" " horiz-adv-x="294" /> +<glyph unicode=" " horiz-adv-x="220" /> +<glyph unicode=" " horiz-adv-x="354" /> +<glyph unicode=" " horiz-adv-x="98" /> +<glyph unicode="‐" horiz-adv-x="1364" d="M1064 1288q48 0 90 -18t73 -50t49 -75t18 -93v-822q0 -48 -18 -91t-49 -75t-73 -51t-90 -19h-828q-48 0 -91 19t-75 51t-51 75t-19 91v822q0 50 19 93t51 75t75 50t91 18h828zM1064 756h-296h-532v-230h532h296v230z" /> +<glyph unicode="‑" horiz-adv-x="1364" d="M1064 1288q48 0 90 -18t73 -50t49 -75t18 -93v-822q0 -48 -18 -91t-49 -75t-73 -51t-90 -19h-828q-48 0 -91 19t-75 51t-51 75t-19 91v822q0 50 19 93t51 75t75 50t91 18h828zM1064 756h-296h-532v-230h532h296v230z" /> +<glyph unicode="‒" horiz-adv-x="1364" d="M1064 1288q48 0 90 -18t73 -50t49 -75t18 -93v-822q0 -48 -18 -91t-49 -75t-73 -51t-90 -19h-828q-48 0 -91 19t-75 51t-51 75t-19 91v822q0 50 19 93t51 75t75 50t91 18h828zM1064 756h-296h-532v-230h532h296v230z" /> +<glyph unicode="–" horiz-adv-x="1094" d="M842 1288q38 0 71 -18t58 -50t39 -75t14 -93v-822q0 -48 -14 -91t-39 -75t-58 -51t-71 -19h-655q-38 0 -72 19t-59.5 51t-40.5 75t-15 91v822q0 50 15 93t40.5 75t59.5 50t72 18h655zM842 756h-234h-421v-230h421h234v230z" /> +<glyph unicode="—" horiz-adv-x="2118" d="M1684 1288q76 0 142.5 -18t115.5 -50t77.5 -75t28.5 -93v-822q0 -48 -28.5 -91t-77.5 -75t-115.5 -51t-142.5 -19h-1310q-76 0 -144.5 19t-119 51t-80.5 75t-30 91v822q0 50 30 93t80.5 75t119 50t144.5 18h1310zM1684 756h-468h-842v-230h842h468v230z" /> +<glyph unicode="‘" horiz-adv-x="512" d="M0 768v512h256v-512h-256z" /> +<glyph unicode="’" horiz-adv-x="512" d="M0 768v512h256v-512h-256z" /> +<glyph unicode="“" horiz-adv-x="1024" d="M0 768v512h256v-512h-256zM512 768v512h256v-512h-256z" /> +<glyph unicode="”" horiz-adv-x="1024" d="M0 768v512h256v-512h-256zM512 768v512h256v-512h-256z" /> +<glyph unicode=" " horiz-adv-x="354" /> +<glyph unicode=" " horiz-adv-x="442" /> +<glyph unicode="" horiz-adv-x="1280" d="M0 1280h1280v-1280h-1280v1280z" /> +</font> +</defs></svg>
\ No newline at end of file diff --git a/addons/web/static/src/font/mnmliconsv21-webfont.ttf b/addons/web/static/src/font/mnmliconsv21-webfont.ttf Binary files differnew file mode 100755 index 00000000000..c38e59cf138 --- /dev/null +++ b/addons/web/static/src/font/mnmliconsv21-webfont.ttf diff --git a/addons/web/static/src/font/mnmliconsv21-webfont.woff b/addons/web/static/src/font/mnmliconsv21-webfont.woff Binary files differnew file mode 100755 index 00000000000..3ddf14fb814 --- /dev/null +++ b/addons/web/static/src/font/mnmliconsv21-webfont.woff diff --git a/addons/web/static/src/img/form_sheetbg.png b/addons/web/static/src/img/form_sheetbg.png Binary files differnew file mode 100644 index 00000000000..d54b6302d5a --- /dev/null +++ b/addons/web/static/src/img/form_sheetbg.png diff --git a/addons/web/static/src/img/form_steps.png b/addons/web/static/src/img/form_steps.png Binary files differnew file mode 100644 index 00000000000..0485c5977aa --- /dev/null +++ b/addons/web/static/src/img/form_steps.png diff --git a/addons/web/static/src/img/list_empty_arrow.png b/addons/web/static/src/img/list_empty_arrow.png Binary files differnew file mode 100644 index 00000000000..b0b1372adfd --- /dev/null +++ b/addons/web/static/src/img/list_empty_arrow.png diff --git a/addons/web/static/src/img/topbar-avatar.png b/addons/web/static/src/img/topbar-avatar.png Binary files differdeleted file mode 100644 index a2c70ea7d49..00000000000 --- a/addons/web/static/src/img/topbar-avatar.png +++ /dev/null diff --git a/addons/web/static/src/js/boot.js b/addons/web/static/src/js/boot.js index 282435f2a71..1251975f41c 100644 --- a/addons/web/static/src/js/boot.js +++ b/addons/web/static/src/js/boot.js @@ -47,26 +47,15 @@ /*--------------------------------------------------------- * OpenERP Web web module split *---------------------------------------------------------*/ - -/** - * @namespace - */ -openerp.web = function(instance) { - openerp.web.core(instance); - if (openerp.web.dates) { - openerp.web.dates(instance); - } - openerp.web.formats(instance); - openerp.web.chrome(instance); - openerp.web.data(instance); - var files = ["views","search","list","form", "page","list_editable","web_mobile","view_tree","data_export","data_import","view_editor"]; +openerp.web = function(session) { + var files = ["corelib","coresetup","dates","formats","chrome","data","views","search","list","form","list_editable","web_mobile","view_tree","data_export","data_import","view_editor"]; for(var i=0; i<files.length; i++) { if(openerp.web[files[i]]) { - openerp.web[files[i]](instance); + openerp.web[files[i]](session); } } - instance.log = function() { - if (instance.connection.debug && window.console) { + session.log = function() { + if (session.connection.debug && window.console) { console.log.apply(console, arguments); } } diff --git a/addons/web/static/src/js/chrome.js b/addons/web/static/src/js/chrome.js index 7743e2b5777..f1d3faef367 100644 --- a/addons/web/static/src/js/chrome.js +++ b/addons/web/static/src/js/chrome.js @@ -1,18 +1,17 @@ /*--------------------------------------------------------- * OpenERP Web chrome *---------------------------------------------------------*/ -openerp.web.chrome = function(openerp) { -var QWeb = openerp.web.qweb, - _t = openerp.web._t; +openerp.web.chrome = function(instance) { +var QWeb = instance.web.qweb, + _t = instance.web._t; -openerp.web.Notification = openerp.web.OldWidget.extend(/** @lends openerp.web.Notification# */{ +instance.web.Notification = instance.web.Widget.extend({ template: 'Notification', - init: function() { this._super.apply(this, arguments); - openerp.notification = this; + // move to instance.web.notification + instance.notification = this; }, - start: function() { this._super.apply(this, arguments); this.$element.notify({ @@ -42,24 +41,16 @@ openerp.web.Notification = openerp.web.OldWidget.extend(/** @lends openerp.web. text: text }, opts); } - }); -openerp.web.dialog = function(element) { +instance.web.dialog = function(element) { var result = element.dialog.apply(element, _.rest(_.toArray(arguments))); result.dialog("widget").addClass("openerp"); return result; } -openerp.web.Dialog = openerp.web.OldWidget.extend(/** @lends openerp.web.Dialog# */{ +instance.web.Dialog = instance.web.Widget.extend({ dialog_title: "", - /** - * @constructs openerp.web.Dialog - * @extends openerp.web.OldWidget - * - * @param parent - * @param options - */ init: function (parent, options, content) { var self = this; this._super(parent); @@ -92,7 +83,7 @@ openerp.web.Dialog = openerp.web.OldWidget.extend(/** @lends openerp.web.Dialog# if (this.dialog_options.autoOpen) { this.open(); } else { - openerp.web.dialog(this.$element, this.get_options()); + instance.web.dialog(this.$element, this.get_options()); } }, get_options: function(options) { @@ -128,10 +119,10 @@ openerp.web.Dialog = openerp.web.OldWidget.extend(/** @lends openerp.web.Dialog# open: function(options) { // TODO fme: bind window on resize if (this.template) { - this.$element.html(this.render()); + this.$element.html(this.renderElement()); } var o = this.get_options(options); - openerp.web.dialog(this.$element, o).dialog('open'); + instance.web.dialog(this.$element, o).dialog('open'); if (o.height === 'auto' && o.max_height) { this.$element.css({ 'max-height': o.max_height, 'overflow-y': 'auto' }); } @@ -149,14 +140,13 @@ openerp.web.Dialog = openerp.web.OldWidget.extend(/** @lends openerp.web.Dialog# //openerp.log("Dialog resized to %d x %d", this.$element.width(), this.$element.height()); }, destroy: function () { - // Destroy widget this.close(); this.$element.dialog('destroy'); this._super(); } }); -openerp.web.CrashManager = openerp.web.CallbackEnabled.extend({ +instance.web.CrashManager = instance.web.CallbackEnabled.extend({ on_rpc_error: function(error) { if (error.data.fault_code) { var split = ("" + error.data.fault_code).split('\n')[0].split(' -- '); @@ -172,7 +162,7 @@ openerp.web.CrashManager = openerp.web.CallbackEnabled.extend({ } }, on_managed_error: function(error) { - openerp.web.dialog($('<div>' + QWeb.render('CrashManagerWarning', {error: error}) + '</div>'), { + instance.web.dialog($('<div>' + QWeb.render('CrashManager.warning', {error: error}) + '</div>'), { title: "OpenERP " + _.str.capitalize(error.type), buttons: [ {text: _t("Ok"), click: function() { $(this).dialog("close"); }} @@ -182,14 +172,14 @@ openerp.web.CrashManager = openerp.web.CallbackEnabled.extend({ on_traceback: function(error) { var self = this; var buttons = {}; - if (openerp.connection.openerp_entreprise) { + if (instance.connection.openerp_entreprise) { buttons[_t("Send OpenERP Enterprise Report")] = function() { var $this = $(this); var issuename = $('#issuename').val(); var explanation = $('#explanation').val(); var remark = $('#remark').val(); // Call the send method from server to send mail with details - new openerp.web.DataSet(self, 'publisher_warranty.contract').call_and_eval('send', [error.data,explanation,remark,issuename]).then(function(result){ + new instance.web.DataSet(self, 'publisher_warranty.contract').call_and_eval('send', [error.data,explanation,remark,issuename]).then(function(result){ if (result === false) { alert('There was a communication error.') } else { @@ -205,7 +195,7 @@ openerp.web.CrashManager = openerp.web.CallbackEnabled.extend({ $(this).dialog("close"); }; } - var dialog = new openerp.web.Dialog(this, { + var dialog = new instance.web.Dialog(this, { title: "OpenERP " + _.str.capitalize(error.type), width: '80%', height: '50%', @@ -213,19 +203,12 @@ openerp.web.CrashManager = openerp.web.CallbackEnabled.extend({ min_height: '600px', buttons: buttons }).open(); - dialog.$element.html(QWeb.render('CrashManagerError', {session: openerp.connection, error: error})); + dialog.$element.html(QWeb.render('CrashManager.error', {session: instance.connection, error: error})); } }); -openerp.web.Loading = openerp.web.OldWidget.extend(/** @lends openerp.web.Loading# */{ +instance.web.Loading = instance.web.Widget.extend({ template: 'Loading', - /** - * @constructs openerp.web.Loading - * @extends openerp.web.OldWidget - * - * @param parent - * @param element_id - */ init: function(parent) { this._super(parent); this.count = 0; @@ -258,10 +241,9 @@ openerp.web.Loading = openerp.web.OldWidget.extend(/** @lends openerp.web.Loadin this.count += increment; if (this.count > 0) { - $(".loading",this.$element).text(_.str.sprintf( - _t("Loading (%d)"), this.count)); - $(".loading",this.$element).show(); - this.getParent().$element.addClass('loading'); + this.$element.text(_.str.sprintf( _t("Loading (%d)"), this.count)); + this.$element.show(); + this.getParent().$element.addClass('oe_wait'); } else { this.count = 0; clearTimeout(this.long_running_timer); @@ -270,73 +252,60 @@ openerp.web.Loading = openerp.web.OldWidget.extend(/** @lends openerp.web.Loadin this.blocked_ui = false; $.unblockUI(); } - $(".loading",this.$element).fadeOut(); - this.getParent().$element.removeClass('loading'); + this.$element.fadeOut(); + this.getParent().$element.removeClass('oe_wait'); } } }); -openerp.web.Database = openerp.web.OldWidget.extend(/** @lends openerp.web.Database# */{ - template: "DatabaseManager", - /** - * @constructs openerp.web.Database - * @extends openerp.web.OldWidget - * - * @param parent - * @param element_id - * @param option_id - */ - init: function(parent, element_id, option_id) { - this._super(parent, element_id); +instance.web.DatabaseManager = instance.web.Widget.extend({ + init: function(parent) { + this._super(parent); this.unblockUIFunction = $.unblockUI; $.validator.addMethod('matches', function (s, _, re) { return new RegExp(re).test(s); }, _t("Invalid database name")); }, start: function() { - this.$option_id = $("#oe_db_options"); - var self = this; var fetch_db = this.rpc("/web/database/get_list", {}).pipe( - function(result) { self.db_list = result.db_list; }, - function (_, ev) { ev.preventDefault(); self.db_list = null; }); - var fetch_langs = this.rpc("/web/session/get_lang_list", {}, function(result) { - if (result.error) { - self.display_error(result); - return; - } + function(result) { + self.db_list = result.db_list; + }, + function (_, ev) { + ev.preventDefault(); + self.db_list = null; + }); + var fetch_langs = this.rpc("/web/session/get_lang_list", {}).then(function(result) { self.lang_list = result.lang_list; }); - $.when(fetch_db, fetch_langs).then(function () {self.do_create();}); - - this.$element.find('#db-create').click(this.do_create); - this.$element.find('#db-drop').click(this.do_drop); - this.$element.find('#db-backup').click(this.do_backup); - this.$element.find('#db-restore').click(this.do_restore); - this.$element.find('#db-change-password').click(this.do_change_password); - this.$element.find('#back-to-login').click(function() { - self.hide(); + return $.when(fetch_db, fetch_langs).then(self.do_render); + }, + do_render: function() { + var self = this; + self.$element.html(QWeb.render("DatabaseManager", { widget : self })); + self.$element.find(".oe_database_manager_menu").tabs(); + self.$element.find("form[name=create_db_form]").validate({ submitHandler: self.do_create }); + self.$element.find("form[name=drop_db_form]").validate({ submitHandler: self.do_drop }); + self.$element.find("form[name=backup_db_form]").validate({ submitHandler: self.do_backup }); + self.$element.find("form[name=restore_db_form]").validate({ submitHandler: self.do_restore }); + self.$element.find("form[name=change_pwd_form]").validate({ + messages: { + old_pwd: "Please enter your previous password", + new_pwd: "Please enter your new password", + confirm_pwd: { + required: "Please confirm your new password", + equalTo: "The confirmation does not match the password" + } + }, + submitHandler: self.do_change_password }); + self.$element.find("#back_to_login").click(self.do_exit); }, destroy: function () { - this.hide(); - this.$option_id.empty(); - - this.$element - .find('#db-create, #db-drop, #db-backup, #db-restore, #db-change-password, #back-to-login') - .unbind('click') - .end() - .empty(); + this.$element.find('#db-create, #db-drop, #db-backup, #db-restore, #db-change-password, #back-to-login').unbind('click').end().empty(); this._super(); }, - show: function () { - this.$element.closest(".login") - .addClass("database_block"); - }, - hide: function () { - this.$element.closest(".login") - .removeClass("database_block") - }, /** * Converts a .serializeArray() result into a dict. Does not bother folding * multiple identical keys into an array, last key wins. @@ -375,7 +344,7 @@ openerp.web.Database = openerp.web.OldWidget.extend(/** @lends openerp.web.Datab * @param {String} error.error message of the error dialog */ display_error: function (error) { - return openerp.web.dialog($('<div>'), { + return instance.web.dialog($('<div>'), { modal: true, title: error.title, buttons: [ @@ -383,157 +352,96 @@ openerp.web.Database = openerp.web.OldWidget.extend(/** @lends openerp.web.Datab ] }).html(error.error); }, - do_create: function() { + do_create: function(form) { var self = this; - self.$option_id.html(QWeb.render("Database.CreateDB", self)); - self.$option_id.find("form[name=create_db_form]").validate({ - submitHandler: function (form) { - var fields = $(form).serializeArray(); - self.rpc("/web/database/create", {'fields': fields}, function(result) { - if (self.db_list) { - self.db_list.push(self.to_object(fields)['db_name']); - self.db_list.sort(); - self.getParent().set_db_list(self.db_list); - } - - var form_obj = self.to_object(fields); - self.getParent().do_login( - form_obj['db_name'], - 'admin', - form_obj['create_admin_pwd']); - self.destroy(); - }); - } + var fields = $(form).serializeArray(); + self.rpc("/web/database/create", {'fields': fields}, function(result) { + var form_obj = self.to_object(fields); + self.getParent().do_login( form_obj['db_name'], 'admin', form_obj['create_admin_pwd']); + self.destroy(); }); + }, - do_drop: function() { + do_drop: function(form) { var self = this; - self.$option_id.html(QWeb.render("DropDB", self)); - self.$option_id.find("form[name=drop_db_form]").validate({ - submitHandler: function (form) { - var $form = $(form), - fields = $form.serializeArray(), - $db_list = $form.find('[name=drop_db]'), - db = $db_list.val(); - - if (!db || !confirm("Do you really want to delete the database: " + db + " ?")) { - return; - } - self.rpc("/web/database/drop", {'fields': fields}, function(result) { - if (result.error) { - self.display_error(result); - return; - } - $db_list.find(':selected').remove(); - if (self.db_list) { - self.db_list.splice(_.indexOf(self.db_list, db, true), 1); - self.getParent().set_db_list(self.db_list); - } - self.do_notify("Dropping database", "The database '" + db + "' has been dropped"); - }); + var $form = $(form), + fields = $form.serializeArray(), + $db_list = $form.find('[name=drop_db]'), + db = $db_list.val(); + if (!db || !confirm("Do you really want to delete the database: " + db + " ?")) { + return; + } + self.rpc("/web/database/drop", {'fields': fields}, function(result) { + if (result.error) { + self.display_error(result); + return; } + self.do_notify("Dropping database", "The database '" + db + "' has been dropped"); + self.start(); }); }, - do_backup: function() { + do_backup: function(form) { var self = this; - self.$option_id - .html(QWeb.render("BackupDB", self)) - .find("form[name=backup_db_form]").validate({ - submitHandler: function (form) { - self.blockUI(); - self.session.get_file({ - form: form, - success: function () { - self.do_notify(_t("Backed"), - _t("Database backed up successfully")); - }, - error: openerp.webclient.crashmanager.on_rpc_error, - complete: function() { - self.unblockUI(); - } - }); + self.blockUI(); + self.session.get_file({ + form: form, + success: function () { + self.do_notify(_t("Backed"), _t("Database backed up successfully")); + }, + error: instance.webclient.crashmanager.on_rpc_error, + complete: function() { + self.unblockUI(); } }); }, - do_restore: function() { + do_restore: function(form) { var self = this; - self.$option_id.html(QWeb.render("RestoreDB", self)); + self.blockUI(); + $(form).ajaxSubmit({ + url: '/web/database/restore', + type: 'POST', + resetForm: true, + success: function (body) { + // If empty body, everything went fine + if (!body) { return; } - self.$option_id.find("form[name=restore_db_form]").validate({ - submitHandler: function (form) { - self.blockUI(); - $(form).ajaxSubmit({ - url: '/web/database/restore', - type: 'POST', - resetForm: true, - success: function (body) { - // TODO: ui manipulations - // note: response objects don't work, but we have the - // HTTP body of the response~~ - - // If empty body, everything went fine - if (!body) { return; } - - if (body.indexOf('403 Forbidden') !== -1) { - self.display_error({ - title: 'Access Denied', - error: 'Incorrect super-administrator password' - }) - } else { - self.display_error({ - title: 'Restore Database', - error: 'Could not restore the database' - }) - } - }, - complete: function() { - self.unblockUI(); - self.do_notify(_t("Restored"), _t("Database restored successfully")); - } - }); + if (body.indexOf('403 Forbidden') !== -1) { + self.display_error({ + title: 'Access Denied', + error: 'Incorrect super-administrator password' + }) + } else { + self.display_error({ + title: 'Restore Database', + error: 'Could not restore the database' + }) + } + }, + complete: function() { + self.unblockUI(); + self.do_notify(_t("Restored"), _t("Database restored successfully")); } }); }, - do_change_password: function() { + do_change_password: function(form) { var self = this; - self.$option_id.html(QWeb.render("Change_DB_Pwd", self)); - - self.$option_id.find("form[name=change_pwd_form]").validate({ - messages: { - old_pwd: "Please enter your previous password", - new_pwd: "Please enter your new password", - confirm_pwd: { - required: "Please confirm your new password", - equalTo: "The confirmation does not match the password" - } - }, - submitHandler: function (form) { - self.rpc("/web/database/change_password", { - 'fields': $(form).serializeArray() - }, function(result) { - if (result.error) { - self.display_error(result); - return; - } - self.do_notify("Changed Password", "Password has been changed successfully"); - }); + self.rpc("/web/database/change_password", { + 'fields': $(form).serializeArray() + }, function(result) { + if (result.error) { + self.display_error(result); + return; } + self.do_notify("Changed Password", "Password has been changed successfully"); }); + }, + do_exit: function () { } }); -openerp.web.Login = openerp.web.OldWidget.extend(/** @lends openerp.web.Login# */{ - remember_credentials: true, - +instance.web.Login = instance.web.Widget.extend({ template: "Login", - /** - * @constructs openerp.web.Login - * @extends openerp.web.OldWidget - * - * @param parent - * @param element_id - */ - + remember_credentials: true, init: function(parent) { this._super(parent); this.has_local_storage = typeof(localStorage) != 'undefined'; @@ -550,29 +458,35 @@ openerp.web.Login = openerp.web.OldWidget.extend(/** @lends openerp.web.Login# }, start: function() { var self = this; - this.database = new openerp.web.Database(this); - this.database.appendTo(this.$element); - this.$element.find('#oe-db-config').click(function() { - self.database.show(); - }); + self.$element.find("form").submit(self.on_submit); - this.$element.find("form").submit(this.on_submit); - - this.rpc("/web/database/get_list", {}, function(result) { + self.$element.find('.oe_login_manage_db').click(function() { + self.$element.find('.oe_login_bottom').hide(); + self.$element.find('.oe_login_pane').hide(); + self.databasemanager = new instance.web.DatabaseManager(self); + self.databasemanager.appendTo(self.$element); + self.databasemanager.do_exit.add_last(function() { + self.databasemanager.destroy(); + self.$element.find('.oe_login_bottom').show(); + self.$element.find('.oe_login_pane').show(); + self.load_db_list(); + }) + }); + self.load_db_list(); + }, + load_db_list: function () { + var self = this; + self.rpc("/web/database/get_list", {}, function(result) { self.set_db_list(result.db_list); - }, - function(error, event) { + }, function(error, event) { if (error.data.fault_code === 'AccessDenied') { event.preventDefault(); } }); - }, set_db_list: function (list) { - this.$element.find("[name=db]").replaceWith( - openerp.web.qweb.render('Login_dblist', { - db_list: list, selected_db: this.selected_db})) + this.$element.find("[name=db]").replaceWith(instance.web.qweb.render('Login.dblist', { db_list: list, selected_db: this.selected_db})) }, on_submit: function(ev) { if(ev) { @@ -598,15 +512,15 @@ openerp.web.Login = openerp.web.OldWidget.extend(/** @lends openerp.web.Login# */ do_login: function (db, login, password) { var self = this; - this.$element.removeClass('login_invalid'); + this.$element.removeClass('oe_login_invalid'); this.session.on_session_invalid.add({ callback: function () { - self.$element.addClass("login_invalid"); + self.$element.addClass("oe_login_invalid"); }, unique: true }); this.session.session_authenticate(db, login, password).then(function() { - self.$element.removeClass("login_invalid"); + self.$element.removeClass("oe_login_invalid"); if (self.has_local_storage) { if(self.remember_credentials) { localStorage.setItem('last_db_login_success', db); @@ -624,13 +538,7 @@ openerp.web.Login = openerp.web.OldWidget.extend(/** @lends openerp.web.Login# } }); -openerp.web.Menu = openerp.web.Widget.extend(/** @lends openerp.web.Menu# */{ - /** - * @constructs openerp.web.Menu - * @extends openerp.web.Widget - * - * @param parent - */ +instance.web.Menu = instance.web.Widget.extend({ template: 'Menu', init: function() { this._super.apply(this, arguments); @@ -774,14 +682,8 @@ openerp.web.Menu = openerp.web.Widget.extend(/** @lends openerp.web.Menu# */{ } }); -openerp.web.UserMenu = openerp.web.Widget.extend(/** @lends openerp.web.UserMenu# */{ +instance.web.UserMenu = instance.web.Widget.extend({ template: "UserMenu", - /** - * @constructs openerp.web.UserMenu - * @extends openerp.web.Widget - * - * @param parent - */ init: function(parent) { this._super(parent); this.update_promise = $.Deferred().resolve(); @@ -805,7 +707,7 @@ openerp.web.UserMenu = openerp.web.Widget.extend(/** @lends openerp.web.UserMen }, change_password :function() { var self = this; - this.dialog = new openerp.web.Dialog(this, { + this.dialog = new instance.web.Dialog(this, { title: _t("Change Password"), width : 'auto' }).open(); @@ -819,14 +721,14 @@ openerp.web.UserMenu = openerp.web.Widget.extend(/** @lends openerp.web.UserMen self.display_error(result); return; } else { - openerp.webclient.on_logout(); + instance.webclient.on_logout(); } }); } }); }, display_error: function (error) { - return openerp.web.dialog($('<div>'), { + return instance.web.dialog($('<div>'), { modal: true, title: error.title, buttons: [ @@ -841,10 +743,10 @@ openerp.web.UserMenu = openerp.web.Widget.extend(/** @lends openerp.web.UserMen $avatar.attr('src', $avatar.data('default-src')); if (!self.session.uid) return; - var func = new openerp.web.Model("res.users").get_func("read"); + var func = new instance.web.Model("res.users").get_func("read"); return func(self.session.uid, ["name", "company_id"]).pipe(function(res) { // TODO: Show company if multicompany is in use - var topbar_name = _.str.sprintf("%s (%s)", res.name, openerp.connection.db, res.company_id[1]); + var topbar_name = _.str.sprintf("%s (%s)", res.name, instance.connection.db, res.company_id[1]); self.$element.find('.oe_topbar_name').text(topbar_name); var avatar_src = _.str.sprintf('%s/web/binary/image?session_id=%s&model=res.users&field=avatar&id=%s', self.session.prefix, self.session.session_id, self.session.uid); $avatar.attr('src', avatar_src); @@ -858,7 +760,7 @@ openerp.web.UserMenu = openerp.web.Widget.extend(/** @lends openerp.web.UserMen shortcut_load :function(){ var self = this, sc = self.session.shortcuts, - shortcuts_ds = new openerp.web.DataSet(this, 'ir.ui.view_sc'); + shortcuts_ds = new instance.web.DataSet(this, 'ir.ui.view_sc'); self.$element.find('.oe_dropdown_options a[data-menu=shortcut]').each(function() { $(this).parent().remove(); }); @@ -905,11 +807,12 @@ openerp.web.UserMenu = openerp.web.Widget.extend(/** @lends openerp.web.UserMen }, on_menu_settings: function() { var self = this; - var action_manager = new openerp.web.ActionManager(this); - var dataset = new openerp.web.DataSet (this,'res.users',this.context); + var action_manager = new instance.web.ActionManager(this); + var dataset = new instance.web.DataSet (this,'res.users',this.context); dataset.call ('action_get','',function (result){ self.rpc('/web/action/load', {action_id:result}, function(result){ action_manager.do_action(_.extend(result['result'], { + target: 'inline', res_id: self.session.uid, res_model: 'res.users', flags: { @@ -922,7 +825,7 @@ openerp.web.UserMenu = openerp.web.Widget.extend(/** @lends openerp.web.UserMen })); }); }); - this.dialog = new openerp.web.Dialog(this,{ + this.dialog = new instance.web.Dialog(this,{ title: _t("Preferences"), width: '700px', buttons: [ @@ -941,7 +844,7 @@ openerp.web.UserMenu = openerp.web.Widget.extend(/** @lends openerp.web.UserMen ] }).open(); action_manager.appendTo(this.dialog.$element); - action_manager.render(this.dialog); + action_manager.renderElement(this.dialog); }, on_menu_about: function() { var self = this; @@ -952,7 +855,7 @@ openerp.web.UserMenu = openerp.web.Widget.extend(/** @lends openerp.web.UserMen window.location = $.param.querystring( window.location.href, 'debug'); }); - openerp.web.dialog($help, {autoOpen: true, + instance.web.dialog($help, {autoOpen: true, modal: true, width: 960, title: _t("About")}); }); }, @@ -968,21 +871,17 @@ openerp.web.UserMenu = openerp.web.Widget.extend(/** @lends openerp.web.UserMen } }); -openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClient */{ - /** - * @constructs openerp.web.WebClient - * @extends openerp.web.Widget - */ +instance.web.WebClient = instance.web.Widget.extend({ init: function(parent) { var self = this; this._super(parent); - openerp.webclient = this; + instance.webclient = this; this.querystring = '?' + jQuery.param.querystring(); this._current_state = null; }, start: function() { var self = this; - this.$element.addClass("openerp openerp2"); + this.$element.addClass("openerp openerp-web-client-container"); if (jQuery.param != undefined && jQuery.deparam(jQuery.param.querystring()).kitten != undefined) { this.$element.addClass("kitten-mode-activated"); this.$element.delegate('img.oe-record-edit-link-img', 'hover', function(e) { @@ -1001,7 +900,7 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie self.menu.do_reload(); if(self.action_manager) self.action_manager.destroy(); - self.action_manager = new openerp.web.ActionManager(self); + self.action_manager = new instance.web.ActionManager(self); self.action_manager.appendTo(self.$element.find('.oe_application')); self.bind_hashchange(); var version_label = _t("OpenERP - Unsupported/Community Version"); @@ -1018,7 +917,7 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie var self = this; this.destroy_content(); this.show_common(); - self.login = new openerp.web.Login(self); + self.login = new instance.web.Login(self); self.login.appendTo(self.$element); }, show_application: function() { @@ -1027,10 +926,10 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie this.show_common(); self.$table = $(QWeb.render("WebClient", {})); self.$element.append(self.$table); - self.menu = new openerp.web.Menu(self); + self.menu = new instance.web.Menu(self); self.menu.replace(this.$element.find('.oe_menu_placeholder')); self.menu.on_action.add(this.proxy('on_menu_action')); - self.user_menu = new openerp.web.UserMenu(self); + self.user_menu = new instance.web.UserMenu(self); self.user_menu.replace(this.$element.find('.oe_user_menu_placeholder')); self.user_menu.on_menu_logout.add(this.proxy('on_logout')); self.user_menu.on_action.add(this.proxy('on_menu_action')); @@ -1038,8 +937,8 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie show_common: function() { var self = this; if (!this.crashmanager) { - this.crashmanager = new openerp.web.CrashManager(); - openerp.connection.on_rpc_error.add(this.crashmanager.on_rpc_error); + this.crashmanager = new instance.web.CrashManager(); + instance.connection.on_rpc_error.add(this.crashmanager.on_rpc_error); window.onerror = function (message, file, line) { self.crashmanager.on_traceback({ type: _t("Client Error"), @@ -1048,9 +947,9 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie }); } } - this.notification = new openerp.web.Notification(this); + this.notification = new instance.web.Notification(this); this.notification.appendTo(this.$element); - this.loading = new openerp.web.Loading(this); + this.loading = new instance.web.Loading(this); this.loading.appendTo(this.$element); }, destroy_content: function() { @@ -1062,7 +961,7 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie do_reload: function() { var self = this; return this.session.session_reload().pipe(function () { - openerp.connection.load_modules(true).pipe( + instance.connection.load_modules(true).pipe( self.menu.proxy('do_reload')); }); }, @@ -1126,16 +1025,15 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie } }); -openerp.web.EmbeddedClient = openerp.web.OldWidget.extend({ +instance.web.EmbeddedClient = instance.web.Widget.extend({ template: 'EmptyComponent', - init: function(action_id, options) { - this._super(); + init: function(parent, action_id, options) { + this._super(parent); // TODO take the xmlid of a action instead of its id this.action_id = action_id; this.options = options || {}; - this.am = new openerp.web.ActionManager(this); + this.am = new instance.web.ActionManager(this); }, - start: function() { var self = this; this.am.appendTo(this.$element.addClass('openerp')); @@ -1152,10 +1050,9 @@ openerp.web.EmbeddedClient = openerp.web.OldWidget.extend({ self.am.do_action(action); }); } - }); -openerp.web.embed = function (origin, dbname, login, key, action, options) { +instance.web.embed = function (origin, dbname, login, key, action, options) { $('head').append($('<link>', { 'rel': 'stylesheet', 'type': 'text/css', @@ -1166,9 +1063,9 @@ openerp.web.embed = function (origin, dbname, login, key, action, options) { var sc = document.getElementsByTagName('script'); currentScript = sc[sc.length-1]; } - openerp.connection.session_bind(origin).then(function () { - openerp.connection.session_authenticate(dbname, login, key, true).then(function () { - var client = new openerp.web.EmbeddedClient(action, options); + instance.connection.session_bind(origin).then(function () { + instance.connection.session_authenticate(dbname, login, key, true).then(function () { + var client = new instance.web.EmbeddedClient(null, action, options); client.insertAfter(currentScript); }); }); diff --git a/addons/web/static/src/js/core.js b/addons/web/static/src/js/corelib.js index 44e15f6dd13..cbd28aca8a3 100644 --- a/addons/web/static/src/js/core.js +++ b/addons/web/static/src/js/corelib.js @@ -1,60 +1,134 @@ -/*--------------------------------------------------------- - * OpenERP Web core - *--------------------------------------------------------*/ -var console; -if (!console) { - console = {log: function () {}}; -} -if (!console.debug) { - console.debug = console.log; -} +/* + * Copyright (c) 2012, OpenERP S.A. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ -openerp.web.core = function(openerp) { +openerp.web.corelib = function(instance) { -// a function to override the "extend()" method of JR's inheritance, allowing -// the usage of "include()" -oe_override_class = function(claz){ - var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? - /\b_super\b/ : /.*/; - - // Create a new Class that inherits from this class - claz.extend = function(prop) { +/** + * Improved John Resig's inheritance, based on: + * + * Simple JavaScript Inheritance + * By John Resig http://ejohn.org/ + * MIT Licensed. + * + * Adds "include()" + * + * Defines The Class object. That object can be used to define and inherit classes using + * the extend() method. + * + * Example: + * + * var Person = instance.web.Class.extend({ + * init: function(isDancing){ + * this.dancing = isDancing; + * }, + * dance: function(){ + * return this.dancing; + * } + * }); + * + * The init() method act as a constructor. This class can be instancied this way: + * + * var person = new Person(true); + * person.dance(); + * + * The Person class can also be extended again: + * + * var Ninja = Person.extend({ + * init: function(){ + * this._super( false ); + * }, + * dance: function(){ + * // Call the inherited version of dance() + * return this._super(); + * }, + * swingSword: function(){ + * return true; + * } + * }); + * + * When extending a class, each re-defined method can use this._super() to call the previous + * implementation of that method. + */ +(function() { + var initializing = false, + fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; + // The web Class implementation (does nothing) + instance.web.Class = function(){}; + + /** + * Subclass an existing class + * + * @param {Object} prop class-level properties (class attributes and instance methods) to set on the new class + */ + instance.web.Class.extend = function() { var _super = this.prototype; - - // Instantiate a base class (but only create the instance, don't run the - // init constructor) - initializing = true; var prototype = new this(); initializing = false; - + // Support mixins arguments + var args = _.toArray(arguments); + args.unshift({}); + var prop = _.extend.apply(_,args); + + // Instantiate a web class (but only create the instance, + // don't run the init constructor) + initializing = true; + var prototype = new this(); + initializing = false; + // Copy the properties over onto the new prototype for (var name in prop) { - // Check if we're overwriting an existing function - prototype[name] = typeof prop[name] == "function" && - typeof _super[name] == "function" && fnTest.test(prop[name]) ? - (function(name, fn){ - return function() { - var tmp = this._super; - - // Add a new ._super() method that is the same method but on the - // super-class - this._super = _super[name]; - - // The method only need to be bound temporarily, so we remove it - // when we're done executing - var ret = fn.apply(this, arguments); this._super = tmp; - - return ret; - }; - })(name, prop[name]) : prop[name]; + // Check if we're overwriting an existing function + prototype[name] = typeof prop[name] == "function" && + typeof _super[name] == "function" && + fnTest.test(prop[name]) ? + (function(name, fn) { + return function() { + var tmp = this._super; + + // Add a new ._super() method that is the same + // method but on the super-class + this._super = _super[name]; + + // The method only need to be bound temporarily, so + // we remove it when we're done executing + var ret = fn.apply(this, arguments); + this._super = tmp; + + return ret; + }; + })(name, prop[name]) : + prop[name]; } - + // The dummy class constructor function Class() { // All construction is actually done in the init method if (!initializing && this.init) { - var ret = this.init.apply(this, arguments); if (ret) { return ret;} - } return this; + var ret = this.init.apply(this, arguments); + if (ret) { return ret; } + } + return this; } - Class.include = function (properties) { for (var name in properties) { if (typeof properties[name] !== 'function' @@ -64,138 +138,617 @@ oe_override_class = function(claz){ && prototype.hasOwnProperty(name)) { prototype[name] = (function (name, fn, previous) { return function () { - var tmp = this._super; this._super = previous; var - ret = fn.apply(this, arguments); this._super = tmp; + var tmp = this._super; + this._super = previous; + var ret = fn.apply(this, arguments); + this._super = tmp; return ret; } })(name, properties[name], prototype[name]); } else if (typeof _super[name] === 'function') { prototype[name] = (function (name, fn) { return function () { - var tmp = this._super; this._super = _super[name]; - var ret = fn.apply(this, arguments); this._super = - tmp; return ret; + var tmp = this._super; + this._super = _super[name]; + var ret = fn.apply(this, arguments); + this._super = tmp; + return ret; } })(name, properties[name]); } } }; - + // Populate our constructed prototype object Class.prototype = prototype; - + // Enforce the constructor to be what we expect - Class.prototype.constructor = Class; - + Class.constructor = Class; + // And make this class extendable Class.extend = arguments.callee; - + return Class; }; -}; -oe_override_class(nova.Class); -oe_override_class(nova.Widget); +})(); -openerp.web.Class = nova.Class; +// Mixins -openerp.web.callback = function(obj, method) { - var callback = function() { - var args = Array.prototype.slice.call(arguments); - var r; - for(var i = 0; i < callback.callback_chain.length; i++) { - var c = callback.callback_chain[i]; - if(c.unique) { - callback.callback_chain.splice(i, 1); - i -= 1; - } - var result = c.callback.apply(c.self, c.args.concat(args)); - if (c.callback === method) { - // return the result of the original method - r = result; +/** + * Mixin to structure objects' life-cycles folowing a parent-children + * relationship. Each object can a have a parent and multiple children. + * When an object is destroyed, all its children are destroyed too releasing + * any resource they could have reserved before. + */ +instance.web.ParentedMixin = { + __parentedMixin : true, + init: function() { + this.__parentedDestroyed = false; + this.__parentedChildren = []; + this.__parentedParent = null; + }, + /** + * Set the parent of the current object. When calling this method, the + * parent will also be informed and will return the current object + * when its getChildren() method is called. If the current object did + * already have a parent, it is unregistered before, which means the + * previous parent will not return the current object anymore when its + * getChildren() method is called. + */ + setParent : function(parent) { + if (this.getParent()) { + if (this.getParent().__parentedMixin) { + this.getParent().__parentedChildren = _.without(this + .getParent().getChildren(), this); } - // TODO special value to stop the chain - // openerp.web.callback_stop - } - return r; - }; - callback.callback_chain = []; - callback.add = function(f) { - if(typeof(f) == 'function') { - f = { callback: f, args: Array.prototype.slice.call(arguments, 1) }; } - f.self = f.self || null; - f.args = f.args || []; - f.unique = !!f.unique; - if(f.position == 'last') { - callback.callback_chain.push(f); - } else { - callback.callback_chain.unshift(f); + this.__parentedParent = parent; + if (parent && parent.__parentedMixin) { + parent.__parentedChildren.push(this); } - return callback; - }; - callback.add_first = function(f) { - return callback.add.apply(null,arguments); - }; - callback.add_last = function(f) { - return callback.add({ - callback: f, - args: Array.prototype.slice.call(arguments, 1), - position: "last" + }, + /** + * Return the current parent of the object (or null). + */ + getParent : function() { + return this.__parentedParent; + }, + /** + * Return a list of the children of the current object. + */ + getChildren : function() { + return _.clone(this.__parentedChildren); + }, + /** + * Returns true if destroy() was called on the current object. + */ + isDestroyed : function() { + return this.__parentedDestroyed; + }, + /** + * Inform the object it should destroy itself, releasing any + * resource it could have reserved. + */ + destroy : function() { + _.each(this.getChildren(), function(el) { + el.destroy(); }); - }; - callback.remove = function(f) { - callback.callback_chain = _.difference(callback.callback_chain, _.filter(callback.callback_chain, function(el) { - return el.callback === f; - })); - return callback; - }; - - return callback.add({ - callback: method, - self:obj, - args:Array.prototype.slice.call(arguments, 2) - }); + this.setParent(undefined); + this.__parentedDestroyed = true; + } }; /** - * web error for lookup failure + * TODO al: move into the the mixin + * + * Backbone's events + * + * (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc. + * Backbone may be freely distributed under the MIT license. + * For all details and documentation: + * http://backbonejs.org + * + * This class just handle the dispatching of events, it is not meant to be extended, + * nor used directly. All integration with parenting and automatic unregistration of + * events is done in EventDispatcherMixin. * - * @class */ -openerp.web.NotFound = openerp.web.Class.extend( /** @lends openerp.web.NotFound# */ { +instance.web.Events = instance.web.Class.extend({ + + on : function(events, callback, context) { + var ev; + events = events.split(/\s+/); + var calls = this._callbacks || (this._callbacks = {}); + while (ev = events.shift()) { + var list = calls[ev] || (calls[ev] = {}); + var tail = list.tail || (list.tail = list.next = {}); + tail.callback = callback; + tail.context = context; + list.tail = tail.next = {}; + } + return this; + }, + + off : function(events, callback, context) { + var ev, calls, node; + if (!events) { + delete this._callbacks; + } else if (calls = this._callbacks) { + events = events.split(/\s+/); + while (ev = events.shift()) { + node = calls[ev]; + delete calls[ev]; + if (!callback || !node) + continue; + while ((node = node.next) && node.next) { + if (node.callback === callback + && (!context || node.context === context)) + continue; + this.on(ev, node.callback, node.context); + } + } + } + return this; + }, + + trigger : function(events) { + var event, node, calls, tail, args, all, rest; + if (!(calls = this._callbacks)) + return this; + all = calls['all']; + (events = events.split(/\s+/)).push(null); + // Save references to the current heads & tails. + while (event = events.shift()) { + if (all) + events.push({ + next : all.next, + tail : all.tail, + event : event + }); + if (!(node = calls[event])) + continue; + events.push({ + next : node.next, + tail : node.tail + }); + } + rest = Array.prototype.slice.call(arguments, 1); + while (node = events.pop()) { + tail = node.tail; + args = node.event ? [ node.event ].concat(rest) : rest; + while ((node = node.next) !== tail) { + node.callback.apply(node.context || this, args); + } + } + return this; + } +}); + +instance.web.EventDispatcherMixin = _.extend({}, instance.web.ParentedMixin, { + __eventDispatcherMixin: true, + init: function() { + instance.web.ParentedMixin.init.call(this); + this.__edispatcherEvents = new instance.web.Events(); + this.__edispatcherRegisteredEvents = []; + }, + on: function(events, dest, func) { + var self = this; + events = events.split(/\s+/); + _.each(events, function(eventName) { + self.__edispatcherEvents.on(eventName, func, dest); + if (dest && dest.__eventDispatcherMixin) { + dest.__edispatcherRegisteredEvents.push({name: eventName, func: func, source: self}); + } + }); + return this; + }, + off: function(events, dest, func) { + var self = this; + events = events.split(/\s+/); + _.each(events, function(eventName) { + self.__edispatcherEvents.off(eventName, func, dest); + if (dest && dest.__eventDispatcherMixin) { + dest.__edispatcherRegisteredEvents = _.filter(dest.__edispatcherRegisteredEvents, function(el) { + return !(el.name === eventName && el.func === func && el.source === self); + }); + } + }); + return this; + }, + trigger: function(events) { + this.__edispatcherEvents.trigger.apply(this.__edispatcherEvents, arguments); + return this; + }, + destroy: function() { + var self = this; + _.each(this.__edispatcherRegisteredEvents, function(event) { + event.source.__edispatcherEvents.off(event.name, event.func, self); + }); + this.__edispatcherRegisteredEvents = []; + if(!this.__edispatcherEvents) { + debugger; + } + this.__edispatcherEvents.off(); + instance.web.ParentedMixin.destroy.call(this); + } +}); + +instance.web.GetterSetterMixin = _.extend({}, instance.web.EventDispatcherMixin, { + init: function() { + instance.web.EventDispatcherMixin.init.call(this); + this.__getterSetterInternalMap = {}; + }, + set: function(map) { + var self = this; + var changed = false; + _.each(map, function(val, key) { + var tmp = self.__getterSetterInternalMap[key]; + if (tmp === val) + return; + changed = true; + self.__getterSetterInternalMap[key] = val; + self.trigger("change:" + key, self, { + oldValue: tmp, + newValue: val + }); + }); + if (changed) + self.trigger("change", self); + }, + get: function(key) { + return this.__getterSetterInternalMap[key]; + } +}); + +instance.web.CallbackEnabledMixin = _.extend({}, instance.web.GetterSetterMixin, { + init: function() { + instance.web.GetterSetterMixin.init.call(this); + var self = this; + var callback_maker = function(obj, name, method) { + var callback = function() { + var args = Array.prototype.slice.call(arguments); + self.trigger.apply(self, [name].concat(args)); + var r; + for(var i = 0; i < callback.callback_chain.length; i++) { + var c = callback.callback_chain[i]; + if(c.unique) { + callback.callback_chain.splice(i, 1); + i -= 1; + } + var result = c.callback.apply(c.self, c.args.concat(args)); + if (c.callback === method) { + // return the result of the original method + r = result; + } + // TODO special value to stop the chain + // instance.web.callback_stop + } + return r; + }; + callback.callback_chain = []; + callback.add = function(f) { + if(typeof(f) == 'function') { + f = { callback: f, args: Array.prototype.slice.call(arguments, 1) }; + } + f.self = f.self || null; + f.args = f.args || []; + f.unique = !!f.unique; + if(f.position == 'last') { + callback.callback_chain.push(f); + } else { + callback.callback_chain.unshift(f); + } + return callback; + }; + callback.add_first = function(f) { + return callback.add.apply(null,arguments); + }; + callback.add_last = function(f) { + return callback.add({ + callback: f, + args: Array.prototype.slice.call(arguments, 1), + position: "last" + }); + }; + callback.remove = function(f) { + callback.callback_chain = _.difference(callback.callback_chain, _.filter(callback.callback_chain, function(el) { + return el.callback === f; + })); + return callback; + }; + + return callback.add({ + callback: method, + self:obj, + args:Array.prototype.slice.call(arguments, 3) + }); + }; + // Transform on_/do_* methods into callbacks + for (var name in this) { + if(typeof(this[name]) == "function") { + this[name].debug_name = name; + if((/^on_|^do_/).test(name)) { + this[name] = callback_maker(this, name, this[name]); + } + } + } + }, + /** + * Proxies a method of the object, in order to keep the right ``this`` on + * method invocations. + * + * This method is similar to ``Function.prototype.bind`` or ``_.bind``, and + * even more so to ``jQuery.proxy`` with a fundamental difference: its + * resolution of the method being called is lazy, meaning it will use the + * method as it is when the proxy is called, not when the proxy is created. + * + * Other methods will fix the bound method to what it is when creating the + * binding/proxy, which is fine in most javascript code but problematic in + * OpenERP Web where developers may want to replace existing callbacks with + * theirs. + * + * The semantics of this precisely replace closing over the method call. + * + * @param {String} method_name name of the method to invoke + * @returns {Function} proxied method + */ + proxy: function (method_name) { + var self = this; + return function () { + return self[method_name].apply(self, arguments); + } + } }); -openerp.web.KeyNotFound = openerp.web.NotFound.extend( /** @lends openerp.web.KeyNotFound# */ { + +instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, { + /** + * Tag name when creating a default $element. + * @type string + */ + tagName: 'div', /** - * Thrown when a key could not be found in a mapping + * Constructs the widget and sets its parent if a parent is given. + * + * @constructs instance.web.Widget + * @extends instance.web.CallbackEnabled * - * @constructs openerp.web.KeyNotFound - * @extends openerp.web.NotFound - * @param {String} key the key which could not be found + * @param {instance.web.Widget} parent Binds the current instance to the given Widget instance. + * When that widget is destroyed by calling destroy(), the current instance will be + * destroyed too. Can be null. + * @param {String} element_id Deprecated. Sets the element_id. Only useful when you want + * to bind the current Widget to an already existing part of the DOM, which is not compatible + * with the DOM insertion methods provided by the current implementation of Widget. So + * for new components this argument should not be provided any more. */ - init: function (key) { - this.key = key; + init: function(parent) { + instance.web.CallbackEnabledMixin.init.call(this); + this.$element = $(document.createElement(this.tagName)); + this.setParent(parent); + }, + /** + * Destroys the current widget, also destroys all its children before destroying itself. + */ + destroy: function() { + _.each(this.getChildren(), function(el) { + el.destroy(); + }); + if(this.$element != null) { + this.$element.remove(); + } + instance.web.GetterSetterMixin.destroy.call(this); + }, + /** + * Renders the current widget and appends it to the given jQuery object or Widget. + * + * @param target A jQuery object or a Widget instance. + */ + appendTo: function(target) { + var self = this; + return this.__widgetRenderAndInsert(function(t) { + self.$element.appendTo(t); + }, target); + }, + /** + * Renders the current widget and prepends it to the given jQuery object or Widget. + * + * @param target A jQuery object or a Widget instance. + */ + prependTo: function(target) { + var self = this; + return this.__widgetRenderAndInsert(function(t) { + self.$element.prependTo(t); + }, target); + }, + /** + * Renders the current widget and inserts it after to the given jQuery object or Widget. + * + * @param target A jQuery object or a Widget instance. + */ + insertAfter: function(target) { + var self = this; + return this.__widgetRenderAndInsert(function(t) { + self.$element.insertAfter(t); + }, target); + }, + /** + * Renders the current widget and inserts it before to the given jQuery object or Widget. + * + * @param target A jQuery object or a Widget instance. + */ + insertBefore: function(target) { + var self = this; + return this.__widgetRenderAndInsert(function(t) { + self.$element.insertBefore(t); + }, target); + }, + /** + * Renders the current widget and replaces the given jQuery object. + * + * @param target A jQuery object or a Widget instance. + */ + replace: function(target) { + return this.__widgetRenderAndInsert(_.bind(function(t) { + this.$element.replaceAll(t); + }, this), target); }, - toString: function () { - return "The key " + this.key + " was not found"; + __widgetRenderAndInsert: function(insertion, target) { + this.renderElement(); + insertion(target); + return this.start(); + }, + /** + * This is the method to implement to render the Widget. + */ + renderElement: function() { + }, + /** + * Method called after rendering. Mostly used to bind actions, perform asynchronous + * calls, etc... + * + * By convention, the method should return a promise to inform the caller when + * this widget has been initialized. + * + * @returns {jQuery.Deferred} + */ + start: function() { + } +}); + +// Classes + +instance.web.CallbackEnabled = instance.web.Class.extend(instance.web.CallbackEnabledMixin, { + init: function() { + instance.web.CallbackEnabledMixin.init.call(this); } }); -openerp.web.ObjectNotFound = openerp.web.NotFound.extend( /** @lends openerp.web.ObjectNotFound# */ { + +/** + * Base class for all visual components. Provides a lot of functionalities helpful + * for the management of a part of the DOM. + * + * Widget handles: + * - Rendering with QWeb. + * - Life-cycle management and parenting (when a parent is destroyed, all its children are + * destroyed too). + * - Insertion in DOM. + * + * Guide to create implementations of the Widget class: + * ============================================== + * + * Here is a sample child class: + * + * MyWidget = instance.base.Widget.extend({ + * // the name of the QWeb template to use for rendering + * template: "MyQWebTemplate", + * + * init: function(parent) { + * this._super(parent); + * // stuff that you want to init before the rendering + * }, + * start: function() { + * // stuff you want to make after the rendering, `this.$element` holds a correct value + * this.$element.find(".my_button").click(/* an example of event binding * /); + * + * // if you have some asynchronous operations, it's a good idea to return + * // a promise in start() + * var promise = this.rpc(...); + * return promise; + * } + * }); + * + * Now this class can simply be used with the following syntax: + * + * var my_widget = new MyWidget(this); + * my_widget.appendTo($(".some-div")); + * + * With these two lines, the MyWidget instance was inited, rendered, it was inserted into the + * DOM inside the ".some-div" div and its events were binded. + * + * And of course, when you don't need that widget anymore, just do: + * + * my_widget.destroy(); + * + * That will kill the widget in a clean way and erase its content from the dom. + */ +instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, { /** - * Thrown when an object path does not designate a valid class or object - * in the openerp hierarchy. + * The name of the QWeb template that will be used for rendering. Must be + * redefined in subclasses or the default render() method can not be used. + * + * @type string + */ + template: null, + /** + * Constructs the widget and sets its parent if a parent is given. * - * @constructs openerp.web.ObjectNotFound - * @extends openerp.web.NotFound - * @param {String} path the invalid object path + * @constructs instance.web.Widget + * @extends instance.web.CallbackEnabled + * + * @param {instance.web.Widget} parent Binds the current instance to the given Widget instance. + * When that widget is destroyed by calling destroy(), the current instance will be + * destroyed too. Can be null. + * @param {String} element_id Deprecated. Sets the element_id. Only useful when you want + * to bind the current Widget to an already existing part of the DOM, which is not compatible + * with the DOM insertion methods provided by the current implementation of Widget. So + * for new components this argument should not be provided any more. + */ + init: function(parent) { + instance.web.WidgetMixin.init.call(this,parent); + this.session = instance.connection; + }, + /** + * Renders the element. The default implementation renders the widget using QWeb, + * `this.template` must be defined. The context given to QWeb contains the "widget" + * key that references `this`. */ - init: function (path) { - this.path = path; + renderElement: function() { + var rendered = null; + if (this.template) + rendered = instance.web.qweb.render(this.template, {widget: this}); + if (_.str.trim(rendered)) { + var elem = $(rendered); + this.$element.replaceWith(elem); + this.$element = elem; + } }, - toString: function () { - return "Could not find any object of path " + this.path; + /** + * Informs the action manager to do an action. This supposes that + * the action manager can be found amongst the ancestors of the current widget. + * If that's not the case this method will simply return `false`. + */ + do_action: function(action, on_finished) { + if (this.getParent()) { + return this.getParent().do_action(action, on_finished); + } + return false; + }, + do_notify: function() { + if (this.getParent()) { + return this.getParent().do_notify.apply(this,arguments); + } + return false; + }, + do_warn: function() { + if (this.getParent()) { + return this.getParent().do_warn.apply(this,arguments); + } + return false; + }, + rpc: function(url, data, success, error) { + var def = $.Deferred().then(success, error); + var self = this; + instance.connection.rpc(url, data). then(function() { + if (!self.isDestroyed()) + def.resolve.apply(def, arguments); + }, function() { + if (!self.isDestroyed()) + def.reject.apply(def, arguments); + }); + return def.promise(); } }); -openerp.web.Registry = openerp.web.Class.extend( /** @lends openerp.web.Registry# */ { + +instance.web.Registry = instance.web.Class.extend({ /** * Stores a mapping of arbitrary key (strings) to object paths (as strings * as well). @@ -204,11 +757,11 @@ openerp.web.Registry = openerp.web.Class.extend( /** @lends openerp.web.Registry * object, even if those objects have been overloaded/replaced after the * registry was created. * - * An object path is simply a dotted name from the openerp root to the - * object pointed to (e.g. ``"openerp.web.Connection"`` for an OpenERP + * An object path is simply a dotted name from the instance root to the + * object pointed to (e.g. ``"instance.web.Connection"`` for an OpenERP * connection object). * - * @constructs openerp.web.Registry + * @constructs instance.web.Registry * @param {Object} mapping a mapping of keys to object-paths */ init: function (mapping) { @@ -220,10 +773,7 @@ openerp.web.Registry = openerp.web.Class.extend( /** @lends openerp.web.Registry * * @param {String} key the key to fetch the object for * @param {Boolean} [silent_error=false] returns undefined if the key or object is not found, rather than throwing an exception - * @returns {Class} the stored class, to initialize - * - * @throws {openerp.web.KeyNotFound} if the object was not in the mapping - * @throws {openerp.web.ObjectNotFound} if the object path was invalid + * @returns {Class} the stored class, to initialize or null if not found */ get_object: function (key, silent_error) { var path_string = this.map[key]; @@ -232,10 +782,10 @@ openerp.web.Registry = openerp.web.Class.extend( /** @lends openerp.web.Registry return this.parent.get_object(key, silent_error); } if (silent_error) { return void 'nooo'; } - throw new openerp.web.KeyNotFound(key); + return null; } - var object_match = openerp; + var object_match = instance; var path = path_string.split('.'); // ignore first section for(var i=1; i<path.length; ++i) { @@ -243,7 +793,7 @@ openerp.web.Registry = openerp.web.Class.extend( /** @lends openerp.web.Registry if (object_match === undefined) { if (silent_error) { return void 'noooooo'; } - throw new openerp.web.ObjectNotFound(path_string); + return null; } } return object_match; @@ -269,9 +819,6 @@ openerp.web.Registry = openerp.web.Class.extend( /** @lends openerp.web.Registry * * @param {Array} keys a sequence of keys to fetch the object for * @returns {Class} the first class found matching an object - * - * @throws {openerp.web.KeyNotFound} if none of the keys was in the mapping - * @trows {openerp.web.ObjectNotFound} if a found object path was invalid */ get_any: function (keys) { for (var i=0; i<keys.length; ++i) { @@ -282,7 +829,7 @@ openerp.web.Registry = openerp.web.Class.extend( /** @lends openerp.web.Registry return this.get_object(key); } - throw new openerp.web.KeyNotFound(keys.join(',')); + return null; }, /** * Adds a new key and value to the registry. @@ -291,7 +838,7 @@ openerp.web.Registry = openerp.web.Class.extend( /** @lends openerp.web.Registry * * @param {String} key * @param {String} object_path fully qualified dotted object path - * @returns {openerp.web.Registry} itself + * @returns {instance.web.Registry} itself */ add: function (key, object_path) { this.map[key] = object_path; @@ -307,7 +854,7 @@ openerp.web.Registry = openerp.web.Class.extend( /** @lends openerp.web.Registry * @param {Object} [mapping={}] a mapping of keys to object-paths */ extend: function (mapping) { - var child = new openerp.web.Registry(mapping); + var child = new instance.web.Registry(mapping); child.parent = this; return child; }, @@ -320,57 +867,10 @@ openerp.web.Registry = openerp.web.Class.extend( /** @lends openerp.web.Registry } }); -openerp.web.CallbackEnabledMixin = { - init: function() { - // Transform on_* method into openerp.web.callbacks - for (var name in this) { - if(typeof(this[name]) == "function") { - this[name].debug_name = name; - // bind ALL function to this not only on_and _do ? - if((/^on_|^do_/).test(name)) { - this[name] = openerp.web.callback(this, this[name]); - } - } - } - }, - /** - * Proxies a method of the object, in order to keep the right ``this`` on - * method invocations. - * - * This method is similar to ``Function.prototype.bind`` or ``_.bind``, and - * even more so to ``jQuery.proxy`` with a fundamental difference: its - * resolution of the method being called is lazy, meaning it will use the - * method as it is when the proxy is called, not when the proxy is created. - * - * Other methods will fix the bound method to what it is when creating the - * binding/proxy, which is fine in most javascript code but problematic in - * OpenERP Web where developers may want to replace existing callbacks with - * theirs. - * - * The semantics of this precisely replace closing over the method call. - * - * @param {String} method_name name of the method to invoke - * @returns {Function} proxied method - */ - proxy: function (method_name) { - var self = this; - return function () { - return self[method_name].apply(self, arguments); - } - } -}; - -openerp.web.CallbackEnabled = openerp.web.Class.extend(_.extend({}, nova.GetterSetterMixin, openerp.web.CallbackEnabledMixin, { - init: function() { - nova.GetterSetterMixin.init.call(this); - openerp.web.CallbackEnabledMixin.init.call(this); - } -})); - -openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp.web.Connection# */{ +instance.web.Connection = instance.web.CallbackEnabled.extend( /** @lends instance.web.Connection# */{ /** - * @constructs openerp.web.Connection - * @extends openerp.web.CallbackEnabled + * @constructs instance.web.Connection + * @extends instance.web.CallbackEnabled * * @param {String} [server] JSON-RPC endpoint hostname * @param {String} [port] JSON-RPC endpoint port @@ -380,7 +880,7 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp. this.server = null; this.debug = ($.deparam($.param.querystring()).debug != undefined); // TODO: session store in cookie should be optional - this.name = openerp._session_id; + this.name = instance._session_id; this.qweb_mutex = new $.Mutex(); }, session_bind: function(origin) { @@ -388,7 +888,7 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp. this.origin = origin ? _.str.rtrim(origin,'/') : window_origin; this.prefix = this.origin; this.server = this.origin; // keep chs happy - openerp.web.qweb.default_dict['_s'] = this.origin; + instance.web.qweb.default_dict['_s'] = this.origin; this.rpc_function = (this.origin == window_origin) ? this.rpc_json : this.rpc_jsonp; this.session_id = false; this.uid = false; @@ -396,7 +896,7 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp. this.user_context= {}; this.db = false; this.openerp_entreprise = false; - this.module_list = openerp._modules.slice(); + this.module_list = instance._modules.slice(); this.module_loaded = {}; _(this.module_list).each(function (mod) { self.module_loaded[mod] = true; @@ -569,7 +1069,7 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp. try { var ctx = this.test_eval_contexts(source.contexts); if (!_.isEqual(ctx, expected.context)) { - openerp.webclient.notification.warn('Context mismatch, report to xmo', + instance.webclient.notification.warn('Context mismatch, report to xmo', _.str.sprintf(match_template, { source: JSON.stringify(source.contexts), local: JSON.stringify(ctx), @@ -577,17 +1077,17 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp. }), true); } } catch (e) { - openerp.webclient.notification.warn('Context fail, report to xmo', + instance.webclient.notification.warn('Context fail, report to xmo', _.str.sprintf(fail_template, { error: e.message, - source: source.contexts + source: JSON.stringify(source.contexts) }), true); } try { var dom = this.test_eval_domains(source.domains, this.test_eval_get_context()); if (!_.isEqual(dom, expected.domain)) { - openerp.webclient.notification.warn('Domains mismatch, report to xmo', + instance.webclient.notification.warn('Domains mismatch, report to xmo', _.str.sprintf(match_template, { source: JSON.stringify(source.domains), local: JSON.stringify(dom), @@ -595,17 +1095,17 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp. }), true); } } catch (e) { - openerp.webclient.notification.warn('Domain fail, report to xmo', + instance.webclient.notification.warn('Domain fail, report to xmo', _.str.sprintf(fail_template, { error: e.message, - source: source.domains + source: JSON.stringify(source.domains) }), true); } try { var groups = this.test_eval_groupby(source.group_by_seq); if (!_.isEqual(groups, expected.group_by)) { - openerp.webclient.notification.warn('GroupBy mismatch, report to xmo', + instance.webclient.notification.warn('GroupBy mismatch, report to xmo', _.str.sprintf(match_template, { source: JSON.stringify(source.group_by_seq), local: JSON.stringify(groups), @@ -613,43 +1113,51 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp. }), true); } } catch (e) { - openerp.webclient.notification.warn('GroupBy fail, report to xmo', + instance.webclient.notification.warn('GroupBy fail, report to xmo', _.str.sprintf(fail_template, { error: e.message, - source: source.group_by_seq + source: JSON.stringify(source.group_by_seq) }), true); } }, - test_eval_contexts: function (contexts) { - var result_context = _.extend({}, this.user_context), - self = this; - _(contexts).each(function (ctx) { + test_eval_contexts: function (contexts, evaluation_context) { + evaluation_context = evaluation_context || {}; + var self = this; + return _(contexts).reduce(function (result_context, ctx) { + // __eval_context evaluations can lead to some of `contexts`'s + // values being null, skip them as well as empty contexts + if (_.isEmpty(ctx)) { return result_context; } + var evaluated = ctx; switch(ctx.__ref) { case 'context': - _.extend(result_context, py.eval(ctx.__debug)); + evaluated = py.eval(ctx.__debug, evaluation_context); break; case 'compound_context': - _.extend( - result_context, self.test_eval_contexts(ctx.__contexts)); + var eval_context = self.test_eval_contexts([ctx.__eval_context]); + evaluated = self.test_eval_contexts( + ctx.__contexts, _.extend({}, evaluation_context, eval_context)); break; - default: - _.extend(result_context, ctx); } - }); - return result_context; + // add newly evaluated context to evaluation context for following + // siblings + _.extend(evaluation_context, evaluated); + return _.extend(result_context, evaluated); + }, _.extend({}, this.user_context)); }, - test_eval_domains: function (domains, eval_context) { + test_eval_domains: function (domains, evaluation_context) { var result_domain = [], self = this; _(domains).each(function (dom) { switch(dom.__ref) { case 'domain': result_domain.push.apply( - result_domain, py.eval(dom.__debug, eval_context)); + result_domain, py.eval(dom.__debug, evaluation_context)); break; case 'compound_domain': + var eval_context = self.test_eval_contexts([dom.__eval_context]); result_domain.push.apply( result_domain, self.test_eval_domains( - dom.__domains, eval_context)); + dom.__domains, _.extend( + {}, evaluation_context, eval_context))); break; default: result_domain.push.apply( @@ -667,7 +1175,8 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp. group = py.eval(ctx.__debug).group_by; break; case 'compound_context': - group = self.test_eval_contexts(ctx.__contexts).group_by; + group = self.test_eval_contexts( + ctx.__contexts, ctx.__eval_context).group_by; break; default: group = ctx.group_by @@ -691,7 +1200,7 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp. * setting the correct session id and session context in the parameter * objects * - * @param {Object} url RPC endpoint + * @param {String} url RPC endpoint * @param {Object} params call parameters * @param {Function} success_callback function to execute on RPC call success * @param {Function} error_callback function to execute on RPC call failure @@ -715,7 +1224,9 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp. }; var deferred = $.Deferred(); this.on_rpc_request(); - this.rpc_function(url, payload).then( + var aborter = params.aborter; + delete params.aborter; + var request = this.rpc_function(url, payload).then( function (response, textStatus, jqXHR) { self.on_rpc_response(); if (!response.error) { @@ -744,6 +1255,16 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp. }; deferred.reject(error, $.Event()); }); + if (aborter) { + aborter.abort_last = function () { + if (!(request.isResolved() || request.isRejected())) { + deferred.fail(function (error, event) { + event.preventDefault(); + }); + request.abort(); + } + }; + } // Allow deferred user to disable on_rpc_error in fail deferred.fail(function() { deferred.fail(function(error, event) { @@ -848,7 +1369,7 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp. // TODO: session store in cookie should be optional this.session_id = this.get_cookie('session_id'); return this.session_reload().pipe(function(result) { - var modules = openerp._modules.join(','); + var modules = instance._modules.join(','); var deferred = self.rpc('/web/webclient/qweblist', {mods: modules}).pipe(self.do_load_qweb); if(self.session_is_valid()) { return deferred.pipe(function() { return self.load_modules(); }); @@ -971,7 +1492,7 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp. self.rpc('/web/webclient/csslist', {mods: to_load}, self.do_load_css), self.rpc('/web/webclient/qweblist', {mods: to_load}).pipe(self.do_load_qweb), self.rpc('/web/webclient/translations', params).pipe(function(trans) { - openerp.web._t.database.set_bundle(trans); + instance.web._t.database.set_bundle(trans); var file_list = ["/web/static/lib/datejs/globalization/" + lang.replace("_", "-") + ".js"]; return self.rpc('/web/webclient/jslist', {mods: to_load}).pipe(function(files) { return self.do_load_js(file_list.concat(files)); @@ -1033,7 +1554,7 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp. self.qweb_mutex.exec(function() { return self.rpc('/web/proxy/load', {path: file}).pipe(function(xml) { if (!xml) { return; } - openerp.web.qweb.add_template(_.str.trim(xml)); + instance.web.qweb.add_template(_.str.trim(xml)); }); }); }); @@ -1044,10 +1565,10 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp. var mod = this.module_list[j]; if(this.module_loaded[mod]) continue; - openerp[mod] = {}; + instance[mod] = {}; // init module mod - if(openerp._openerp[mod] != undefined) { - openerp._openerp[mod](openerp); + if(instance._openerp[mod] != undefined) { + instance._openerp[mod](instance); this.module_loaded[mod] = true; } } @@ -1167,164 +1688,10 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp. } }); -/** - * Base class for all visual components. Provides a lot of functionalities helpful - * for the management of a part of the DOM. - * - * Widget handles: - * - Rendering with QWeb. - * - Life-cycle management and parenting (when a parent is destroyed, all its children are - * destroyed too). - * - Insertion in DOM. - * - * Guide to create implementations of the Widget class: - * ============================================== - * - * Here is a sample child class: - * - * MyWidget = openerp.base.Widget.extend({ - * // the name of the QWeb template to use for rendering - * template: "MyQWebTemplate", - * - * init: function(parent) { - * this._super(parent); - * // stuff that you want to init before the rendering - * }, - * start: function() { - * // stuff you want to make after the rendering, `this.$element` holds a correct value - * this.$element.find(".my_button").click(/* an example of event binding * /); - * - * // if you have some asynchronous operations, it's a good idea to return - * // a promise in start() - * var promise = this.rpc(...); - * return promise; - * } - * }); - * - * Now this class can simply be used with the following syntax: - * - * var my_widget = new MyWidget(this); - * my_widget.appendTo($(".some-div")); - * - * With these two lines, the MyWidget instance was inited, rendered, it was inserted into the - * DOM inside the ".some-div" div and its events were binded. - * - * And of course, when you don't need that widget anymore, just do: - * - * my_widget.destroy(); - * - * That will kill the widget in a clean way and erase its content from the dom. - */ -openerp.web.Widget = nova.Widget.extend(_.extend({}, openerp.web.CallbackEnabledMixin, { - /** - * The name of the QWeb template that will be used for rendering. Must be - * redefined in subclasses or the default render() method can not be used. - * - * @type string - */ - template: null, - /** - * Constructs the widget and sets its parent if a parent is given. - * - * @constructs openerp.web.Widget - * @extends openerp.web.CallbackEnabled - * - * @param {openerp.web.Widget} parent Binds the current instance to the given Widget instance. - * When that widget is destroyed by calling destroy(), the current instance will be - * destroyed too. Can be null. - * @param {String} element_id Deprecated. Sets the element_id. Only useful when you want - * to bind the current Widget to an already existing part of the DOM, which is not compatible - * with the DOM insertion methods provided by the current implementation of Widget. So - * for new components this argument should not be provided any more. - */ - init: function(parent) { - this._super(parent); - openerp.web.CallbackEnabledMixin.init.call(this); - this.session = openerp.connection; - }, - /** - * Renders the element. The default implementation renders the widget using QWeb, - * `this.template` must be defined. The context given to QWeb contains the "widget" - * key that references `this`. - */ - renderElement: function() { - var rendered = null; - if (this.template) - rendered = openerp.web.qweb.render(this.template, {widget: this}); - if (_.str.trim(rendered)) { - var elem = $(rendered); - this.$element.replaceWith(elem); - this.$element = elem; - } - }, - /** - * Informs the action manager to do an action. This supposes that - * the action manager can be found amongst the ancestors of the current widget. - * If that's not the case this method will simply return `false`. - */ - do_action: function(action, on_finished) { - if (this.getParent()) { - return this.getParent().do_action(action, on_finished); - } - return false; - }, - do_notify: function() { - if (this.getParent()) { - return this.getParent().do_notify.apply(this,arguments); - } - return false; - }, - do_warn: function() { - if (this.getParent()) { - return this.getParent().do_warn.apply(this,arguments); - } - return false; - }, - rpc: function(url, data, success, error) { - var def = $.Deferred().then(success, error); - var self = this; - openerp.connection.rpc(url, data). then(function() { - if (!self.isDestroyed()) - def.resolve.apply(def, arguments); - }, function() { - if (!self.isDestroyed()) - def.reject.apply(def, arguments); - }); - return def.promise(); - } -})); - -/** - * @deprecated use :class:`openerp.web.Widget` - */ -openerp.web.OldWidget = openerp.web.Widget.extend({ - init: function(parent, element_id) { - this._super(parent); - this.element_id = element_id; - this.element_id = this.element_id || _.uniqueId('widget-'); - var tmp = document.getElementById(this.element_id); - this.$element = tmp ? $(tmp) : $(document.createElement(this.tagName)); - }, - renderElement: function() { - var rendered = this.render(); - if (rendered) { - var elem = $(rendered); - this.$element.replaceWith(elem); - this.$element = elem; - } - return this; - }, - render: function (additional) { - if (this.template) - return openerp.web.qweb.render(this.template, _.extend({widget: this}, additional || {})); - return null; - } -}); - -openerp.web.TranslationDataBase = openerp.web.Class.extend(/** @lends openerp.web.TranslationDataBase# */{ +instance.web.TranslationDataBase = instance.web.Class.extend(/** @lends instance.web.TranslationDataBase# */{ /** - * @constructs openerp.web.TranslationDataBase - * @extends openerp.web.Class + * @constructs instance.web.TranslationDataBase + * @extends instance.web.Class */ init: function() { this.db = {}; @@ -1374,138 +1741,33 @@ openerp.web.TranslationDataBase = openerp.web.Class.extend(/** @lends openerp.we } }); -/** Configure blockui */ -if ($.blockUI) { - $.blockUI.defaults.baseZ = 1100; - $.blockUI.defaults.message = '<img src="/web/static/src/img/throbber2.gif">'; -} - -/** Configure default qweb */ -openerp.web._t = new openerp.web.TranslationDataBase().build_translation_function(); -/** - * Lazy translation function, only performs the translation when actually - * printed (e.g. inserted into a template) - * - * Useful when defining translatable strings in code evaluated before the - * translation database is loaded, as class attributes or at the top-level of - * an OpenERP Web module - * - * @param {String} s string to translate - * @returns {Object} lazy translation object - */ -openerp.web._lt = function (s) { - return {toString: function () { return openerp.web._t(s); }} -}; -openerp.web.qweb = new QWeb2.Engine(); -openerp.web.qweb.debug = ($.deparam($.param.querystring()).debug != undefined); -openerp.web.qweb.default_dict = { - '_' : _, - '_t' : openerp.web._t -}; -openerp.web.qweb.preprocess_node = function() { - // Note that 'this' is the Qweb Node - switch (this.node.nodeType) { - case 3: - case 4: - // Text and CDATAs - var translation = this.node.parentNode.attributes['t-translation']; - if (translation && translation.value === 'off') { - return; - } - var ts = _.str.trim(this.node.data); - if (ts.length === 0) { - return; - } - var tr = openerp.web._t(ts); - if (tr !== ts) { - this.node.data = tr; - } - break; - case 1: - // Element - var attr, attrs = ['label', 'title', 'alt']; - while (attr = attrs.pop()) { - if (this.attributes[attr]) { - this.attributes[attr] = openerp.web._t(this.attributes[attr]); - } - } - } -}; - /** - * A small utility function to check if a class implements correctly an interface, assuming that - * interface is simply specified using a dictionary containing methods and attributes with the - * correct type. It only performs the check when in debug mode and the only effect of an invalid - * check is messages in the console. + * @deprecated use :class:`instance.web.Widget` */ -openerp.web.check_interface = function(_class, _interface) { - if (! openerp.web.check_interface.debug) - return; - for (var member in _interface) { - if ( (typeof _class.prototype[member] != typeof _interface[member]) ) { - console.error("class failed to implement interface member '" + member + "'"); +instance.web.OldWidget = instance.web.Widget.extend({ + init: function(parent, element_id) { + this._super(parent); + this.element_id = element_id; + this.element_id = this.element_id || _.uniqueId('widget-'); + var tmp = document.getElementById(this.element_id); + this.$element = tmp ? $(tmp) : $(document.createElement(this.tagName)); + }, + renderElement: function() { + var rendered = this.render(); + if (rendered) { + var elem = $(rendered); + this.$element.replaceWith(elem); + this.$element = elem; } + return this; + }, + render: function (additional) { + if (this.template) + return instance.web.qweb.render(this.template, _.extend({widget: this}, additional || {})); + return null; } -} -openerp.web.check_interface.debug = ($.deparam($.param.querystring()).debug != undefined); - -/** Jquery extentions */ -$.Mutex = (function() { - function Mutex() { - this.def = $.Deferred().resolve(); - } - Mutex.prototype.exec = function(action) { - var current = this.def; - var next = this.def = $.Deferred(); - return current.pipe(function() { - return $.when(action()).always(function() { - next.resolve(); - }); - }); - }; - return Mutex; -})(); - -/** Setup default connection */ -openerp.connection = new openerp.web.Connection(); -openerp.web.qweb.default_dict['__debug__'] = openerp.connection.debug; - - -$.async_when = function() { - var async = false; - var def = $.Deferred(); - $.when.apply($, arguments).then(function() { - var args = arguments; - var action = function() { - def.resolve.apply(def, args); - }; - if (async) - action(); - else - setTimeout(action, 0); - }, function() { - var args = arguments; - var action = function() { - def.reject.apply(def, args); - }; - if (async) - action(); - else - setTimeout(action, 0); - }); - async = true; - return def; -}; - -// special tweak for the web client -var old_async_when = $.async_when; -$.async_when = function() { - if (openerp.connection.synch) - return $.when.apply(this, arguments); - else - return old_async_when.apply(this, arguments); -}; +}); -}; +} // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: diff --git a/addons/web/static/src/js/coresetup.js b/addons/web/static/src/js/coresetup.js new file mode 100644 index 00000000000..86040302668 --- /dev/null +++ b/addons/web/static/src/js/coresetup.js @@ -0,0 +1,142 @@ +/*--------------------------------------------------------- + * OpenERP Web core + *--------------------------------------------------------*/ +var console; +if (!console) { + console = {log: function () {}}; +} +if (!console.debug) { + console.debug = console.log; +} + +openerp.web.coresetup = function(instance) { + +/** Configure default qweb */ +instance.web._t = new instance.web.TranslationDataBase().build_translation_function(); +/** + * Lazy translation function, only performs the translation when actually + * printed (e.g. inserted into a template) + * + * Useful when defining translatable strings in code evaluated before the + * translation database is loaded, as class attributes or at the top-level of + * an OpenERP Web module + * + * @param {String} s string to translate + * @returns {Object} lazy translation object + */ +instance.web._lt = function (s) { + return {toString: function () { return instance.web._t(s); }} +}; +instance.web.qweb = new QWeb2.Engine(); +instance.web.qweb.debug = ($.deparam($.param.querystring()).debug != undefined); +instance.web.qweb.default_dict = { + '_' : _, + '_t' : instance.web._t +}; +instance.web.qweb.preprocess_node = function() { + // Note that 'this' is the Qweb Node + switch (this.node.nodeType) { + case 3: + case 4: + // Text and CDATAs + var translation = this.node.parentNode.attributes['t-translation']; + if (translation && translation.value === 'off') { + return; + } + var ts = _.str.trim(this.node.data); + if (ts.length === 0) { + return; + } + var tr = instance.web._t(ts); + if (tr !== ts) { + this.node.data = tr; + } + break; + case 1: + // Element + var attr, attrs = ['label', 'title', 'alt']; + while (attr = attrs.pop()) { + if (this.attributes[attr]) { + this.attributes[attr] = instance.web._t(this.attributes[attr]); + } + } + } +}; + +/** Configure blockui */ +if ($.blockUI) { + $.blockUI.defaults.baseZ = 1100; + $.blockUI.defaults.message = '<img src="/web/static/src/img/throbber2.gif">'; +} + +/** Custom jQuery plugins */ +$.fn.getAttributes = function() { + var o = {}; + if (this.length) { + for (var attr, i = 0, attrs = this[0].attributes, l = attrs.length; i < l; i++) { + attr = attrs.item(i) + o[attr.nodeName] = attr.nodeValue; + } + } + return o; +} + +/** Jquery extentions */ +$.Mutex = (function() { + function Mutex() { + this.def = $.Deferred().resolve(); + } + Mutex.prototype.exec = function(action) { + var current = this.def; + var next = this.def = $.Deferred(); + return current.pipe(function() { + return $.when(action()).always(function() { + next.resolve(); + }); + }); + }; + return Mutex; +})(); + +/** Setup default connection */ +instance.connection = new instance.web.Connection(); +instance.web.qweb.default_dict['__debug__'] = instance.connection.debug; + +$.async_when = function() { + var async = false; + var def = $.Deferred(); + $.when.apply($, arguments).then(function() { + var args = arguments; + var action = function() { + def.resolve.apply(def, args); + }; + if (async) + action(); + else + setTimeout(action, 0); + }, function() { + var args = arguments; + var action = function() { + def.reject.apply(def, args); + }; + if (async) + action(); + else + setTimeout(action, 0); + }); + async = true; + return def; +}; + +// special tweak for the web client +var old_async_when = $.async_when; +$.async_when = function() { + if (instance.connection.synch) + return $.when.apply(this, arguments); + else + return old_async_when.apply(this, arguments); +}; + +}; + +// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: diff --git a/addons/web/static/src/js/data.js b/addons/web/static/src/js/data.js index a29a9c5ad36..2dc66aaa135 100644 --- a/addons/web/static/src/js/data.js +++ b/addons/web/static/src/js/data.js @@ -1,5 +1,5 @@ -openerp.web.data = function(openerp) { +openerp.web.data = function(instance) { /** * Serializes the sort criterion array of a dataset into a form which can be @@ -8,7 +8,7 @@ openerp.web.data = function(openerp) { * @param {Array} criterion array of fields, from first to last criteria, prefixed with '-' for reverse sorting * @returns {String} SQL-like sorting string (``ORDER BY``) clause */ -openerp.web.serialize_sort = function (criterion) { +instance.web.serialize_sort = function (criterion) { return _.map(criterion, function (criteria) { if (criteria[0] === '-') { @@ -18,7 +18,7 @@ openerp.web.serialize_sort = function (criterion) { }).join(', '); }; -openerp.web.Query = openerp.web.Class.extend({ +instance.web.Query = instance.web.Class.extend({ init: function (model, fields) { this._model = model; this._fields = fields; @@ -30,7 +30,7 @@ openerp.web.Query = openerp.web.Class.extend({ }, clone: function (to_set) { to_set = to_set || {}; - var q = new openerp.web.Query(this._model, this._fields); + var q = new instance.web.Query(this._model, this._fields); q._context = this._context; q._filter = this._filter; q._limit = this._limit; @@ -41,11 +41,11 @@ openerp.web.Query = openerp.web.Class.extend({ if (!to_set.hasOwnProperty(key)) { continue; } switch(key) { case 'filter': - q._filter = new openerp.web.CompoundDomain( + q._filter = new instance.web.CompoundDomain( q._filter, to_set.filter); break; case 'context': - q._context = new openerp.web.CompoundContext( + q._context = new instance.web.CompoundContext( q._context, to_set.context); break; case 'limit': @@ -58,14 +58,14 @@ openerp.web.Query = openerp.web.Class.extend({ }, _execute: function () { var self = this; - return openerp.connection.rpc('/web/dataset/search_read', { + return instance.connection.rpc('/web/dataset/search_read', { model: this._model.name, fields: this._fields || false, domain: this._model.domain(this._filter), context: this._model.context(this._context), offset: this._offset, limit: this._limit, - sort: openerp.web.serialize_sort(this._order_by) + sort: instance.web.serialize_sort(this._order_by) }).pipe(function (results) { self._count = results.length; return results.records; @@ -127,10 +127,10 @@ openerp.web.Query = openerp.web.Class.extend({ context: this._model.context(this._context), offset: this._offset, limit: this._limit, - orderby: openerp.web.serialize_sort(this._order_by) || false + orderby: instance.web.serialize_sort(this._order_by) || false }).pipe(function (results) { return _(results).map(function (result) { - return new openerp.web.data.Group( + return new instance.web.data.Group( self._model.name, grouping[0], result); }); }); @@ -194,10 +194,10 @@ openerp.web.Query = openerp.web.Class.extend({ } }); -openerp.web.Model = openerp.web.Class.extend(/** @lends openerp.web.Model# */{ +instance.web.Model = instance.web.Class.extend(/** @lends openerp.web.Model# */{ /** - * @constructs openerp.web.Model - * @extends openerp.web.Class + * @constructs instance.web.Model + * @extends instance.web.Class * * @param {String} model_name name of the OpenERP model this object is bound to * @param {Object} [context] @@ -233,7 +233,7 @@ openerp.web.Model = openerp.web.Class.extend(/** @lends openerp.web.Model# */{ kwargs = args; args = []; } - return openerp.connection.rpc('/web/dataset/call_kw', { + return instance.connection.rpc('/web/dataset/call_kw', { model: this.name, method: method, args: args, @@ -247,7 +247,7 @@ openerp.web.Model = openerp.web.Class.extend(/** @lends openerp.web.Model# */{ * @returns {openerp.web.Query} */ query: function (fields) { - return new openerp.web.Query(this, fields); + return new instance.web.Query(this, fields); }, /** * Executes a signal on the designated workflow, on the bound OpenERP model @@ -256,7 +256,7 @@ openerp.web.Model = openerp.web.Class.extend(/** @lends openerp.web.Model# */{ * @param {String} signal signal to trigger on the workflow */ exec_workflow: function (id, signal) { - return openerp.connection.rpc('/web/dataset/exec_workflow', { + return instance.connection.rpc('/web/dataset/exec_workflow', { model: this.name, id: id, signal: signal @@ -270,7 +270,7 @@ openerp.web.Model = openerp.web.Class.extend(/** @lends openerp.web.Model# */{ */ domain: function (domain) { if (!domain) { return this._domain; } - return new openerp.web.CompoundDomain( + return new instance.web.CompoundDomain( this._domain, domain); }, /** @@ -281,8 +281,8 @@ openerp.web.Model = openerp.web.Class.extend(/** @lends openerp.web.Model# */{ * @returns The union of the user's context and the model's internal context, as well as the provided context if any. In that order. */ context: function (context) { - return new openerp.web.CompoundContext( - openerp.connection.user_context, this._context, context || {}); + return new instance.web.CompoundContext( + instance.connection.user_context, this._context, context || {}); }, /** * Button action caller, needs to perform cleanup if an action is returned @@ -292,7 +292,7 @@ openerp.web.Model = openerp.web.Class.extend(/** @lends openerp.web.Model# */{ * FIXME: remove when evaluator integrated */ call_button: function (method, args) { - return openerp.connection.rpc('/web/dataset/call_button', { + return instance.connection.rpc('/web/dataset/call_button', { model: this.name, method: method, domain_id: null, @@ -302,12 +302,12 @@ openerp.web.Model = openerp.web.Class.extend(/** @lends openerp.web.Model# */{ }, }); -openerp.web.Traverser = openerp.web.Class.extend(/** @lends openerp.web.Traverser# */{ +instance.web.Traverser = instance.web.Class.extend(/** @lends openerp.web.Traverser# */{ /** - * @constructs openerp.web.Traverser - * @extends openerp.web.Class + * @constructs instance.web.Traverser + * @extends instance.web.Class * - * @param {openerp.web.Model} model instance this traverser is bound to + * @param {instance.web.Model} model instance this traverser is bound to */ init: function (model) { this._model = model; @@ -387,11 +387,11 @@ openerp.web.Traverser = openerp.web.Class.extend(/** @lends openerp.web.Traverse * * @namespace */ -openerp.web.data = { - Group: openerp.web.Class.extend(/** @lends openerp.web.data.Group# */{ +instance.web.data = { + Group: instance.web.Class.extend(/** @lends openerp.web.data.Group# */{ /** - * @constructs openerp.web.data.Group - * @extends openerp.web.Class + * @constructs instance.web.data.Group + * @extends instance.web.Class */ init: function (model, grouping_field, read_group_group) { // In cases where group_by_no_leaf and no group_by, the result of @@ -411,7 +411,7 @@ openerp.web.data = { aggregates[key] = value || 0; }); - this.model = new openerp.web.Model( + this.model = new instance.web.Model( model, fixed_group.__context, fixed_group.__domain); var group_size = fixed_group[grouping_field + '_count'] || fixed_group.__count || 0; @@ -439,7 +439,7 @@ openerp.web.data = { }) }; -openerp.web.DataGroup = openerp.web.OldWidget.extend( /** @lends openerp.web.DataGroup# */{ +instance.web.DataGroup = instance.web.OldWidget.extend( /** @lends openerp.web.DataGroup# */{ /** * Management interface between views and grouped collections of OpenERP * records. @@ -447,13 +447,13 @@ openerp.web.DataGroup = openerp.web.OldWidget.extend( /** @lends openerp.web.Da * The root DataGroup is instantiated with the relevant information * (a session, a model, a domain, a context and a group_by sequence), the * domain and context may be empty. It is then interacted with via - * :js:func:`~openerp.web.DataGroup.list`, which is used to read the + * :js:func:`~instance.web.DataGroup.list`, which is used to read the * content of the current grouping level. * - * @constructs openerp.web.DataGroup - * @extends openerp.web.OldWidget + * @constructs instance.web.DataGroup + * @extends instance.web.OldWidget * - * @param {openerp.web.OldWidget} parent widget + * @param {instance.web.OldWidget} parent widget * @param {String} model name of the model managed by this DataGroup * @param {Array} domain search domain for this DataGroup * @param {Object} context context of the DataGroup's searches @@ -462,7 +462,7 @@ openerp.web.DataGroup = openerp.web.OldWidget.extend( /** @lends openerp.web.Da */ init: function(parent, model, domain, context, group_by, level) { this._super(parent, null); - this.model = new openerp.web.Model(model, context, domain); + this.model = new instance.web.Model(model, context, domain); this.group_by = group_by; this.context = context; this.domain = domain; @@ -476,7 +476,7 @@ openerp.web.DataGroup = openerp.web.OldWidget.extend( /** @lends openerp.web.Da .group_by(this.group_by)).then(function (groups) { if (!groups) { ifRecords(_.extend( - new openerp.web.DataSetSearch( + new instance.web.DataSetSearch( self, self.model.name, self.model.context(), self.model.domain()), @@ -487,7 +487,7 @@ openerp.web.DataGroup = openerp.web.OldWidget.extend( /** @lends openerp.web.Da var child_context = _.extend( {}, self.model.context(), group.model.context()); return _.extend( - new openerp.web.DataGroup( + new instance.web.DataGroup( self, self.model.name, group.model.domain(), child_context, group.model._context.group_by, self.level + 1), @@ -504,16 +504,16 @@ openerp.web.DataGroup = openerp.web.OldWidget.extend( /** @lends openerp.web.Da }); } }); -openerp.web.ContainerDataGroup = openerp.web.DataGroup.extend({ }); -openerp.web.GrouplessDataGroup = openerp.web.DataGroup.extend({ }); +instance.web.ContainerDataGroup = instance.web.DataGroup.extend({ }); +instance.web.GrouplessDataGroup = instance.web.DataGroup.extend({ }); -openerp.web.StaticDataGroup = openerp.web.GrouplessDataGroup.extend( /** @lends openerp.web.StaticDataGroup# */ { +instance.web.StaticDataGroup = instance.web.GrouplessDataGroup.extend( /** @lends openerp.web.StaticDataGroup# */ { /** * A specialization of groupless data groups, relying on a single static * dataset as its records provider. * - * @constructs openerp.web.StaticDataGroup - * @extends openerp.web.GrouplessDataGroup + * @constructs instance.web.StaticDataGroup + * @extends instance.web.GrouplessDataGroup * @param {openep.web.DataSetStatic} dataset a static dataset backing the groups */ init: function (dataset) { @@ -524,13 +524,13 @@ openerp.web.StaticDataGroup = openerp.web.GrouplessDataGroup.extend( /** @lends } }); -openerp.web.DataSet = openerp.web.OldWidget.extend( /** @lends openerp.web.DataSet# */{ +instance.web.DataSet = instance.web.OldWidget.extend( /** @lends openerp.web.DataSet# */{ /** * DateaManagement interface between views and the collection of selected * OpenERP records (represents the view's state?) * - * @constructs openerp.web.DataSet - * @extends openerp.web.OldWidget + * @constructs instance.web.DataSet + * @extends instance.web.OldWidget * * @param {String} model the OpenERP model this dataset will manage */ @@ -540,7 +540,7 @@ openerp.web.DataSet = openerp.web.OldWidget.extend( /** @lends openerp.web.Data this.context = context || {}; this.index = null; this._sort = []; - this._model = new openerp.web.Model(model, context); + this._model = new instance.web.Model(model, context); }, previous: function () { this.index -= 1; @@ -789,7 +789,7 @@ openerp.web.DataSet = openerp.web.OldWidget.extend( /** @lends openerp.web.Data */ sort: function (field, force_reverse) { if (!field) { - return openerp.web.serialize_sort(this._sort); + return instance.web.serialize_sort(this._sort); } var reverse = force_reverse || (this._sort[0] === field); this._sort.splice.apply( @@ -806,7 +806,7 @@ openerp.web.DataSet = openerp.web.OldWidget.extend( /** @lends openerp.web.Data this.ids = n_ids; }, }); -openerp.web.DataSetStatic = openerp.web.DataSet.extend({ +instance.web.DataSetStatic = instance.web.DataSet.extend({ init: function(parent, model, context, ids) { this._super(parent, model, context); // all local records @@ -836,10 +836,10 @@ openerp.web.DataSetStatic = openerp.web.DataSet.extend({ this.set_ids(_.without.apply(null, [this.ids].concat(ids))); } }); -openerp.web.DataSetSearch = openerp.web.DataSet.extend(/** @lends openerp.web.DataSetSearch */{ +instance.web.DataSetSearch = instance.web.DataSet.extend(/** @lends openerp.web.DataSetSearch */{ /** - * @constructs openerp.web.DataSetSearch - * @extends openerp.web.DataSet + * @constructs instance.web.DataSetSearch + * @extends instance.web.DataSet * * @param {Object} parent * @param {String} model @@ -851,7 +851,7 @@ openerp.web.DataSetSearch = openerp.web.DataSet.extend(/** @lends openerp.web.D this.domain = domain || []; this._length = null; this.ids = []; - this._model = new openerp.web.Model(model, context, domain); + this._model = new instance.web.Model(model, context, domain); }, /** * Read a slice of the records represented by this DataSet, based on its @@ -906,7 +906,7 @@ openerp.web.DataSetSearch = openerp.web.DataSet.extend(/** @lends openerp.web.D return this._super(); } }); -openerp.web.BufferedDataSet = openerp.web.DataSetStatic.extend({ +instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({ virtual_id_prefix: "one2many_v_id_", debug_mode: true, init: function() { @@ -1071,9 +1071,9 @@ openerp.web.BufferedDataSet = openerp.web.DataSetStatic.extend({ this.on_change(); }, }); -openerp.web.BufferedDataSet.virtual_id_regex = /^one2many_v_id_.*$/; +instance.web.BufferedDataSet.virtual_id_regex = /^one2many_v_id_.*$/; -openerp.web.ProxyDataSet = openerp.web.DataSetSearch.extend({ +instance.web.ProxyDataSet = instance.web.DataSetSearch.extend({ init: function() { this._super.apply(this, arguments); this.create_function = null; @@ -1119,7 +1119,7 @@ openerp.web.ProxyDataSet = openerp.web.DataSetSearch.extend({ on_unlink: function(ids) {} }); -openerp.web.CompoundContext = openerp.web.Class.extend({ +instance.web.CompoundContext = instance.web.Class.extend({ init: function () { this.__ref = "compound_context"; this.__contexts = []; @@ -1142,7 +1142,7 @@ openerp.web.CompoundContext = openerp.web.Class.extend({ } }); -openerp.web.CompoundDomain = openerp.web.Class.extend({ +instance.web.CompoundDomain = instance.web.Class.extend({ init: function () { this.__ref = "compound_domain"; this.__domains = []; @@ -1165,10 +1165,10 @@ openerp.web.CompoundDomain = openerp.web.Class.extend({ } }); -openerp.web.DropMisordered = openerp.web.Class.extend(/** @lends openerp.web.DropMisordered# */{ +instance.web.DropMisordered = instance.web.Class.extend(/** @lends openerp.web.DropMisordered# */{ /** - * @constructs openerp.web.DropMisordered - * @extends openerp.web.Class + * @constructs instance.web.DropMisordered + * @extends instance.web.Class * * @param {Boolean} [failMisordered=false] whether mis-ordered responses should be failed or just ignored */ diff --git a/addons/web/static/src/js/data_export.js b/addons/web/static/src/js/data_export.js index 3d15a4e3607..ebe82ddb4b4 100644 --- a/addons/web/static/src/js/data_export.js +++ b/addons/web/static/src/js/data_export.js @@ -1,14 +1,14 @@ -openerp.web.data_export = function(openerp) { -var QWeb = openerp.web.qweb, - _t = openerp.web._t; -openerp.web.DataExport = openerp.web.Dialog.extend({ +openerp.web.data_export = function(instance) { +var QWeb = instance.web.qweb, + _t = instance.web._t; +instance.web.DataExport = instance.web.Dialog.extend({ template: 'ExportTreeView', dialog_title: {toString: function () { return _t("Export Data"); }}, init: function(parent, dataset) { this._super(parent); this.records = {}; this.dataset = dataset; - this.exports = new openerp.web.DataSetSearch( + this.exports = new instance.web.DataSetSearch( this, 'ir.exports', this.dataset.get_context()); }, start: function() { diff --git a/addons/web/static/src/js/data_import.js b/addons/web/static/src/js/data_import.js index 3054a7784c4..30f9323f723 100644 --- a/addons/web/static/src/js/data_import.js +++ b/addons/web/static/src/js/data_import.js @@ -1,6 +1,6 @@ -openerp.web.data_import = function(openerp) { -var QWeb = openerp.web.qweb, - _t = openerp.web._t; +openerp.web.data_import = function(instance) { +var QWeb = instance.web.qweb, + _t = instance.web._t; /** * Safari does not deal well at all with raw JSON data being returned. As a * result, we're going to cheat by using a pseudo-jsonp: instead of getting @@ -29,7 +29,7 @@ function jsonp(form, attributes, callback) { $(form).ajaxSubmit(attributes); } -openerp.web.DataImport = openerp.web.Dialog.extend({ +instance.web.DataImport = instance.web.Dialog.extend({ template: 'ImportDataView', dialog_title: {toString: function () { return _t("Import Data"); }}, init: function(parent, dataset){ @@ -79,10 +79,10 @@ openerp.web.DataImport = openerp.web.Dialog.extend({ this.$element.delegate('fieldset legend', 'click', function() { $(this).parent().toggleClass('oe-closed'); }); - this.ready.push(new openerp.web.DataSet(this, this.model).call( + this.ready.push(new instance.web.DataSet(this, this.model).call( 'fields_get', [], function (fields) { self.graft_fields(fields); - self.ready.push(new openerp.web.DataSet(self, self.model) + self.ready.push(new instance.web.DataSet(self, self.model) .default_get(_.pluck(self.fields, 'id')).then(function (fields) { _.each(fields, function(val, key) { if (val) { @@ -140,7 +140,7 @@ openerp.web.DataImport = openerp.web.Dialog.extend({ f.fields = []; // only fetch sub-fields to a depth of 2 levels if (level < 2) { - self.ready.push(new openerp.web.DataSet(self, field.relation).call( + self.ready.push(new instance.web.DataSet(self, field.relation).call( 'fields_get', [], function (fields) { self.graft_fields(fields, f, level+1); })); @@ -151,7 +151,7 @@ openerp.web.DataImport = openerp.web.Dialog.extend({ }); }, toggle_import_button: function (newstate) { - openerp.web.dialog(this.$element, 'widget') + instance.web.dialog(this.$element, 'widget') .find('.oe-dialog-import-button') .button('option', 'disabled', !newstate); }, diff --git a/addons/web/static/src/js/dates.js b/addons/web/static/src/js/dates.js index d61223113f8..3db48c8412a 100644 --- a/addons/web/static/src/js/dates.js +++ b/addons/web/static/src/js/dates.js @@ -1,5 +1,5 @@ -openerp.web.dates = function(openerp) { +openerp.web.dates = function(instance) { /** * Converts a string to a Date javascript object using OpenERP's @@ -11,7 +11,7 @@ openerp.web.dates = function(openerp) { * @param {String} str A string representing a datetime. * @returns {Date} */ -openerp.web.str_to_datetime = function(str) { +instance.web.str_to_datetime = function(str) { if(!str) { return str; } @@ -38,7 +38,7 @@ openerp.web.str_to_datetime = function(str) { * @param {String} str A string representing a date. * @returns {Date} */ -openerp.web.str_to_date = function(str) { +instance.web.str_to_date = function(str) { if(!str) { return str; } @@ -65,7 +65,7 @@ openerp.web.str_to_date = function(str) { * @param {String} str A string representing a time. * @returns {Date} */ -openerp.web.str_to_time = function(str) { +instance.web.str_to_time = function(str) { if(!str) { return str; } @@ -104,7 +104,7 @@ var zpad = function(str, size) { * @param {Date} obj * @returns {String} A string representing a datetime. */ -openerp.web.datetime_to_str = function(obj) { +instance.web.datetime_to_str = function(obj) { if (!obj) { return false; } @@ -124,7 +124,7 @@ openerp.web.datetime_to_str = function(obj) { * @param {Date} obj * @returns {String} A string representing a date. */ -openerp.web.date_to_str = function(obj) { +instance.web.date_to_str = function(obj) { if (!obj) { return false; } @@ -143,7 +143,7 @@ openerp.web.date_to_str = function(obj) { * @param {Date} obj * @returns {String} A string representing a time. */ -openerp.web.time_to_str = function(obj) { +instance.web.time_to_str = function(obj) { if (!obj) { return false; } diff --git a/addons/web/static/src/js/formats.js b/addons/web/static/src/js/formats.js index 53863e63e97..640ba4d5a93 100644 --- a/addons/web/static/src/js/formats.js +++ b/addons/web/static/src/js/formats.js @@ -1,6 +1,6 @@ -openerp.web.formats = function(openerp) { -var _t = openerp.web._t; +openerp.web.formats = function(instance) { +var _t = instance.web._t; /** * Intersperses ``separator`` in ``str`` at the positions indicated by @@ -23,7 +23,7 @@ var _t = openerp.web._t; * @param {String} separator * @returns {String} */ -openerp.web.intersperse = function (str, indices, separator) { +instance.web.intersperse = function (str, indices, separator) { separator = separator || ''; var result = [], last = str.length; @@ -55,10 +55,10 @@ openerp.web.intersperse = function (str, indices, separator) { * @param {String} num * @returns {String} */ -openerp.web.insert_thousand_seps = function (num) { +instance.web.insert_thousand_seps = function (num) { var negative = num[0] === '-'; num = (negative ? num.slice(1) : num); - return (negative ? '-' : '') + openerp.web.intersperse( + return (negative ? '-' : '') + instance.web.intersperse( num, _t.database.parameters.grouping, _t.database.parameters.thousands_sep); }; @@ -69,7 +69,7 @@ openerp.web.insert_thousand_seps = function (num) { * * @param {String} value original format */ -openerp.web.strip_raw_chars = function (value) { +instance.web.strip_raw_chars = function (value) { var isletter = /[a-zA-Z]/, output = []; for(var index=0; index < value.length; ++index) { var character = value[index]; @@ -81,7 +81,7 @@ openerp.web.strip_raw_chars = function (value) { return output.join(''); }; var normalize_format = function (format) { - return Date.normalizeFormat(openerp.web.strip_raw_chars(format)); + return Date.normalizeFormat(instance.web.strip_raw_chars(format)); }; /** * Formats a single atomic value based on a field descriptor @@ -93,7 +93,7 @@ var normalize_format = function (format) { * @param {Object} [descriptor.digits] used for the formatting of floats * @param {String} [value_if_empty=''] returned if the ``value`` argument is considered empty */ -openerp.web.format_value = function (value, descriptor, value_if_empty) { +instance.web.format_value = function (value, descriptor, value_if_empty) { // If NaN value, display as with a `false` (empty cell) if (typeof value === 'number' && isNaN(value)) { value = false; @@ -111,16 +111,16 @@ openerp.web.format_value = function (value, descriptor, value_if_empty) { return value_if_empty === undefined ? '' : value_if_empty; } var l10n = _t.database.parameters; - switch (descriptor.widget || descriptor.type) { + switch (descriptor.type || (descriptor.field && descriptor.field.type)) { case 'id': return value.toString(); case 'integer': - return openerp.web.insert_thousand_seps( + return instance.web.insert_thousand_seps( _.str.sprintf('%d', value)); case 'float': var precision = descriptor.digits ? descriptor.digits[1] : 2; var formatted = _.str.sprintf('%.' + precision + 'f', value).split('.'); - formatted[0] = openerp.web.insert_thousand_seps(formatted[0]); + formatted[0] = instance.web.insert_thousand_seps(formatted[0]); return formatted.join(l10n.decimal_point); case 'float_time': var pattern = '%02d:%02d'; @@ -136,17 +136,17 @@ openerp.web.format_value = function (value, descriptor, value_if_empty) { return value[1]; case 'datetime': if (typeof(value) == "string") - value = openerp.web.auto_str_to_date(value); + value = instance.web.auto_str_to_date(value); return value.toString(normalize_format(l10n.date_format) + ' ' + normalize_format(l10n.time_format)); case 'date': if (typeof(value) == "string") - value = openerp.web.auto_str_to_date(value); + value = instance.web.auto_str_to_date(value); return value.toString(normalize_format(l10n.date_format)); case 'time': if (typeof(value) == "string") - value = openerp.web.auto_str_to_date(value); + value = instance.web.auto_str_to_date(value); return value.toString(normalize_format(l10n.time_format)); case 'selection': case 'statusbar': // Each choice is [value, label] @@ -163,7 +163,7 @@ openerp.web.format_value = function (value, descriptor, value_if_empty) { } }; -openerp.web.parse_value = function (value, descriptor, value_if_empty) { +instance.web.parse_value = function (value, descriptor, value_if_empty) { var date_pattern = normalize_format(_t.database.parameters.date_format), time_pattern = normalize_format(_t.database.parameters.time_format); switch (value) { @@ -171,12 +171,12 @@ openerp.web.parse_value = function (value, descriptor, value_if_empty) { case "": return value_if_empty === undefined ? false : value_if_empty; } - switch (descriptor.widget || descriptor.type) { + switch (descriptor.type || (descriptor.field && descriptor.field.type)) { case 'integer': var tmp; do { tmp = value; - value = value.replace(openerp.web._t.database.parameters.thousands_sep, ""); + value = value.replace(instance.web._t.database.parameters.thousands_sep, ""); } while(tmp !== value); tmp = Number(value); if (isNaN(tmp)) @@ -190,9 +190,9 @@ openerp.web.parse_value = function (value, descriptor, value_if_empty) { var tmp2 = value; do { tmp = tmp2; - tmp2 = tmp.replace(openerp.web._t.database.parameters.thousands_sep, ""); + tmp2 = tmp.replace(instance.web._t.database.parameters.thousands_sep, ""); } while(tmp !== tmp2); - var reformatted_value = tmp.replace(openerp.web._t.database.parameters.decimal_point, "."); + var reformatted_value = tmp.replace(instance.web._t.database.parameters.decimal_point, "."); var parsed = Number(reformatted_value); if (isNaN(parsed)) throw new Error(value + " is not a correct float"); @@ -205,62 +205,62 @@ openerp.web.parse_value = function (value, descriptor, value_if_empty) { } var float_time_pair = value.split(":"); if (float_time_pair.length != 2) - return factor * openerp.web.parse_value(value, {type: "float"}); - var hours = openerp.web.parse_value(float_time_pair[0], {type: "integer"}); - var minutes = openerp.web.parse_value(float_time_pair[1], {type: "integer"}); + return factor * instance.web.parse_value(value, {type: "float"}); + var hours = instance.web.parse_value(float_time_pair[0], {type: "integer"}); + var minutes = instance.web.parse_value(float_time_pair[1], {type: "integer"}); return factor * (hours + (minutes / 60)); case 'progressbar': - return openerp.web.parse_value(value, {type: "float"}); + return instance.web.parse_value(value, {type: "float"}); case 'datetime': var datetime = Date.parseExact( value, (date_pattern + ' ' + time_pattern)); if (datetime !== null) - return openerp.web.datetime_to_str(datetime); + return instance.web.datetime_to_str(datetime); datetime = Date.parse(value); if (datetime !== null) - return openerp.web.datetime_to_str(datetime); + return instance.web.datetime_to_str(datetime); throw new Error(value + " is not a valid datetime"); case 'date': var date = Date.parseExact(value, date_pattern); if (date !== null) - return openerp.web.date_to_str(date); + return instance.web.date_to_str(date); date = Date.parse(value); if (date !== null) - return openerp.web.date_to_str(date); + return instance.web.date_to_str(date); throw new Error(value + " is not a valid date"); case 'time': var time = Date.parseExact(value, time_pattern); if (time !== null) - return openerp.web.time_to_str(time); + return instance.web.time_to_str(time); time = Date.parse(value); if (time !== null) - return openerp.web.time_to_str(time); + return instance.web.time_to_str(time); throw new Error(value + " is not a valid time"); } return value; }; -openerp.web.auto_str_to_date = function(value, type) { +instance.web.auto_str_to_date = function(value, type) { try { - return openerp.web.str_to_datetime(value); + return instance.web.str_to_datetime(value); } catch(e) {} try { - return openerp.web.str_to_date(value); + return instance.web.str_to_date(value); } catch(e) {} try { - return openerp.web.str_to_time(value); + return instance.web.str_to_time(value); } catch(e) {} throw new Error("'" + value + "' is not a valid date, datetime nor time"); }; -openerp.web.auto_date_to_str = function(value, type) { +instance.web.auto_date_to_str = function(value, type) { switch(type) { case 'datetime': - return openerp.web.datetime_to_str(value); + return instance.web.datetime_to_str(value); case 'date': - return openerp.web.date_to_str(value); + return instance.web.date_to_str(value); case 'time': - return openerp.web.time_to_str(value); + return instance.web.time_to_str(value); default: throw new Error(type + " is not convertible to date, datetime nor time"); } @@ -290,7 +290,7 @@ openerp.web.auto_date_to_str = function(value, type) { * @param {Number} [options.id] current record's id * */ -openerp.web.format_cell = function (row_data, column, options) { +instance.web.format_cell = function (row_data, column, options) { options = options || {}; var attrs = {}; if (options.process_modifiers !== false) { @@ -303,9 +303,9 @@ openerp.web.format_cell = function (row_data, column, options) { '<img src="<%-prefix%>/web/static/src/img/icons/<%-icon%>.png" alt="<%-alt%>"/>' + '</button>', { title: column.string || '', - additional_attributes: isNaN(row_data["id"].value) && openerp.web.BufferedDataSet.virtual_id_regex.test(row_data["id"].value) ? + additional_attributes: isNaN(row_data["id"].value) && instance.web.BufferedDataSet.virtual_id_regex.test(row_data["id"].value) ? 'disabled="disabled" class="oe-listview-button-disabled"' : '', - prefix: openerp.connection.prefix, + prefix: instance.connection.prefix, icon: column.icon, alt: column.string || '' }); @@ -320,11 +320,11 @@ openerp.web.format_cell = function (row_data, column, options) { row_data[column.id].value ? 'checked="checked"' : ''); case "binary": var text = _t("Download"), - download_url = _.str.sprintf('/web/binary/saveas?session_id=%s&model=%s&field=%s&id=%d', openerp.connection.session_id, options.model, column.id, options.id); + download_url = _.str.sprintf('/web/binary/saveas?session_id=%s&model=%s&field=%s&id=%d', instance.connection.session_id, options.model, column.id, options.id); if (column.filename) { download_url += '&filename_field=' + column.filename; if (row_data[column.filename]) { - text = _.str.sprintf(_t("Download \"%s\""), openerp.web.format_value( + text = _.str.sprintf(_t("Download \"%s\""), instance.web.format_value( row_data[column.filename].value, {type: 'char'})); } } @@ -340,7 +340,7 @@ openerp.web.format_cell = function (row_data, column, options) { }); } - return _.escape(openerp.web.format_value( + return _.escape(instance.web.format_value( row_data[column.id].value, column, options.value_if_empty)); } diff --git a/addons/web/static/src/js/search.js b/addons/web/static/src/js/search.js index 773c10382f6..c2df866b5c6 100644 --- a/addons/web/static/src/js/search.js +++ b/addons/web/static/src/js/search.js @@ -1,13 +1,80 @@ -openerp.web.search = function(openerp) { -var QWeb = openerp.web.qweb, - _t = openerp.web._t, - _lt = openerp.web._lt; +openerp.web.search = function(instance) { +var QWeb = instance.web.qweb, + _t = instance.web._t, + _lt = instance.web._lt; +_.mixin({ + sum: function (obj) { return _.reduce(obj, function (a, b) { return a + b; }, 0); } +}); + +// Have SearchBox optionally use callback function to produce inputs and facets +// (views) set on callbacks.make_facet and callbacks.make_input keys when +// initializing VisualSearch +var SearchBox_renderFacet = function (facet, position) { + var view = new (this.app.options.callbacks['make_facet'] || VS.ui.SearchFacet)({ + app : this.app, + model : facet, + order : position + }); -openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.SearchView# */{ - template: "EmptyComponent", + // Input first, facet second. + this.renderSearchInput(); + this.facetViews.push(view); + this.$('.VS-search-inner').children().eq(position*2).after(view.render().el); + + view.calculateSize(); + _.defer(_.bind(view.calculateSize, view)); + + return view; + }; // warning: will not match +// Ensure we're replacing the function we think +if (SearchBox_renderFacet.toString() !== VS.ui.SearchBox.prototype.renderFacet.toString().replace(/(VS\.ui\.SearchFacet)/, "(this.app.options.callbacks['make_facet'] || $1)")) { + throw new Error( + "Trying to replace wrong version of VS.ui.SearchBox#renderFacet. " + + "Please fix replacement."); +} +var SearchBox_renderSearchInput = function () { + var input = new (this.app.options.callbacks['make_input'] || VS.ui.SearchInput)({position: this.inputViews.length, app: this.app}); + this.$('.VS-search-inner').append(input.render().el); + this.inputViews.push(input); + }; +// Ensure we're replacing the function we think +if (SearchBox_renderSearchInput.toString() !== VS.ui.SearchBox.prototype.renderSearchInput.toString().replace(/(VS\.ui\.SearchInput)/, "(this.app.options.callbacks['make_input'] || $1)")) { + throw new Error( + "Trying to replace wrong version of VS.ui.SearchBox#renderSearchInput. " + + "Please fix replacement."); +} +var SearchBox_searchEvent = function (e) { + var query = null; + this.renderFacets(); + this.focusSearch(e); + this.app.options.callbacks.search(query, this.app.searchQuery); + }; +if (SearchBox_searchEvent.toString() !== VS.ui.SearchBox.prototype.searchEvent.toString().replace( + /this\.value\(\);\n[ ]{4}this\.focusSearch\(e\);\n[ ]{4}this\.value\(query\)/, + 'null;\n this.renderFacets();\n this.focusSearch(e)')) { + throw new Error( + "Trying to replace wrong version of VS.ui.SearchBox#searchEvent. " + + "Please fix replacement."); +} +_.extend(VS.ui.SearchBox.prototype, { + renderFacet: SearchBox_renderFacet, + renderSearchInput: SearchBox_renderSearchInput, + searchEvent: SearchBox_searchEvent +}); +_.extend(VS.model.SearchFacet.prototype, { + value: function () { + if (this.has('json')) { + return this.get('json'); + } + return this.get('value'); + } +}); + +instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.SearchView# */{ + template: "SearchView", /** - * @constructs openerp.web.SearchView - * @extends openerp.web.OldWidget + * @constructs instance.web.SearchView + * @extends instance.web.OldWidget * * @param parent * @param element_id @@ -25,9 +92,7 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea this.has_defaults = !_.isEmpty(this.defaults); this.inputs = []; - this.enabled_filters = []; - - this.has_focus = false; + this.controls = {}; this.hidden = !!hidden; this.headless = this.hidden && !this.has_defaults; @@ -37,20 +102,63 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea this.ready = $.Deferred(); }, start: function() { - this._super(); + var self = this; + var p = this._super(); + + this.setup_global_completion(); + this.vs = VS.init({ + container: this.$element, + query: '', + callbacks: { + make_facet: this.proxy('make_visualsearch_facet'), + make_input: this.proxy('make_visualsearch_input'), + search: function (query, searchCollection) { + self.do_search(); + }, + facetMatches: function (callback) { + }, + valueMatches : function(facet, searchTerm, callback) { + } + } + }); + + var search = function () { self.vs.searchBox.searchEvent({}); }; + // searchQuery operations + this.vs.searchQuery + .off('add').on('add', search) + .off('change').on('change', search) + .off('reset').on('reset', search) + .off('remove').on('remove', function (record, collection, options) { + if (options['trigger_search']) { + search(); + } + }); + if (this.hidden) { this.$element.hide(); } if (this.headless) { this.ready.resolve(); } else { - this.rpc("/web/searchview/load", { + var load_view = this.rpc("/web/searchview/load", { model: this.model, view_id: this.view_id, - context: this.dataset.get_context() - }, this.on_loaded); + context: this.dataset.get_context() }); + // FIXME: local eval of domain and context to get rid of special endpoint + var filters = this.rpc('/web/searchview/get_filters', { + model: this.model + }).then(function (filters) { self.custom_filters = filters; }); + + $.when(load_view, filters) + .pipe(function (load) { return load[0]; }) + .then(this.on_loaded); } - return this.ready.promise(); + + this.$element.on('click', '.oe_vs_unfold_drawer', function () { + self.$element.toggleClass('oe_searchview_open_drawer'); + }); + + return $.when(p, this.ready); }, show: function () { this.$element.show(); @@ -58,61 +166,227 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea hide: function () { this.$element.hide(); }, + + /** + * Sets up thingie where all the mess is put? + */ + setup_stuff_drawer: function () { + var self = this; + $('<div class="oe_vs_unfold_drawer">').appendTo(this.$element.find('.VS-search-box')); + var $drawer = $('<div class="oe_searchview_drawer">').appendTo(this.$element); + var $filters = $('<div class="oe_searchview_filters">').appendTo($drawer); + + var running_count = 0; + // get total filters count + var is_group = function (i) { return i instanceof instance.web.search.FilterGroup; }; + var filters_count = _(this.controls).chain() + .flatten() + .filter(is_group) + .map(function (i) { return i.filters.length; }) + .sum() + .value(); + + var col1 = [], col2 = _(this.controls).map(function (inputs, group) { + var filters = _(inputs).filter(is_group); + return { + name: group === 'null' ? _t("Filters") : group, + filters: filters, + length: _(filters).chain().map(function (i) { + return i.filters.length; }).sum().value() + }; + }); + + while (col2.length) { + // col1 + group should be smaller than col2 + group + if ((running_count + col2[0].length) <= (filters_count - running_count)) { + running_count += col2[0].length; + col1.push(col2.shift()); + } else { + break; + } + } + + // Create a Custom Filter FilterGroup for each custom filter read from + // the db, add all of this as a group in the smallest column + [].push.call(col1.length <= col2.length ? col1 : col2, { + name: _t("Custom Filters"), + filters: _.map(this.custom_filters, function (filter) { + // FIXME: handling of ``disabled`` being set + var f = new instance.web.search.Filter({attrs: { + string: filter.name, + context: filter.context, + domain: filter.domain + }}, self); + return new instance.web.search.FilterGroup([f], self); + }), + length: 3 + }); + + return $.when( + this.render_column(col1, $('<div>').appendTo($filters)), + this.render_column(col2, $('<div>').appendTo($filters)), + (new instance.web.search.Advanced(this).appendTo($drawer))); + }, + render_column: function (column, $el) { + return $.when.apply(null, _(column).map(function (group) { + $('<h3>').text(group.name).appendTo($el); + return $.when.apply(null, + _(group.filters).invoke('appendTo', $el)); + })); + }, + /** + * Sets up search view's view-wide auto-completion widget + */ + setup_global_completion: function () { + // Prevent keydown from within a facet's input from reaching the + // auto-completion widget and opening the completion list + this.$element.on('keydown', '.search_facet input', function (e) { + e.stopImmediatePropagation(); + }); + + this.$element.autocomplete({ + source: this.proxy('complete_global_search'), + select: this.proxy('select_completion'), + focus: function (e) { e.preventDefault(); }, + html: true, + minLength: 0, + delay: 0 + }).data('autocomplete')._renderItem = function (ul, item) { + // item of completion list + var $item = $( "<li></li>" ) + .data( "item.autocomplete", item ) + .appendTo( ul ); + + if (item.value !== undefined) { + // regular completion item + return $item.append( + (item.label) + ? $('<a>').html(item.label) + : $('<a>').text(item.value)); + } + return $item.text(item.category) + .css({ + borderTop: '1px solid #cccccc', + margin: 0, + padding: 0, + zoom: 1, + 'float': 'left', + clear: 'left', + width: '100%' + }); + } + }, + /** + * Provide auto-completion result for req.term (an array to `resp`) + * + * @param {Object} req request to complete + * @param {String} req.term searched term to complete + * @param {Function} resp response callback + */ + complete_global_search: function (req, resp) { + $.when.apply(null, _(this.inputs).chain() + .invoke('complete', req.term) + .value()).then(function () { + resp(_(_(arguments).compact()).flatten(true)); + }); + }, + + /** + * Action to perform in case of selection: create a facet (model) + * and add it to the search collection + * + * @param {Object} e selection event, preventDefault to avoid setting value on object + * @param {Object} ui selection information + * @param {Object} ui.item selected completion item + */ + select_completion: function (e, ui) { + e.preventDefault(); + this.vs.searchQuery.add(new VS.model.SearchFacet(_.extend( + {app: this.vs}, ui.item))); + this.vs.searchBox.searchEvent({}); + }, + + /** + * Builds the right SearchFacet view based on the facet object to render + * (e.g. readonly facets for filters) + * + * @param {Object} options + * @param {VS.model.SearchFacet} options.model facet object to render + */ + make_visualsearch_facet: function (options) { + return new instance.web.search.FilterGroupFacet(options); + +// if (options.model.get('field') instanceof instance.web.search.FilterGroup) { +// return new instance.web.search.FilterGroupFacet(options); +// } +// return new VS.ui.SearchFacet(options); + }, + /** + * Proxies searches on a SearchInput to the search view's global completion + * + * Also disables SearchInput.autocomplete#_move so search view's + * autocomplete can get the corresponding events, or something. + * + * @param options + */ + make_visualsearch_input: function (options) { + var self = this, input = new VS.ui.SearchInput(options); + input.setupAutocomplete = function () { + _.extend(this.box.autocomplete({ + minLength: 1, + delay: 0, + search: function () { + self.$element.autocomplete('search', input.box.val()); + return false; + } + }).data('autocomplete'), { + _move: function () {}, + close: function () { self.$element.autocomplete('close'); } + }); + }; + return input; + }, + /** * Builds a list of widget rows (each row is an array of widgets) * * @param {Array} items a list of nodes to convert to widgets * @param {Object} fields a mapping of field names to (ORM) field attributes - * @returns Array + * @param {String} [group_name] name of the group to put the new controls in */ - make_widgets: function (items, fields) { - var rows = [], - row = []; - rows.push(row); + make_widgets: function (items, fields, group_name) { + group_name = group_name || null; + if (!(group_name in this.controls)) { + this.controls[group_name] = []; + } + var self = this, group = this.controls[group_name]; var filters = []; _.each(items, function (item) { if (filters.length && item.tag !== 'filter') { - row.push( - new openerp.web.search.FilterGroup( - filters, this)); + group.push(new instance.web.search.FilterGroup(filters, this)); filters = []; } - if (item.tag === 'newline') { - row = []; - rows.push(row); - } else if (item.tag === 'filter') { - if (!this.has_focus) { - item.attrs.default_focus = '1'; - this.has_focus = true; - } - filters.push( - new openerp.web.search.Filter( - item, this)); - } else if (item.tag === 'separator') { - // a separator is a no-op - } else { - if (item.tag === 'group') { - // TODO: group and field should be fetched from registries, maybe even filters - row.push( - new openerp.web.search.Group( - item, this, fields)); - } else if (item.tag === 'field') { - if (!this.has_focus) { - item.attrs.default_focus = '1'; - this.has_focus = true; - } - row.push( - this.make_field( - item, fields[item['attrs'].name])); - } + switch (item.tag) { + case 'separator': case 'newline': + break; + case 'filter': + filters.push(new instance.web.search.Filter(item, this)); + break; + case 'group': + self.make_widgets(item.children, fields, item.attrs.string); + break; + case 'field': + group.push(this.make_field(item, fields[item['attrs'].name])); + // filters + self.make_widgets(item.children, fields, group_name); + break; } }, this); + if (filters.length) { - row.push(new openerp.web.search.FilterGroup(filters, this)); + group.push(new instance.web.search.FilterGroup(filters, this)); } - - return rows; }, /** * Creates a field for the provided field descriptor item (which comes @@ -120,18 +394,13 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea * * @param {Object} item fields_view_get node for the field * @param {Object} field fields_get result for the field - * @returns openerp.web.search.Field + * @returns instance.web.search.Field */ make_field: function (item, field) { - try { - return new (openerp.web.search.fields.get_any( - [item.attrs.widget, field.type])) - (item, field, this); - } catch (e) { - if (! e instanceof openerp.web.KeyNotFound) { - throw e; - } - // KeyNotFound means unknown field type + var obj = instance.web.search.fields.get_any( [item.attrs.widget, field.type]); + if(obj) { + return new (obj) (item, field, this); + } else { console.group('Unknown field type ' + field.type); console.error('View node', item); console.info('View field', field); @@ -141,6 +410,7 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea } }, on_loaded: function(data) { + var self = this; this.fields_view = data.fields_view; if (data.fields_view.type !== 'search' || data.fields_view.arch.tag !== 'search') { @@ -148,52 +418,20 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea "Got non-search view after asking for a search view: type %s, arch root %s", data.fields_view.type, data.fields_view.arch.tag)); } - var self = this, - lines = this.make_widgets( - data.fields_view['arch'].children, - data.fields_view.fields); - - // for extended search view - var ext = new openerp.web.search.ExtendedSearch(this, this.model); - lines.push([ext]); - this.extended_search = ext; - - var render = QWeb.render("SearchView", { - 'view': data.fields_view['arch'], - 'lines': lines, - 'defaults': this.defaults - }); - this.$element.html(render); + this.make_widgets( + data.fields_view['arch'].children, + data.fields_view.fields); - var f = this.$element.find('form'); - this.$element.find('form') - .submit(this.do_search) - .bind('reset', this.do_clear); - // start() all the widgets - var widget_starts = _(lines).chain().flatten().map(function (widget) { - return widget.start(); - }).value(); - - $.when.apply(null, widget_starts).then(function () { - self.ready.resolve(); - }); - - this.reload_managed_filters(); - }, - reload_managed_filters: function() { - var self = this; - return this.rpc('/web/searchview/get_filters', { - model: this.dataset.model - }).then(function(result) { - self.managed_filters = result; - var filters = self.$element.find(".oe_search-view-filters-management"); - filters.html(QWeb.render("SearchView.managed-filters", { - filters: result, - disabled_filter_message: _t('Filter disabled due to invalid syntax') - })); - filters.change(self.on_filters_management); - }); + // load defaults + return $.when( + this.setup_stuff_drawer(), + $.when.apply(null, _(this.inputs).invoke('facet_for_defaults', this.defaults)) + .then(function () { + self.vs.searchQuery.reset(_(arguments).compact(), {silent: true}); + self.vs.searchBox.renderFacets(); + })) + .then(function () { self.ready.resolve(); }) }, /** * Handle event when the user make a selection in the filters management select box. @@ -222,11 +460,11 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea break; case 'save_filter': var data = this.build_search_data(); - var context = new openerp.web.CompoundContext(); + var context = new instance.web.CompoundContext(); _.each(data.contexts, function(x) { context.add(x); }); - var domain = new openerp.web.CompoundDomain(); + var domain = new instance.web.CompoundDomain(); _.each(data.domains, function(x) { domain.add(x); }); @@ -234,7 +472,7 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea context.add({"group_by": groupbys}); var dial_html = QWeb.render("SearchView.managed-filters.add"); var $dial = $(dial_html); - openerp.web.dialog($dial, { + instance.web.dialog($dial, { modal: true, title: _t("Filter Entry"), buttons: [ @@ -287,13 +525,13 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea on_add_to_dashboard: function() { this.$element.find(".oe_search-view-filters-management")[0].selectedIndex = 0; var self = this, - menu = openerp.webclient.menu, + menu = instance.webclient.menu, $dialog = $(QWeb.render("SearchView.add_to_dashboard", { dashboards : menu.data.data.children, selected_menu_id : menu.$element.find('a.active').data('menu') })); $dialog.find('input').val(this.fields_view.name); - openerp.web.dialog($dialog, { + instance.web.dialog($dialog, { modal: true, title: _t("Add to Dashboard"), buttons: [ @@ -305,8 +543,8 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea var menu_id = $(this).find("select").val(), title = $(this).find("input").val(), data = self.build_search_data(), - context = new openerp.web.CompoundContext(), - domain = new openerp.web.CompoundDomain(); + context = new instance.web.CompoundContext(), + domain = new instance.web.CompoundDomain(); _.each(data.contexts, function(x) { context.add(x); }); @@ -335,47 +573,33 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea * Performs the search view collection of widget data. * * If the collection went well (all fields are valid), then triggers - * :js:func:`openerp.web.SearchView.on_search`. + * :js:func:`instance.web.SearchView.on_search`. * * If at least one field failed its validation, triggers - * :js:func:`openerp.web.SearchView.on_invalid` instead. + * :js:func:`instance.web.SearchView.on_invalid` instead. * * @param e jQuery event object coming from the "Search" button */ - do_search: function (e) { - if (this.headless && !this.has_defaults) { - return this.on_search([], [], []); - } - - if (e && e.preventDefault) { e.preventDefault(); } - - var data = this.build_search_data(); + do_search: function () { + var domains = [], contexts = [], groupbys = [], errors = []; - if (data.errors.length) { - this.on_invalid(data.errors); - return; - } - - this.on_search(data.domains, data.contexts, data.groupbys); - }, - build_search_data: function() { - var domains = [], - contexts = [], - errors = []; - - _.each(this.inputs, function (input) { + this.vs.searchQuery.each(function (facet) { + var field = facet.get('field'); try { - var domain = input.get_domain(); + var domain = field.get_domain(facet); if (domain) { domains.push(domain); } - - var context = input.get_context(); + var context = field.get_context(facet); if (context) { contexts.push(context); } + var group_by = field.get_groupby(facet); + if (group_by) { + groupbys.push.apply(groupbys, group_by); + } } catch (e) { - if (e instanceof openerp.web.search.Invalid) { + if (e instanceof instance.web.search.Invalid) { errors.push(e); } else { throw e; @@ -383,23 +607,11 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea } }); - // TODO: do we need to handle *fields* with group_by in their context? - var groupbys = _(this.enabled_filters) - .chain() - .map(function (filter) { return filter.get_context();}) - .compact() - .value(); - - if (this.filter_data.contexts) { - contexts = this.filter_data.contexts.concat(contexts) - } - if (this.filter_data.domains) { - domains = this.filter_data.domains.concat(domains); - } - if (this.filter_data.groupbys) { - groupbys = this.filter_data.groupbys.concat(groupbys); + if (!_.isEmpty(errors)) { + this.on_invalid(errors); + return; } - return {domains: domains, contexts: contexts, errors: errors, groupbys: groupbys}; + return this.on_search(domains, contexts, groupbys); }, /** * Triggered after the SearchView has collected all relevant domains and @@ -432,84 +644,68 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea */ on_invalid: function (errors) { this.do_notify(_t("Invalid Search"), _t("triggered from search view")); - }, - /** - * @param {Boolean} [reload_view=true] - */ - do_clear: function (reload_view) { - this.filter_data = {}; - this.$element.find(".oe_search-view-filters-management").val(''); + } +}); + +/** @namespace */ +instance.web.search = {}; - this.$element.find('.filter_label, .filter_icon').removeClass('enabled'); - this.enabled_filters.splice(0); - var string = $('a.searchview_group_string'); - _.each(string, function(str){ - $(str).closest('div.searchview_group').removeClass("expanded").addClass('folded'); - }); - this.$element.find('table:last').hide(); +instance.web.search.FilterGroupFacet = VS.ui.SearchFacet.extend({ + events: _.extend({ + 'click': 'selectFacet' + }, VS.ui.SearchFacet.prototype.events), - $('.searchview_extended_groups_list').empty(); - return $.async_when.apply( - null, _(this.inputs).invoke('clear')).pipe( - reload_view !== false ? this.on_clear : null); + render: function () { + this.setMode('not', 'editing'); + this.setMode('not', 'selected'); + + var value = this.model.get('value'); + this.$el.html(QWeb.render('SearchView.filters.facet', { + facet: this.model + })); + // virtual input so SearchFacet code has something to play with + this.box = $('<input>').val(value); + + return this; }, - /** - * Triggered when the search view gets cleared - * - * @event - */ - on_clear: function () { - this.do_search(); + enableEdit: function () { + this.selectFacet() }, - /** - * Called by a filter propagating its state changes - * - * @param {openerp.web.search.Filter} filter a filter which got toggled - * @param {Boolean} default_enabled filter got enabled through the default values, at render time. - */ - do_toggle_filter: function (filter, default_enabled) { - if (default_enabled || filter.is_enabled()) { - this.enabled_filters.push(filter); - } else { - this.enabled_filters = _.without( - this.enabled_filters, filter); - } - - if (!default_enabled) { - // selecting a filter after initial loading automatically - // triggers refresh - this.$element.find('form').submit(); + keydown: function (e) { + var key = VS.app.hotkeys.key(e); + if (key !== 'right') { + return VS.ui.SearchFacet.prototype.keydown.call(this, e); } + e.preventDefault(); + this.deselectFacet(); + this.options.app.searchBox.focusNextFacet(this, 1); } }); - -/** @namespace */ -openerp.web.search = {}; /** - * Registry of search fields, called by :js:class:`openerp.web.SearchView` to + * Registry of search fields, called by :js:class:`instance.web.SearchView` to * find and instantiate its field widgets. */ -openerp.web.search.fields = new openerp.web.Registry({ - 'char': 'openerp.web.search.CharField', - 'text': 'openerp.web.search.CharField', - 'boolean': 'openerp.web.search.BooleanField', - 'integer': 'openerp.web.search.IntegerField', - 'id': 'openerp.web.search.IntegerField', - 'float': 'openerp.web.search.FloatField', - 'selection': 'openerp.web.search.SelectionField', - 'datetime': 'openerp.web.search.DateTimeField', - 'date': 'openerp.web.search.DateField', - 'many2one': 'openerp.web.search.ManyToOneField', - 'many2many': 'openerp.web.search.CharField', - 'one2many': 'openerp.web.search.CharField' +instance.web.search.fields = new instance.web.Registry({ + 'char': 'instance.web.search.CharField', + 'text': 'instance.web.search.CharField', + 'boolean': 'instance.web.search.BooleanField', + 'integer': 'instance.web.search.IntegerField', + 'id': 'instance.web.search.IntegerField', + 'float': 'instance.web.search.FloatField', + 'selection': 'instance.web.search.SelectionField', + 'datetime': 'instance.web.search.DateTimeField', + 'date': 'instance.web.search.DateField', + 'many2one': 'instance.web.search.ManyToOneField', + 'many2many': 'instance.web.search.CharField', + 'one2many': 'instance.web.search.CharField' }); -openerp.web.search.Invalid = openerp.web.Class.extend( /** @lends openerp.web.search.Invalid# */{ +instance.web.search.Invalid = instance.web.Class.extend( /** @lends instance.web.search.Invalid# */{ /** * Exception thrown by search widgets when they hold invalid values, * which they can not return when asked. * - * @constructs openerp.web.search.Invalid - * @extends openerp.web.Class + * @constructs instance.web.search.Invalid + * @extends instance.web.Class * * @param field the name of the field holding an invalid value * @param value the invalid value @@ -527,92 +723,42 @@ openerp.web.search.Invalid = openerp.web.Class.extend( /** @lends openerp.web.se ); } }); -openerp.web.search.Widget = openerp.web.OldWidget.extend( /** @lends openerp.web.search.Widget# */{ +instance.web.search.Widget = instance.web.OldWidget.extend( /** @lends instance.web.search.Widget# */{ template: null, /** * Root class of all search widgets * - * @constructs openerp.web.search.Widget - * @extends openerp.web.OldWidget + * @constructs instance.web.search.Widget + * @extends instance.web.OldWidget * * @param view the ancestor view of this widget */ init: function (view) { this._super(view); this.view = view; - }, - /** - * Sets and returns a globally unique identifier for the widget. - * - * If a prefix is specified, the identifier will be appended to it. - * - * @params prefix prefix sections, empty/falsy sections will be removed - */ - make_id: function () { - this.element_id = _.uniqueId( - ['search'].concat( - _.compact(_.toArray(arguments)), - ['']).join('_')); - return this.element_id; - }, - /** - * "Starts" the widgets. Called at the end of the rendering, this allows - * widgets to hook themselves to their view sections. - * - * On widgets, if they kept a reference to a view and have an element_id, - * will fetch and set their root element on $element. - */ - start: function () { - this._super(); - if (this.view && this.element_id) { - // id is unique, and no getElementById on elements - this.$element = $(document.getElementById( - this.element_id)); - } - }, - /** - * "Stops" the widgets. Called when the view destroys itself, this - * lets the widgets clean up after themselves. - */ - destroy: function () { - delete this.view; - this._super(); - }, - render: function (defaults) { - // FIXME - return this._super(_.extend(this, {defaults: defaults})); } }); -openerp.web.search.add_expand_listener = function($root) { +instance.web.search.add_expand_listener = function($root) { $root.find('a.searchview_group_string').click(function (e) { $root.toggleClass('folded expanded'); e.stopPropagation(); e.preventDefault(); }); }; -openerp.web.search.Group = openerp.web.search.Widget.extend({ +instance.web.search.Group = instance.web.search.Widget.extend({ template: 'SearchView.group', init: function (view_section, view, fields) { this._super(view); this.attrs = view_section.attrs; this.lines = view.make_widgets( view_section.children, fields); - this.make_id('group'); - }, - start: function () { - this._super(); - openerp.web.search.add_expand_listener(this.$element); - var widget_starts = _(this.lines).chain().flatten() - .map(function (widget) { return widget.start(); }) - .value(); - return $.when.apply(null, widget_starts); } }); -openerp.web.search.Input = openerp.web.search.Widget.extend( /** @lends openerp.web.search.Input# */{ +instance.web.search.Input = instance.web.search.Widget.extend( /** @lends instance.web.search.Input# */{ /** - * @constructs openerp.web.search.Input - * @extends openerp.web.search.Widget + * @constructs instance.web.search.Input + * @extends instance.web.search.Widget * * @param view */ @@ -621,10 +767,44 @@ openerp.web.search.Input = openerp.web.search.Widget.extend( /** @lends openerp. this.view.inputs.push(this); this.style = undefined; }, + /** + * Fetch auto-completion values for the widget. + * + * The completion values should be an array of objects with keys category, + * label, value prefixed with an object with keys type=section and label + * + * @param {String} value value to complete + * @returns {jQuery.Deferred<null|Array>} + */ + complete: function (value) { + return $.when(null) + }, + /** + * Returns a VS.model.SearchFacet instance for the provided defaults if + * they apply to this widget, or null if they don't. + * + * This default implementation will try calling + * :js:func:`instance.web.search.Input#facet_for` if the widget's name + * matches the input key + * + * @param {Object} defaults + * @returns {jQuery.Deferred<null|Object>} + */ + facet_for_defaults: function (defaults) { + if (!this.attrs || + !(this.attrs.name in defaults && defaults[this.attrs.name])) { + return $.when(null); + } + return this.facet_for(defaults[this.attrs.name]); + }, get_context: function () { throw new Error( "get_context not implemented for widget " + this.attrs.type); }, + get_groupby: function () { + throw new Error( + "get_groupby not implemented for widget " + this.attrs.type); + }, get_domain: function () { throw new Error( "get_domain not implemented for widget " + this.attrs.type); @@ -638,42 +818,80 @@ openerp.web.search.Input = openerp.web.search.Widget.extend( /** @lends openerp. } } this.attrs = attrs; - }, - /** - * Specific clearing operations, if any - */ - clear: function () {} + } }); -openerp.web.search.FilterGroup = openerp.web.search.Input.extend(/** @lends openerp.web.search.FilterGroup# */{ +instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends instance.web.search.FilterGroup# */{ template: 'SearchView.filters', /** * Inclusive group of filters, creates a continuous "button" with clickable * sections (the normal display for filters is to be a self-contained button) * - * @constructs openerp.web.search.FilterGroup - * @extends openerp.web.search.Input + * @constructs instance.web.search.FilterGroup + * @extends instance.web.search.Input * - * @param {Array<openerp.web.search.Filter>} filters elements of the group - * @param {openerp.web.SearchView} view view in which the filters are contained + * @param {Array<instance.web.search.Filter>} filters elements of the group + * @param {instance.web.SearchView} view view in which the filters are contained */ init: function (filters, view) { this._super(view); this.filters = filters; - this.length = filters.length; }, start: function () { - this._super(); - _.each(this.filters, function (filter) { - filter.start(); + this.$element.on('click', 'li', this.proxy('toggle_filter')); + return $.when(null); + }, + facet_for_defaults: function (defaults) { + var fs = _(this.filters).filter(function (f) { + return f.attrs && f.attrs.name && !!defaults[f.attrs.name]; }); + if (_.isEmpty(fs)) { return $.when(null); } + return $.when(new VS.model.SearchFacet({ + category: _t("Filter"), + value: _(fs).map(function (f) { + return f.attrs.string || f.attrs.name }).join(' | '), + json: fs, + field: this, + app: this.view.vs + })); + }, + /** + * Fetches contexts for all enabled filters in the group + * + * @param {VS.model.SearchFacet} facet + * @return {*} combined contexts of the enabled filters in this group + */ + get_context: function (facet) { + var contexts = _(facet.get('json')).chain() + .map(function (filter) { return filter.attrs.context; }) + .reject(_.isEmpty) + .value(); + + if (!contexts.length) { return; } + if (contexts.length === 1) { return contexts[0]; } + return _.extend(new instance.web.CompoundContext, { + __contexts: contexts + }); + }, + /** + * Fetches group_by sequence for all enabled filters in the group + * + * @param {VS.model.SearchFacet} facet + * @return {Array} enabled filters in this group + */ + get_groupby: function (facet) { + return _(facet.get('json')).chain() + .map(function (filter) { return filter.attrs.context; }) + .reject(_.isEmpty) + .value(); }, - get_context: function () { }, /** * Handles domains-fetching for all the filters within it: groups them. + * + * @param {VS.model.SearchFacet} facet + * @return {*} combined domains of the enabled filters in this group */ - get_domain: function () { - var domains = _(this.filters).chain() - .filter(function (filter) { return filter.is_enabled(); }) + get_domain: function (facet) { + var domains = _(facet.get('json')).chain() .map(function (filter) { return filter.attrs.domain; }) .reject(_.isEmpty) .value(); @@ -683,19 +901,62 @@ openerp.web.search.FilterGroup = openerp.web.search.Input.extend(/** @lends open for (var i=domains.length; --i;) { domains.unshift(['|']); } - return _.extend(new openerp.web.CompoundDomain(), { + return _.extend(new instance.web.CompoundDomain(), { __domains: domains }); + }, + toggle_filter: function (e) { + this.toggle(this.filters[$(e.target).index()]); + }, + toggle: function (filter) { + // FIXME: oh god, my eyes, they hurt + var self = this, fs; + var facet = this.view.vs.searchQuery.detect(function (f) { + return f.get('field') === self; }); + if (facet) { + fs = facet.get('json'); + + if (_.include(fs, filter)) { + fs = _.without(fs, filter); + } else { + fs.push(filter); + } + if (_(fs).isEmpty()) { + this.view.vs.searchQuery.remove(facet, {trigger_search: true}); + } else { + facet.set({ + json: fs, + value: _(fs).map(function (f) { + return f.attrs.string || f.attrs.name }).join(' | ') + }); + } + return; + } else { + fs = [filter]; + } + + this.view.vs.searchQuery.add({ + category: _t("Filter"), + value: _(fs).map(function (f) { + return f.attrs.string || f.attrs.name }).join(' | '), + json: fs, + field: this, + app: this.view.vs + }); } }); -openerp.web.search.Filter = openerp.web.search.Input.extend(/** @lends openerp.web.search.Filter# */{ +instance.web.search.Filter = instance.web.search.Input.extend(/** @lends instance.web.search.Filter# */{ template: 'SearchView.filter', /** * Implementation of the OpenERP filters (button with a context and/or * a domain sent as-is to the search view) * - * @constructs openerp.web.search.Filter - * @extends openerp.web.search.Input + * Filters are only attributes holder, the actual work (compositing + * domains and contexts, converting between facets and filters) is + * performed by the filter group. + * + * @constructs instance.web.search.Filter + * @extends instance.web.search.Input * * @param node * @param view @@ -703,56 +964,17 @@ openerp.web.search.Filter = openerp.web.search.Input.extend(/** @lends openerp.w init: function (node, view) { this._super(view); this.load_attrs(node.attrs); - this.classes = [this.attrs.string ? 'filter_label' : 'filter_icon']; - this.make_id('filter', this.attrs.name); - }, - start: function () { - this._super(); - var self = this; - this.$element.click(function (e) { - $(this).toggleClass('enabled'); - self.view.do_toggle_filter(self); - }); - }, - /** - * Returns whether the filter is currently enabled (in use) or not. - * - * @returns a boolean - */ - is_enabled:function () { - return this.$element.hasClass('enabled'); - }, - /** - * If the filter is present in the defaults (and has a truthy value), - * enable the filter. - * - * @param {Object} defaults the search view's default values - */ - render: function (defaults) { - if (this.attrs.name && defaults[this.attrs.name]) { - this.classes.push('enabled'); - this.view.do_toggle_filter(this, true); - } - return this._super(defaults); }, - get_context: function () { - if (!this.is_enabled()) { - return; - } - return this.attrs.context; - }, - /** - * Does not return anything: filter domain is handled at the FilterGroup - * level - */ - get_domain: function () { } + facet_for: function () { return $.when(null); }, + get_context: function () { }, + get_domain: function () { }, }); -openerp.web.search.Field = openerp.web.search.Input.extend( /** @lends openerp.web.search.Field# */ { +instance.web.search.Field = instance.web.search.Input.extend( /** @lends instance.web.search.Field# */ { template: 'SearchView.field', default_operator: '=', /** - * @constructs openerp.web.search.Field - * @extends openerp.web.search.Input + * @constructs instance.web.search.Field + * @extends instance.web.search.Input * * @param view_section * @param field @@ -761,25 +983,21 @@ openerp.web.search.Field = openerp.web.search.Input.extend( /** @lends openerp.w init: function (view_section, field, view) { this._super(view); this.load_attrs(_.extend({}, field, view_section.attrs)); - this.filters = new openerp.web.search.FilterGroup(_.compact(_.map( - view_section.children, function (filter_node) { - if (filter_node.attrs.string && - typeof console !== 'undefined' && console.debug) { - console.debug("Filter-in-field with a 'string' attribute " - + "in view", view); - } - delete filter_node.attrs.string; - return new openerp.web.search.Filter( - filter_node, view); - })), view); - this.make_id('input', field.type, this.attrs.name); }, - start: function () { - this._super(); - this.filters.start(); + facet_for: function (value) { + return $.when(new VS.model.SearchFacet({ + category: this.attrs.string || this.attrs.name, + value: String(value), + json: value, + field: this, + app: this.view.vs + })); }, - get_context: function () { - var val = this.get_value(); + get_value: function (facet) { + return facet.value(); + }, + get_context: function (facet) { + var val = this.get_value(facet); // A field needs a value to be "active", and a context to send when // active var has_value = (val !== null && val !== ''); @@ -787,10 +1005,10 @@ openerp.web.search.Field = openerp.web.search.Input.extend( /** @lends openerp.w if (!(has_value && context)) { return; } - return _.extend( - {}, context, - {own_values: {self: val}}); + return new instance.web.CompoundContext(context) + .set_eval_context({self: val}); }, + get_groupby: function () { }, /** * Function creating the returned domain for the field, override this * methods in children if you only need to customize the field's domain @@ -802,11 +1020,11 @@ openerp.web.search.Field = openerp.web.search.Input.extend( /** @lends openerp.w * @param {Number|String} value parsed value for the field * @returns {Array<Array>} domain to include in the resulting search */ - make_domain: function (name, operator, value) { - return [[name, operator, value]]; + make_domain: function (name, operator, facet) { + return [[name, operator, this.get_value(facet)]]; }, - get_domain: function () { - var val = this.get_value(); + get_domain: function (facet) { + var val = this.get_value(facet); if (val === null || val === '') { return; } @@ -816,9 +1034,10 @@ openerp.web.search.Field = openerp.web.search.Input.extend( /** @lends openerp.w return this.make_domain( this.attrs.name, this.attrs.operator || this.default_operator, - val); + facet); } - return _.extend({}, domain, {own_values: {self: val}}); + return new instance.web.CompoundDomain(domain) + .set_eval_context({self: val}); } }); /** @@ -829,15 +1048,25 @@ openerp.web.search.Field = openerp.web.search.Input.extend( /** @lends openerp.w * * The Javascript and the HTML values are identical (strings) * * @class - * @extends openerp.web.search.Field + * @extends instance.web.search.Field */ -openerp.web.search.CharField = openerp.web.search.Field.extend( /** @lends openerp.web.search.CharField# */ { +instance.web.search.CharField = instance.web.search.Field.extend( /** @lends instance.web.search.CharField# */ { default_operator: 'ilike', - get_value: function () { - return this.$element.val(); + complete: function (value) { + if (_.isEmpty(value)) { return $.when(null); } + var label = _.str.sprintf(_.str.escapeHTML( + _t("Search %(field)s for: %(value)s")), { + field: '<em>' + this.attrs.string + '</em>', + value: '<strong>' + _.str.escapeHTML(value) + '</strong>'}); + return $.when([{ + category: this.attrs.string, + label: label, + value: value, + field: this + }]); } }); -openerp.web.search.NumberField = openerp.web.search.Field.extend(/** @lends openerp.web.search.NumberField# */{ +instance.web.search.NumberField = instance.web.search.Field.extend(/** @lends instance.web.search.NumberField# */{ get_value: function () { if (!this.$element.val()) { return null; @@ -846,7 +1075,7 @@ openerp.web.search.NumberField = openerp.web.search.Field.extend(/** @lends open check = Number(this.$element.val()); if (isNaN(val) || val !== check) { this.$element.addClass('error'); - throw new openerp.web.search.Invalid( + throw new instance.web.search.Invalid( this.attrs.name, this.$element.val(), this.error_message); } this.$element.removeClass('error'); @@ -855,13 +1084,13 @@ openerp.web.search.NumberField = openerp.web.search.Field.extend(/** @lends open }); /** * @class - * @extends openerp.web.search.NumberField + * @extends instance.web.search.NumberField */ -openerp.web.search.IntegerField = openerp.web.search.NumberField.extend(/** @lends openerp.web.search.IntegerField# */{ +instance.web.search.IntegerField = instance.web.search.NumberField.extend(/** @lends instance.web.search.IntegerField# */{ error_message: _t("not a valid integer"), parse: function (value) { try { - return openerp.web.parse_value(value, {'widget': 'integer'}); + return instance.web.parse_value(value, {'widget': 'integer'}); } catch (e) { return NaN; } @@ -869,13 +1098,13 @@ openerp.web.search.IntegerField = openerp.web.search.NumberField.extend(/** @len }); /** * @class - * @extends openerp.web.search.NumberField + * @extends instance.web.search.NumberField */ -openerp.web.search.FloatField = openerp.web.search.NumberField.extend(/** @lends openerp.web.search.FloatField# */{ +instance.web.search.FloatField = instance.web.search.NumberField.extend(/** @lends instance.web.search.FloatField# */{ error_message: _t("not a valid number"), parse: function (value) { try { - return openerp.web.parse_value(value, {'widget': 'float'}); + return instance.web.parse_value(value, {'widget': 'float'}); } catch (e) { return NaN; } @@ -883,9 +1112,9 @@ openerp.web.search.FloatField = openerp.web.search.NumberField.extend(/** @lends }); /** * @class - * @extends openerp.web.search.Field + * @extends instance.web.search.Field */ -openerp.web.search.SelectionField = openerp.web.search.Field.extend(/** @lends openerp.web.search.SelectionField# */{ +instance.web.search.SelectionField = instance.web.search.Field.extend(/** @lends instance.web.search.SelectionField# */{ // This implementation is a basic <select> field, but it may have to be // altered to be more in line with the GTK client, which uses a combo box // (~ jquery.autocomplete): @@ -904,48 +1133,48 @@ openerp.web.search.SelectionField = openerp.web.search.Field.extend(/** @lends o return !item[1]; }); }, - get_value: function () { - var index = parseInt(this.$element.val(), 10); - if (isNaN(index)) { return null; } - var value = this.attrs.selection[index][0]; - if (value === false) { return null; } - return value; - }, - /** - * The selection field needs a default ``false`` value in case none is - * provided, so that selector options with a ``false`` value (convention - * for explicitly empty options) get selected by default rather than the - * first (value-holding) option in the selection. - * - * @param {Object} defaults search default values - */ - render: function (defaults) { - if (!defaults[this.attrs.name]) { - defaults[this.attrs.name] = false; - } - return this._super(defaults); - }, - clear: function () { - var self = this, d = $.Deferred(), selection = this.attrs.selection; - for(var index=0; index<selection.length; ++index) { - var item = selection[index]; - if (!item[1]) { - setTimeout(function () { - // won't override mutable, because we immediately bail out - //noinspection JSReferencingMutableVariableFromClosure - self.$element.val(index); - d.resolve(); - }, 0); - return d.promise(); - } - } - return d.resolve().promise(); + complete: function (needle) { + var self = this; + var results = _(this.attrs.selection).chain() + .filter(function (sel) { + var value = sel[0], label = sel[1]; + if (!value) { return false; } + return label.toLowerCase().indexOf(needle.toLowerCase()) !== -1; + }) + .map(function (sel) { + return { + category: self.attrs.string, + field: self, + value: sel[1], + json: sel[0] + }; + }).value(); + if (_.isEmpty(results)) { return $.when(null); } + return $.when.apply(null, [{ + category: this.attrs.string + }].concat(results)); + }, + facet_for: function (value) { + var match = _(this.attrs.selection).detect(function (sel) { + return sel[0] === value; + }); + if (!match) { return $.when(null); } + return $.when(new VS.model.SearchFacet({ + category: this.attrs.string, + value: match[1], + json: match[0], + field: this, + app: this.view.app + })); + }, + get_value: function (facet) { + return facet.get('json'); } }); -openerp.web.search.BooleanField = openerp.web.search.SelectionField.extend(/** @lends openerp.web.search.BooleanField# */{ +instance.web.search.BooleanField = instance.web.search.SelectionField.extend(/** @lends instance.web.search.BooleanField# */{ /** - * @constructs openerp.web.search.BooleanField - * @extends openerp.web.search.BooleanField + * @constructs instance.web.search.BooleanField + * @extends instance.web.search.BooleanField */ init: function () { this._super.apply(this, arguments); @@ -954,25 +1183,8 @@ openerp.web.search.BooleanField = openerp.web.search.SelectionField.extend(/** @ ['false', _t("No")] ]; }, - /** - * Search defaults likely to be boolean values (for a boolean field). - * - * In the HTML, we only want/get strings, and our strings here are ``true`` - * and ``false``, so ensure we use precisely those by truth-testing the - * default value (iif there is one in the view's defaults). - * - * @param {Object} defaults default values for this search view - * @returns {String} rendered boolean field - */ - render: function (defaults) { - var name = this.attrs.name; - if (name in defaults) { - defaults[name] = defaults[name] ? "true" : "false"; - } - return this._super(defaults); - }, - get_value: function () { - switch (this._super()) { + get_value: function (facet) { + switch (this._super(facet)) { case 'false': return false; case 'true': return true; default: return null; @@ -981,26 +1193,27 @@ openerp.web.search.BooleanField = openerp.web.search.SelectionField.extend(/** @ }); /** * @class - * @extends openerp.web.search.DateField + * @extends instance.web.search.DateField */ -openerp.web.search.DateField = openerp.web.search.Field.extend(/** @lends openerp.web.search.DateField# */{ - template: "SearchView.date", - start: function () { - this._super(); - // FIXME: this insanity puts a div inside a span - this.datewidget = new openerp.web.DateWidget(this); - this.datewidget.prependTo(this.$element); - this.datewidget.$element.find("input") - .attr("size", 15) - .attr("autofocus", this.attrs.default_focus === '1' ? 'autofocus' : null) - .removeAttr('style'); - this.datewidget.set_value(this.defaults[this.attrs.name] || false); - }, - get_value: function () { - return this.datewidget.get_value() || null; - }, - clear: function () { - this.datewidget.set_value(false); +instance.web.search.DateField = instance.web.search.Field.extend(/** @lends instance.web.search.DateField# */{ + get_value: function (facet) { + return instance.web.date_to_str(facet.get('json')); + }, + complete: function (needle) { + var d = Date.parse(needle); + if (!d) { return $.when(null); } + var value = instance.web.format_value(d, this.attrs); + var label = _.str.sprintf(_.str.escapeHTML( + _t("Search %(field)s at: %(value)s")), { + field: '<em>' + this.attrs.string + '</em>', + value: '<strong>' + value + '</strong>'}); + return $.when([{ + category: this.attrs.string, + label: label, + value: value, + json: d, + field: this + }]); } }); /** @@ -1012,205 +1225,135 @@ openerp.web.search.DateField = openerp.web.search.Field.extend(/** @lends opener * spanning the whole day selected by the date widget * * @class - * @extends openerp.web.DateField + * @extends instance.web.DateField */ -openerp.web.search.DateTimeField = openerp.web.search.DateField.extend(/** @lends openerp.web.search.DateTimeField# */{ - make_domain: function (name, operator, value) { - return ['&', [name, '>=', value + ' 00:00:00'], - [name, '<=', value + ' 23:59:59']]; +instance.web.search.DateTimeField = instance.web.search.DateField.extend(/** @lends instance.web.search.DateTimeField# */{ + get_value: function (facet) { + return instance.web.datetime_to_str(facet.get('json')); } }); -openerp.web.search.ManyToOneField = openerp.web.search.CharField.extend({ +instance.web.search.ManyToOneField = instance.web.search.CharField.extend({ init: function (view_section, field, view) { this._super(view_section, field, view); + this.model = new instance.web.Model(this.attrs.relation); + }, + complete: function (needle) { var self = this; - this.got_name = $.Deferred().then(function () { - self.$element.val(self.name); + // TODO: context + // FIXME: "concurrent" searches (multiple requests, mis-ordered responses) + return this.model.call('name_search', [], { + name: needle, + limit: 8, + context: {} + }).pipe(function (results) { + if (_.isEmpty(results)) { return null; } + return [{category: self.attrs.string}].concat( + _(results).map(function (result) { + return { + category: self.attrs.string, + value: result[1], + json: result[0], + field: self + }; + })); }); - this.model = new openerp.web.Model(this.attrs['relation']); - - this.orderer = new openerp.web.DropMisordered(); - }, - start: function () { - this._super(); - this.setup_autocomplete(); - var started = $.Deferred(); - this.got_name.then(function () { started.resolve();}, - function () { started.resolve(); }); - return started.promise(); }, - setup_autocomplete: function () { + facet_for: function (value) { var self = this; - this.$element.autocomplete({ - source: function (req, resp) { - self.orderer.add(self.model.call('name_search', null, { - name: req.term, - args: self.attrs.domain, - limit: 8 - })).then(function (data) { - resp(_.map(data, function (result) { - return {id: result[0], label: result[1]} - })); - }); - }, - select: function (event, ui) { - self.id = ui.item.id; - self.name = ui.item.label; - }, - delay: 100 - }) - }, - on_name_get: function (name_get) { - if (!name_get.length) { - delete this.id; - this.got_name.reject(); - return; - } - this.name = name_get[0][1]; - this.got_name.resolve(); - }, - render: function (defaults) { - if (defaults[this.attrs.name]) { - this.id = defaults[this.attrs.name]; - if (this.id instanceof Array) - this.id = this.id[0]; - // TODO: maybe this should not be completely removed - delete defaults[this.attrs.name]; - this.model.call('name_get', [[this.id]]).then( - this.proxy('on_name_get')); - } else { - this.got_name.reject(); + if (value instanceof Array) { + return $.when(new VS.model.SearchFacet({ + category: this.attrs.string, + value: value[1], + json: value[0], + field: this, + app: this.view.vs + })); } - return this._super(defaults); + return this.model.call('name_get', [value], {}).pipe(function (names) { + return new VS.model.SearchFacet({ + category: self.attrs.string, + value: names[0][1], + json: names[0][0], + field: self, + app: self.view.vs + }); + }) }, - make_domain: function (name, operator, value) { - if (this.id && this.name) { - if (value === this.name) { - return [[name, '=', this.id]]; - } else { - delete this.id; - delete this.name; - } + make_domain: function (name, operator, facet) { + // ``json`` -> actual auto-completed id + if (facet.get('json')) { + return [[name, '=', facet.get('json')]]; } - return this._super(name, operator, value); + + return this._super(name, operator, facet); } }); -openerp.web.search.ExtendedSearch = openerp.web.search.Input.extend({ - template: 'SearchView.extended_search', - init: function (parent, model) { - this._super(parent); - this.model = model; - }, - add_group: function() { - var group = new openerp.web.search.ExtendedSearchGroup(this, this.fields); - group.appendTo(this.$element.find('.searchview_extended_groups_list')); - this.check_last_element(); - }, +instance.web.search.Advanced = instance.web.search.Input.extend({ + template: 'SearchView.advanced', start: function () { - this._super(); - this.$element.closest("table.oe-searchview-render-line").css("display", "none"); var self = this; - this.rpc("/web/searchview/fields_get", - {"model": this.model}, function(data) { - self.fields = data.fields; - if (!('id' in self.fields)) { - self.fields.id = { - string: 'ID', - type: 'id' - } - } - openerp.web.search.add_expand_listener(self.$element); - self.$element.find('.searchview_extended_add_group').click(function (e) { - self.add_group(); + this.$element + .on('keypress keydown keyup', function (e) { e.stopPropagation(); }) + .on('click', 'h4', function () { + self.$element.toggleClass('oe_opened'); + }).on('click', 'button.oe_add_condition', function () { + self.append_proposition(); + }).on('submit', 'form', function (e) { + e.preventDefault(); + self.commit_search(); }); + return $.when( + this._super(), + this.rpc("/web/searchview/fields_get", {model: this.view.model}, function(data) { + self.fields = _.extend({ + id: { string: 'ID', type: 'id' } + }, data.fields); + })).then(function () { + self.append_proposition(); }); }, - get_context: function() { - return null; - }, - get_domain: function() { - if (!this.$element) { - return null; // not a logical state but sometimes it happens - } - if(this.$element.closest("table.oe-searchview-render-line").css("display") == "none") { - return null; - } - return _.reduce(this.getChildren(), - function(mem, x) { return mem.concat(x.get_domain());}, []); - }, - on_activate: function() { - this.add_group(); - var table = this.$element.closest("table.oe-searchview-render-line"); - table.css("display", ""); - if(this.$element.hasClass("folded")) { - this.$element.toggleClass("folded expanded"); - } - }, - hide: function() { - var table = this.$element.closest("table.oe-searchview-render-line"); - table.css("display", "none"); - if(this.$element.hasClass("expanded")) { - this.$element.toggleClass("folded expanded"); - } - }, - check_last_element: function() { - _.each(this.getChildren(), function(x) {x.set_last_group(false);}); - if (this.getChildren().length >= 1) { - this.getChildren()[this.getChildren().length - 1].set_last_group(true); - } - } -}); - -openerp.web.search.ExtendedSearchGroup = openerp.web.OldWidget.extend({ - template: 'SearchView.extended_search.group', - init: function (parent, fields) { - this._super(parent); - this.fields = fields; - }, - add_prop: function() { - var prop = new openerp.web.search.ExtendedSearchProposition(this, this.fields); - var render = prop.render({'index': this.getChildren().length - 1}); - this.$element.find('.searchview_extended_propositions_list').append(render); - prop.start(); + append_proposition: function () { + return (new instance.web.search.ExtendedSearchProposition(this, this.fields)) + .appendTo(this.$element.find('ul')); }, - start: function () { - var _this = this; - this.add_prop(); - this.$element.find('.searchview_extended_add_proposition').click(function () { - _this.add_prop(); + commit_search: function () { + var self = this; + // Get domain sections from all propositions + var children = this.getChildren(), + domain = _.invoke(children, 'get_proposition'); + var filters = _(domain).map(function (section) { + return new instance.web.search.Filter({attrs: { + string: _.str.sprintf('%s(%s)%s', + section[0], section[1], section[2]), + domain: [section] + }}, self.view); }); - this.$element.find('.searchview_extended_delete_group').click(function () { - _this.destroy(); + // Create Filter (& FilterGroup around it) with that domain + var f = new instance.web.search.FilterGroup(filters, this.view); + // add group to query + this.view.vs.searchQuery.add({ + category: _t("Advanced"), + value: _(filters).map(function (f) { + return f.attrs.string || f.attrs.name }).join(' | '), + json: filters, + field: f, + app: this.view.vs }); - }, - get_domain: function() { - var props = _(this.getChildren()).chain().map(function(x) { - return x.get_proposition(); - }).compact().value(); - var choice = this.$element.find(".searchview_extended_group_choice").val(); - var op = choice == "all" ? "&" : "|"; - return choice == "none" ? ['!'] : [].concat( - _.map(_.range(_.max([0,props.length - 1])), function() { return op; }), - props); - }, - destroy: function() { - var parent = this.getParent(); - if (this.getParent().getChildren().length == 1) - this.getParent().hide(); - this._super(); - parent.check_last_element(); - }, - set_last_group: function(is_last) { - this.$element.toggleClass('last_group', is_last); + // remove all propositions + _.invoke(children, 'destroy'); + // add new empty proposition + this.append_proposition(); + // TODO: API on searchview + this.view.$element.removeClass('oe_searchview_open_drawer'); } }); -openerp.web.search.ExtendedSearchProposition = openerp.web.OldWidget.extend(/** @lends openerp.web.search.ExtendedSearchProposition# */{ +instance.web.search.ExtendedSearchProposition = instance.web.OldWidget.extend(/** @lends instance.web.search.ExtendedSearchProposition# */{ template: 'SearchView.extended_search.proposition', /** - * @constructs openerp.web.search.ExtendedSearchProposition - * @extends openerp.web.OldWidget + * @constructs instance.web.search.ExtendedSearchProposition + * @extends instance.web.OldWidget * * @param parent * @param fields @@ -1225,8 +1368,6 @@ openerp.web.search.ExtendedSearchProposition = openerp.web.OldWidget.extend(/** this.value = null; }, start: function () { - this.$element = $("#" + this.element_id); - this.select_field(this.fields.length > 0 ? this.fields[0] : null); var _this = this; this.$element.find(".searchview_extended_prop_field").change(function() { _this.changed(); @@ -1234,14 +1375,7 @@ openerp.web.search.ExtendedSearchProposition = openerp.web.OldWidget.extend(/** this.$element.find('.searchview_extended_delete_prop').click(function () { _this.destroy(); }); - }, - destroy: function() { - var parent; - if (this.getParent().getChildren().length == 1) - parent = this.getParent(); - this._super(); - if (parent) - parent.destroy(); + this.changed(); }, changed: function() { var nval = this.$element.find(".searchview_extended_prop_field").val(); @@ -1267,17 +1401,11 @@ openerp.web.search.ExtendedSearchProposition = openerp.web.OldWidget.extend(/** } var type = field.type; - try { - openerp.web.search.custom_filters.get_object(type); - } catch (e) { - if (! e instanceof openerp.web.KeyNotFound) { - throw e; - } - type = "char"; - console.log('Unknow field type ' + e.key); + var obj = instance.web.search.custom_filters.get_object(type); + if(obj === null) { + obj = instance.web.search.custom_filters.get_object("char"); } - this.value = new (openerp.web.search.custom_filters.get_object(type)) - (this); + this.value = new (obj) (this); if(this.value.set_field) { this.value.set_field(field); } @@ -1301,28 +1429,24 @@ openerp.web.search.ExtendedSearchProposition = openerp.web.OldWidget.extend(/** } }); -openerp.web.search.ExtendedSearchProposition.Field = openerp.web.OldWidget.extend({ +instance.web.search.ExtendedSearchProposition.Field = instance.web.OldWidget.extend({ start: function () { this.$element = $("#" + this.element_id); } }); -openerp.web.search.ExtendedSearchProposition.Char = openerp.web.search.ExtendedSearchProposition.Field.extend({ +instance.web.search.ExtendedSearchProposition.Char = instance.web.search.ExtendedSearchProposition.Field.extend({ template: 'SearchView.extended_search.proposition.char', operators: [ {value: "ilike", text: _lt("contains")}, {value: "not ilike", text: _lt("doesn't contain")}, {value: "=", text: _lt("is equal to")}, - {value: "!=", text: _lt("is not equal to")}, - {value: ">", text: _lt("greater than")}, - {value: "<", text: _lt("less than")}, - {value: ">=", text: _lt("greater or equal than")}, - {value: "<=", text: _lt("less or equal than")} + {value: "!=", text: _lt("is not equal to")} ], get_value: function() { return this.$element.val(); } }); -openerp.web.search.ExtendedSearchProposition.DateTime = openerp.web.search.ExtendedSearchProposition.Field.extend({ +instance.web.search.ExtendedSearchProposition.DateTime = instance.web.search.ExtendedSearchProposition.Field.extend({ template: 'SearchView.extended_search.proposition.empty', operators: [ {value: "=", text: _lt("is equal to")}, @@ -1337,11 +1461,11 @@ openerp.web.search.ExtendedSearchProposition.DateTime = openerp.web.search.Exten }, start: function() { this._super(); - this.datewidget = new openerp.web.DateTimeWidget(this); + this.datewidget = new instance.web.DateTimeWidget(this); this.datewidget.prependTo(this.$element); } }); -openerp.web.search.ExtendedSearchProposition.Date = openerp.web.search.ExtendedSearchProposition.Field.extend({ +instance.web.search.ExtendedSearchProposition.Date = instance.web.search.ExtendedSearchProposition.Field.extend({ template: 'SearchView.extended_search.proposition.empty', operators: [ {value: "=", text: _lt("is equal to")}, @@ -1356,11 +1480,11 @@ openerp.web.search.ExtendedSearchProposition.Date = openerp.web.search.ExtendedS }, start: function() { this._super(); - this.datewidget = new openerp.web.DateWidget(this); + this.datewidget = new instance.web.DateWidget(this); this.datewidget.prependTo(this.$element); } }); -openerp.web.search.ExtendedSearchProposition.Integer = openerp.web.search.ExtendedSearchProposition.Field.extend({ +instance.web.search.ExtendedSearchProposition.Integer = instance.web.search.ExtendedSearchProposition.Field.extend({ template: 'SearchView.extended_search.proposition.integer', operators: [ {value: "=", text: _lt("is equal to")}, @@ -1372,16 +1496,16 @@ openerp.web.search.ExtendedSearchProposition.Integer = openerp.web.search.Extend ], get_value: function() { try { - return openerp.web.parse_value(this.$element.val(), {'widget': 'integer'}); + return instance.web.parse_value(this.$element.val(), {'widget': 'integer'}); } catch (e) { return ""; } } }); -openerp.web.search.ExtendedSearchProposition.Id = openerp.web.search.ExtendedSearchProposition.Integer.extend({ +instance.web.search.ExtendedSearchProposition.Id = instance.web.search.ExtendedSearchProposition.Integer.extend({ operators: [{value: "=", text: _lt("is")}] }); -openerp.web.search.ExtendedSearchProposition.Float = openerp.web.search.ExtendedSearchProposition.Field.extend({ +instance.web.search.ExtendedSearchProposition.Float = instance.web.search.ExtendedSearchProposition.Field.extend({ template: 'SearchView.extended_search.proposition.float', operators: [ {value: "=", text: _lt("is equal to")}, @@ -1393,13 +1517,13 @@ openerp.web.search.ExtendedSearchProposition.Float = openerp.web.search.Extended ], get_value: function() { try { - return openerp.web.parse_value(this.$element.val(), {'widget': 'float'}); + return instance.web.parse_value(this.$element.val(), {'widget': 'float'}); } catch (e) { return ""; } } }); -openerp.web.search.ExtendedSearchProposition.Selection = openerp.web.search.ExtendedSearchProposition.Field.extend({ +instance.web.search.ExtendedSearchProposition.Selection = instance.web.search.ExtendedSearchProposition.Field.extend({ template: 'SearchView.extended_search.proposition.selection', operators: [ {value: "=", text: _lt("is")}, @@ -1412,7 +1536,7 @@ openerp.web.search.ExtendedSearchProposition.Selection = openerp.web.search.Exte return this.$element.val(); } }); -openerp.web.search.ExtendedSearchProposition.Boolean = openerp.web.search.ExtendedSearchProposition.Field.extend({ +instance.web.search.ExtendedSearchProposition.Boolean = instance.web.search.ExtendedSearchProposition.Field.extend({ template: 'SearchView.extended_search.proposition.boolean', operators: [ {value: "=", text: _lt("is true")}, @@ -1423,21 +1547,21 @@ openerp.web.search.ExtendedSearchProposition.Boolean = openerp.web.search.Extend } }); -openerp.web.search.custom_filters = new openerp.web.Registry({ - 'char': 'openerp.web.search.ExtendedSearchProposition.Char', - 'text': 'openerp.web.search.ExtendedSearchProposition.Char', - 'one2many': 'openerp.web.search.ExtendedSearchProposition.Char', - 'many2one': 'openerp.web.search.ExtendedSearchProposition.Char', - 'many2many': 'openerp.web.search.ExtendedSearchProposition.Char', +instance.web.search.custom_filters = new instance.web.Registry({ + 'char': 'instance.web.search.ExtendedSearchProposition.Char', + 'text': 'instance.web.search.ExtendedSearchProposition.Char', + 'one2many': 'instance.web.search.ExtendedSearchProposition.Char', + 'many2one': 'instance.web.search.ExtendedSearchProposition.Char', + 'many2many': 'instance.web.search.ExtendedSearchProposition.Char', - 'datetime': 'openerp.web.search.ExtendedSearchProposition.DateTime', - 'date': 'openerp.web.search.ExtendedSearchProposition.Date', - 'integer': 'openerp.web.search.ExtendedSearchProposition.Integer', - 'float': 'openerp.web.search.ExtendedSearchProposition.Float', - 'boolean': 'openerp.web.search.ExtendedSearchProposition.Boolean', - 'selection': 'openerp.web.search.ExtendedSearchProposition.Selection', + 'datetime': 'instance.web.search.ExtendedSearchProposition.DateTime', + 'date': 'instance.web.search.ExtendedSearchProposition.Date', + 'integer': 'instance.web.search.ExtendedSearchProposition.Integer', + 'float': 'instance.web.search.ExtendedSearchProposition.Float', + 'boolean': 'instance.web.search.ExtendedSearchProposition.Boolean', + 'selection': 'instance.web.search.ExtendedSearchProposition.Selection', - 'id': 'openerp.web.search.ExtendedSearchProposition.Id' + 'id': 'instance.web.search.ExtendedSearchProposition.Id' }); }; diff --git a/addons/web/static/src/js/view_editor.js b/addons/web/static/src/js/view_editor.js index 2c0dc3d70b4..59d1fed9345 100644 --- a/addons/web/static/src/js/view_editor.js +++ b/addons/web/static/src/js/view_editor.js @@ -1,15 +1,15 @@ -openerp.web.view_editor = function(openerp) { -var _t = openerp.web._t; -var QWeb = openerp.web.qweb; -openerp.web.ViewEditor = openerp.web.OldWidget.extend({ +openerp.web.view_editor = function(instance) { +var _t = instance.web._t; +var QWeb = instance.web.qweb; +instance.web.ViewEditor = instance.web.OldWidget.extend({ init: function(parent, element_id, dataset, view, options) { this._super(parent); this.element_id = element_id; this.parent = parent; - this.dataset = new openerp.web.DataSetSearch(this, 'ir.ui.view', null, null), + this.dataset = new instance.web.DataSetSearch(this, 'ir.ui.view', null, null), this.model = dataset.model; this.xml_element_id = 0; - this.property = openerp.web.ViewEditor.property_widget; + this.property = instance.web.ViewEditor.property_widget; this.one_object = false; }, start: function() { @@ -39,7 +39,7 @@ openerp.web.ViewEditor = openerp.web.OldWidget.extend({ select_view_id: self.parent.fields_view.view_id } }; - this.view_edit_dialog = new openerp.web.Dialog(this, { + this.view_edit_dialog = new instance.web.Dialog(this, { title: action_title, width: 850, buttons: [ @@ -51,7 +51,7 @@ openerp.web.ViewEditor = openerp.web.OldWidget.extend({ }).open(); this.view_edit_dialog.on_close.add_last(function(){window.location.reload();}); this.main_view_id = this.parent.fields_view.view_id; - this.action_manager = new openerp.web.ActionManager(this); + this.action_manager = new instance.web.ActionManager(this); this.action_manager.appendTo(this.view_edit_dialog); $.when(this.action_manager.do_action(action)).then(function() { var viewmanager = self.action_manager.inner_viewmanager, @@ -67,7 +67,7 @@ openerp.web.ViewEditor = openerp.web.OldWidget.extend({ }, on_create_view: function() { var self = this; - this.create_view_dialog = new openerp.web.Dialog(this, { + this.create_view_dialog = new instance.web.Dialog(this, { title: _.str.sprintf(_t("Create a view (%s)"), self.model), buttons: [ {text: _t("Save"), click: function () { @@ -115,8 +115,8 @@ openerp.web.ViewEditor = openerp.web.OldWidget.extend({ }, do_save_view: function(values) { def = $.Deferred(); - var field_dataset = new openerp.web.DataSetSearch(this, this.model, null, null); - var model_dataset = new openerp.web.DataSetSearch(this, 'ir.model', null, null); + var field_dataset = new instance.web.DataSetSearch(this, this.model, null, null); + var model_dataset = new instance.web.DataSetSearch(this, 'ir.model', null, null); var view_string = "", field_name = false, self = this; field_dataset.call( 'fields_get', [], function(fields) { _.each(['name', 'x_name'], function(value) { @@ -360,7 +360,7 @@ openerp.web.ViewEditor = openerp.web.OldWidget.extend({ edit_view: function(one_object) { var self = this; this.one_object = one_object; - this.edit_xml_dialog = new openerp.web.Dialog(this, { + this.edit_xml_dialog = new instance.web.Dialog(this, { title: _.str.sprintf(_t("View Editor %d - %s"), self.main_view_id, self.model), height: '90%', buttons: [ @@ -392,7 +392,7 @@ openerp.web.ViewEditor = openerp.web.OldWidget.extend({ action_buttons: false } }; - var action_manager = new openerp.web.ActionManager(self); + var action_manager = new instance.web.ActionManager(self); action_manager.do_action(action); }}, {text: _t("Close"), click: function(){ @@ -517,7 +517,7 @@ openerp.web.ViewEditor = openerp.web.OldWidget.extend({ var self = this; var tr = self.get_object_by_id(this.one_object.clicked_tr_id, this.one_object['main_object'], [])[0].att_list[0]; var parent_tr = ($(side).prevAll("tr[level=" + String(this.one_object.clicked_tr_level - 1) + "]"))[0]; - var field_dataset = new openerp.web.DataSetSearch(this, this.model, null, null); + var field_dataset = new instance.web.DataSetSearch(this, this.model, null, null); parent_tr = $(parent_tr).find('a').text(); field_dataset.call( 'fields_get', [], function(result) { var fields = _.keys(result); @@ -811,7 +811,7 @@ openerp.web.ViewEditor = openerp.web.OldWidget.extend({ }, on_edit_node: function(properties){ var self = this; - this.edit_node_dialog = new openerp.web.Dialog(this,{ + this.edit_node_dialog = new instance.web.Dialog(this,{ title: _t("Properties"), width: 450, buttons: [ @@ -880,7 +880,7 @@ openerp.web.ViewEditor = openerp.web.OldWidget.extend({ this.edit_widget = []; self.ready = $.when(self.on_groups(properties)).then(function () { _PROPERTIES_ATTRIBUTES['groups']['selection'] = self.groups; - var values = _.keys( openerp.web.form.widgets.map); + var values = _.keys( instance.web.form.widgets.map); values.push(''); values.sort(); _PROPERTIES_ATTRIBUTES['widget']['selection'] = values; @@ -908,8 +908,8 @@ openerp.web.ViewEditor = openerp.web.OldWidget.extend({ def.resolve(); } var group_ids = [], group_names = {}, groups = []; - var res_groups = new openerp.web.DataSetSearch(this,'res.groups', null, null), - model_data = new openerp.web.DataSetSearch(self,'ir.model.data', null, null); + var res_groups = new instance.web.DataSetSearch(this,'res.groups', null, null), + model_data = new instance.web.DataSetSearch(self,'ir.model.data', null, null); res_groups.read_slice([], {}).done(function (res_grp) { _.each(res_grp, function (res) { var key = res.id; @@ -935,7 +935,7 @@ openerp.web.ViewEditor = openerp.web.OldWidget.extend({ {'name': 'field_value','selection': fields, 'value': false, 'string': '','type': 'selection'}, {'name': 'position','selection': ['After','Before','Inside'], 'value': false, 'string': 'Position','type': 'selection'}]; this.add_widget = []; - this.add_node_dialog = new openerp.web.Dialog(this,{ + this.add_node_dialog = new instance.web.Dialog(this,{ title: _t("Properties"), width: 450, buttons: [ @@ -980,7 +980,7 @@ openerp.web.ViewEditor = openerp.web.OldWidget.extend({ }); table_selector.find("td[id^=]").attr("width","100px"); self.add_node_dialog.$element.find('#new_field').click(function() { - model_data = new openerp.web.DataSetSearch(self,'ir.model', null, null); + model_data = new instance.web.DataSetSearch(self,'ir.model', null, null); model_data.read_slice([], {domain: [['model','=', self.model]]}).then(function(result) { self.render_new_field(result[0].id); }); @@ -998,7 +998,7 @@ openerp.web.ViewEditor = openerp.web.OldWidget.extend({ action_buttons: true } }; - var action_manager = new openerp.web.ActionManager(self); + var action_manager = new instance.web.ActionManager(self); $.when(action_manager.do_action(action)).then(function() { var controller = action_manager.dialog_viewmanager.views['form'].controller; controller.on_button_cancel.add_last(function(){ @@ -1015,7 +1015,7 @@ openerp.web.ViewEditor = openerp.web.OldWidget.extend({ }); } }); -openerp.web.ViewEditor.Field = openerp.web.Class.extend({ +instance.web.ViewEditor.Field = instance.web.Class.extend({ init: function(view, widget) { this.$element = view.$element; this.dirty = false; @@ -1041,7 +1041,7 @@ openerp.web.ViewEditor.Field = openerp.web.Class.extend({ validate: function() { this.is_invalid = false; try { - var value = openerp.web.parse_value(this.get_value(), this, ''); + var value = instance.web.parse_value(this.get_value(), this, ''); this.is_invalid = this.required && value === ''; } catch(e) { this.is_invalid = true; @@ -1051,7 +1051,7 @@ openerp.web.ViewEditor.Field = openerp.web.Class.extend({ return _.str.sprintf("<td id = %s>%s</td>", this.name, QWeb.render(this.template, {widget: this})) } }); -openerp.web.ViewEditor.FieldBoolean = openerp.web.ViewEditor.Field.extend({ +instance.web.ViewEditor.FieldBoolean = instance.web.ViewEditor.Field.extend({ template : "vieweditor_boolean", start: function() { var self = this; @@ -1069,7 +1069,7 @@ openerp.web.ViewEditor.FieldBoolean = openerp.web.ViewEditor.Field.extend({ return this.$element.find("input[id=" + this.name + "]").is(':checked')? "1" : null; } }); -openerp.web.ViewEditor.FieldChar = openerp.web.ViewEditor.Field.extend({ +instance.web.ViewEditor.FieldChar = instance.web.ViewEditor.Field.extend({ template : "vieweditor_char", start: function () { var self = this; @@ -1085,7 +1085,7 @@ openerp.web.ViewEditor.FieldChar = openerp.web.ViewEditor.Field.extend({ return this.$element.find("input[id=" + this.name + "]").val(); } }); -openerp.web.ViewEditor.FieldSelect = openerp.web.ViewEditor.Field.extend({ +instance.web.ViewEditor.FieldSelect = instance.web.ViewEditor.Field.extend({ template : "vieweditor_selection", start: function () { var self = this; @@ -1115,7 +1115,7 @@ openerp.web.ViewEditor.FieldSelect = openerp.web.ViewEditor.Field.extend({ return this.$element.find("select[id=" + this.name + "]").val(); } }); -openerp.web.ViewEditor.FieldSelectMulti = openerp.web.ViewEditor.FieldSelect.extend({ +instance.web.ViewEditor.FieldSelectMulti = instance.web.ViewEditor.FieldSelect.extend({ start: function () { this._super(); this.$element.find("select[id=" + this.name + "]").css('height', '100px').attr("multiple", true); @@ -1131,7 +1131,7 @@ openerp.web.ViewEditor.FieldSelectMulti = openerp.web.ViewEditor.FieldSelect.ext }); } }); -openerp.web.ViewEditor.FieldFloat = openerp.web.ViewEditor.FieldChar.extend({ +instance.web.ViewEditor.FieldFloat = instance.web.ViewEditor.FieldChar.extend({ }); var _PROPERTIES = { @@ -1196,11 +1196,11 @@ var _ICONS = ['','STOCK_ABOUT', 'STOCK_ADD', 'STOCK_APPLY', 'STOCK_BOLD', 'terp-sale', 'terp-tools', 'terp-administration', 'terp-hr', 'terp-partner', 'terp-project', 'terp-report', 'terp-stock', 'terp-calendar', 'terp-graph' ]; -openerp.web.ViewEditor.property_widget = new openerp.web.Registry({ - 'boolean' : 'openerp.web.ViewEditor.FieldBoolean', - 'selection_multi' : 'openerp.web.ViewEditor.FieldSelectMulti', - 'selection' : 'openerp.web.ViewEditor.FieldSelect', - 'char' : 'openerp.web.ViewEditor.FieldChar', - 'float' : 'openerp.web.ViewEditor.FieldFloat' +instance.web.ViewEditor.property_widget = new instance.web.Registry({ + 'boolean' : 'instance.web.ViewEditor.FieldBoolean', + 'selection_multi' : 'instance.web.ViewEditor.FieldSelectMulti', + 'selection' : 'instance.web.ViewEditor.FieldSelect', + 'char' : 'instance.web.ViewEditor.FieldChar', + 'float' : 'instance.web.ViewEditor.FieldFloat' }); }; diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 50c5dcb976a..7e5b173fd37 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -1,31 +1,32 @@ -openerp.web.form = function (openerp) { +openerp.web.form = function (instance) { +var _t = instance.web._t, + _lt = instance.web._lt; +var QWeb = instance.web.qweb; -var _t = openerp.web._t, - _lt = openerp.web._lt; -var QWeb = openerp.web.qweb; +/** @namespace */ +instance.web.form = {}; -openerp.web.views.add('form', 'openerp.web.FormView'); -openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# */{ +instance.web.views.add('form', 'instance.web.FormView'); +instance.web.FormView = instance.web.View.extend({ /** * Indicates that this view is not searchable, and thus that no search * view should be displayed (if there is one active). */ searchable: false, - readonly : false, - form_template: "FormView", + template: "FormView", display_name: _lt('Form'), + view_type: "form", /** - * @constructs openerp.web.FormView - * @extends openerp.web.View + * @constructs instance.web.FormView + * @extends instance.web.View * - * @param {openerp.web.Session} session the current openerp session - * @param {openerp.web.DataSet} dataset the dataset this view will work with + * @param {instance.web.Session} session the current openerp session + * @param {instance.web.DataSet} dataset the dataset this view will work with * @param {String} view_id the identifier of the OpenERP view object * @param {Object} options - * - sidebar : [true|false] * - resize_textareas : [true|false|max_height] * - * @property {openerp.web.Registry} registry=openerp.web.form.widgets widgets registry for this form view instance + * @property {instance.web.Registry} registry=instance.web.form.widgets widgets registry for this form view instance */ init: function(parent, dataset, view_id, options) { this._super(parent); @@ -34,98 +35,86 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# this.model = dataset.model; this.view_id = view_id || false; this.fields_view = {}; - this.widgets = {}; - this.widgets_counter = 0; this.fields = {}; this.fields_order = []; this.datarecord = {}; this.default_focus_field = null; this.default_focus_button = null; - this.registry = openerp.web.form.widgets; + this.fields_registry = instance.web.form.widgets; + this.tags_registry = instance.web.form.tags; this.has_been_loaded = $.Deferred(); - this.$form_header = null; this.translatable_fields = []; _.defaults(this.options, { - "not_interactible_on_create": false + "not_interactible_on_create": false, + "initial_mode": "view", }); this.is_initialized = $.Deferred(); this.mutating_mutex = new $.Mutex(); this.on_change_mutex = new $.Mutex(); this.reload_mutex = new $.Mutex(); - }, - start: function() { - this._super(); - return this.init_view(); - }, - init_view: function() { - if (this.embedded_view) { - var def = $.Deferred().then(this.on_loaded); - var self = this; - $.async_when().then(function() {def.resolve(self.embedded_view);}); - return def.promise(); - } else { - var context = new openerp.web.CompoundContext(this.dataset.get_context()); - return this.rpc("/web/view/load", { - "model": this.model, - "view_id": this.view_id, - "view_type": "form", - toolbar: this.options.sidebar, - context: context - }, this.on_loaded); - } + this.mode = null; + this.rendering_engine = new instance.web.form.FormRenderingEngineReadonly(this); + this.qweb = null; // A QWeb instance will be created if the view is a QWeb template }, destroy: function() { - if (this.sidebar) { - this.sidebar.attachments.destroy(); - this.sidebar.destroy(); - } - _.each(this.widgets, function(w) { + _.each(this.get_widgets(), function(w) { w.destroy(); }); this._super(); }, - reposition: function ($e) { - this.$element = $e; - this.on_loaded(); - }, on_loaded: function(data) { var self = this; if (!data) { - throw "No data provided."; + throw new Error("No data provided."); } - if (this.root_frame) { - throw "Form view does not support multiple calls to on_loaded"; + if (this.arch) { + throw "Form view does not support multiple calls to on_loaded"; } this.fields_order = []; this.fields_view = data; - this.rendered = QWeb.render(this.form_template, {'widget': this}); - this.$element.html(this.rendered); - - this.root_frame = instanciate_widget(this.registry.get_object('frame'), this, this.fields_view.arch); - var to_append = $(".oe_form_header", this.$element); - this.root_frame.appendTo(to_append.length > 0 ? to_append : this.$element); - this.root_frame.$element.children().unwrap(); - - this.$form_header = this.$element.find('.oe_form_header:first'); - this.$form_header.find('div.oe_form_pager button[data-pager-action]').click(function() { + this.rendering_engine.set_fields_registry(this.fields_registry); + this.rendering_engine.set_tags_registry(this.tags_registry); + if (!this.extract_qweb_template(data)) { + this.rendering_engine.set_fields_view(data); + var $dest = this.$element.hasClass("oe_form_container") ? this.$element : this.$element.find('.oe_form_container'); + this.rendering_engine.render_to($dest); + } + + this.$sidebar = this.options.$sidebar || this.$element.find('.oe_form_sidebar'); + + this.$buttons = $(QWeb.render("FormView.buttons", {'widget':self})); + if (this.options.$buttons) { + this.$buttons.appendTo(this.options.$buttons); + } else { + this.$element.find('.oe_form_buttons').replaceWith(this.$buttons); + } + this.$buttons + .on('click','button.oe_form_button_save',this.on_button_save) + .on('click','button.oe_form_button_cancel',this.on_button_cancel) + .on('click','button.oe_form_button_edit',this.on_button_edit) + .on('click','button.oe_form_button_create',this.on_button_create) + .on('click','button.oe_form_button_duplicate',this.on_button_duplicate) + .on('click','button.oe_form_button_delete',this.on_button_delete); + + this.$pager = $(QWeb.render("FormView.pager", {'widget':self})); + if (this.options.$pager) { + this.$pager.appendTo(this.options.$pager); + } else { + this.$element.find('.oe_form_pager').replaceWith(this.$pager); + } + this.$pager.on('click','a[data-pager-action]',function() { var action = $(this).data('pager-action'); self.on_pager_action(action); }); - this.$form_header.find('button.oe_form_button_save').click(this.on_button_save); - this.$form_header.find('button.oe_form_button_cancel').click(this.on_button_cancel); - - if (!this.sidebar && this.options.sidebar && this.options.sidebar_id) { - this.sidebar = new openerp.web.Sidebar(this, this.options.sidebar_id); - this.sidebar.start(); - this.sidebar.do_unfold(); - this.sidebar.attachments = new openerp.web.form.SidebarAttachments(this.sidebar, this); - this.sidebar.add_toolbar(this.fields_view.toolbar); - this.set_common_sidebar_sections(this.sidebar); - - this.sidebar.add_section(_t('Customize'), 'customize'); - this.sidebar.add_items('customize', [{ + if (!this.sidebar && this.options.$sidebar) { + this.sidebar = new instance.web.Sidebar(this); + this.sidebar.appendTo(this.$sidebar); + if(this.fields_view.toolbar) { + this.sidebar.add_toolbar(this.fields_view.toolbar); + } + this.sidebar.add_items('other', [{ label: _t('Set Default'), form: this, callback: function (item) { @@ -133,9 +122,61 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# } }]); } + this.on("change:mode", this, this.switch_mode); + this.set({mode: this.options.initial_mode}); this.has_been_loaded.resolve(); + return $.when(); + }, + extract_qweb_template: function(fvg) { + for (var i=0, ii=fvg.arch.children.length; i < ii; i++) { + var child = fvg.arch.children[i]; + if (child.tag === "templates") { + this.qweb = new QWeb2.Engine(); + this.qweb.add_template(instance.web.json_node_to_xml(child)); + if (!this.qweb.has_template('form')) { + throw new Error("No QWeb template found for form view"); + } + return true; + } + } + this.qweb = null; + return false; + }, + get_fvg_from_qweb: function(record) { + var view = this.qweb.render('form', this.get_qweb_context(record)); + var fvg = _.clone(this.fields_view); + fvg.arch = instance.web.xml_to_json(instance.web.str_to_xml(view).firstChild); + return fvg; + }, + get_qweb_context: function(record) { + var self = this, + new_record = {}; + _.each(record, function(value_, name) { + var r = _.clone(self.fields_view.fields[name] || {}); + if ((r.type === 'date' || r.type === 'datetime') && value_) { + r.raw_value = instance.web.auto_str_to_date(value_); + } else { + r.raw_value = value_; + } + r.value = instance.web.format_value(value_, r); + new_record[name] = r; + }); + return { + record : new_record, + new_record : !record.id + }; + }, + kill_current_form: function() { + _.each(this.getChildren(), function(el) { + el.destroy(); + }); + this.fields = {}; + this.fields_order = []; + this.default_focus_field = null; + this.default_focus_button = null; + this.translatable_fields = []; + this.$element.find('.oe_form_container').empty(); }, - do_load_state: function(state, warm) { if (state.id && this.datarecord.id != state.id) { if (!this.dataset.get_id_index(state.id)) { @@ -147,9 +188,18 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# } } }, - do_show: function () { var self = this; + if (this.sidebar) { + this.sidebar.$element.show(); + } + if (this.$buttons) { + this.$buttons.show(); + this.$buttons.find('.oe_form_button_save').removeClass('oe_form_button_save_dirty'); + } + if (this.$pager) { + this.$pager.show(); + } this.$element.show().css('visibility', 'hidden'); this.$element.removeClass('oe_form_dirty'); return this.has_been_loaded.pipe(function() { @@ -165,17 +215,20 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# result.pipe(function() { self.$element.css('visibility', 'visible'); }); - if (self.sidebar) { - self.sidebar.$element.show(); - } return result; }); }, do_hide: function () { - this._super(); if (this.sidebar) { this.sidebar.$element.hide(); } + if (this.$buttons) { + this.$buttons.hide(); + } + if (this.$pager) { + this.$pager.hide(); + } + this._super(); }, on_record_loaded: function(record) { var self = this, set_values = []; @@ -185,13 +238,17 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# } this.datarecord = record; + if (this.qweb) { + this.kill_current_form(); + this.rendering_engine.set_fields_view(this.get_fvg_from_qweb(record)); + var $dest = this.$element.hasClass("oe_form_container") ? this.$element : this.$element.find('.oe_form_container'); + this.rendering_engine.render_to($dest); + } + _(this.fields).each(function (field, f) { - field.reset(); + field._dirty_flag = false; var result = field.set_value(self.datarecord[f] || false); set_values.push(result); - $.when(result).then(function() { - field.validate(); - }); }); return $.when.apply(null, set_values).pipe(function() { if (!record.id) { @@ -200,7 +257,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# _.each(self.fields_order, function(field_name) { if (record[field_name] !== undefined) { var field = self.fields[field_name]; - field.dirty = true; + field._dirty_flag = true; self.do_onchange(field); } }); @@ -209,7 +266,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# self.is_initialized.resolve(); self.do_update_pager(record.id == null); if (self.sidebar) { - self.sidebar.attachments.do_update(); + self.sidebar.do_attachement_update(self.dataset, self.datarecord.id); } if (self.default_focus_field) { self.default_focus_field.focus(); @@ -218,20 +275,15 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# self.do_push_state({id:record.id}); } self.$element.removeClass('oe_form_dirty'); + self.$buttons.find('.oe_form_button_save').removeClass('oe_form_button_save_dirty'); }); }, on_form_changed: function() { - for (var w in this.widgets) { - w = this.widgets[w]; - w.process_modifiers(); - if (w.field) { - w.validate(); - } - w.update_dom(); - } + this.trigger("view_content_has_changed"); }, do_notify_change: function() { this.$element.addClass('oe_form_dirty'); + this.$buttons.find('.oe_form_button_save').addClass('oe_form_button_save_dirty'); }, on_pager_action: function(action) { if (this.can_be_discarded()) { @@ -253,11 +305,10 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# } }, do_update_pager: function(hide_index) { - var $pager = this.$form_header.find('div.oe_form_pager'); var index = hide_index ? '-' : this.dataset.index + 1; - $pager.find('button').prop('disabled', this.dataset.ids.length < 2); - $pager.find('span.oe_pager_index').html(index); - $pager.find('span.oe_pager_count').html(this.dataset.ids.length); + this.$pager.find('button').prop('disabled', this.dataset.ids.length < 2).end() + .find('span.oe_pager_index').html(index).end() + .find('span.oe_pager_count').html(this.dataset.ids.length); }, parse_on_change: function (on_change, widget) { var self = this; @@ -278,7 +329,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# 'None': function () {return null;}, 'context': function (i) { context_index = i; - var ctx = new openerp.web.CompoundContext(self.dataset.get_context(), widget.build_context() ? widget.build_context() : {}); + var ctx = new instance.web.CompoundContext(self.dataset.get_context(), widget.build_context() ? widget.build_context() : {}); return ctx; } }; @@ -296,8 +347,8 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# } // form field if (self.fields[field]) { - var value = self.fields[field].get_on_change_value(); - return value == null ? false : value; + var value_ = self.fields[field].get_value(); + return value_ == null ? false : value_; } // parent field var splitted = field.split('.'); @@ -360,17 +411,17 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# } if (widget.field['change_default']) { - var fieldname = widget.name, value; + var fieldname = widget.name, value_; if (response.value && (fieldname in response.value)) { // Use value from onchange if onchange executed - value = response.value[fieldname]; + value_ = response.value[fieldname]; } else { // otherwise get form value for field - value = self.fields[fieldname].get_on_change_value(); + value_ = self.fields[fieldname].get_value(); } - var condition = fieldname + '=' + value; + var condition = fieldname + '=' + value_; - if (value) { + if (value_) { can_process_onchange = self.rpc({ url: '/web/dataset/call', async: false @@ -411,10 +462,10 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# var field = this.fields[f]; // If field is not defined in the view, just ignore it if (field) { - var value = result.value[f]; - if (field.get_value() != value) { - field.set_value(value); - field.dirty = true; + var value_ = result.value[f]; + if (field.get_value() != value_) { + field.set_value(value_); + field._dirty_flag = true; if (!_.contains(processed, field.name)) { this.do_onchange(field, processed); } @@ -424,7 +475,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# this.on_form_changed(); } if (!_.isEmpty(result.warning)) { - openerp.web.dialog($(QWeb.render("CrashManagerWarning", result.warning)), { + instance.web.dialog($(QWeb.render("CrashManagerWarning", result.warning)), { modal: true, buttons: [ {text: _t("Ok"), click: function() { $(this).dialog("close"); }} @@ -447,19 +498,38 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# return $.Deferred().reject(); } }, + switch_mode: function() { + var mode = this.get("mode"); + var self = this; + if(mode=="view") { + self.$buttons.find('.oe_form_buttons_edit').hide(); + self.$buttons.find('.oe_form_buttons_view').show(); + _.each(this.fields,function(field){ + field.set({"force_readonly": true}); + }); + } else { + self.$buttons.find('.oe_form_buttons_edit').show(); + self.$buttons.find('.oe_form_buttons_view').hide(); + _.each(this.fields,function(field){ + field.set({"force_readonly": false}); + }); + } + self.mode = mode; + }, on_button_save: function() { var self = this; return this.do_save().then(function(result) { - self.do_prev_view({'created': result.created, 'default': 'page'}); + self.set({mode: "view"}); }); }, on_button_cancel: function() { if (this.can_be_discarded()) { - return this.do_prev_view({'default': 'page'}); + this.set({mode: "view"}); } }, on_button_new: function() { var self = this; + this.set({mode: "edit"}); var def = $.Deferred(); $.when(this.has_been_loaded).then(function() { if (self.can_be_discarded()) { @@ -477,6 +547,44 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# }); return def.promise(); }, + on_button_edit: function() { + return this.set({mode: "edit"}); + }, + on_button_create: function() { + this.dataset.index = null; + this.do_show(); + }, + on_button_duplicate: function() { + var self = this; + var def = $.Deferred(); + $.when(this.has_been_loaded).then(function() { + self.dataset.call('copy', [self.datarecord.id, {}, self.dataset.context]).then(function(new_id) { + return self.on_created({ result : new_id }); + }).then(function() { + return self.set({mode: "edit"}); + }).then(function() { + def.resolve(); + }); + }); + return def.promise(); + }, + on_button_delete: function() { + var self = this; + var def = $.Deferred(); + $.when(this.has_been_loaded).then(function() { + if (self.datarecord.id && confirm(_t("Do you really want to delete this record?"))) { + self.dataset.unlink([self.datarecord.id]).then(function() { + self.on_pager_action('next'); + def.resolve(); + }); + } else { + $.async_when().then(function () { + def.reject(); + }) + } + }); + return def.promise(); + }, can_be_discarded: function() { return !this.$element.is('.oe_form_dirty') || confirm(_t("Warning, the record has been modified, your changes will be discarded.")); }, @@ -499,11 +607,10 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# f = self.fields[f]; if (!f.is_valid()) { form_invalid = true; - f.update_dom(true); if (!first_invalid_field) { first_invalid_field = f; } - } else if (f.name !== 'id' && !f.readonly && (!self.datarecord.id || f.is_dirty())) { + } else if (f.name !== 'id' && !f.get("readonly") && (!self.datarecord.id || f._dirty_flag)) { // Special case 'id' field, do not save this field // on 'create' : save all non readonly fields // on 'edit' : save non readonly modified fields @@ -511,20 +618,23 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# } } if (form_invalid) { + self.set({'display_invalid_fields': true}); first_invalid_field.focus(); self.on_invalid(); return $.Deferred().reject(); } else { + self.set({'display_invalid_fields': false}); var save_deferral; if (!self.datarecord.id) { //console.log("FormView(", self, ") : About to create", values); save_deferral = self.dataset.create(values).pipe(function(r) { return self.on_created(r, undefined, prepend_on_create); }, null); - } else if (_.isEmpty(values)) { + } else if (_.isEmpty(values) && ! self.force_dirty) { //console.log("FormView(", self, ") : Nothing to save"); save_deferral = $.Deferred().resolve({}).promise(); } else { + self.force_dirty = false; //console.log("FormView(", self, ") : About to save", values); save_deferral = self.dataset.write(self.datarecord.id, values, {}).pipe(function(r) { return self.on_saved(r); @@ -542,7 +652,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# var msg = "<ul>"; _.each(this.fields, function(f) { if (!f.is_valid()) { - msg += "<li>" + f.string + "</li>"; + msg += "<li>" + f.node.attrs.string + "</li>"; } }); msg += "</ul>"; @@ -580,14 +690,14 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# this.dataset.alter_ids(this.dataset.ids.concat([this.datarecord.id])); this.dataset.index = this.dataset.ids.length - 1; } else { - this.dataset.alter_ids([this.datarecord.id].concat(this.dataset.ids)); + this.dataset.alter_ids([this.datarecord.id].concat(this.dataset.ids)); this.dataset.index = 0; } this.do_update_pager(); if (this.sidebar) { - this.sidebar.attachments.do_update(); + this.sidebar.do_attachement_update(this.dataset, this.datarecord.id); } - //openerp.log("The record has been created with id #" + this.datarecord.id); + //instance.log("The record has been created with id #" + this.datarecord.id); this.reload(); return $.when(_.extend(r, {created: true})).then(success); } @@ -598,6 +708,10 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# reload: function() { var self = this; return this.reload_mutex.exec(function() { + if (self.dataset.index == null) { + self.do_prev_view(); + return $.Deferred().reject().promise(); + } if (self.dataset.index == null || self.dataset.index < 0) { return $.when(self.on_button_new()); } else { @@ -607,15 +721,20 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# } }); }, + get_widgets: function() { + return _.filter(this.getChildren(), function(obj) { + return obj instanceof instance.web.form.Widget; + }); + }, get_fields_values: function(blacklist) { blacklist = blacklist || []; var values = {}; var ids = this.get_selected_ids(); values["id"] = ids.length > 0 ? ids[0] : false; - _.each(this.fields, function(value, key) { + _.each(this.fields, function(value_, key) { if (_.include(blacklist, key)) return; - var val = value.get_value(); + var val = value_.get_value(); values[key] = val; }); return values; @@ -632,8 +751,8 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# }); }, is_dirty: function() { - return _.any(this.fields, function (value) { - return value.is_dirty(); + return _.any(this.fields, function (value_) { + return value_._dirty_flag; }); }, is_interactible_record: function() { @@ -642,7 +761,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# if (this.options.not_interactible_on_create) return false; } else if (typeof(id) === "string") { - if(openerp.web.BufferedDataSet.virtual_id_regex.test(id)) + if(instance.web.BufferedDataSet.virtual_id_regex.test(id)) return false; } return true; @@ -654,12 +773,12 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# var self = this; var fields = _.chain(this.fields) .map(function (field, name) { - var value = field.get_value(); + var value_ = field.get_value(); // ignore fields which are empty, invisible, readonly, o2m // or m2m - if (!value - || field.invisible - || field.readonly + if (!value_ + || field.get('invisible') + || field.get("readonly") || field.field.type === 'one2many' || field.field.type === 'many2many') { return false; @@ -668,33 +787,33 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# switch(field.field.type) { case 'selection': displayed = _(field.values).find(function (option) { - return option[0] === value; + return option[0] === value_; })[1]; break; case 'many2one': - displayed = field.value[1] || value; + displayed = value_; break; default: - displayed = value; + displayed = value_; } return { name: name, - string: field.string, - value: value, + string: field.node_atts.string, + value: value_, displayed: displayed, // convert undefined to false change_default: !!field.field.change_default } }) .compact() - .sortBy(function (field) { return field.string; }) + .sortBy(function (field) { return field.node_atts.string; }) .value(); var conditions = _.chain(fields) .filter(function (field) { return field.change_default; }) .value(); - var d = new openerp.web.Dialog(this, { + var d = new instance.web.Dialog(this, { title: _t("Set Default"), args: { fields: fields, @@ -706,12 +825,12 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# var $defaults = d.$element.find('#formview_default_fields'); var field_to_set = $defaults.val(); if (!field_to_set) { - $defaults.parent().addClass('invalid'); + $defaults.parent().addClass('oe_form_invalid'); return; } var condition = d.$element.find('#formview_default_conditions').val(), all_users = d.$element.find('#formview_default_all').is(':checked'); - new openerp.web.DataSet(self, 'ir.values').call( + new instance.web.DataSet(self, 'ir.values').call( 'set_default', [ self.dataset.model, field_to_set, @@ -725,9 +844,368 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# }); d.template = 'FormView.set_default'; d.open(); - } + }, + register_field: function(field, name) { + this.fields[name] = field; + this.fields_order.push(name); + if (this.get_field(name).translate) { + this.translatable_fields.push(field); + } + field.on('changed_value', this, function() { + field._dirty_flag = true; + if (field.is_syntax_valid()) { + this.do_onchange(field); + this.on_form_changed(true); + this.do_notify_change(); + } + }); + }, + get_field: function(field_name) { + return this.fields_view.fields[field_name]; + }, + is_create_mode: function() { + return !this.datarecord.id; + }, }); -openerp.web.FormDialog = openerp.web.Dialog.extend({ + +/** + * Interface to be implemented by rendering engines for the form view. + */ +instance.web.form.FormRenderingEngineInterface = { + set_fields_view: function(fields_view) {}, + set_fields_registry: function(fields_registry) {}, + render_to: function($element) {}, +}; + +/** + * Default rendering engine for the form view. + * + * It is necessary to set the view using set_view() before usage. + */ +instance.web.form.FormRenderingEngine = instance.web.Class.extend({ + init: function(view) { + this.view = view; + }, + set_fields_view: function(fvg) { + this.fvg = fvg; + }, + set_tags_registry: function(tags_registry) { + this.tags_registry = tags_registry; + }, + set_fields_registry: function(fields_registry) { + this.fields_registry = fields_registry; + }, + render_to: function($target) { + var self = this; + this.$target = $target; + + // TODO: I know this will save the world and all the kitten for a moment, + // but one day, we will have to get rid of xml2json + var xml = instance.web.json_node_to_xml(this.fvg.arch); + this.$form = $('<div class="oe_form">' + xml + '</div>'); + if (this.fvg.arch.attrs && this.fvg.arch.attrs['layout'] !== 'manual') { + this.$form.attr('layout', 'auto'); + } + + this.fields_to_init = []; + this.tags_to_init = []; + this.labels = {}; + this.process(this.$form); + + this.$form.appendTo(this.$target); + + _.each(this.fields_to_init, function($elem) { + var name = $elem.attr("name"); + if (!self.fvg.fields[name]) { + throw new Error("Field '" + name + "' specified in view could not be found."); + } + var obj = self.fields_registry.get_any([$elem.attr('widget'), self.fvg.fields[name].type]); + if (!obj) { + throw new Error("Widget type '"+ key + "' is not implemented"); + } + var w = new (obj)(self.view, instance.web.xml_to_json($elem[0])); + var $label = self.labels[$elem.attr("name")]; + if ($label) { + w.set_input_id($label.attr("for")); + } + self.alter_field(w); + self.view.register_field(w, $elem.attr("name")); + w.replace($elem); + }); + _.each(this.tags_to_init, function($elem) { + var tag_name = $elem[0].tagName.toLowerCase(); + var obj = self.tags_registry.get_object(tag_name); + var w = new (obj)(self.view, instance.web.xml_to_json($elem[0])); + w.replace($elem); + }) + // TODO: return a deferred + }, + render_element: function(template, layout/* dictionaries */) { + var dicts = [].slice.call(arguments).slice(2); + dicts.unshift({ 'layout' : layout }); + var dict = _.extend.apply(_, dicts); + dict['classnames'] = dict['class'] || ''; // class is a reserved word and might caused problem to Safari when used from QWeb + var alt_template = template + '.' + layout; + template = QWeb.has_template(alt_template) ? alt_template : template; + return $(QWeb.render(template, dict)); + }, + alter_field: function(field) { + }, + toggle_layout_debugging: function() { + if (!this.$target.has('.oe_layout_debug_cell:first').length) { + this.$target.find('[title]').removeAttr('title'); + this.$target.find('.oe_form_group_cell').each(function() { + var text = 'W:' + ($(this).attr('width') || '') + ' - C:' + $(this).attr('colspan'); + $(this).attr('title', text); + }); + } + this.$target.toggleClass('oe_layout_debugging'); + }, + process: function($tag, layout) { + var self = this; + if ($tag.attr('layout') === 'auto') { + $tag.addClass('oe_form_autolayout'); + } + layout = $tag.attr('layout') || layout || 'auto'; + $tag.removeAttr('layout'); + var tagname = $tag[0].nodeName.toLowerCase(); + if (this.tags_registry.contains(tagname)) { + this.tags_to_init.push($tag); + return $tag; + } + var fn = self['process_' + tagname]; + if (fn) { + var args = [].slice.call(arguments); + args[0] = $tag; + args[1] = layout; + return fn.apply(self, args); + } else { + // generic tag handling, just process children + $tag.children().each(function() { + self.process($(this), layout); + }); + self.handle_common_properties($tag, $tag); + $tag.removeAttr("modifiers"); + return $tag; + } + }, + process_sheet: function() { + this.process_form.apply(this, arguments); + }, + process_form: function($form, layout) { + var $new_form = this.render_element('FormRenderingForm', layout, $form.getAttributes()); + var $dst = (layout === 'auto') ? $new_form.find('group:first') : $new_form; + $new_form.attr("modifiers", $form.attr("modifiers")); + $form.children().appendTo($dst); + if ($form[0] === this.$form[0]) { + // If root element, replace it + this.$form = $new_form; + } else { + $form.before($new_form).remove(); + } + this.process($new_form); + }, + preprocess_field: function($field) { + var self = this; + var name = $field.attr('name'), + field_colspan = parseInt($field.attr('colspan'), 10), + field_modifiers = JSON.parse($field.attr('modifiers') || '{}'); + + if ($field.attr('nolabel') === '1') + return; + $field.attr('nolabel', '1'); + var found = false; + this.$form.find('label[for="' + name + '"]').each(function(i ,el) { + $(el).parents().each(function(unused, tag) { + var name = tag.tagName.toLowerCase(); + if (name === "field" || name in self.tags_registry.map) + found = true; + }); + }); + if (found) + return; + + $label = $('<label/>').attr({ + 'for' : name, + "modifiers": JSON.stringify({invisible: field_modifiers.invisible}), + "string": $field.attr('string'), + "help": $field.attr('help'), + }); + $label.insertBefore($field); + if (field_colspan > 1) { + $field.attr('colspan', field_colspan - 1); + } + return $label; + }, + process_field: function($field, layout) { + var $label = this.preprocess_field($field); + if ($label) + this.process($label, layout); + this.fields_to_init.push($field); + return $field; + }, + process_group: function($group, layout) { + var self = this; + if ($group.parent().is('.oe_form_group_cell')) { + $group.parent().addClass('oe_form_group_nested'); + } + $group.children('field').each(function() { + self.preprocess_field($(this)); + }); + var $new_group = this.render_element('FormRenderingGroup', layout, $group.getAttributes()), + $table; + if ($new_group.is('table')) { + $table = $new_group; + } else { + $table = $new_group.find('table:first'); + } + $table.addClass('oe_form_group'); + var $tr, $td, + cols = parseInt($group.attr('col') || 4, 10), + row_cols = cols; + + var children = []; + $group.children().each(function(a,b,c) { + var $child = $(this), + colspan = parseInt($child.attr('colspan') || 1, 10), + tagName = $child[0].tagName.toLowerCase(); + if (tagName === 'newline') { + $tr = null; + return; + } + if (!$tr || row_cols < colspan) { + $tr = $('<tr/>').addClass('oe_form_group_row').appendTo($table); + row_cols = cols; + } + row_cols -= colspan; + + $td = $('<td/>').addClass('oe_form_group_cell').attr('colspan', colspan); + // invisibility transfer + var field_modifiers = JSON.parse($child.attr('modifiers') || '{}'); + var invisible = field_modifiers.invisible; + field_modifiers.invisible = undefined; + $child.attr('modifiers', JSON.stringify(field_modifiers)); + self.handle_common_properties($td, $("<dummy>").attr("modifiers", JSON.stringify({invisible: invisible}))); + + $tr.append($td.append($child)); + children.push($child[0]); + }); + if (row_cols && $td) { + $td.attr('colspan', parseInt($td.attr('colspan'), 10) + row_cols); + } + $group.before($new_group).remove(); + + // Now compute width of cells + $table.find('> tbody > tr').each(function() { + var to_compute = [], + row_cols = cols, + total = 100; + $(this).children().each(function() { + var $td = $(this), + $child = $td.children(':first'); + switch ($child[0].tagName.toLowerCase()) { + case 'separator': + if ($child.attr('orientation') === 'vertical') { + $td.addClass('oe_vertical_separator').attr('width', '1'); + $td.empty(); + row_cols--; + } + break; + case 'label': + if ($child.attr('for')) { + $td.attr('width', '1%'); + row_cols--; + total--; + } + break; + default: + to_compute.push($td); + } + }); + var unit = Math.floor(total / row_cols); + _.each(to_compute, function($td, i) { + var width = parseInt($td.attr('colspan'), 10) * unit; + $td.attr('width', ((i == to_compute.length - 1) ? total : width) + '%'); + total -= width; + }); + }); + _.each(children, function(el) { + self.process($(el)); + }); + this.handle_common_properties($new_group, $group); + return $new_group; + }, + process_notebook: function($notebook, layout) { + var self = this; + var pages = []; + $notebook.find('> page').each(function() { + var $page = $(this); + var page_attrs = $page.getAttributes(); + page_attrs.id = _.uniqueId('notebook_page_'); + pages.push(page_attrs); + var $new_page = self.render_element('FormRenderingNotebookPage', layout, page_attrs); + var $dst = (layout === 'auto') ? $new_page.find('group:first') : $new_page; + $page.children().appendTo($dst); + $page.before($new_page).remove(); + self.handle_common_properties($new_page, $page); + }); + var $new_notebook = this.render_element('FormRenderingNotebook', layout, { pages : pages }); + $notebook.children().appendTo($new_notebook); + $notebook.before($new_notebook).remove(); + $new_notebook.children().each(function() { + self.process($(this)); + }); + $new_notebook.tabs(); + this.handle_common_properties($new_notebook, $notebook); + return $new_notebook; + }, + process_separator: function($separator, layout) { + var $new_separator = this.render_element('FormRenderingSeparator', layout, $separator.getAttributes()); + $separator.before($new_separator).remove(); + this.handle_common_properties($new_separator, $separator); + return $new_separator; + }, + process_label: function($label, layout) { + var name = $label.attr("for"), + field_orm = this.fvg.fields[name]; + var dict = { + string: $label.attr('string') || (field_orm || {}).string || '', + help: $label.attr('help') || (field_orm || {}).help || '', + _for: name ? _.uniqueId('oe-field-input-') : undefined, + }; + var align = parseFloat(dict.align); + if (isNaN(align) || align === 1) { + align = 'right'; + } else if (align === 0) { + align = 'left'; + } else { + align = 'center'; + } + dict.align = align; + var $new_label = this.render_element('FormRenderingLabel', layout, dict); + $label.before($new_label).remove(); + this.handle_common_properties($new_label, $label); + if (name) { + this.labels[name] = $new_label; + } + return $new_label; + }, + handle_common_properties: function($new_element, $node) { + var str_modifiers = $node.attr("modifiers") || "{}" + var modifiers = JSON.parse(str_modifiers); + if (modifiers.invisible !== undefined) + new instance.web.form.InvisibilityChanger(this.view, this.view, modifiers.invisible, $new_element); + $new_element.addClass($node.attr("class") || ""); + }, +}); + +instance.web.form.FormRenderingEngineReadonly = instance.web.form.FormRenderingEngine.extend({ + alter_field: function(field) { + field.set({"force_readonly": true}); + }, +}); + +instance.web.form.FormDialog = instance.web.Dialog.extend({ init: function(parent, options, view_id, dataset) { this._super(parent, options); this.dataset = dataset; @@ -736,8 +1214,7 @@ openerp.web.FormDialog = openerp.web.Dialog.extend({ }, start: function() { this._super(); - this.form = new openerp.web.FormView(this, this.dataset, this.view_id, { - sidebar: false, + this.form = new instance.web.FormView(this, this.dataset, this.view_id, { pager: false }); this.form.appendTo(this.$element); @@ -758,62 +1235,7 @@ openerp.web.FormDialog = openerp.web.Dialog.extend({ } }); -/** @namespace */ -openerp.web.form = {}; - -openerp.web.form.SidebarAttachments = openerp.web.OldWidget.extend({ - init: function(parent, form_view) { - var $section = parent.add_section(_t('Attachments'), 'attachments'); - this.$div = $('<div class="oe-sidebar-attachments"></div>'); - $section.append(this.$div); - - this._super(parent, $section.attr('id')); - this.view = form_view; - }, - do_update: function() { - if (!this.view.datarecord.id) { - this.on_attachments_loaded([]); - } else { - (new openerp.web.DataSetSearch( - this, 'ir.attachment', this.view.dataset.get_context(), - [ - ['res_model', '=', this.view.dataset.model], - ['res_id', '=', this.view.datarecord.id], - ['type', 'in', ['binary', 'url']] - ])).read_slice(['name', 'url', 'type'], {}).then(this.on_attachments_loaded); - } - }, - on_attachments_loaded: function(attachments) { - this.attachments = attachments; - this.$div.html(QWeb.render('FormView.sidebar.attachments', this)); - this.$element.find('.oe-binary-file').change(this.on_attachment_changed); - this.$element.find('.oe-sidebar-attachment-delete').click(this.on_attachment_delete); - }, - on_attachment_changed: function(e) { - window[this.element_id + '_iframe'] = this.do_update; - var $e = $(e.target); - if ($e.val() != '') { - this.$element.find('form.oe-binary-form').submit(); - $e.parent().find('input[type=file]').prop('disabled', true); - $e.parent().find('button').prop('disabled', true).find('img, span').toggle(); - } - }, - on_attachment_delete: function(e) { - var self = this, $e = $(e.currentTarget); - var name = _.str.trim($e.parent().find('a.oe-sidebar-attachments-link').text()); - if (confirm(_.str.sprintf(_t("Do you really want to delete the attachment %s?"), name))) { - this.rpc('/web/dataset/unlink', { - model: 'ir.attachment', - ids: [parseInt($e.attr('data-id'))] - }, function(r) { - $e.parent().remove(); - self.do_notify("Delete an attachment", "The attachment '" + name + "' has been deleted"); - }); - } - } -}); - -openerp.web.form.compute_domain = function(expr, fields) { +instance.web.form.compute_domain = function(expr, fields) { var stack = []; for (var i = expr.length - 1; i >= 0; i--) { var ex = expr[i]; @@ -842,7 +1264,7 @@ openerp.web.form.compute_domain = function(expr, fields) { _t("Unknown field %s in domain %s"), ex[0], JSON.stringify(expr))); } - var field_value = field.get_value ? fields[ex[0]].get_value() : fields[ex[0]].value; + var field_value = field.get_value ? field.get_value() : field.value; var op = ex[1]; var val = ex[2]; @@ -884,60 +1306,84 @@ openerp.web.form.compute_domain = function(expr, fields) { return _.all(stack, _.identity); }; -openerp.web.form.Widget = openerp.web.OldWidget.extend(/** @lends openerp.web.form.Widget# */{ - form_template: 'Widget', +/** + * Must be applied over an class already possessing the GetterSetterMixin. + * + * Apply the result of the "invisible" domain to this.$element. + */ +instance.web.form.InvisibilityChangerMixin = { + init: function(field_manager, invisible_domain) { + this._ic_field_manager = field_manager + this._ic_invisible_modifier = invisible_domain; + this._ic_field_manager.on("view_content_has_changed", this, function() { + var result = this._ic_invisible_modifier === undefined ? false : + instance.web.form.compute_domain(this._ic_invisible_modifier, this._ic_field_manager.fields); + this.set({"invisible": result}); + }); + this.set({invisible: this._ic_invisible_modifier === true}); + }, + start: function() { + var check_visibility = function() { + if (this.get("invisible")) { + this.$element.hide(); + } else { + this.$element.show(); + } + }; + this.on("change:invisible", this, check_visibility); + _.bind(check_visibility, this)(); + }, +}; + +instance.web.form.InvisibilityChanger = instance.web.Class.extend(_.extend({}, instance.web.GetterSetterMixin, instance.web.form.InvisibilityChangerMixin, { + init: function(parent, field_manager, invisible_domain, $element) { + this.setParent(parent); + instance.web.GetterSetterMixin.init.call(this); + instance.web.form.InvisibilityChangerMixin.init.call(this, field_manager, invisible_domain); + this.$element = $element; + this.start(); + }, +})); + +instance.web.form.Widget = instance.web.Widget.extend(_.extend({}, instance.web.form.InvisibilityChangerMixin, { /** - * @constructs openerp.web.form.Widget - * @extends openerp.web.OldWidget + * @constructs instance.web.form.Widget + * @extends instance.web.Widget * * @param view * @param node */ init: function(view, node) { + this._super(view); this.view = view; this.node = node; this.modifiers = JSON.parse(this.node.attrs.modifiers || '{}'); - this.always_invisible = (this.modifiers.invisible && this.modifiers.invisible === true); - this.type = this.type || node.tag; - this.element_name = this.element_name || this.type; - - this._super(view); - - this.children = node.children; - this.colspan = parseInt(node.attrs.colspan || 1, 10); - this.decrease_max_width = 0; - - this.string = this.string || node.attrs.string; - this.help = this.help || node.attrs.help; - this.invisible = this.modifiers['invisible'] === true; - this.classname = 'oe_form_' + this.type; - - this.align = parseFloat(this.node.attrs.align); - if (isNaN(this.align) || this.align === 1) { - this.align = 'right'; - } else if (this.align === 0) { - this.align = 'left'; - } else { - this.align = 'center'; - } + instance.web.form.InvisibilityChangerMixin.init.call(this, view, this.modifiers.invisible); - this.width = this.node.attrs.width; + this.view.on("view_content_has_changed", this, this.process_modifiers); + }, + renderElement: function() { + this._super(); + this.$element.addClass(this.node.attrs["class"] || ""); + }, + start: function() { + this._super(); + instance.web.form.InvisibilityChangerMixin.start.call(this); }, destroy: function() { - this._super.apply(this, arguments); $.fn.tipsy.clear(); + this._super.apply(this, arguments); }, process_modifiers: function() { - var compute_domain = openerp.web.form.compute_domain; + var compute_domain = instance.web.form.compute_domain; + var to_set = {}; for (var a in this.modifiers) { - this[a] = compute_domain(this.modifiers[a], this.view.fields); + if (!_.include(["invisible"], a)) { + var val = compute_domain(this.modifiers[a], this.view.fields); + to_set[a] = val; + } } - }, - update_dom: function() { - this.$element.toggle(!this.invisible); - }, - renderElement: function() { - this.$element.html(QWeb.render(this.form_template, { "widget": this })); + this.set(to_set); }, do_attach_tooltip: function(widget, trigger, options) { widget = widget || this; @@ -947,12 +1393,12 @@ openerp.web.form.Widget = openerp.web.OldWidget.extend(/** @lends openerp.web.fo delayOut: 0, fade: true, title: function() { - var template = widget.form_template + '.tooltip'; + var template = widget.template + '.tooltip'; if (!QWeb.has_template(template)) { template = 'WidgetLabel.tooltip'; } return QWeb.render(template, { - debug: openerp.connection.debug, + debug: instance.connection.debug, widget: widget })}, gravity: $.fn.tipsy.autoBounds(50, 'nw'), @@ -979,7 +1425,7 @@ openerp.web.form.Widget = openerp.web.OldWidget.extend(/** @lends openerp.web.fo }, _build_eval_context: function(blacklist) { var a_dataset = this.view.dataset; - return new openerp.web.CompoundContext(a_dataset.get_context(), this._build_view_fields_values(blacklist)); + return new instance.web.CompoundContext(a_dataset.get_context(), this._build_view_fields_values(blacklist)); }, /** * Builds a new context usable for operations related to fields by merging @@ -994,7 +1440,7 @@ openerp.web.form.Widget = openerp.web.OldWidget.extend(/** @lends openerp.web.fo if (v_context.__ref || true) { //TODO: remove true var fields_values = this._build_eval_context(blacklist); - v_context = new openerp.web.CompoundContext(v_context).set_eval_context(fields_values); + v_context = new instance.web.CompoundContext(v_context).set_eval_context(fields_values); } return v_context; }, @@ -1005,235 +1451,28 @@ openerp.web.form.Widget = openerp.web.OldWidget.extend(/** @lends openerp.web.fo var final_domain = n_domain !== null ? n_domain : f_domain; if (!(final_domain instanceof Array) || true) { //TODO: remove true var fields_values = this._build_eval_context(); - final_domain = new openerp.web.CompoundDomain(final_domain).set_eval_context(fields_values); + final_domain = new instance.web.CompoundDomain(final_domain).set_eval_context(fields_values); } return final_domain; } -}); +})); -openerp.web.form.WidgetFrame = openerp.web.form.Widget.extend({ - form_template: 'WidgetFrame', - init: function(view, node) { - this._super(view, node); - this.columns = parseInt(node.attrs.col || 4, 10); - this.x = 0; - this.y = 0; - this.table = []; - this.add_row(); - for (var i = 0; i < node.children.length; i++) { - var n = node.children[i]; - if (n.tag == "newline") { - this.add_row(); - } else { - this.handle_node(n); - } - } - this.set_row_cells_with(this.table[this.table.length - 1]); - }, - renderElement: function() { - this._super(); - var self = this; - _.each(this.table, function(row) { - _.each(row, function(td) { - td.$element = self.$element.find('.' + td.element_class); - td.renderElement(); - }); - }); - }, - start: function() { - _.each(this.table, function(row) { - _.each(row, function(td) { - td.start(); - }); - }); - }, - add_row: function(){ - if (this.table.length) { - this.set_row_cells_with(this.table[this.table.length - 1]); - } - var row = []; - this.table.push(row); - this.x = 0; - this.y += 1; - return row; - }, - set_row_cells_with: function(row) { - var bypass = 0, - max_width = 100, - row_length = row.length; - for (var i = 0; i < row.length; i++) { - if (row[i].always_invisible) { - row_length--; - } else { - bypass += row[i].width === undefined ? 0 : 1; - max_width -= row[i].decrease_max_width; - } - } - var size_unit = Math.round(max_width / (this.columns - bypass)), - colspan_sum = 0; - for (var i = 0; i < row.length; i++) { - var w = row[i]; - if (w.always_invisible) { - continue; - } - colspan_sum += w.colspan; - if (w.width === undefined) { - var width = (i === row_length - 1 && colspan_sum === this.columns) ? max_width : Math.round(size_unit * w.colspan); - max_width -= width; - w.width = width + '%'; - } - } - }, - handle_node: function(node) { - var type = {}; - if (node.tag == 'field') { - type = this.view.fields_view.fields[node.attrs.name] || {}; - if (node.attrs.widget == 'statusbar' && node.attrs.nolabel !== '1') { - // This way we can retain backward compatibility between addons and old clients - node.attrs.colspan = (parseInt(node.attrs.colspan, 10) || 1) + 1; - node.attrs.nolabel = '1'; - } - } - var widget = instanciate_widget(this.view.registry.get_any( - [node.attrs.widget, type.type, node.tag]), this.view, node); - if (node.tag == 'field') { - if (!this.view.default_focus_field || node.attrs.default_focus == '1') { - this.view.default_focus_field = widget; - } - if (node.attrs.nolabel != '1') { - var label = instanciate_widget(this.view.registry.get_object('label'), this.view, node); - label["for"] = widget; - this.add_widget(label, widget.colspan + 1); - } - } - this.add_widget(widget); - }, - add_widget: function(widget, colspan) { - var current_row = this.table[this.table.length - 1]; - if (!widget.always_invisible) { - colspan = colspan || widget.colspan; - if (current_row.length && (this.x + colspan) > this.columns) { - current_row = this.add_row(); - } - this.x += widget.colspan; - } - current_row.push(widget); - return widget; - } -}); - -openerp.web.form.WidgetGroup = openerp.web.form.WidgetFrame.extend({ - form_template: 'WidgetGroup' -}), - -openerp.web.form.WidgetNotebook = openerp.web.form.Widget.extend({ - form_template: 'WidgetNotebook', - init: function(view, node) { - this._super(view, node); - this.pages = []; - for (var i = 0; i < node.children.length; i++) { - var n = node.children[i]; - if (n.tag == "page") { - var page = instanciate_widget(this.view.registry.get_object('notebookpage'), - this.view, n, this, this.pages.length); - this.pages.push(page); - } - } - }, - renderElement: function() { - this._super(); - var self = this; - _.each(this.pages, function(page) { - page.$element = self.$element.find('.' + page.element_class); - page.renderElement(); - }); - }, - start: function() { - var self = this; - this._super.apply(this, arguments); - this.$element.find('> ul > li').each(function (index, tab_li) { - var page = self.pages[index], - id = _.uniqueId(self.element_name + '-'); - page.element_id = id; - $(tab_li).find('a').attr('href', '#' + id); - }); - this.$element.find('> div').each(function (index, page) { - page.id = self.pages[index].element_id; - }); - this.$element.tabs(); - this.view.on_button_new.add_first(this.do_select_first_visible_tab); - if (openerp.connection.debug) { - this.do_attach_tooltip(this, this.$element.find('ul:first'), { - gravity: 's' - }); - } - _.each(this.pages, function(page) { - page.start(); - }); - }, - do_select_first_visible_tab: function() { - for (var i = 0; i < this.pages.length; i++) { - var page = this.pages[i]; - if (page.invisible === false) { - this.$element.tabs('select', page.index); - break; - } - } - } -}); - -openerp.web.form.WidgetNotebookPage = openerp.web.form.WidgetFrame.extend({ - form_template: 'WidgetNotebookPage', - init: function(view, node, notebook, index) { - this.notebook = notebook; - this.index = index; - this.element_name = 'page_' + index; - this._super(view, node); - }, - start: function() { - this._super.apply(this, arguments); - this.$element_tab = this.notebook.$element.find( - '> ul > li:eq(' + this.index + ')'); - }, - update_dom: function() { - if (this.invisible && this.index === this.notebook.$element.tabs('option', 'selected')) { - this.notebook.do_select_first_visible_tab(); - } - this.$element_tab.toggle(!this.invisible); - this.$element.toggle(!this.invisible); - } -}); - -openerp.web.form.WidgetSeparator = openerp.web.form.Widget.extend({ - form_template: 'WidgetSeparator', - init: function(view, node) { - this._super(view, node); - this.orientation = node.attrs.orientation || 'horizontal'; - if (this.orientation === 'vertical') { - this.width = '1'; - } - this.classname += '_' + this.orientation; - } -}); - -openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({ - form_template: 'WidgetButton', +instance.web.form.WidgetButton = instance.web.form.Widget.extend({ + template: 'WidgetButton', init: function(view, node) { this._super(view, node); this.force_disabled = false; - if (this.string) { - // We don't have button key bindings in the webclient - this.string = this.string.replace(/_/g, ''); - } - if (node.attrs.default_focus == '1') { + this.string = (this.node.attrs.string || '').replace(/_/g, ''); + if (this.node.attrs.default_focus == '1') { // TODO fme: provide enter key binding to widgets this.view.default_focus_button = this; } + this.view.on('view_content_has_changed', this, this.check_disable); }, start: function() { this._super.apply(this, arguments); - this.$element.find("button").click(this.on_click); - if (this.help || openerp.connection.debug) { + this.$element.click(this.on_click); + if (this.node.attrs.help || instance.connection.debug) { this.do_attach_tooltip(); } }, @@ -1251,7 +1490,7 @@ openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({ var exec_action = function() { if (self.node.attrs.confirm) { var def = $.Deferred(); - var dialog = openerp.web.dialog($('<div>' + self.node.attrs.confirm + '</div>'), { + var dialog = instance.web.dialog($('<div/>').text(self.node.attrs.confirm), { title: _t('Confirm'), modal: true, buttons: [ @@ -1275,6 +1514,7 @@ openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({ } }; if (!this.node.attrs.special) { + this.view.force_dirty = true; return this.view.recursive_save().pipe(exec_action); } else { return exec_action(); @@ -1285,7 +1525,7 @@ openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({ var context = this.node.attrs.context; if (context && context.__ref) { - context = new openerp.web.CompoundContext(context); + context = new instance.web.CompoundContext(context); context.set_eval_context(this._build_eval_context()); } @@ -1295,81 +1535,57 @@ openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({ self.view.reload(); }); }, - update_dom: function() { - this._super.apply(this, arguments); - this.check_disable(); - }, check_disable: function() { - var disabled = (this.readonly || this.force_disabled || !this.view.is_interactible_record()); - this.$element.find('button').prop('disabled', disabled); - this.$element.find("button").css('color', disabled ? 'grey' : ''); + var disabled = (this.force_disabled || !this.view.is_interactible_record()); + this.$element.prop('disabled', disabled); + this.$element.css('color', disabled ? 'grey' : ''); } }); -openerp.web.form.WidgetLabel = openerp.web.form.Widget.extend({ - form_template: 'WidgetLabel', - init: function(view, node) { - this.element_name = 'label_' + node.attrs.name; - - this._super(view, node); - - if (this.node.tag == 'label' && !this.string && this.node.children.length) { - this.string = this.node.children[0]; - this.align = 'left'; - } - - if (this.node.tag == 'label' && (this.align === 'left' || this.node.attrs.colspan || (this.string && this.string.length > 32))) { - this.form_template = "WidgetParagraph"; - this.colspan = parseInt(this.node.attrs.colspan || 1, 10); - // Widgets default to right-aligned, but paragraph defaults to - // left-aligned - if (isNaN(parseFloat(this.node.attrs.align))) { - this.align = 'left'; - } - - this.multilines = this.string && _.str.lines(this.string).length > 1; - } else { - this.colspan = 1; - this.width = '1%'; - this.decrease_max_width = 1; - this.nowrap = true; - } - }, - renderElement: function() { - var rendered; - if (this['for'] && this.type !== 'label') { - rendered = QWeb.render(this.form_template, {widget: this['for']}); - } else { - // Actual label widgets should not have a false and have type label - rendered = QWeb.render(this.form_template, {widget: this}); - } - this.$element.html(rendered); - }, - start: function() { - this._super(); - var self = this; - if (this['for'] && (this['for'].help || openerp.connection.debug)) { - this.do_attach_tooltip(self['for']); - } - this.$element.find("label").dblclick(function() { - var widget = self['for'] || self; - openerp.log(widget.element_class , widget); - window.w = widget; - }); - } -}); +/** + * Interface implemented by the form view or any other object + * able to provide the features necessary for the fields to work. + * + * Properties: + * - display_invalid_fields : if true, all fields where is_valid() return true should + * be displayed as invalid. + * Events: + * - view_content_has_changed : when the values of the fields have changed. When + * this event is triggered all fields should reprocess their modifiers. + */ +instance.web.form.FieldManagerInterface = { + /** + * Must return the asked field as in fields_get. + */ + get_field: function(field_name) {}, + /** + * Called by the field when the translate button is clicked. + */ + open_translate_dialog: function(field) {}, + /** + * Returns true when the view is in create mode. + */ + is_create_mode: function() {}, +}; /** * Interface to be implemented by fields. * - * Novajs Attributes: - * - ... - * - * Novajs Events: - * - ... + * Properties: + * - readonly: boolean. If set to true the field should appear in readonly mode. + * - force_readonly: boolean, When it is true, the field should always appear + * in read only mode, no matter what the value of the "readonly" property can be. + * Events: + * - changed_value: triggered to inform the view to check on_changes * */ -openerp.web.form.FieldInterface = { +instance.web.form.FieldInterface = { + /** + * Constructor takes 2 arguments: + * - field_manager: Implements FieldManagerInterface + * - node: the "<field>" node in json form + */ + init: function(field_manager, node) {}, /** * Called by the form view to indicate the value of the field. * @@ -1386,7 +1602,7 @@ openerp.web.form.FieldInterface = { * as well as any format commonly returned in a on_change. It must be able to autodetect those formats as * no information is ever given to know which format is used. */ - set_value: function(value) {}, + set_value: function(value_) {}, /** * Get the current value of the widget. * @@ -1405,221 +1621,307 @@ openerp.web.form.FieldInterface = { * syntax for "read" and "write" (example: m2o: set_value([0, "Administrator"]), get_value() => 0). */ get_value: function() {}, + /** + * Inform the current object of the id it should use to match a html <label> that exists somewhere in the + * view. + */ + set_input_id: function(id) {}, + /** + * Returns true if is_syntax_valid() returns true and the value is semantically + * valid too according to the semantic restrictions applied to the field. + */ + is_valid: function() {}, + /** + * Returns true if the field holds a value which is syntaxically correct, ignoring + * the potential semantic restrictions applied to the field. + */ + is_syntax_valid: function() {}, + /** + * Must set the focus on the field. + */ + focus: function() {}, }; /** - * Abstract class for classes implementing FieldInterface. Should be renamed to AbstractField some - * day. + * Abstract class for classes implementing FieldInterface. + * + * Properties: + * - effective_readonly: when it is true, the widget is displayed as readonly. Vary depending + * the values of the "readonly" property and the "force_readonly" property on the field manager. + * - value: useful property to hold the value of the field. By default, set_value() and get_value() + * set and retrieve the value property. Changing the value property also triggers automatically + * a 'changed_value' event that inform the view to trigger on_changes. + * */ -openerp.web.form.Field = openerp.web.form.Widget.extend(/** @lends openerp.web.form.Field# */{ +instance.web.form.AbstractField = instance.web.form.Widget.extend(/** @lends instance.web.form.AbstractField# */{ /** - * @constructs openerp.web.form.Field - * @extends openerp.web.form.Widget + * @constructs instance.web.form.AbstractField + * @extends instance.web.form.Widget * - * @param view + * @param field_manager * @param node */ - init: function(view, node) { - this.name = node.attrs.name; - this.value = undefined; - view.fields[this.name] = this; - view.fields_order.push(this.name); - this.type = node.attrs.widget || view.fields_view.fields[node.attrs.name].type; - this.element_name = "field_" + this.name + "_" + this.type; - - this._super(view, node); - - if (node.attrs.nolabel != '1' && this.colspan > 1) { - this.colspan--; - } - this.field = view.fields_view.fields[node.attrs.name] || {}; - this.string = node.attrs.string || this.field.string; - this.help = node.attrs.help || this.field.help; - this.nolabel = (this.field.nolabel || node.attrs.nolabel) === '1'; - this.readonly = this.modifiers['readonly'] === true; - this.required = this.modifiers['required'] === true; - this.invalid = this.dirty = false; - - this.classname = 'oe_form_field_' + this.type; + init: function(field_manager, node) { + this._super(field_manager, node); + this.field_manager = field_manager; + this.name = this.node.attrs.name; + this.set({'value': false}); + this.field = this.field_manager.get_field(this.name); + this.set({required: this.modifiers['required'] === true}); + + // some events to make the property "effective_readonly" sync automatically with "readonly" and + // "force_readonly" + this.set({"readonly": this.modifiers['readonly'] === true}); + var test_effective_readonly = function() { + this.set({"effective_readonly": this.get("readonly") || !!this.get("force_readonly")}); + }; + this.on("change:readonly", this, test_effective_readonly); + this.on("change:force_readonly", this, test_effective_readonly); + _.bind(test_effective_readonly, this)(); + + this.on("change:value", this, function() { + if (! this._inhibit_on_change) + this.trigger('changed_value'); + this._check_css_flags(); + }); }, start: function() { this._super.apply(this, arguments); if (this.field.translate) { - this.view.translatable_fields.push(this); this.$element.addClass('oe_form_field_translatable'); - this.$element.find('.oe_field_translate').click(this.on_translate); + this.$element.find('.oe_field_translate').click(_.bind(function() { + this.field_manager.open_translate_dialog(this); + }, this)); } - if (this.nolabel && openerp.connection.debug) { + if (instance.connection.debug) { this.do_attach_tooltip(this, this.$element); } + if (!this.disable_utility_classes) { + var set_required = function() { + this.$element.toggleClass('oe_form_required', this.get("required")); + }; + this.on("change:required", this, set_required); + _.bind(set_required, this)(); + } }, - set_value: function(value) { - this.value = value; - this.invalid = false; - this.update_dom(); - this.on_value_changed(); - }, - set_value_from_ui: function() { - this.on_value_changed(); - }, - on_value_changed: function() { - }, - on_translate: function() { - this.view.open_translate_dialog(this); + set_value: function(value_) { + this._inhibit_on_change = true; + this.set({'value': value_}); + this._inhibit_on_change = false; }, get_value: function() { - return this.value; + return this.get('value'); }, is_valid: function() { - return !this.invalid; + return this.is_syntax_valid() && (! this.get('required') || ! this.is_false()); }, - is_dirty: function() { - return this.dirty && !this.readonly; + is_syntax_valid: function() { + return true; }, - get_on_change_value: function() { - return this.get_value(); + /** + * Method useful to implement to ease validity testing. Must return true if the current + * value is similar to false in OpenERP. + */ + is_false: function() { + return this.get('value') === false; }, - update_dom: function(show_invalid) { - this._super.apply(this, arguments); + _check_css_flags: function(show_invalid) { if (this.field.translate) { - this.$element.find('.oe_field_translate').toggle(!!this.view.datarecord.id); + this.$element.find('.oe_field_translate').toggle(!this.field_manager.is_create_mode()); } if (!this.disable_utility_classes) { - this.$element.toggleClass('disabled', this.readonly); - this.$element.toggleClass('required', this.required); - if (show_invalid) { - this.$element.toggleClass('invalid', !this.is_valid()); + if (this.field_manager.get('display_invalid_fields')) { + this.$element.toggleClass('oe_form_invalid', !this.is_valid()); } } }, - on_ui_change: function() { - this.dirty = true; - this.validate(); - if (this.is_valid()) { - this.set_value_from_ui(); - this.view.do_onchange(this); - this.view.on_form_changed(true); - this.view.do_notify_change(); - } else { - this.update_dom(true); - } - }, - validate: function() { - this.invalid = false; + focus: function() { }, - focus: function($element) { - if ($element) { - setTimeout(function() { - $element.focus(); - }, 50); - } - }, - reset: function() { - this.dirty = false; + /** + * Utility method to focus an element, but only after a small amount of time. + */ + delay_focus: function($elem) { + setTimeout(function() { + $elem.focus(); + }, 50); }, + /** + * Utility method to get the widget options defined in the field xml description. + */ get_definition_options: function() { if (!this.definition_options) { var str = this.node.attrs.options || '{}'; this.definition_options = JSON.parse(str); } return this.definition_options; - } + }, + set_input_id: function(id) { + this.id_for_label = id; + }, }); -openerp.web.form.FieldChar = openerp.web.form.Field.extend({ - form_template: 'FieldChar', - init: function (view, node) { - this._super(view, node); - this.password = this.node.attrs.password === 'True' || this.node.attrs.password === '1'; - }, +/** + * A mixin to apply on any field that has to completely re-render when its readonly state + * switch. + */ +instance.web.form.ReinitializeFieldMixin = { + /** + * Default implementation of start(), use it or call explicitly initialize_field(). + */ start: function() { - this._super.apply(this, arguments); - this.$element.find('input').change(this.on_ui_change); + this._super(); + this.initialize_field(); }, - set_value: function(value) { - this._super.apply(this, arguments); - var show_value = openerp.web.format_value(value, this, ''); - this.$element.find('input').val(show_value); - return show_value; + initialize_field: function() { + this.on("change:effective_readonly", this, function() { + this.destroy_content(); + this.renderElement(); + this.initialize_content(); + this.render_value(); + }); + this.initialize_content(); + this.render_value(); }, - update_dom: function() { - this._super.apply(this, arguments); - this.$element.find('input').prop('readonly', this.readonly); + /** + * Called to destroy anything that could have been created previously, called before a + * re-initialization. + */ + destroy_content: function() {}, + /** + * Called to initialize the content. + */ + initialize_content: function() {}, + /** + * Called to render the value. Should also be explicitly called at the end of a set_value(). + */ + render_value: function() {}, +}; + +instance.web.form.FieldChar = instance.web.form.AbstractField.extend(_.extend({}, instance.web.form.ReinitializeFieldMixin, { + template: 'FieldChar', + init: function (field_manager, node) { + this._super(field_manager, node); + this.password = this.node.attrs.password === 'True' || this.node.attrs.password === '1'; }, - set_value_from_ui: function() { - this.value = openerp.web.parse_value(this.$element.find('input').val(), this); - this._super(); + initialize_content: function() { + var self = this; + this.$element.find('input').change(function() { + self.set({'value': instance.web.parse_value(self.$element.find('input').val(), self)}); + }); }, - validate: function() { - this.invalid = false; - try { - var value = openerp.web.parse_value(this.$element.find('input').val(), this, ''); - this.invalid = this.required && value === ''; - } catch(e) { - this.invalid = true; + 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")) { + this.$element.find('input').val(show_value); + } else { + if (this.password) { + show_value = new Array(show_value.length + 1).join('*'); + } + this.$element.text(show_value); } }, - focus: function($element) { - this._super($element || this.$element.find('input:first')); + is_syntax_valid: function() { + if (!this.get("effective_readonly")) { + try { + var value_ = instance.web.parse_value(this.$element.find('input').val(), this, ''); + return true; + } catch(e) { + return false; + } + } + return true; + }, + is_false: function() { + return this.get('value') === ''; + }, + focus: function() { + this.delay_focus(this.$element.find('input:first')); } -}); +})); -openerp.web.form.FieldID = openerp.web.form.FieldChar.extend({ - update_dom: function() { - this._super.apply(this, arguments); - this.$element.find('input').prop('readonly', true); - } +instance.web.form.FieldID = instance.web.form.FieldChar.extend({ + }); -openerp.web.form.FieldEmail = openerp.web.form.FieldChar.extend({ - form_template: 'FieldEmail', - start: function() { - this._super.apply(this, arguments); +instance.web.form.FieldEmail = instance.web.form.FieldChar.extend({ + template: 'FieldEmail', + initialize_content: function() { + this._super(); this.$element.find('button').click(this.on_button_clicked); }, + render_value: function() { + if (!this.get("effective_readonly")) { + this._super(); + } else { + this.$element.find('a') + .attr('href', 'mailto:' + this.get('value')) + .text(this.get('value')); + } + }, on_button_clicked: function() { - if (!this.value || !this.is_valid()) { + if (!this.get('value') || !this.is_syntax_valid()) { this.do_warn("E-mail error", "Can't send email to invalid e-mail address"); } else { - location.href = 'mailto:' + this.value; + location.href = 'mailto:' + this.get('value'); } } }); -openerp.web.form.FieldUrl = openerp.web.form.FieldChar.extend({ - form_template: 'FieldUrl', - start: function() { - this._super.apply(this, arguments); +instance.web.form.FieldUrl = instance.web.form.FieldChar.extend({ + template: 'FieldUrl', + initialize_content: function() { + this._super(); this.$element.find('button').click(this.on_button_clicked); }, + render_value: function() { + if (!this.get("effective_readonly")) { + this._super(); + } else { + var tmp = this.get('value'); + var s = /(\w+):(.+)/.exec(tmp); + if (!s) { + tmp = "http://" + this.get('value'); + } + this.$element.find('a').attr('href', tmp).text(tmp); + } + }, on_button_clicked: function() { - if (!this.value) { + if (!this.get('value')) { this.do_warn("Resource error", "This resource is empty"); } else { - window.open(this.value); + var url = $.trim(this.get('value')); + if(/^www\./i.test(url)) + url = 'http://'+url; + window.open(url); } } }); -openerp.web.form.FieldFloat = openerp.web.form.FieldChar.extend({ - init: function (view, node) { - this._super(view, node); - if (node.attrs.digits) { +instance.web.form.FieldFloat = instance.web.form.FieldChar.extend({ + is_field_number: true, + init: function (field_manager, node) { + this._super(field_manager, node); + this.set({'value': 0}); + if (this.node.attrs.digits) { this.digits = py.eval(node.attrs.digits); } else { - this.digits = view.fields_view.fields[node.attrs.name].digits; + this.digits = this.field.digits; } }, - set_value: function(value) { - if (value === false || value === undefined) { + set_value: function(value_) { + if (value_ === false || value_ === undefined) { // As in GTK client, floats default to 0 - value = 0; + value_ = 0; } - this._super.apply(this, [value]); + this._super.apply(this, [value_]); } }); -openerp.web.DateTimeWidget = openerp.web.OldWidget.extend({ +instance.web.DateTimeWidget = instance.web.OldWidget.extend({ template: "web.datetimepicker", jqueryui_object: 'datetimepicker', type_of_date: "datetime", @@ -1640,46 +1942,46 @@ openerp.web.DateTimeWidget = openerp.web.OldWidget.extend({ showButtonPanel: true }); this.$element.find('img.oe_datepicker_trigger').click(function() { - if (!self.readonly && !self.picker('widget').is(':visible')) { - self.picker('setDate', self.value ? openerp.web.auto_str_to_date(self.value) : new Date()); + if (!self.get("effective_readonly") && !self.picker('widget').is(':visible')) { + self.picker('setDate', self.get('value') ? instance.web.auto_str_to_date(self.get('value')) : new Date()); self.$input_picker.show(); self.picker('show'); self.$input_picker.hide(); } }); this.set_readonly(false); - this.value = false; + this.set({'value': false}); }, picker: function() { return $.fn[this.jqueryui_object].apply(this.$input_picker, arguments); }, - on_picker_select: function(text, instance) { + on_picker_select: function(text, instance_) { var date = this.picker('getDate'); this.$input.val(date ? this.format_client(date) : '').change(); }, - set_value: function(value) { - this.value = value; - this.$input.val(value ? this.format_client(value) : ''); + set_value: function(value_) { + this.set({'value': value_}); + this.$input.val(value_ ? this.format_client(value_) : ''); }, get_value: function() { - return this.value; + return this.get('value'); }, - set_value_from_ui: function() { - var value = this.$input.val() || false; - this.value = this.parse_client(value); + set_value_from_ui_: function() { + var value_ = this.$input.val() || false; + this.set({'value': this.parse_client(value_)}); }, set_readonly: function(readonly) { this.readonly = readonly; this.$input.prop('readonly', this.readonly); this.$element.find('img.oe_datepicker_trigger').toggleClass('oe_input_icon_disabled', readonly); }, - is_valid: function(required) { - var value = this.$input.val(); - if (value === "") { - return !required; + is_valid_: function() { + var value_ = this.$input.val(); + if (value_ === "") { + return true; } else { try { - this.parse_client(value); + this.parse_client(value_); return true; } catch(e) { return false; @@ -1687,99 +1989,120 @@ openerp.web.DateTimeWidget = openerp.web.OldWidget.extend({ } }, parse_client: function(v) { - return openerp.web.parse_value(v, {"widget": this.type_of_date}); + return instance.web.parse_value(v, {"widget": this.type_of_date}); }, format_client: function(v) { - return openerp.web.format_value(v, {"widget": this.type_of_date}); + return instance.web.format_value(v, {"widget": this.type_of_date}); }, on_change: function() { - if (this.is_valid()) { - this.set_value_from_ui(); + if (this.is_valid_()) { + this.set_value_from_ui_(); } } }); -openerp.web.DateWidget = openerp.web.DateTimeWidget.extend({ +instance.web.DateWidget = instance.web.DateTimeWidget.extend({ jqueryui_object: 'datepicker', type_of_date: "date" }); -openerp.web.form.FieldDatetime = openerp.web.form.Field.extend({ - form_template: "EmptyComponent", +instance.web.form.FieldDatetime = instance.web.form.AbstractField.extend(_.extend({}, instance.web.form.ReinitializeFieldMixin, { + template: "EmptyComponent", build_widget: function() { - return new openerp.web.DateTimeWidget(this); + return new instance.web.DateTimeWidget(this); }, - start: function() { - var self = this; - this._super.apply(this, arguments); - this.datewidget = this.build_widget(); - this.datewidget.on_change.add_last(this.on_ui_change); - this.datewidget.appendTo(this.$element); + destroy_content: function() { + if (this.datewidget) { + this.datewidget.destroy(); + this.datewidget = undefined; + } + }, + initialize_content: function() { + if (!this.get("effective_readonly")) { + this.datewidget = this.build_widget(); + this.datewidget.on_change.add_last(_.bind(function() { + this.set({'value': this.datewidget.get_value()}); + }, this)); + this.datewidget.appendTo(this.$element); + } }, - set_value: function(value) { - this._super(value); - this.datewidget.set_value(value); + set_value: function(value_) { + this._super(value_); + this.render_value(); }, - get_value: function() { - return this.datewidget.get_value(); + render_value: function() { + if (!this.get("effective_readonly")) { + this.datewidget.set_value(this.get('value')); + } else { + this.$element.text(instance.web.format_value(this.get('value'), this, '')); + } }, - update_dom: function() { - this._super.apply(this, arguments); - this.datewidget.set_readonly(this.readonly); + is_syntax_valid: function() { + if (!this.get("effective_readonly")) { + return this.datewidget.is_valid_(); + } + return true; }, - validate: function() { - this.invalid = !this.datewidget.is_valid(this.required); + is_false: function() { + return this.get('value') === ''; }, - focus: function($element) { - this._super($element || this.datewidget.$input); + focus: function() { + if (this.datewidget && this.datewidget.$input) + this.delay_focus(this.datewidget.$input); } -}); +})); -openerp.web.form.FieldDate = openerp.web.form.FieldDatetime.extend({ +instance.web.form.FieldDate = instance.web.form.FieldDatetime.extend({ build_widget: function() { - return new openerp.web.DateWidget(this); + return new instance.web.DateWidget(this); } }); -openerp.web.form.FieldText = openerp.web.form.Field.extend({ - form_template: 'FieldText', - start: function() { - this._super.apply(this, arguments); - this.$element.find('textarea').change(this.on_ui_change); +instance.web.form.FieldText = instance.web.form.AbstractField.extend(_.extend({}, instance.web.form.ReinitializeFieldMixin, { + template: 'FieldText', + initialize_content: function() { + this.$textarea = this.$element.find('textarea'); this.resized = false; + if (!this.get("effective_readonly")) { + this.$textarea.change(_.bind(function() { + this.set({'value': instance.web.parse_value(this.$textarea.val(), this)}); + }, this)); + } else { + this.$textarea.attr('disabled', 'disabled'); + } }, - set_value: function(value) { + set_value: function(value_) { this._super.apply(this, arguments); - var show_value = openerp.web.format_value(value, this, ''); - this.$element.find('textarea').val(show_value); + this.render_value(); + }, + render_value: function() { + var show_value = instance.web.format_value(this.get('value'), this, ''); + this.$textarea.val(show_value); if (!this.resized && this.view.options.resize_textareas) { this.do_resize(this.view.options.resize_textareas); this.resized = true; } }, - update_dom: function() { - this._super.apply(this, arguments); - this.$element.find('textarea').prop('readonly', this.readonly); - }, - set_value_from_ui: function() { - this.value = openerp.web.parse_value(this.$element.find('textarea').val(), this); - this._super(); - }, - validate: function() { - this.invalid = false; - try { - var value = openerp.web.parse_value(this.$element.find('textarea').val(), this, ''); - this.invalid = this.required && value === ''; - } catch(e) { - this.invalid = true; + is_syntax_valid: function() { + if (!this.get("effective_readonly")) { + try { + var value_ = instance.web.parse_value(this.$textarea.val(), this, ''); + return true; + } catch(e) { + return false; + } } + return true; + }, + is_false: function() { + return this.get('value') === ''; }, focus: function($element) { - this._super($element || this.$element.find('textarea:first')); + this.delay_focus(this.$textarea); }, do_resize: function(max_height) { max_height = parseInt(max_height, 10); - var $input = this.$element.find('textarea'), + var $input = this.$textarea, $div = $('<div style="position: absolute; z-index: 1000; top: 0"/>').width($input.width()), new_height; $div.text($input.val()); @@ -1797,64 +2120,60 @@ openerp.web.form.FieldText = openerp.web.form.Field.extend({ $div.remove(); $input.height(new_height); }, - reset: function() { - this.resized = false; - } -}); +})); -openerp.web.form.FieldBoolean = openerp.web.form.Field.extend({ - form_template: 'FieldBoolean', +instance.web.form.FieldBoolean = instance.web.form.AbstractField.extend({ + template: 'FieldBoolean', start: function() { - var self = this; this._super.apply(this, arguments); - this.$element.find('input').click(self.on_ui_change); - }, - set_value: function(value) { - this._super.apply(this, arguments); - this.$element.find('input')[0].checked = value; - }, - set_value_from_ui: function() { - this.value = this.$element.find('input').is(':checked'); - this._super(); + this.$checkbox = $("input", this.$element); + this.$element.click(_.bind(function() { + this.set({'value': this.$checkbox.is(':checked')}); + }, this)); + var check_readonly = function() { + this.$checkbox.prop('disabled', this.get("effective_readonly")); + }; + this.on("change:effective_readonly", this, check_readonly); + _.bind(check_readonly, this)(); }, - update_dom: function() { + set_value: function(value_) { this._super.apply(this, arguments); - this.$element.find('input').prop('disabled', this.readonly); + this.$checkbox[0].checked = value_; }, - focus: function($element) { - this._super($element || this.$element.find('input:first')); + focus: function() { + this.delay_focus(this.$checkbox); } }); -openerp.web.form.FieldProgressBar = openerp.web.form.Field.extend({ - form_template: 'FieldProgressBar', +instance.web.form.FieldProgressBar = instance.web.form.AbstractField.extend({ + template: 'FieldProgressBar', start: function() { this._super.apply(this, arguments); - this.$element.find('div').progressbar({ - value: this.value, - disabled: this.readonly + this.$element.progressbar({ + value: this.get('value'), + disabled: this.get("effective_readonly") }); }, - set_value: function(value) { + set_value: function(value_) { this._super.apply(this, arguments); - var show_value = Number(value); + var show_value = Number(value_); if (isNaN(show_value)) { show_value = 0; } - var formatted_value = openerp.web.format_value(show_value, { type : 'float' }, '0'); - this.$element.find('div').progressbar('option', 'value', show_value).find('span').html(formatted_value + '%'); + var formatted_value = instance.web.format_value(show_value, { type : 'float' }, '0'); + this.$element.progressbar('option', 'value', show_value).find('span').html(formatted_value + '%'); } }); -openerp.web.form.FieldTextXml = openerp.web.form.Field.extend({ +instance.web.form.FieldTextXml = instance.web.form.AbstractField.extend({ // to replace view editor }); -openerp.web.form.FieldSelection = openerp.web.form.Field.extend({ - form_template: 'FieldSelection', - init: function(view, node) { +instance.web.form.FieldSelection = instance.web.form.AbstractField.extend(_.extend({}, instance.web.form.ReinitializeFieldMixin, { + template: 'FieldSelection', + init: function(field_manager, node) { var self = this; - this._super(view, node); + this._super(field_manager, node); this.values = _.clone(this.field.selection); _.each(this.values, function(v, i) { if (v[0] === false && v[1] === '') { @@ -1863,7 +2182,7 @@ openerp.web.form.FieldSelection = openerp.web.form.Field.extend({ }); this.values.unshift([false, '']); }, - start: function() { + initialize_content: function() { // Flag indicating whether we're in an event chain containing a change // event on the select, in order to know what to do on keyup[RETURN]: // * If the user presses [RETURN] as part of changing the value of a @@ -1876,9 +2195,10 @@ openerp.web.form.FieldSelection = openerp.web.form.Field.extend({ // changing the selected value), takes the action as validating the // row var ischanging = false; - this._super.apply(this, arguments); this.$element.find('select') - .change(this.on_ui_change) + .change(_.bind(function() { + this.set({'value': this.values[this.$element.find('select')[0].selectedIndex][0]}); + }, this)) .change(function () { ischanging = true; }) .click(function () { ischanging = false; }) .keyup(function (e) { @@ -1887,32 +2207,37 @@ openerp.web.form.FieldSelection = openerp.web.form.Field.extend({ ischanging = false; }); }, - set_value: function(value) { - value = value === null ? false : value; - value = value instanceof Array ? value[0] : value; - this._super(value); - var index = 0; - for (var i = 0, ii = this.values.length; i < ii; i++) { - if (this.values[i][0] === value) index = i; - } - this.$element.find('select')[0].selectedIndex = index; - }, - set_value_from_ui: function() { - this.value = this.values[this.$element.find('select')[0].selectedIndex][0]; - this._super(); + set_value: function(value_) { + value_ = value_ === null ? false : value_; + value_ = value_ instanceof Array ? value_[0] : value_; + this._super(value_); + this.render_value(); }, - update_dom: function() { - this._super.apply(this, arguments); - this.$element.find('select').prop('disabled', this.readonly); + render_value: function() { + if (!this.get("effective_readonly")) { + var index = 0; + for (var i = 0, ii = this.values.length; i < ii; i++) { + if (this.values[i][0] === this.get('value')) index = i; + } + this.$element.find('select')[0].selectedIndex = index; + } else { + var self = this; + var option = _(this.values) + .detect(function (record) { return record[0] === self.get('value'); }); + this.$element.text(option ? option[1] : this.values[0][1]); + } }, - validate: function() { - var value = this.values[this.$element.find('select')[0].selectedIndex]; - this.invalid = !(value && !(this.required && value[0] === false)); + is_syntax_valid: function() { + if (this.get("effective_readonly")) { + return true; + } + var value_ = this.values[this.$element.find('select')[0].selectedIndex]; + return !! value_; }, - focus: function($element) { - this._super($element || this.$element.find('select:first')); + focus: function() { + this.delay_focus(this.$element.find('select:first')); } -}); +})); // jquery autocomplete tweak to allow html (function() { @@ -1921,8 +2246,8 @@ openerp.web.form.FieldSelection = openerp.web.form.Field.extend({ function filter( array, term ) { var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" ); - return $.grep( array, function(value) { - return matcher.test( $( "<div>" ).html( value.label || value.value || value ).text() ); + return $.grep( array, function(value_) { + return matcher.test( $( "<div>" ).html( value_.label || value_.value || value_ ).text() ); }); } @@ -1946,114 +2271,88 @@ openerp.web.form.FieldSelection = openerp.web.form.Field.extend({ }); })(); -openerp.web.form.dialog = function(content, options) { +instance.web.form.dialog = function(content, options) { options = _.extend({ width: '90%', height: 'auto', min_width: '800px' }, options || {}); - var dialog = new openerp.web.Dialog(null, options, content).open(); + var dialog = new instance.web.Dialog(null, options, content).open(); return dialog.$element; }; -openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({ - form_template: 'FieldMany2One', - init: function(view, node) { - this._super(view, node); +instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(_.extend({}, instance.web.form.ReinitializeFieldMixin, { + template: "FieldMany2One", + init: function(field_manager, node) { + this._super(field_manager, node); this.limit = 7; - this.value = null; - this.cm_id = _.uniqueId('m2o_cm_'); + this.set({'value': false}); + this.display_value = {}; this.last_search = []; - this.tmp_value = undefined; + this.floating = false; + this.inhibit_on_change = false; this.orderer = new openerp.web.DropMisordered(); }, start: function() { this._super(); + instance.web.form.ReinitializeFieldMixin.start.call(this); + this.on("change:value", this, function() { + this.floating = false; + this.render_value(); + }); + }, + initialize_content: function() { + if (!this.get("effective_readonly")) + this.render_editable(); + this.render_value(); + }, + render_editable: function() { var self = this; this.$input = this.$element.find("input"); + + self.$input.tipsy({ + title: function() { + return "No element was selected, you should create or select one from the dropdown list."; + }, + trigger:'manual', + fade: true, + }); + this.$drop_down = this.$element.find(".oe-m2o-drop-down-button"); - this.$menu_btn = this.$element.find(".oe-m2o-cm-button"); - - // context menu - var init_context_menu_def = $.Deferred().then(function(e) { - var rdataset = new openerp.web.DataSet(self, "ir.values", self.build_context()); - rdataset.call("get", ['action', 'client_action_relate', - [[self.field.relation, false]], false, rdataset.get_context()], false, 0) - .then(function(result) { - self.related_entries = result; - - var $cmenu = $("#" + self.cm_id); - $cmenu.append(QWeb.render("FieldMany2One.context_menu", {widget: self})); - var bindings = {}; - bindings[self.cm_id + "_search"] = function() { - if (self.readonly) - return; - self._search_create_popup("search"); - }; - bindings[self.cm_id + "_create"] = function() { - if (self.readonly) - return; - self._search_create_popup("form"); - }; - bindings[self.cm_id + "_open"] = function() { - if (!self.value) { - return; - } - var pop = new openerp.web.form.FormOpenPopup(self.view); - pop.show_element( - self.field.relation, - self.value[0], - self.build_context(), - { - title: _t("Open: ") + (self.string || self.name) - } - ); - pop.on_write_completed.add_last(function() { - self.set_value(self.value[0]); - }); - }; - _.each(_.range(self.related_entries.length), function(i) { - bindings[self.cm_id + "_related_" + i] = function() { - self.open_related(self.related_entries[i]); - }; - }); - var cmenu = self.$menu_btn.contextMenu(self.cm_id, {'noRightClick': true, - bindings: bindings, itemStyle: {"color": ""}, - onContextMenu: function() { - if(self.value) { - $("#" + self.cm_id + " .oe_m2o_menu_item_mandatory").removeClass("oe-m2o-disabled-cm"); - } else { - $("#" + self.cm_id + " .oe_m2o_menu_item_mandatory").addClass("oe-m2o-disabled-cm"); - } - if (!self.readonly) { - $("#" + self.cm_id + " .oe_m2o_menu_item_noreadonly").removeClass("oe-m2o-disabled-cm"); - } else { - $("#" + self.cm_id + " .oe_m2o_menu_item_noreadonly").addClass("oe-m2o-disabled-cm"); - } - return true; - }, menuStyle: {width: "200px"} - }); - $.async_when().then(function() {self.$menu_btn.trigger(e);}); + this.$follow_button = $(".oe-m2o-cm-button", this.$element); + + this.$follow_button.click(function() { + if (!self.get('value')) { + return; + } + var pop = new instance.web.form.FormOpenPopup(self.view); + pop.show_element( + self.field.relation, + self.get("value"), + self.build_context(), + { + title: _t("Open: ") + (self.string || self.name) + } + ); + pop.on_write_completed.add_last(function() { + self.display_value = {}; + self.render_value(); }); }); - var ctx_callback = function(e) {init_context_menu_def.resolve(e); e.preventDefault()}; - this.$menu_btn.click(ctx_callback); // some behavior for input this.$input.keyup(function() { if (self.$input.val() === "") { - self._change_int_value(null); - } else if (self.value === null || (self.value && self.$input.val() !== self.value[1])) { - self._change_int_value(undefined); + self.set({value: false}); + } else { + self.floating = true; } }); this.$drop_down.click(function() { - if (self.readonly) - return; if (self.$input.autocomplete("widget").is(":visible")) { self.$input.autocomplete("close"); } else { - if (self.value) { + if (self.get("value") && ! self.floating) { self.$input.autocomplete("search", ""); } else { self.$input.autocomplete("search"); @@ -2061,16 +2360,43 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({ self.$input.focus(); } }); + var tip_def = $.Deferred(); + var untip_def = $.Deferred(); + var tip_delay = 200; + var tip_duration = 3000; var anyoneLoosesFocus = function() { - if (!self.$input.is(":focus") && - !self.$input.autocomplete("widget").is(":visible") && - !self.value) { - if (self.value === undefined && self.last_search.length > 0) { - self._change_int_ext_value(self.last_search[0]); + if (self.floating) { + if (self.last_search.length > 0) { + if (self.last_search[0][0] != self.get("value")) { + self.display_value = {}; + self.display_value["" + self.last_search[0][0]] = self.last_search[0][1]; + self.set({value: self.last_search[0][0]}); + } else { + self.render_value(); + } } else { - self._change_int_ext_value(null); + self.set({value: false}); } } + if (! self.get("value")) { + tip_def.reject(); + untip_def.reject(); + tip_def = $.Deferred(); + tip_def.then(function() { + self.$input.tipsy("show"); + }); + setTimeout(function() { + tip_def.resolve(); + untip_def.reject(); + untip_def = $.Deferred(); + untip_def.then(function() { + self.$input.tipsy("hide"); + }); + setTimeout(function() {untip_def.resolve();}, tip_duration); + }, tip_delay); + } else { + tip_def.reject(); + } }; this.$input.focusout(anyoneLoosesFocus); @@ -2082,9 +2408,11 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({ isSelecting = true; var item = ui.item; if (item.id) { - self._change_int_value([item.id, item.name]); + self.display_value = {}; + self.display_value["" + item.id] = item.name; + self.set({value: item.id}); } else if (item.action) { - self._change_int_value(undefined); + self.floating = true; item.action(); return false; } @@ -2112,7 +2440,7 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({ var search_val = request.term; var self = this; - var dataset = new openerp.web.DataSet(this, this.field.relation, self.build_context()); + var dataset = new instance.web.DataSet(this, this.field.relation, self.build_context()); this.orderer.add(dataset.name_search( search_val, self.build_domain(), 'ilike', this.limit + 1)).then(function(data) { @@ -2133,7 +2461,6 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({ values.push({label: _t("<em> Search More...</em>"), action: function() { dataset.name_search(search_val, self.build_domain(), 'ilike' , false, function(data) { - self._change_int_value(null); self._search_create_popup("search", data); }); }}); @@ -2142,7 +2469,7 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({ var raw_result = _(data.result).map(function(x) {return x[1];}); if (search_val.length > 0 && !_.include(raw_result, search_val) && - (!self.value || search_val !== self.value[1])) { + (!self.get("value") || self.floating)) { values.push({label: _.str.sprintf(_t('<em> Create "<strong>%s</strong>"</em>'), $('<span />').text(search_val).html()), action: function() { self._quick_create(search_val); @@ -2150,7 +2477,6 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({ } // create... values.push({label: _t("<em> Create and Edit...</em>"), action: function() { - self._change_int_value(null); self._search_create_popup("form", undefined, {"default_name": search_val}); }}); @@ -2160,13 +2486,14 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({ _quick_create: function(name) { var self = this; var slow_create = function () { - self._change_int_value(null); self._search_create_popup("form", undefined, {"default_name": name}); }; if (self.get_definition_options().quick_create === undefined || self.get_definition_options().quick_create) { - new openerp.web.DataSet(this, this.field.relation, self.build_context()) + new instance.web.DataSet(this, this.field.relation, self.build_context()) .name_create(name, function(data) { - self._change_int_ext_value(data); + self.display_value = {}; + self.display_value["" + data[0]] = data[1]; + self.set({value: data[0]}); }).fail(function(error, event) { event.preventDefault(); slow_create(); @@ -2177,7 +2504,7 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({ // all search/create popup handling _search_create_popup: function(view, ids, context) { var self = this; - var pop = new openerp.web.form.SelectCreatePopup(this); + var pop = new instance.web.form.SelectCreatePopup(this); pop.select_element( self.field.relation, { @@ -2187,100 +2514,71 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({ disable_multiple_selection: true }, self.build_domain(), - new openerp.web.CompoundContext(self.build_context(), context || {}) + new instance.web.CompoundContext(self.build_context(), context || {}) ); pop.on_select_elements.add(function(element_ids) { - new openerp.web.DataSet(self, self.field.relation, self.build_context()) - .name_get([element_ids[0]], function(data) { - self._change_int_ext_value(data[0]); - }); + self.set({value: element_ids[0]}); }); }, - _change_int_ext_value: function(value) { - this._change_int_value(value); - this.$input.val(this.value ? this.value[1] : ""); - }, - _change_int_value: function(value) { - this.value = value; - var back_orig_value = this.original_value; - if (this.value === null || this.value) { - this.original_value = this.value; + render_value: function(no_recurse) { + var self = this; + if (! this.get("value")) { + this.display_string(""); + return; } - if (back_orig_value === undefined) { // first use after a set_value() + var display = this.display_value["" + this.get("value")]; + if (display) { + this.display_string(display); return; } - if (this.value !== undefined && ((back_orig_value ? back_orig_value[0] : null) - !== (this.value ? this.value[0] : null))) { - this.on_ui_change(); + if (! no_recurse) { + var dataset = new instance.web.DataSetStatic(this, this.field.relation, self.view.dataset.get_context()); + dataset.name_get([self.get("value")], function(data) { + self.display_value["" + self.get("value")] = data[0][1]; + self.render_value(true); + }); } }, - set_value: function(value) { - value = value || null; - this.invalid = false; + display_string: function(str) { var self = this; - this.tmp_value = value; - self.update_dom(); - self.on_value_changed(); - var real_set_value = function(rval) { - self.tmp_value = undefined; - self.value = rval; - self.original_value = undefined; - self._change_int_ext_value(rval); - }; - if (value && !(value instanceof Array)) { - // name_get in a m2o does not use the context of the field - new openerp.web.DataSet(this, this.field.relation, self.view.dataset.get_context()) - .name_get([value], function(data) { - real_set_value(data[0]); - }).fail(function() {self.tmp_value = undefined;}); + if (!this.get("effective_readonly")) { + this.$input.val(str); } else { - $.async_when().then(function() {real_set_value(value);}); - } - }, - get_value: function() { - if (this.tmp_value !== undefined) { - if (this.tmp_value instanceof Array) { - return this.tmp_value[0]; - } - return this.tmp_value ? this.tmp_value : false; - } - if (this.value === undefined) - return this.original_value ? this.original_value[0] : false; - return this.value ? this.value[0] : false; - }, - validate: function() { - this.invalid = false; - var val = this.tmp_value !== undefined ? this.tmp_value : this.value; - if (val === null) { - this.invalid = this.required; + this.$element.find('a') + .unbind('click') + .text(str) + .click(function () { + self.do_action({ + type: 'ir.actions.act_window', + res_model: self.field.relation, + res_id: self.get("value"), + context: self.build_context(), + views: [[false, 'form']], + target: 'current' + }); + return false; + }); } }, - open_related: function(related) { + set_value: function(value_) { var self = this; - if (!self.value) - return; - var additional_context = { - active_id: self.value[0], - active_ids: [self.value[0]], - active_model: self.field.relation - }; - self.rpc("/web/action/load", { - action_id: related[2].id, - context: additional_context - }, function(result) { - result.result.context = _.extend(result.result.context || {}, additional_context); - self.do_action(result.result); - }); + if (value_ instanceof Array) { + this.display_value = {}; + this.display_value["" + value_[0]] = value_[1]; + value_ = value_[0]; + } + value_ = value_ || false; + this.inhibit_on_change = true; + this._super(value_); + this.inhibit_on_change = false; }, - focus: function ($element) { - this._super($element || this.$input); + is_false: function() { + return ! this.get("value"); }, - update_dom: function() { - this._super.apply(this, arguments); - this.$input.prop('readonly', this.readonly); + focus: function () { + this.delay_focus(this.$input); } -}); -openerp.web.check_interface(openerp.web.form.FieldMany2One, openerp.web.form.FieldInterface); +})); /* # Values: (0, 0, { fields }) create @@ -2327,24 +2625,24 @@ var commands = { return [6, false, ids]; } }; -openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({ - form_template: 'FieldOne2Many', +instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({ multi_selection: false, - init: function(view, node) { - this._super(view, node); + disable_utility_classes: true, + init: function(field_manager, node) { + this._super(field_manager, node); + lazy_build_o2m_kanban_view(); this.is_loaded = $.Deferred(); this.initial_is_loaded = this.is_loaded; this.is_setted = $.Deferred(); this.form_last_update = $.Deferred(); this.init_form_last_update = this.form_last_update; - this.disable_utility_classes = true; }, start: function() { this._super.apply(this, arguments); var self = this; - this.dataset = new openerp.web.form.One2ManyDataSet(this, this.field.relation); + this.dataset = new instance.web.form.One2ManyDataSet(this, this.field.relation); this.dataset.o2m = this; this.dataset.parent_view = this.view; this.dataset.child_name = this.name; @@ -2356,16 +2654,23 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({ this.is_setted.then(function() { self.load_views(); }); + this.is_loaded.then(function() { + self.on("change:effective_readonly", self, function() { + self.is_loaded = self.is_loaded.pipe(function() { + self.viewmanager.destroy(); + return $.when(self.load_views()).then(function() { + self.reload_current_view(); + }); + }); + }); + }); }, trigger_on_change: function() { var tmp = this.doing_on_change; this.doing_on_change = true; - this.on_ui_change(); + this.trigger('changed_value'); this.doing_on_change = tmp; }, - is_readonly: function() { - return this.readonly || this.force_readonly; - }, load_views: function() { var self = this; @@ -2376,21 +2681,21 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({ var view = { view_id: false, view_type: mode == "tree" ? "list" : mode, - options: { sidebar : false } + options: {} }; if (self.field.views && self.field.views[mode]) { view.embedded_view = self.field.views[mode]; } if(view.view_type === "list") { view.options.selectable = self.multi_selection; - if (self.is_readonly()) { + if (self.get("effective_readonly")) { view.options.addable = null; view.options.deletable = null; view.options.isClarkGable = false; } } else if (view.view_type === "form") { - if (self.is_readonly()) { - view.view_type = 'page'; + if (self.get("effective_readonly")) { + view.view_type = 'form'; } view.options.not_interactible_on_create = true; } @@ -2398,12 +2703,14 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({ }); this.views = views; - this.viewmanager = new openerp.web.ViewManager(this, this.dataset, views, {}); + this.viewmanager = new instance.web.ViewManager(this, this.dataset, views, { + $sidebar: false, + }); this.viewmanager.template = 'One2Many.viewmanager'; - this.viewmanager.registry = openerp.web.views.extend({ - list: 'openerp.web.form.One2ManyListView', - form: 'openerp.web.form.One2ManyFormView', - page: 'openerp.web.PageView' + this.viewmanager.registry = instance.web.views.extend({ + list: 'instance.web.form.One2ManyListView', + form: 'instance.web.form.One2ManyFormView', + kanban: 'instance.web.form.One2ManyKanbanView', }); var once = $.Deferred().then(function() { self.init_form_last_update.resolve(); @@ -2412,12 +2719,12 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({ self.initial_is_loaded.resolve(); }); this.viewmanager.on_controller_inited.add_last(function(view_type, controller) { + controller.o2m = self; if (view_type == "list") { - controller.o2m = self; - if (self.is_readonly()) + if (self.get("effective_readonly")) controller.set_editable(false); - } else if (view_type == "form" || view_type == 'page') { - if (view_type == 'page' || self.is_readonly()) { + } else if (view_type === "form") { + if (self.get("effective_readonly")) { $(".oe_form_buttons", controller.$element).children().remove(); } controller.on_record_loaded.add_last(function() { @@ -2451,7 +2758,7 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({ var view = self.viewmanager.views[active_view].controller; if(active_view === "list") { return view.reload_content(); - } else if (active_view === "form" || active_view === 'page') { + } else if (active_view === "form") { if (self.dataset.index === null && self.dataset.ids.length >= 1) { self.dataset.index = 0; } @@ -2460,18 +2767,18 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({ }; self.form_last_update = self.form_last_update.pipe(act, act); return self.form_last_update; - } else if (active_view === "graph") { + } else if (view.do_search) { return view.do_search(self.build_domain(), self.dataset.get_context(), []); } }, undefined); }, - set_value: function(value) { - value = value || []; + set_value: function(value_) { + value_ = value_ || []; var self = this; this.dataset.reset_ids([]); - if(value.length >= 1 && value[0] instanceof Array) { + if(value_.length >= 1 && value_[0] instanceof Array) { var ids = []; - _.each(value, function(command) { + _.each(value_, function(command) { var obj = {values: command[2]}; switch (command[0]) { case commands.CREATE: @@ -2500,10 +2807,10 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({ }); this._super(ids); this.dataset.set_ids(ids); - } else if (value.length >= 1 && typeof(value[0]) === "object") { + } else if (value_.length >= 1 && typeof(value_[0]) === "object") { var ids = []; this.dataset.delete_all = true; - _.each(value, function(command) { + _.each(value_, function(command) { var obj = {values: command}; obj['id'] = _.uniqueId(self.dataset.virtual_id_prefix); obj.defaults = {}; @@ -2514,8 +2821,8 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({ this._super(ids); this.dataset.set_ids(ids); } else { - this._super(value); - this.dataset.reset_ids(value); + this._super(value_); + this.dataset.reset_ids(value_); } if (this.dataset.index === null && this.dataset.ids.length > 0) { this.dataset.index = 0; @@ -2572,61 +2879,37 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({ return false; }, this)); }, - is_valid: function() { - this.validate(); - return this._super(); - }, - validate: function() { - this.invalid = false; + is_syntax_valid: function() { if (!this.viewmanager.views[this.viewmanager.active_view]) - return; + return true; var view = this.viewmanager.views[this.viewmanager.active_view].controller; if (this.viewmanager.active_view === "form") { for (var f in view.fields) { f = view.fields[f]; if (!f.is_valid()) { - this.invalid = true; - return; + return false; } } } + return true; }, - is_dirty: function() { - this.save_any_view(); - return this._super(); - }, - update_dom: function() { - this._super.apply(this, arguments); - var self = this; - if (this.previous_readonly !== this.readonly) { - this.previous_readonly = this.readonly; - if (this.viewmanager) { - this.is_loaded = this.is_loaded.pipe(function() { - self.viewmanager.destroy(); - return $.when(self.load_views()).then(function() { - self.reload_current_view(); - }); - }); - } - } - } }); -openerp.web.form.One2ManyDataSet = openerp.web.BufferedDataSet.extend({ +instance.web.form.One2ManyDataSet = instance.web.BufferedDataSet.extend({ get_context: function() { this.context = this.o2m.build_context([this.o2m.name]); return this.context; } }); -openerp.web.form.One2ManyListView = openerp.web.ListView.extend({ +instance.web.form.One2ManyListView = instance.web.ListView.extend({ _template: 'One2Many.listview', do_add_record: function () { if (this.options.editable) { this._super.apply(this, arguments); } else { var self = this; - var pop = new openerp.web.form.SelectCreatePopup(this); + var pop = new instance.web.form.SelectCreatePopup(this); pop.on_default_get.add(self.dataset.on_default_get); pop.select_element( self.o2m.field.relation, @@ -2657,7 +2940,7 @@ openerp.web.form.One2ManyListView = openerp.web.ListView.extend({ }, do_activate_record: function(index, id) { var self = this; - var pop = new openerp.web.form.FormOpenPopup(self.o2m.view); + var pop = new instance.web.form.FormOpenPopup(self.o2m.view); pop.show_element(self.o2m.field.relation, id, self.o2m.build_context(), { title: _t("Open: ") + self.name, auto_write: false, @@ -2668,7 +2951,7 @@ openerp.web.form.One2ManyListView = openerp.web.ListView.extend({ return self.o2m.dataset.read_ids.apply(self.o2m.dataset, arguments); }, form_view_options: {'not_interactible_on_create':true}, - readonly: self.o2m.is_readonly() + readonly: self.o2m.get("effective_readonly") }); pop.on_write.add(function(id, data) { self.o2m.dataset.write(id, data, {}, function(r) { @@ -2683,12 +2966,12 @@ openerp.web.form.One2ManyListView = openerp.web.ListView.extend({ } }); -openerp.web.form.One2ManyFormView = openerp.web.FormView.extend({ +instance.web.form.One2ManyFormView = instance.web.FormView.extend({ form_template: 'One2Many.formview', on_loaded: function(data) { this._super(data); var self = this; - this.$form_header.find('button.oe_form_button_create').click(function() { + this.$buttons.find('button.oe_form_button_create').click(function() { self.do_save().then(self.on_button_new); }); }, @@ -2701,12 +2984,43 @@ openerp.web.form.One2ManyFormView = openerp.web.FormView.extend({ } }); -openerp.web.form.FieldMany2Many = openerp.web.form.Field.extend({ - form_template: 'FieldMany2Many', +var lazy_build_o2m_kanban_view = function() { +if (! instance.web_kanban || instance.web.form.One2ManyKanbanView) + return; +instance.web.form.One2ManyKanbanView = instance.web_kanban.KanbanView.extend({ + open_record: function(id) { + var self = this; + var pop = new instance.web.form.FormOpenPopup(self.o2m.view); + pop.show_element(self.o2m.field.relation, id, self.o2m.build_context(), { + title: _t("Open: ") + self.name, + auto_write: false, + alternative_form_view: self.o2m.field.views ? self.o2m.field.views["form"] : undefined, + parent_view: self.o2m.view, + child_name: self.o2m.name, + read_function: function() { + return self.o2m.dataset.read_ids.apply(self.o2m.dataset, arguments); + }, + form_view_options: {'not_interactible_on_create':true}, + readonly: self.o2m.get("effective_readonly"), + }); + pop.on_write.add(function(id, data) { + self.o2m.dataset.write(id, data, {}, function(r) { + self.o2m.reload_current_view(); + }); + }); + + }, +}); +} + +/* + * TODO niv: clean those deferred stuff, it could be better + */ +instance.web.form.FieldMany2Many = instance.web.form.AbstractField.extend({ multi_selection: false, - init: function(view, node) { - this._super(view, node); - this.list_id = _.uniqueId("many2many"); + disable_utility_classes: true, + init: function(field_manager, node) { + this._super(field_manager, node); this.is_loaded = $.Deferred(); this.initial_is_loaded = this.is_loaded; this.is_setted = $.Deferred(); @@ -2716,43 +3030,44 @@ openerp.web.form.FieldMany2Many = openerp.web.form.Field.extend({ var self = this; - this.dataset = new openerp.web.form.Many2ManyDataSet(this, this.field.relation); + this.dataset = new instance.web.form.Many2ManyDataSet(this, this.field.relation); this.dataset.m2m = this; this.dataset.on_unlink.add_last(function(ids) { - self.on_ui_change(); + self.dataset_changed(); }); this.is_setted.then(function() { self.load_view(); }); + this.is_loaded.then(function() { + self.on("change:effective_readonly", self, function() { + self.is_loaded = self.is_loaded.pipe(function() { + self.list_view.destroy(); + return $.when(self.load_view()).then(function() { + self.reload_content(); + }); + }); + }); + }) }, - set_value: function(value) { - value = value || []; - if (value.length >= 1 && value[0] instanceof Array) { - value = value[0][2]; + set_value: function(value_) { + value_ = value_ || []; + if (value_.length >= 1 && value_[0] instanceof Array) { + value_ = value_[0][2]; } - this._super(value); - this.dataset.set_ids(value); + this._super(value_); + this.dataset.set_ids(value_); var self = this; self.reload_content(); this.is_setted.resolve(); }, - get_value: function() { - return [commands.replace_with(this.dataset.ids)]; - }, - validate: function() { - this.invalid = false; - }, - is_readonly: function() { - return this.readonly || this.force_readonly; - }, load_view: function() { var self = this; - this.list_view = new openerp.web.form.Many2ManyListView(this, this.dataset, false, { - 'addable': self.is_readonly() ? null : _t("Add"), - 'deletable': self.is_readonly() ? false : true, + this.list_view = new instance.web.form.Many2ManyListView(this, this.dataset, false, { + 'addable': self.get("effective_readonly") ? null : _t("Add"), + 'deletable': self.get("effective_readonly") ? false : true, 'selectable': self.multi_selection, - 'isClarkGable': self.is_readonly() ? false : true + 'isClarkGable': self.get("effective_readonly") ? false : true }); var embedded = (this.field.views || {}).tree; if (embedded) { @@ -2765,7 +3080,7 @@ openerp.web.form.FieldMany2Many = openerp.web.form.Field.extend({ loaded.resolve(); }); $.async_when().then(function () { - self.list_view.appendTo($("#" + self.list_id)); + self.list_view.appendTo(self.$element); }); return loaded; }, @@ -2775,24 +3090,12 @@ openerp.web.form.FieldMany2Many = openerp.web.form.Field.extend({ return self.list_view.reload_content(); }); }, - update_dom: function() { - this._super.apply(this, arguments); - var self = this; - if (this.previous_readonly !== this.readonly) { - this.previous_readonly = this.readonly; - if (this.list_view) { - this.is_loaded = this.is_loaded.pipe(function() { - self.list_view.destroy(); - return $.when(self.load_view()).then(function() { - self.reload_content(); - }); - }); - } - } - } + dataset_changed: function() { + this.set({'value': [commands.replace_with(this.dataset.ids)]}); + }, }); -openerp.web.form.Many2ManyDataSet = openerp.web.DataSetStatic.extend({ +instance.web.form.Many2ManyDataSet = instance.web.DataSetStatic.extend({ get_context: function() { this.context = this.m2m.build_context(); return this.context; @@ -2801,25 +3104,25 @@ openerp.web.form.Many2ManyDataSet = openerp.web.DataSetStatic.extend({ /** * @class - * @extends openerp.web.ListView + * @extends instance.web.ListView */ -openerp.web.form.Many2ManyListView = openerp.web.ListView.extend(/** @lends openerp.web.form.Many2ManyListView# */{ +instance.web.form.Many2ManyListView = instance.web.ListView.extend(/** @lends instance.web.form.Many2ManyListView# */{ do_add_record: function () { - var pop = new openerp.web.form.SelectCreatePopup(this); + var pop = new instance.web.form.SelectCreatePopup(this); pop.select_element( this.model, { title: _t("Add: ") + this.name }, - new openerp.web.CompoundDomain(this.m2m_field.build_domain(), ["!", ["id", "in", this.m2m_field.dataset.ids]]), + new instance.web.CompoundDomain(this.m2m_field.build_domain(), ["!", ["id", "in", this.m2m_field.dataset.ids]]), this.m2m_field.build_context() ); var self = this; pop.on_select_elements.add(function(element_ids) { - _.each(element_ids, function(element_id) { - if(! _.detect(self.dataset.ids, function(x) {return x == element_id;})) { - self.dataset.set_ids([].concat(self.dataset.ids, [element_id])); - self.m2m_field.on_ui_change(); + _.each(element_ids, function(one_id) { + if(! _.detect(self.dataset.ids, function(x) {return x == one_id;})) { + self.dataset.set_ids([].concat(self.dataset.ids, [one_id])); + self.m2m_field.dataset_changed(); self.reload_content(); } }); @@ -2827,10 +3130,10 @@ openerp.web.form.Many2ManyListView = openerp.web.ListView.extend(/** @lends open }, do_activate_record: function(index, id) { var self = this; - var pop = new openerp.web.form.FormOpenPopup(this); + var pop = new instance.web.form.FormOpenPopup(this); pop.show_element(this.dataset.model, id, this.m2m_field.build_context(), { title: _t("Open: ") + this.name, - readonly: this.getParent().is_readonly() + readonly: this.getParent().get("effective_readonly") }); pop.on_write_completed.add_last(function() { self.reload_content(); @@ -2840,9 +3143,9 @@ openerp.web.form.Many2ManyListView = openerp.web.ListView.extend(/** @lends open /** * @class - * @extends openerp.web.OldWidget + * @extends instance.web.OldWidget */ -openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends openerp.web.form.SelectCreatePopup# */{ +instance.web.form.SelectCreatePopup = instance.web.OldWidget.extend(/** @lends instance.web.form.SelectCreatePopup# */{ template: "SelectCreatePopup", /** * options: @@ -2868,7 +3171,7 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope this.initial_ids = this.options.initial_ids; this.created_elements = []; this.renderElement(); - openerp.web.form.dialog(this.$element, { + instance.web.form.dialog(this.$element, { close: function() { self.check_exit(); }, @@ -2879,7 +3182,7 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope start: function() { this._super(); var self = this; - this.dataset = new openerp.web.ProxyDataSet(this, this.model, + this.dataset = new instance.web.ProxyDataSet(this, this.model, this.context); this.dataset.create_function = function() { return self.options.create_function.apply(null, arguments).then(function(r) { @@ -2899,10 +3202,10 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope contexts: [this.context] }, function (results) { var search_defaults = {}; - _.each(results.context, function (value, key) { + _.each(results.context, function (value_, key) { var match = /^search_default_(.*)$/.exec(key); if (match) { - search_defaults[match[1]] = value; + search_defaults[match[1]] = value_; } }); self.setup_search_view(search_defaults); @@ -2920,7 +3223,7 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope if (this.searchview) { this.searchview.destroy(); } - this.searchview = new openerp.web.SearchView(this, + this.searchview = new instance.web.SearchView(this, this.dataset, false, search_defaults); this.searchview.on_search.add(function(domains, contexts, groupbys) { if (self.initial_ids) { @@ -2932,13 +3235,13 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope } }); this.searchview.on_loaded.add_last(function () { - self.view_list = new openerp.web.form.SelectCreateListView(self, + self.view_list = new instance.web.form.SelectCreateListView(self, self.dataset, false, _.extend({'deletable': false, 'selectable': !self.options.disable_multiple_selection }, self.options.list_view_options || {})); self.view_list.popup = self; - self.view_list.appendTo($("#" + self.element_id + "_view_list")).pipe(function() { + self.view_list.appendTo($(".oe-select-create-popup-view-list", self.$element)).pipe(function() { self.view_list.do_show(); }).pipe(function() { self.searchview.do_search(); @@ -2960,7 +3263,7 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope }); }); }); - this.searchview.appendTo($("#" + this.element_id + "_search")); + this.searchview.appendTo($(".oe-select-create-popup-view-list", self.$element)); }, do_search: function(domains, contexts, groupbys) { var self = this; @@ -2974,14 +3277,14 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope }, create_row: function() { var self = this; - var wdataset = new openerp.web.DataSetSearch(this, this.model, this.context, this.domain); + var wdataset = new instance.web.DataSetSearch(this, this.model, this.context, this.domain); wdataset.parent_view = this.options.parent_view; wdataset.child_name = this.options.child_name; return wdataset.create.apply(wdataset, arguments); }, write_row: function() { var self = this; - var wdataset = new openerp.web.DataSetSearch(this, this.model, this.context, this.domain); + var wdataset = new instance.web.DataSetSearch(this, this.model, this.context, this.domain); wdataset.parent_view = this.options.parent_view; wdataset.child_name = this.options.child_name; return wdataset.write.apply(wdataset, arguments); @@ -3005,11 +3308,11 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope this.view_list.$element.hide(); } this.dataset.index = null; - this.view_form = new openerp.web.FormView(this, this.dataset, false, self.options.form_view_options); + this.view_form = new instance.web.FormView(this, this.dataset, false, self.options.form_view_options); if (this.options.alternative_form_view) { this.view_form.set_embedded_view(this.options.alternative_form_view); } - this.view_form.appendTo(this.$element.find("#" + this.element_id + "_view_form")); + this.view_form.appendTo(this.$element.find(".oe-select-create-popup-view-form")); this.view_form.on_loaded.add_last(function() { var $buttons = self.view_form.$element.find(".oe_form_buttons"); $buttons.html(QWeb.render("SelectCreatePopup.form.buttons", {widget:self})); @@ -3045,7 +3348,7 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope on_default_get: function(res) {} }); -openerp.web.form.SelectCreateListView = openerp.web.ListView.extend({ +instance.web.form.SelectCreateListView = instance.web.ListView.extend({ do_add_record: function () { this.popup.new_object(); }, @@ -3061,9 +3364,9 @@ openerp.web.form.SelectCreateListView = openerp.web.ListView.extend({ /** * @class - * @extends openerp.web.OldWidget + * @extends instance.web.OldWidget */ -openerp.web.form.FormOpenPopup = openerp.web.OldWidget.extend(/** @lends openerp.web.form.FormOpenPopup# */{ +instance.web.form.FormOpenPopup = instance.web.OldWidget.extend(/** @lends instance.web.form.FormOpenPopup# */{ template: "FormOpenPopup", /** * options: @@ -3081,7 +3384,7 @@ openerp.web.form.FormOpenPopup = openerp.web.OldWidget.extend(/** @lends openerp this.context = context || {}; this.options = _.defaults(options || {}, {"auto_write": true}); this.renderElement(); - openerp.web.dialog(this.$element, { + instance.web.dialog(this.$element, { title: options.title || '', modal: true, width: 960, @@ -3091,7 +3394,7 @@ openerp.web.form.FormOpenPopup = openerp.web.OldWidget.extend(/** @lends openerp }, start: function() { this._super(); - this.dataset = new openerp.web.form.FormOpenDataset(this, this.model, this.context); + this.dataset = new instance.web.form.FormOpenDataset(this, this.model, this.context); this.dataset.fop = this; this.dataset.ids = [this.row_id]; this.dataset.index = 0; @@ -3103,7 +3406,7 @@ openerp.web.form.FormOpenPopup = openerp.web.OldWidget.extend(/** @lends openerp if (!this.options.auto_write) return; var self = this; - var wdataset = new openerp.web.DataSetSearch(this, this.model, this.context, this.domain); + var wdataset = new instance.web.DataSetSearch(this, this.model, this.context, this.domain); wdataset.parent_view = this.options.parent_view; wdataset.child_name = this.options.child_name; wdataset.write(id, data, {}, function(r) { @@ -3113,14 +3416,14 @@ openerp.web.form.FormOpenPopup = openerp.web.OldWidget.extend(/** @lends openerp on_write_completed: function() {}, setup_form_view: function() { var self = this; - var FormClass = this.options.readonly - ? openerp.web.views.get_object('page') - : openerp.web.views.get_object('form'); - this.view_form = new FormClass(this, this.dataset, false, self.options.form_view_options); + var FormClass = instance.web.views.get_object('form'); + var options = _.clone(self.options.form_view_options) || {}; + options.initial_mode = this.options.readonly ? "view" : "edit"; + this.view_form = new FormClass(this, this.dataset, false, options); if (this.options.alternative_form_view) { this.view_form.set_embedded_view(this.options.alternative_form_view); } - this.view_form.appendTo(this.$element.find("#" + this.element_id + "_view_form")); + this.view_form.appendTo(this.$element.find(".oe-form-open-popup-form-view")); this.view_form.on_loaded.add_last(function() { var $buttons = self.view_form.$element.find(".oe_form_buttons"); $buttons.html(QWeb.render("FormOpenPopup.form.buttons")); @@ -3144,7 +3447,7 @@ openerp.web.form.FormOpenPopup = openerp.web.OldWidget.extend(/** @lends openerp } }); -openerp.web.form.FormOpenDataset = openerp.web.ProxyDataSet.extend({ +instance.web.form.FormOpenDataset = instance.web.ProxyDataSet.extend({ read_ids: function() { if (this.fop.options.read_function) { return this.fop.options.read_function.apply(null, arguments); @@ -3154,40 +3457,28 @@ openerp.web.form.FormOpenDataset = openerp.web.ProxyDataSet.extend({ } }); -openerp.web.form.FieldReference = openerp.web.form.Field.extend({ - form_template: 'FieldReference', - init: function(view, node) { - this._super(view, node); +instance.web.form.FieldReference = instance.web.form.AbstractField.extend(_.extend({}, instance.web.form.ReinitializeFieldMixin, { + template: 'FieldReference', + init: function(field_manager, node) { + this._super(field_manager, node); this.fields_view = { fields: { selection: { - selection: view.fields_view.fields[this.name].selection + selection: this.view.fields_view.fields[this.name].selection }, m2o: { relation: null } } }; - this.get_fields_values = view.get_fields_values; - this.get_selected_ids = view.get_selected_ids; + this.get_fields_values = this.view.get_fields_values; + this.get_selected_ids = this.view.get_selected_ids; this.do_onchange = this.on_form_changed = this.do_notify_change = this.on_nop; this.dataset = this.view.dataset; - this.widgets_counter = 0; this.view_id = 'reference_' + _.uniqueId(); - this.widgets = {}; this.fields = {}; this.fields_order = []; - this.selection = new openerp.web.form.FieldSelection(this, { attrs: { - name: 'selection', - widget: 'selection' - }}); this.reference_ready = true; - this.selection.on_value_changed.add_last(this.on_selection_changed); - this.m2o = new openerp.web.form.FieldMany2One(this, { attrs: { - name: 'm2o', - widget: 'many2one' - }}); - this.m2o.on_ui_change.add_last(this.on_ui_change); }, on_nop: function() { }, @@ -3195,59 +3486,82 @@ openerp.web.form.FieldReference = openerp.web.form.Field.extend({ if (this.reference_ready) { var sel = this.selection.get_value(); this.m2o.field.relation = sel; - this.m2o.set_value(null); + this.m2o.set_value(false); this.m2o.$element.toggle(sel !== false); } }, - start: function() { - this._super(); + destroy_content: function() { + if (this.selection) { + this.selection.destroy(); + this.selection = undefined; + } + if (this.m2o) { + this.m2o.destroy(); + this.m2o = undefined; + } + }, + initialize_content: function() { + this.selection = new instance.web.form.FieldSelection(this, { attrs: { + name: 'selection', + widget: 'selection' + }}); + this.selection.set({readonly: this.get('effective_readonly')}); + this.selection.on("change:value", this, this.on_selection_changed); this.selection.$element = $(".oe_form_view_reference_selection", this.$element); this.selection.renderElement(); this.selection.start(); + + this.m2o = new instance.web.form.FieldMany2One(this, { attrs: { + name: 'm2o', + widget: 'many2one' + }}); + this.m2o.set({"readonly": this.get("effective_readonly")}); + this.m2o.on("change:value", this, this.data_changed); this.m2o.$element = $(".oe_form_view_reference_m2o", this.$element); this.m2o.renderElement(); this.m2o.start(); }, - is_valid: function() { - return this.required === false || typeof(this.get_value()) === 'string'; + is_false: function() { + return typeof(this.get_value()) !== 'string'; }, - is_dirty: function() { - return this.selection.is_dirty() || this.m2o.is_dirty(); + set_value: function(value_) { + this._super(value_); + this.render_value(); }, - set_value: function(value) { - this._super(value); + render_value: function() { this.reference_ready = false; var vals = [], sel_val, m2o_val; - if (typeof(value) === 'string') { - vals = value.split(','); + if (typeof(this.get('value')) === 'string') { + vals = this.get('value').split(','); } sel_val = vals[0] || false; m2o_val = vals[1] ? parseInt(vals[1], 10) : false; - this.selection.set_value(sel_val); + if (!this.get("effective_readonly")) { + this.selection.set_value(sel_val); + } this.m2o.field.relation = sel_val; this.m2o.set_value(m2o_val); this.m2o.$element.toggle(sel_val !== false); this.reference_ready = true; }, - get_value: function() { + data_changed: function() { var model = this.selection.get_value(), id = this.m2o.get_value(); if (typeof(model) === 'string' && typeof(id) === 'number') { - return model + ',' + id; + this.set({'value': model + ',' + id}); } else { - return false; + this.set({'value': false}); } - } -}); + }, +})); -openerp.web.form.FieldBinary = openerp.web.form.Field.extend({ - init: function(view, node) { - this._super(view, node); +instance.web.form.FieldBinary = instance.web.form.AbstractField.extend(_.extend({}, instance.web.form.ReinitializeFieldMixin, { + init: function(field_manager, node) { + this._super(field_manager, node); this.iframe = this.element_id + '_iframe'; this.binary_value = false; }, - start: function() { - this._super.apply(this, arguments); + initialize_content: function() { this.$element.find('input.oe-binary-file').change(this.on_file_change); this.$element.find('button.oe-binary-file-save').click(this.on_save_as); this.$element.find('.oe-binary-file-clear').click(this.on_clear); @@ -3281,7 +3595,6 @@ openerp.web.form.FieldBinary = openerp.web.form.Field.extend({ console.warn("Error while uploading file : ", name); } else { this.on_file_uploaded_and_valid.apply(this, arguments); - this.on_ui_change(); } this.$element.find('.oe-binary-progress').hide(); this.$element.find('.oe-binary').show(); @@ -3300,48 +3613,64 @@ openerp.web.form.FieldBinary = openerp.web.form.Field.extend({ context: this.view.dataset.get_context() })}, complete: $.unblockUI, - error: openerp.webclient.crashmanager.on_rpc_error + error: instance.webclient.crashmanager.on_rpc_error }); }, on_clear: function() { - if (this.value !== false) { - this.value = false; + if (this.get('value') !== false) { this.binary_value = false; - this.on_ui_change(); + this.set({'value': false}); } return false; } -}); +})); -openerp.web.form.FieldBinaryFile = openerp.web.form.FieldBinary.extend({ - form_template: 'FieldBinaryFile', - update_dom: function() { - this._super.apply(this, arguments); - this.$element.find('.oe-binary-file-set, .oe-binary-file-clear').toggle(!this.readonly); - this.$element.find('input[type=text]').prop('readonly', this.readonly); +instance.web.form.FieldBinaryFile = instance.web.form.FieldBinary.extend({ + template: 'FieldBinaryFile', + initialize_content: function() { + this._super(); + if (this.get("effective_readonly")) { + var self = this; + this.$element.find('a').click(function() { + if (self.get('value')) { + self.on_save_as(); + } + return false; + }); + } }, - set_value: function(value) { + set_value: function(value_) { this._super.apply(this, arguments); - var show_value; - if (this.node.attrs.filename) { - show_value = this.view.datarecord[this.node.attrs.filename] || ''; + this.render_value(); + }, + render_value: function() { + if (!this.get("effective_readonly")) { + var show_value; + if (this.node.attrs.filename) { + show_value = this.view.datarecord[this.node.attrs.filename] || ''; + } else { + show_value = (this.get('value') != null && this.get('value') !== false) ? this.get('value') : ''; + } + this.$element.find('input').eq(0).val(show_value); } else { - show_value = (value != null && value !== false) ? value : ''; + this.$element.find('a').show(!!this.get('value')); + if (this.get('value')) { + var show_value = _t("Download") + " " + (this.view.datarecord[this.node.attrs.filename] || ''); + this.$element.find('a').text(show_value); + } } - this.$element.find('input').eq(0).val(show_value); }, on_file_uploaded_and_valid: function(size, name, content_type, file_base64) { - this.value = file_base64; this.binary_value = true; + this.set({'value': file_base64}); var show_value = name + " (" + this.human_filesize(size) + ")"; this.$element.find('input').eq(0).val(show_value); this.set_filename(name); }, - set_filename: function(value) { + set_filename: function(value_) { var filename = this.node.attrs.filename; if (this.view.fields[filename]) { - this.view.fields[filename].set_value(value); - this.view.fields[filename].on_ui_change(); + this.view.fields[filename].set({value: value_}); } }, on_clear: function() { @@ -3351,58 +3680,59 @@ openerp.web.form.FieldBinaryFile = openerp.web.form.FieldBinary.extend({ } }); -openerp.web.form.FieldBinaryImage = openerp.web.form.FieldBinary.extend({ - form_template: 'FieldBinaryImage', - start: function() { - this._super.apply(this, arguments); - this.$image = this.$element.find('img.oe-binary-image'); +instance.web.form.FieldBinaryImage = instance.web.form.FieldBinary.extend({ + template: 'FieldBinaryImage', + initialize_content: function() { + this._super(); + this.$placeholder = $(".oe_form_field-binary-image-placeholder", this.$element); + if (!this.get("effective_readonly")) + this.$element.find('.oe-binary').show(); + else + this.$element.find('.oe-binary').hide(); }, - update_dom: function() { + set_value: function(value_) { this._super.apply(this, arguments); - this.$element.find('.oe-binary').toggle(!this.readonly); + this.render_value(); }, - set_value: function(value) { - this._super.apply(this, arguments); - this.set_image_maxwidth(); - + render_value: function() { var url; - if (value && value.substr(0, 10).indexOf(' ') == -1) { - url = 'data:image/png;base64,' + this.value; - } else { + if (this.get('value') && this.get('value').substr(0, 10).indexOf(' ') == -1) { + url = 'data:image/png;base64,' + this.get('value'); + } else if (this.get('value')) { url = '/web/binary/image?session_id=' + this.session.session_id + '&model=' + this.view.dataset.model +'&id=' + (this.view.datarecord.id || '') + '&field=' + this.name + '&t=' + (new Date().getTime()); + } else { + url = "/web/static/src/img/placeholder.png"; } - this.$image.attr('src', url); - }, - set_image_maxwidth: function() { - this.$image.css('max-width', this.$element.width()); + var rendered = QWeb.render("FieldBinaryImage-img", {widget: this, url: url});; + this.$placeholder.html(rendered); }, on_file_change: function() { - this.set_image_maxwidth(); + this.render_value(); this._super.apply(this, arguments); }, on_file_uploaded_and_valid: function(size, name, content_type, file_base64) { - this.value = file_base64; + this.set({'value': file_base64}); this.binary_value = true; - this.$image.attr('src', 'data:' + (content_type || 'image/png') + ';base64,' + file_base64); + this.render_value(); }, on_clear: function() { this._super.apply(this, arguments); - this.$image.attr('src', '/web/static/src/img/placeholder.png'); + this.render_value(); } }); -openerp.web.form.FieldStatus = openerp.web.form.Field.extend({ - form_template: "EmptyComponent", +instance.web.form.FieldStatus = instance.web.form.AbstractField.extend({ + template: "EmptyComponent", start: function() { this._super(); this.selected_value = null; this.render_list(); }, - set_value: function(value) { - this._super(value); - this.selected_value = value; + set_value: function(value_) { + this._super(value_); + this.selected_value = value_; this.render_list(); }, @@ -3420,7 +3750,7 @@ openerp.web.form.FieldStatus = openerp.web.form.Field.extend({ }); } - var content = openerp.web.qweb.render("FieldStatus.content", {widget: this, _:_}); + var content = instance.web.qweb.render("FieldStatus.content", {widget: this, _:_}); this.$element.html(content); var colors = JSON.parse((this.node.attrs || {}).statusbar_colors || "{}"); @@ -3457,77 +3787,36 @@ openerp.web.form.FieldStatus = openerp.web.form.Field.extend({ } }); -openerp.web.form.WidgetHtml = openerp.web.form.Widget.extend({ - renderElement: function () { - var $root = $('<div class="oe_form_html_view">'); - this.render_children(this, $root); - var rendered = $root.html(); - this.$element.html(rendered); - }, - render_children: function (object, $into) { - var self = this, - fields = this.view.fields_view.fields; - _(object.children).each(function (child) { - if (typeof child === 'string') { - $into.text(child); - } else if (child.tag === 'field') { - var widget = instanciate_widget(self.view.registry.get_object('frame'), - self.view, {tag: 'ueule', attrs: {}, children: [child] }); - widget.appendTo($into); - } else { - var $child = $(document.createElement(child.tag)) - .attr(child.attrs) - .appendTo($into); - self.render_children(child, $child); - } - }); - } -}); - - /** - * Registry of form widgets, called by :js:`openerp.web.FormView` + * Registry of form widgets, called by :js:`instance.web.FormView` */ -openerp.web.form.widgets = new openerp.web.Registry({ - 'frame' : 'openerp.web.form.WidgetFrame', - 'group' : 'openerp.web.form.WidgetGroup', - 'notebook' : 'openerp.web.form.WidgetNotebook', - 'notebookpage' : 'openerp.web.form.WidgetNotebookPage', - 'separator' : 'openerp.web.form.WidgetSeparator', - 'label' : 'openerp.web.form.WidgetLabel', - 'button' : 'openerp.web.form.WidgetButton', - 'char' : 'openerp.web.form.FieldChar', - 'id' : 'openerp.web.form.FieldID', - 'email' : 'openerp.web.form.FieldEmail', - 'url' : 'openerp.web.form.FieldUrl', - 'text' : 'openerp.web.form.FieldText', - 'date' : 'openerp.web.form.FieldDate', - 'datetime' : 'openerp.web.form.FieldDatetime', - 'selection' : 'openerp.web.form.FieldSelection', - 'many2one' : 'openerp.web.form.FieldMany2One', - 'many2many' : 'openerp.web.form.FieldMany2Many', - 'one2many' : 'openerp.web.form.FieldOne2Many', - 'one2many_list' : 'openerp.web.form.FieldOne2Many', - 'reference' : 'openerp.web.form.FieldReference', - 'boolean' : 'openerp.web.form.FieldBoolean', - 'float' : 'openerp.web.form.FieldFloat', - 'integer': 'openerp.web.form.FieldFloat', - 'float_time': 'openerp.web.form.FieldFloat', - 'progressbar': 'openerp.web.form.FieldProgressBar', - 'image': 'openerp.web.form.FieldBinaryImage', - 'binary': 'openerp.web.form.FieldBinaryFile', - 'statusbar': 'openerp.web.form.FieldStatus', - 'html': 'openerp.web.form.WidgetHtml' +instance.web.form.widgets = new instance.web.Registry({ + 'char' : 'instance.web.form.FieldChar', + 'id' : 'instance.web.form.FieldID', + 'email' : 'instance.web.form.FieldEmail', + 'url' : 'instance.web.form.FieldUrl', + 'text' : 'instance.web.form.FieldText', + 'date' : 'instance.web.form.FieldDate', + 'datetime' : 'instance.web.form.FieldDatetime', + 'selection' : 'instance.web.form.FieldSelection', + 'many2one' : 'instance.web.form.FieldMany2One', + 'many2many' : 'instance.web.form.FieldMany2Many', + 'one2many' : 'instance.web.form.FieldOne2Many', + 'one2many_list' : 'instance.web.form.FieldOne2Many', + 'reference' : 'instance.web.form.FieldReference', + 'boolean' : 'instance.web.form.FieldBoolean', + 'float' : 'instance.web.form.FieldFloat', + 'integer': 'instance.web.form.FieldFloat', + 'float_time': 'instance.web.form.FieldFloat', + 'progressbar': 'instance.web.form.FieldProgressBar', + 'image': 'instance.web.form.FieldBinaryImage', + 'binary': 'instance.web.form.FieldBinaryFile', + 'statusbar': 'instance.web.form.FieldStatus' }); -var instanciate_widget = function(claz, view, node, o1, o2) { - var widget = new (claz)(view, node, o1, o2); - widget.element_class = (['formview', view.view_id, widget.element_name, - view.widgets_counter++].join("_")).replace(/[^\r\n\f0-9A-Za-z_-]/g, "_"); - view.widgets[widget.element_class] = widget; - return widget; -} - +instance.web.form.tags = new instance.web.Registry({ + 'button' : 'instance.web.form.WidgetButton', +}); }; diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js index e8fff450c6e..c29957cac2d 100644 --- a/addons/web/static/src/js/view_list.js +++ b/addons/web/static/src/js/view_list.js @@ -1,9 +1,9 @@ -openerp.web.list = function (openerp) { -var _t = openerp.web._t, - _lt = openerp.web._lt; -var QWeb = openerp.web.qweb; -openerp.web.views.add('list', 'openerp.web.ListView'); -openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView# */ { +openerp.web.list = function (instance) { +var _t = instance.web._t, + _lt = instance.web._lt; +var QWeb = instance.web.qweb; +instance.web.views.add('list', 'instance.web.ListView'); +instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListView# */ { _template: 'ListView', display_name: _lt('List'), defaults: { @@ -33,11 +33,11 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView# * See constructor parameters and method documentations for information on * the default behaviors and possible options for the list view. * - * @constructs openerp.web.ListView - * @extends openerp.web.View + * @constructs instance.web.ListView + * @extends instance.web.View * * @param parent parent object - * @param {openerp.web.DataSet} dataset the dataset the view should work with + * @param {instance.web.DataSet} dataset the dataset the view should work with * @param {String} view_id the listview's identifier, if any * @param {Object} options A set of options used to configure the view * @param {Boolean} [options.selectable=true] determines whether view rows are selectable (e.g. via a checkbox) @@ -61,12 +61,12 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView# this.records = new Collection(); - this.set_groups(new openerp.web.ListView.Groups(this)); + this.set_groups(new instance.web.ListView.Groups(this)); - if (this.dataset instanceof openerp.web.DataSetStatic) { - this.groups.datagroup = new openerp.web.StaticDataGroup(this.dataset); + if (this.dataset instanceof instance.web.DataSetStatic) { + this.groups.datagroup = new instance.web.StaticDataGroup(this.dataset); } else { - this.groups.datagroup = new openerp.web.DataGroup( + this.groups.datagroup = new instance.web.DataGroup( this, this.model, dataset.get_domain(), dataset.get_context(), @@ -104,7 +104,7 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView# /** * Set a custom Group construct as the root of the List View. * - * @param {openerp.web.ListView.Groups} groups + * @param {instance.web.ListView.Groups} groups */ set_groups: function (groups) { var self = this; @@ -136,7 +136,6 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView# * @returns {$.Deferred} loading promise */ start: function() { - this._super(); this.$element.addClass('oe-listview'); return this.reload_view(null, null, true); }, @@ -208,6 +207,7 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView# this.$element.html(QWeb.render(this._template, this)); // Head hook + // Selecting records this.$element.find('.all-record-selector').click(function(){ self.$element.find('.oe-record-selector input').prop('checked', self.$element.find('.all-record-selector').prop('checked') || false); @@ -216,70 +216,95 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView# 'selected', [selection.ids, selection.records]); }); - this.$element.find('.oe-list-add') - .click(this.proxy('do_add_record')) - .attr('disabled', grouped && this.options.editable); - this.$element.find('.oe-list-delete') - .attr('disabled', true) - .click(this.proxy('do_delete_selected')); + // Sorting columns this.$element.find('thead').delegate('th.oe-sortable[data-id]', 'click', function (e) { e.stopPropagation(); - var $this = $(this); self.dataset.sort($this.data('id')); if ($this.find('span').length) { - $this.find('span').toggleClass( - 'ui-icon-triangle-1-s ui-icon-triangle-1-n'); + $this.find('span').toggleClass( 'ui-icon-triangle-1-s ui-icon-triangle-1-n'); } else { - $this.append('<span class="ui-icon ui-icon-triangle-1-n">') - .siblings('.oe-sortable').find('span').remove(); + $this.append('<span class="ui-icon ui-icon-triangle-1-n">') .siblings('.oe-sortable').find('span').remove(); } self.reload_content(); }); - this.$element.find('.oe-list-pager') - .delegate('button', 'click', function () { - var $this = $(this); - switch ($this.data('pager-action')) { - case 'first': - self.page = 0; break; - case 'last': - self.page = Math.floor( - self.dataset.size() / self.limit()); - break; - case 'next': - self.page += 1; break; - case 'previous': - self.page -= 1; break; - } - self.reload_content(); - }).find('.oe-pager-state') - .click(function (e) { - e.stopPropagation(); + // Add and delete + if (!this.$buttons) { + this.$buttons = $(QWeb.render("ListView.buttons", {'widget':self})); + if (this.options.$buttons) { + this.$buttons.appendTo(this.options.$buttons); + } else { + this.$element.find('.oe_list_buttons').replaceWith(this.$buttons); + } + this.$buttons.find('.oe_list_add') + .click(this.proxy('do_add_record')) + .prop('disabled', grouped && this.options.editable) + .end() + .find('.oe_list_delete') + .click(this.proxy('do_delete_selected')) + .prop('disabled', true); + } + + // Pager + if (!this.$pager) { + this.$pager = $(QWeb.render("ListView.pager", {'widget':self})); + if (this.options.$buttons) { + this.$pager.appendTo(this.options.$pager); + } else { + this.$element.find('.oe_list_pager').replaceWith(this.$pager); + } + + this.$pager + .on('click', 'a[data-pager-action]', function () { var $this = $(this); + var max_page = Math.floor(self.dataset.size() / self.limit()); + switch ($this.data('pager-action')) { + case 'first': + self.page = 0; break; + case 'last': + self.page = max_page - 1; + break; + case 'next': + self.page += 1; break; + case 'previous': + self.page -= 1; break; + } + if (self.page < 0) { + self.page = max_page; + } else if (self.page > max_page) { + self.page = 0; + } + self.reload_content(); + }).find('.oe-pager-state') + .click(function (e) { + e.stopPropagation(); + var $this = $(this); + + var $select = $('<select>') + .appendTo($this.empty()) + .click(function (e) {e.stopPropagation();}) + .append('<option value="80">80</option>' + + '<option value="100">100</option>' + + '<option value="200">200</option>' + + '<option value="500">500</option>' + + '<option value="NaN">' + _t("Unlimited") + '</option>') + .change(function () { + var val = parseInt($select.val(), 10); + self._limit = (isNaN(val) ? null : val); + self.page = 0; + self.reload_content(); + }) + .val(self._limit || 'NaN'); + }); + } - var $select = $('<select>') - .appendTo($this.empty()) - .click(function (e) {e.stopPropagation();}) - .append('<option value="80">80</option>' + - '<option value="100">100</option>' + - '<option value="200">200</option>' + - '<option value="500">500</option>' + - '<option value="NaN">' + _t("Unlimited") + '</option>') - .change(function () { - var val = parseInt($select.val(), 10); - self._limit = (isNaN(val) ? null : val); - self.page = 0; - self.reload_content(); - }) - .val(self._limit || 'NaN'); - }); - if (!this.sidebar && this.options.sidebar && this.options.sidebar_id) { - this.sidebar = new openerp.web.Sidebar(this, this.options.sidebar_id); - this.sidebar.start(); + // Sidebar + if (!this.sidebar && this.options.$sidebar) { + this.sidebar = new instance.web.Sidebar(this); + this.sidebar.appendTo(this.options.$sidebar); this.sidebar.add_toolbar(this.fields_view.toolbar); - this.set_common_sidebar_sections(this.sidebar); } }, /** @@ -287,7 +312,7 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView# * * Horrifying side-effect: sets the dataset's data on this.dataset? * - * @param {openerp.web.DataSet} dataset + * @param {instance.web.DataSet} dataset */ configure_pager: function (dataset) { this.dataset.ids = dataset.ids; @@ -296,28 +321,11 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView# this.dataset._length = dataset._length; } - var limit = this.limit(), - total = dataset.size(), - first = (this.page * limit), - last; - if (!limit || (total - first) < limit) { - last = total; - } else { - last = first + limit; - } - this.$element.find('span.oe-pager-state').empty().text(_.str.sprintf( - _t("[%(first_record)d to %(last_record)d] of %(records_count)d"), { - first_record: first + 1, - last_record: last, - records_count: total - })); + var page = this.page + 1, + total = Math.floor(dataset.size() / this.limit()) + 1; - this.$element - .find('button[data-pager-action=first], button[data-pager-action=previous]') - .attr('disabled', this.page === 0) - .end() - .find('button[data-pager-action=last], button[data-pager-action=next]') - .attr('disabled', last === total); + this.$pager.find('.oe-pager-state').text(isNaN(total) + ? '-' : _.str.sprintf('%d / %d', page, total)); }, /** * Sets up the listview's columns: merges view and fields data, move @@ -328,7 +336,7 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView# * @param {Boolean} [grouped] Should the grouping columns (group and count) be displayed */ setup_columns: function (fields, grouped) { - var domain_computer = openerp.web.form.compute_domain; + var domain_computer = instance.web.form.compute_domain; var noop = function () { return {}; }; var field_to_column = function (field) { @@ -397,7 +405,7 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView# * * The default implementation asks the list view's view manager to switch * to a different view (by calling - * :js:func:`~openerp.web.ViewManager.on_mode_switch`), using the + * :js:func:`~instance.web.ViewManager.on_mode_switch`), using the * provided record index (within the current list view's dataset). * * If the index is null, ``switch_to_record`` asks for the creation of a @@ -407,7 +415,7 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView# * @param {String} [view="page"] the view type to switch to */ select_record:function (index, view) { - view = view || index == null ? 'form' : 'page'; + view = view || index == null ? 'form' : 'form'; this.dataset.index = index; _.delay(_.bind(function () { this.do_switch_view(view); @@ -418,12 +426,24 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView# if (this.sidebar) { this.sidebar.$element.show(); } + if (this.$buttons) { + this.$buttons.show(); + } + if (this.$pager) { + this.$pager.show(); + } }, do_hide: function () { - this._super(); if (this.sidebar) { this.sidebar.$element.hide(); } + if (this.$buttons) { + this.$buttons.hide(); + } + if (this.$pager) { + this.$pager.hide(); + } + this._super(); }, /** * Reloads the list view based on the current settings (dataset & al) @@ -444,7 +464,7 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView# view_id: this.view_id, view_type: "tree", context: this.dataset.get_context(context), - toolbar: this.options.sidebar + toolbar: !!this.options.$sidebar }, callback); } }, @@ -479,7 +499,6 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView# reload: function () { return this.reload_content(); }, - do_load_state: function(state, warm) { var reload = false; if (state.page && this.page !== state.page) { @@ -507,7 +526,7 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView# */ do_search: function (domain, context, group_by) { this.page = 0; - this.groups.datagroup = new openerp.web.DataGroup( + this.groups.datagroup = new instance.web.DataGroup( this, this.model, domain, context, group_by); this.groups.datagroup.sort = this.dataset._sort; @@ -544,16 +563,20 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView# * @param {Array} records selected record values */ do_select: function (ids, records) { - this.$element.find('.oe-list-delete').attr('disabled', !ids.length); + this.$buttons.find('.oe_list_delete').attr('disabled', !ids.length); if (!ids.length) { this.dataset.index = 0; - if (this.sidebar) { this.sidebar.do_fold(); } + if (this.sidebar) { + this.sidebar.$element.hide(); + } this.compute_aggregates(); return; } this.dataset.index = _(this.dataset.ids).indexOf(ids[0]); - if (this.sidebar) { this.sidebar.do_unfold(); } + if (this.sidebar) { + this.sidebar.$element.show(); + } this.compute_aggregates(_(records).map(function (record) { return {count: 1, values: record}; @@ -575,7 +598,7 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView# return; } - var c = new openerp.web.CompoundContext(); + var c = new instance.web.CompoundContext(); c.set_eval_context(_.extend({ active_id: id, active_ids: [id], @@ -592,7 +615,7 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView# * * @param {Number} index index of the record in the dataset * @param {Object} id identifier of the activated record - * @param {openerp.web.DataSet} dataset dataset in which the record is available (may not be the listview's dataset in case of nested groups) + * @param {instance.web.DataSet} dataset dataset in which the record is available (may not be the listview's dataset in case of nested groups) */ do_activate_record: function (index, id, dataset, view) { this.dataset.ids = dataset.ids; @@ -616,11 +639,11 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView# /** * Computes the aggregates for the current list view, either on the * records provided or on the records of the internal - * :js:class:`~openerp.web.ListView.Group`, by calling - * :js:func:`~openerp.web.ListView.group.get_records`. + * :js:class:`~instance.web.ListView.Group`, by calling + * :js:func:`~instance.web.ListView.group.get_records`. * * Then displays the aggregates in the table through - * :js:method:`~openerp.web.ListView.display_aggregates`. + * :js:method:`~instance.web.ListView.display_aggregates`. * * @param {Array} [records] */ @@ -696,7 +719,7 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView# } $footer_cells.filter(_.str.sprintf('[data-field=%s]', column.id)) - .html(openerp.web.format_cell(aggregation, column, { + .html(instance.web.format_cell(aggregation, column, { process_modifiers: false })); }); @@ -747,9 +770,20 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView# .attr('colspan', this.previous_colspan); this.previous_colspan = null; } + }, + no_result: function () { + if (this.groups.group_by + || !this.options.action + || !this.options.action.help) { + return; + } + this.$element.children('table').replaceWith( + $('<div class="oe_listview_nocontent">') + .append($('<img>', { src: '/web/static/src/img/list_empty_arrow.png' })) + .append($('<div>').html(this.options.action.help))); } }); -openerp.web.ListView.List = openerp.web.Class.extend( /** @lends openerp.web.ListView.List# */{ +instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web.ListView.List# */{ /** * List display for the ListView, handles basic DOM events and transforms * them in the relevant higher-level events, to which the list view (or @@ -774,10 +808,10 @@ openerp.web.ListView.List = openerp.web.Class.extend( /** @lends openerp.web.Lis * Triggered when a row of the table is clicked, provides the index (in * the rows array) and id of the selected record to the handle function. * - * @constructs openerp.web.ListView.List - * @extends openerp.web.Class + * @constructs instance.web.ListView.List + * @extends instance.web.Class * - * @param {Object} opts display options, identical to those of :js:class:`openerp.web.ListView` + * @param {Object} opts display options, identical to those of :js:class:`instance.web.ListView` */ init: function (group, opts) { var self = this; @@ -824,7 +858,7 @@ openerp.web.ListView.List = openerp.web.Class.extend( /** @lends openerp.web.Lis this.records.bind(event, callback); }, this); - this.$_element = $('<tbody class="ui-widget-content">') + this.$_element = $('<tbody>') .appendTo(document.body) .delegate('th.oe-record-selector', 'click', function (e) { e.stopPropagation(); @@ -892,7 +926,7 @@ openerp.web.ListView.List = openerp.web.Class.extend( /** @lends openerp.web.Lis // to get a correctly displayable value in the field var model = ref_match[1], id = parseInt(ref_match[2], 10); - new openerp.web.DataSet(this.view, model).name_get([id], function(names) { + new instance.web.DataSet(this.view, model).name_get([id], function(names) { if (!names.length) { return; } record.set(column.id, names[0][1]); }); @@ -907,14 +941,14 @@ openerp.web.ListView.List = openerp.web.Class.extend( /** @lends openerp.web.Lis // fetch the name, set it on the record (in the right field) // and let the various registered events handle refreshing the // row - new openerp.web.DataSet(this.view, column.relation) + new instance.web.DataSet(this.view, column.relation) .name_get([value], function (names) { if (!names.length) { return; } record.set(column.id, names[0]); }); } } - return openerp.web.format_cell(record.toForm().data, column, { + return instance.web.format_cell(record.toForm().data, column, { model: this.dataset.model, id: record.get('id') }); @@ -1068,19 +1102,19 @@ openerp.web.ListView.List = openerp.web.Class.extend( /** @lends openerp.web.Lis }); } }); -openerp.web.ListView.Groups = openerp.web.Class.extend( /** @lends openerp.web.ListView.Groups# */{ +instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.web.ListView.Groups# */{ passtrough_events: 'action deleted row_link', /** * Grouped display for the ListView. Handles basic DOM events and interacts - * with the :js:class:`~openerp.web.DataGroup` bound to it. + * with the :js:class:`~instance.web.DataGroup` bound to it. * * Provides events similar to those of - * :js:class:`~openerp.web.ListView.List` + * :js:class:`~instance.web.ListView.List` * - * @constructs openerp.web.ListView.Groups - * @extends openerp.web.Class + * @constructs instance.web.ListView.Groups + * @extends instance.web.Class * - * @param {openerp.web.ListView} view + * @param {instance.web.ListView} view * @param {Object} [options] * @param {Collection} [options.records] * @param {Object} [options.options] @@ -1191,7 +1225,7 @@ openerp.web.ListView.Groups = openerp.web.Class.extend( /** @lends openerp.web.L self.records.proxy(group.value).reset(); delete self.children[group.value]; } - var child = self.children[group.value] = new openerp.web.ListView.Groups(self.view, { + var child = self.children[group.value] = new instance.web.ListView.Groups(self.view, { records: self.records.proxy(group.value), options: self.options, columns: self.columns @@ -1227,7 +1261,7 @@ openerp.web.ListView.Groups = openerp.web.Class.extend( /** @lends openerp.web.L var group_column = _(self.columns).detect(function (column) { return column.id === group.grouped_on; }); try { - $group_column.html(openerp.web.format_cell( + $group_column.html(instance.web.format_cell( row_data, group_column, { value_if_empty: _t("Undefined"), process_modifiers: false @@ -1264,7 +1298,7 @@ openerp.web.ListView.Groups = openerp.web.Class.extend( /** @lends openerp.web.L var r = {}; r[column.id] = {value: group.aggregates[column.id]}; $('<td class="oe-number">') - .html(openerp.web.format_cell( + .html(instance.web.format_cell( r, column, {process_modifiers: false})) .appendTo($row); } else { @@ -1297,7 +1331,7 @@ openerp.web.ListView.Groups = openerp.web.Class.extend( /** @lends openerp.web.L }, render_dataset: function (dataset) { var self = this, - list = new openerp.web.ListView.List(this, { + list = new instance.web.ListView.List(this, { options: this.options, columns: this.columns, dataset: dataset, @@ -1344,6 +1378,9 @@ openerp.web.ListView.Groups = openerp.web.Class.extend( /** @lends openerp.web.L self.records.add(records, {silent: true}); list.render(); d.resolve(list); + if (_.isEmpty(records)) { + view.no_result(); + } });}); return d.promise(); }, @@ -1510,10 +1547,10 @@ var Events = /** @lends Events# */{ return this; } }; -var Record = openerp.web.Class.extend(/** @lends Record# */{ +var Record = instance.web.Class.extend(/** @lends Record# */{ /** * @constructs Record - * @extends openerp.web.Class + * @extends instance.web.Class * * @mixes Events * @param {Object} [data] @@ -1591,7 +1628,7 @@ var Record = openerp.web.Class.extend(/** @lends Record# */{ } }); Record.include(Events); -var Collection = openerp.web.Class.extend(/** @lends Collection# */{ +var Collection = instance.web.Class.extend(/** @lends Collection# */{ /** * Smarter collections, with events, very strongly inspired by Backbone's. * @@ -1599,7 +1636,7 @@ var Collection = openerp.web.Class.extend(/** @lends Collection# */{ * various serious * * @constructs Collection - * @extends openerp.web.Class + * @extends instance.web.Class * * @mixes Events * @param {Array} [records] records to initialize the collection with @@ -1631,19 +1668,19 @@ var Collection = openerp.web.Class.extend(/** @lends Collection# */{ var records = record instanceof Array ? record : [record]; for(var i=0, length=records.length; i<length; ++i) { - var instance = (records[i] instanceof Record) ? records[i] : new Record(records[i]); - instance.bind(null, this._onRecordEvent); - this._byId[instance.get('id')] = instance; + var instance_ = (records[i] instanceof Record) ? records[i] : new Record(records[i]); + instance_.bind(null, this._onRecordEvent); + this._byId[instance_.get('id')] = instance_; if (options.at == undefined) { - this.records.push(instance); + this.records.push(instance_); if (!options.silent) { - this.trigger('add', this, instance, this.records.length-1); + this.trigger('add', this, instance_, this.records.length-1); } } else { var insertion_index = options.at + i; - this.records.splice(insertion_index, 0, instance); + this.records.splice(insertion_index, 0, instance_); if (!options.silent) { - this.trigger('add', this, instance, insertion_index); + this.trigger('add', this, instance_, insertion_index); } } this.length++; @@ -1774,7 +1811,7 @@ var Collection = openerp.web.Class.extend(/** @lends Collection# */{ } }); Collection.include(Events); -openerp.web.list = { +instance.web.list = { Events: Events, Record: Record, Collection: Collection diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index b8699bcc006..ac19e3f341d 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -2,16 +2,16 @@ * handles editability case for lists, because it depends on form and forms already depends on lists it had to be split out * @namespace */ -openerp.web.list_editable = function (openerp) { +openerp.web.list_editable = function (instance) { var KEY_RETURN = 13, KEY_ESCAPE = 27; - var QWeb = openerp.web.qweb; + var QWeb = instance.web.qweb; // editability status of list rows - openerp.web.ListView.prototype.defaults.editable = null; + instance.web.ListView.prototype.defaults.editable = null; // TODO: not sure second @lends on existing item is correct, to check - openerp.web.ListView.include(/** @lends openerp.web.ListView# */{ + instance.web.ListView.include(/** @lends instance.web.ListView# */{ init: function () { var self = this; this._super.apply(this, arguments); @@ -37,7 +37,7 @@ openerp.web.list_editable = function (openerp) { * * @param {Number} index index of the record in the dataset * @param {Object} id identifier of the record being edited - * @param {openerp.web.DataSet} dataset dataset in which the record is available + * @param {instance.web.DataSet} dataset dataset in which the record is available */ do_edit: function (index, id, dataset) { _.extend(this.dataset, dataset); @@ -93,8 +93,8 @@ openerp.web.list_editable = function (openerp) { } }); - openerp.web.ListView.Groups.include(/** @lends openerp.web.ListView.Groups# */{ - passtrough_events: openerp.web.ListView.Groups.prototype.passtrough_events + " edit saved", + 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(); @@ -113,7 +113,7 @@ openerp.web.list_editable = function (openerp) { } }); - openerp.web.ListView.List.include(/** @lends openerp.web.ListView.List# */{ + instance.web.ListView.List.include(/** @lends instance.web.ListView.List# */{ row_clicked: function (event) { if (!this.options.editable) { return this._super.apply(this, arguments); @@ -235,7 +235,8 @@ openerp.web.list_editable = function (openerp) { } self.edition = true; self.edition_id = record_id; - self.edition_form = new openerp.web.ListEditableFormView(self.view, self.dataset, false); + $new_row.addClass("oe_form_container"); + self.edition_form = new instance.web.ListEditableFormView(self.view, self.dataset, false); self.edition_form.$element = $new_row; self.edition_form.editable_list = self; // HO HO @@ -273,7 +274,7 @@ openerp.web.list_editable = function (openerp) { // insert after the source record var index = self.records.indexOf( self.records.get(source_record_id)) + 1; - record = new openerp.web.list.Record({id: id}); + record = new instance.web.list.Record({id: id}); self.records.add(record, {at: index}); self.dataset.ids.splice(index, 0, id); } @@ -366,52 +367,57 @@ openerp.web.list_editable = function (openerp) { }); } }); - if (!openerp.web.list) { - openerp.web.list = {}; - } - if (!openerp.web.list.form) { - openerp.web.list.form = {}; - } - openerp.web.list.form.WidgetFrame = openerp.web.form.WidgetFrame.extend({ - form_template: 'ListView.row.frame' - }); - var form_widgets = openerp.web.form.widgets; - openerp.web.list.form.widgets = form_widgets.extend({ - 'frame': 'openerp.web.list.form.WidgetFrame' - }); - // All form widgets inherit a problematic behavior from - // openerp.web.form.WidgetFrame: the cell itself is removed when invisible - // whether it's @invisible or @attrs[invisible]. In list view, only the - // former should completely remove the cell. We need to override update_dom - // on all widgets since we can't just hit on widget itself (I think) - var list_form_widgets = openerp.web.list.form.widgets; - _(form_widgets.map).each(function (widget_path, key) { - if (key === 'frame') { return; } - var new_path = 'openerp.web.list.form.' + key; - - openerp.web.list.form[key] = (form_widgets.get_object(key)).extend({ - update_dom: function () { - this.$element.children().css('visibility', ''); - if (this.modifiers.tree_invisible) { - var old_invisible = this.invisible; - this.invisible = true; - this._super(); - this.invisible = old_invisible; - } else if (this.invisible) { - this.$element.children().css('visibility', 'hidden'); - } else { - this._super(); - } - } - }); - list_form_widgets.add(key, new_path); - }); - openerp.web.ListEditableFormView = openerp.web.FormView.extend({ - form_template: 'ListView.row.form', + instance.web.ListEditableFormView = instance.web.FormView.extend({ init: function() { - this._super.apply(this, arguments); - this.registry = openerp.web.list.form.widgets; + this._super.apply(this, arguments); + this.rendering_engine = new instance.web.ListEditableRenderingEngine(this); + }, + renderElement: function() {} + }); + + instance.web.ListEditableRenderingEngine = instance.web.Class.extend({ + init: function(view) { + this.view = view; + }, + set_fields_view: function(fields_view) { + this.fvg = fields_view; + }, + set_tags_registry: function(tags_registry) { + this.tags_registry = tags_registry; + }, + set_fields_registry: function(fields_registry) { + this.fields_registry = fields_registry; + }, + render_to: function($element) { + var self = this; + + var xml = instance.web.json_node_to_xml(this.fvg.arch); + var $xml = $(xml); + + if (this.view.editable_list.options.selectable) + $("<td>").appendTo($element); + if (this.view.editable_list.options.isClarkGable) + $("<td>").appendTo($element); + + $xml.children().each(function(i, el) { + var modifiers = JSON.parse($(el).attr("modifiers") || "{}"); + var $td = $("<td>"); + if (modifiers.tree_invisible === true) + $td.hide(); + var tag_name = el.tagName.toLowerCase(); + var key = tag_name; + if (tag_name === "field") { + var name = $(el).attr("name"); + key = $(el).attr('widget') || self.fvg.fields[name].type; + } + var obj = self.view.fields_registry.get_object(key); + var w = new (obj)(self.view, instance.web.xml_to_json(el)); + self.view.register_field(w, $(el).attr("name")); + w.appendTo($td); + $td.appendTo($element); + }); + $("<td><button class='oe-edit-row-save' type='button'></button></td>").appendTo($element); }, }); }; diff --git a/addons/web/static/src/js/view_page.js b/addons/web/static/src/js/view_page.js deleted file mode 100644 index 8949169838a..00000000000 --- a/addons/web/static/src/js/view_page.js +++ /dev/null @@ -1,288 +0,0 @@ -openerp.web.page = function (openerp) { - var _t = openerp.web._t, - _lt = openerp.web._lt; - - openerp.web.views.add('page', 'openerp.web.PageView'); - openerp.web.PageView = openerp.web.FormView.extend({ - form_template: "PageView", - display_name: _lt('Page'), - init: function () { - this._super.apply(this, arguments); - this.registry = openerp.web.page.readonly; - }, - reload: function () { - if (this.dataset.index == null) { - this.do_prev_view(); - return $.Deferred().reject().promise(); - } - return this._super(); - }, - on_loaded: function(data) { - this._super(data); - this.$form_header.find('button.oe_form_button_edit').click(this.on_button_edit); - this.$form_header.find('button.oe_form_button_create').click(this.on_button_create); - this.$form_header.find('button.oe_form_button_duplicate').click(this.on_button_duplicate); - this.$form_header.find('button.oe_form_button_delete').click(this.on_button_delete); - }, - on_button_edit: function() { - return this.do_switch_view('form'); - }, - on_button_create: function() { - this.dataset.index = null; - return this.do_switch_view('form'); - }, - on_button_duplicate: function() { - var self = this; - var def = $.Deferred(); - $.when(this.has_been_loaded).then(function() { - self.dataset.call('copy', [self.datarecord.id, {}, self.dataset.context]).then(function(new_id) { - return self.on_created({ result : new_id }); - }).then(function() { - return self.do_switch_view('form'); - }).then(function() { - def.resolve(); - }); - }); - return def.promise(); - }, - on_button_delete: function() { - var self = this; - var def = $.Deferred(); - $.when(this.has_been_loaded).then(function() { - if (self.datarecord.id && confirm(_t("Do you really want to delete this record?"))) { - self.dataset.unlink([self.datarecord.id]).then(function() { - self.on_pager_action('next'); - def.resolve(); - }); - } else { - $.async_when().then(function () { - def.reject(); - }) - } - }); - return def.promise(); - } - }); - - /** @namespace */ - openerp.web.page = {}; - - openerp.web.page.WidgetFrameReadonly = openerp.web.form.WidgetFrame.extend({ - form_template: 'WidgetFrame.readonly' - }); - openerp.web.page.FieldReadonly = openerp.web.form.Field.extend({ - - }); - openerp.web.page.FieldCharReadonly = openerp.web.page.FieldReadonly.extend({ - form_template: 'FieldChar.readonly', - init: function(view, node) { - this._super(view, node); - this.password = this.node.attrs.password === 'True' || this.node.attrs.password === '1'; - }, - set_value: function (value) { - this._super.apply(this, arguments); - var show_value = openerp.web.format_value(value, this, ''); - if (this.password) { - show_value = new Array(show_value.length + 1).join('*'); - } - this.$element.find('div').text(show_value); - return show_value; - } - }); - openerp.web.page.FieldFloatReadonly = openerp.web.page.FieldCharReadonly.extend({ - init: function (view, node) { - this._super(view, node); - if (node.attrs.digits) { - this.digits = py.eval(node.attrs.digits); - } else { - this.digits = view.fields_view.fields[node.attrs.name].digits; - } - } - }); - openerp.web.page.FieldURIReadonly = openerp.web.page.FieldCharReadonly.extend({ - form_template: 'FieldURI.readonly', - scheme: null, - format_value: function (value) { - return value; - }, - set_value: function (value) { - if (!value) { - this.$element.find('a').text('').attr('href', '#'); - return; - } - this.$element.find('a') - .attr('href', this.scheme + ':' + value) - .text(this.format_value(value)); - } - }); - openerp.web.page.FieldEmailReadonly = openerp.web.page.FieldURIReadonly.extend({ - scheme: 'mailto' - }); - openerp.web.page.FieldUrlReadonly = openerp.web.page.FieldURIReadonly.extend({ - set_value: function (value) { - if (!value) { - this.$element.find('a').text('').attr('href', '#'); - return; - } - var s = /(\w+):(.+)/.exec(value); - if (!s) { - value = "http://" + value; - } - this.$element.find('a').attr('href', value).text(value); - } - }); - openerp.web.page.FieldBooleanReadonly = openerp.web.form.FieldBoolean.extend({ - update_dom: function() { - this._super.apply(this, arguments); - this.$element.find('input').prop('disabled', true); - } - }); - openerp.web.page.FieldSelectionReadonly = openerp.web.page.FieldReadonly.extend({ - form_template: 'FieldChar.readonly', - init: function(view, node) { - // lifted straight from r/w version - var self = this; - this._super(view, node); - this.values = _.clone(this.field.selection); - _.each(this.values, function(v, i) { - if (v[0] === false && v[1] === '') { - self.values.splice(i, 1); - } - }); - this.values.unshift([false, '']); - }, - set_value: function (value) { - value = value === null ? false : value; - value = value instanceof Array ? value[0] : value; - var option = _(this.values) - .detect(function (record) { return record[0] === value; }); - this._super(value); - this.$element.find('div').text(option ? option[1] : this.values[0][1]); - } - }); - openerp.web.page.FieldMany2OneReadonly = openerp.web.page.FieldURIReadonly.extend({ - set_value: function (value) { - value = value || null; - this.invalid = false; - var self = this; - this.value = value; - self.update_dom(); - self.on_value_changed(); - var real_set_value = function(rval) { - self.value = rval; - self.$element.find('a') - .unbind('click') - .text(rval ? rval[1] : '') - .click(function () { - self.do_action({ - type: 'ir.actions.act_window', - res_model: self.field.relation, - res_id: self.value[0], - context: self.build_context(), - views: [[false, 'page'], [false, 'form']], - target: 'current' - }); - return false; - }); - }; - if (value && !(value instanceof Array)) { - new openerp.web.DataSet( - this, this.field.relation, self.build_context()) - .name_get([value], function(data) { - real_set_value(data[0]); - }); - } else { - $.async_when().then(function() {real_set_value(value);}); - } - }, - get_value: function() { - if (!this.value) { - return false; - } else if (this.value instanceof Array) { - return this.value[0]; - } else { - return this.value; - } - } - }); - openerp.web.page.FieldReferenceReadonly = openerp.web.page.FieldMany2OneReadonly.extend({ - set_value: function (value) { - if (!value) { - return this._super(null); - } - var reference = value.split(','); - this.field.relation = reference[0]; - var id = parseInt(reference[1], 10); - return this._super(id); - }, - get_value: function () { - if (!this.value) { - return null; - } - var id; - if (typeof this.value === 'number') { - // name_get has not run yet - id = this.value; - } else { - id = this.value[0]; - } - return _.str.sprintf('%s,%d', this.field.relation, id); - } - }); - - openerp.web.page.FieldMany2ManyReadonly = openerp.web.form.FieldMany2Many.extend({ - force_readonly: true - }); - openerp.web.page.FieldOne2ManyReadonly = openerp.web.form.FieldOne2Many.extend({ - force_readonly: true - }); - openerp.web.page.FieldBinaryImageReaonly = openerp.web.form.FieldBinaryImage.extend({ - update_dom: function() { - this._super.apply(this, arguments); - this.$element.find('.oe-binary').hide(); - } - }); - openerp.web.page.FieldBinaryFileReadonly = openerp.web.form.FieldBinary.extend({ - form_template: 'FieldURI.readonly', - start: function() { - this._super.apply(this, arguments); - var self = this; - this.$element.find('a').click(function() { - if (self.value) { - self.on_save_as(); - } - return false; - }); - }, - set_value: function(value) { - this._super.apply(this, arguments); - this.$element.find('a').show(!!value); - if (value) { - var show_value = _t("Download") + " " + (this.view.datarecord[this.node.attrs.filename] || ''); - this.$element.find('a').text(show_value); - } - } - }); - openerp.web.page.readonly = openerp.web.form.widgets.extend({ - 'frame': 'openerp.web.page.WidgetFrameReadonly', - 'char': 'openerp.web.page.FieldCharReadonly', - 'id': 'openerp.web.page.FieldCharReadonly', - 'email': 'openerp.web.page.FieldEmailReadonly', - 'url': 'openerp.web.page.FieldUrlReadonly', - 'text': 'openerp.web.page.FieldCharReadonly', - 'date': 'openerp.web.page.FieldCharReadonly', - 'datetime': 'openerp.web.page.FieldCharReadonly', - 'selection' : 'openerp.web.page.FieldSelectionReadonly', - 'many2one': 'openerp.web.page.FieldMany2OneReadonly', - 'many2many' : 'openerp.web.page.FieldMany2ManyReadonly', - 'one2many' : 'openerp.web.page.FieldOne2ManyReadonly', - 'one2many_list' : 'openerp.web.page.FieldOne2ManyReadonly', - 'reference': 'openerp.web.page.FieldReferenceReadonly', - 'boolean': 'openerp.web.page.FieldBooleanReadonly', - 'float': 'openerp.web.page.FieldFloatReadonly', - 'integer': 'openerp.web.page.FieldCharReadonly', - 'float_time': 'openerp.web.page.FieldCharReadonly', - 'binary': 'openerp.web.page.FieldBinaryFileReadonly', - 'image': 'openerp.web.page.FieldBinaryImageReaonly' - }); -}; diff --git a/addons/web/static/src/js/view_tree.js b/addons/web/static/src/js/view_tree.js index 6039e4a0822..9e938fe293a 100644 --- a/addons/web/static/src/js/view_tree.js +++ b/addons/web/static/src/js/view_tree.js @@ -2,12 +2,12 @@ * OpenERP web library *---------------------------------------------------------*/ -openerp.web.view_tree = function(openerp) { -var QWeb = openerp.web.qweb, - _lt = openerp.web._lt; +openerp.web.view_tree = function(instance) { +var QWeb = instance.web.qweb, + _lt = instance.web._lt; -openerp.web.views.add('tree', 'openerp.web.TreeView'); -openerp.web.TreeView = openerp.web.View.extend(/** @lends openerp.web.TreeView# */{ +instance.web.views.add('tree', 'instance.web.TreeView'); +instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeView# */{ display_name: _lt('Tree'), /** * Indicates that this view is not searchable, and thus that no search @@ -17,8 +17,8 @@ openerp.web.TreeView = openerp.web.View.extend(/** @lends openerp.web.TreeView# /** * Genuine tree view (the one displayed as a tree, not the list) * - * @constructs openerp.web.TreeView - * @extends openerp.web.View + * @constructs instance.web.TreeView + * @extends instance.web.View * * @param parent * @param dataset @@ -39,7 +39,6 @@ openerp.web.TreeView = openerp.web.View.extend(/** @lends openerp.web.TreeView# }, start: function () { - this._super(); return this.rpc("/web/treeview/load", { model: this.model, view_id: this.view_id, @@ -204,7 +203,7 @@ openerp.web.TreeView = openerp.web.View.extend(/** @lends openerp.web.TreeView# 'fields_view': self.fields_view.arch.children, 'fields': self.fields, 'level': $curr_node.data('level') || 0, - 'render': openerp.web.format_value, + 'render': instance.web.format_value, 'color_for': self.color_for }); @@ -227,12 +226,12 @@ openerp.web.TreeView = openerp.web.View.extend(/** @lends openerp.web.TreeView# return this.rpc('/web/treeview/action', { id: id, model: this.dataset.model, - context: new openerp.web.CompoundContext( + context: new instance.web.CompoundContext( this.dataset.get_context(), local_context) }).pipe(function (actions) { if (!actions.length) { return; } var action = actions[0][2]; - var c = new openerp.web.CompoundContext(local_context); + var c = new instance.web.CompoundContext(local_context); if (action.context) { c.add(action.context); } diff --git a/addons/web/static/src/js/views.js b/addons/web/static/src/js/views.js index 43ecc8444fc..36e3c2b802b 100644 --- a/addons/web/static/src/js/views.js +++ b/addons/web/static/src/js/views.js @@ -2,21 +2,11 @@ * OpenERP web library *---------------------------------------------------------*/ -openerp.web.views = function(session) { -var QWeb = session.web.qweb, - _t = session.web._t; +openerp.web.views = function(instance) { +var QWeb = instance.web.qweb, + _t = instance.web._t; -/** - * Registry for all the client actions key: tag value: widget - */ -session.web.client_actions = new session.web.Registry(); - -/** - * Registry for all the main views - */ -session.web.views = new session.web.Registry(); - -session.web.ActionManager = session.web.OldWidget.extend({ +instance.web.ActionManager = instance.web.Widget.extend({ init: function(parent) { this._super(parent); this.inner_action = null; @@ -25,9 +15,6 @@ session.web.ActionManager = session.web.OldWidget.extend({ this.dialog_viewmanager = null; this.client_widget = null; }, - render: function() { - return '<div id="' + this.element_id + '" style="height: 100%;"></div>'; - }, dialog_stop: function () { if (this.dialog) { this.dialog_viewmanager.destroy(); @@ -65,8 +52,8 @@ session.web.ActionManager = session.web.OldWidget.extend({ if (run_action) { this.null_action(); action_loaded = this.do_action(state.action_id); - session.webclient.menu.has_been_loaded.then(function() { - session.webclient.menu.open_action(state.action_id); + instance.webclient.menu.has_been_loaded.then(function() { + instance.webclient.menu.open_action(state.action_id); }); } } else if (state.model && state.id) { @@ -76,7 +63,7 @@ session.web.ActionManager = session.web.OldWidget.extend({ res_model: state.model, res_id: state.id, type: 'ir.actions.act_window', - views: [[false, 'page'], [false, 'form']] + views: [[false, 'form']] }; action_loaded = this.do_action(action); } else if (state.sa) { @@ -112,12 +99,13 @@ session.web.ActionManager = session.web.OldWidget.extend({ } var type = action.type.replace(/\./g,'_'); var popup = action.target === 'new'; + var inline = action.target === 'inline'; action.flags = _.extend({ - views_switcher : !popup, - search_view : !popup, - action_buttons : !popup, - sidebar : !popup, - pager : !popup, + views_switcher : !popup && !inline, + search_view : !popup && !inline, + action_buttons : !popup && !inline, + sidebar : !popup && !inline, + pager : !popup && !inline, display_title : !popup }, action.flags || {}); if (!(type in this)) { @@ -136,31 +124,31 @@ session.web.ActionManager = session.web.OldWidget.extend({ .contains(action.res_model)) { var old_close = on_close; on_close = function () { - session.webclient.do_reload().then(old_close); + instance.webclient.do_reload().then(old_close); }; } if (action.target === 'new') { if (this.dialog == null) { - this.dialog = new session.web.Dialog(this, { width: '80%' }); + this.dialog = new instance.web.Dialog(this, { width: '80%' }); if(on_close) this.dialog.on_close.add(on_close); } else { this.dialog_viewmanager.destroy(); } this.dialog.dialog_title = action.name; - this.dialog_viewmanager = new session.web.ViewManagerAction(this, action); + this.dialog_viewmanager = new instance.web.ViewManagerAction(this, action); this.dialog_viewmanager.appendTo(this.dialog.$element); this.dialog.open(); } else { if(action.menu_id) { return this.getParent().do_action(action, function () { - session.webclient.menu.open_menu(action.menu_id); + instance.webclient.menu.open_menu(action.menu_id); }); } this.dialog_stop(); this.content_stop(); this.inner_action = action; - this.inner_viewmanager = new session.web.ViewManagerAction(this, action); + this.inner_viewmanager = new instance.web.ViewManagerAction(this, action); this.inner_viewmanager.appendTo(this.$element); } }, @@ -182,7 +170,7 @@ session.web.ActionManager = session.web.OldWidget.extend({ ir_actions_client: function (action) { this.content_stop(); this.dialog_stop(); - var ClientWidget = session.web.client_actions.get_object(action.tag); + var ClientWidget = instance.web.client_actions.get_object(action.tag); (this.client_widget = new ClientWidget(this, action.params)).appendTo(this.$element); }, ir_actions_report_xml: function(action, on_closed) { @@ -204,7 +192,7 @@ session.web.ActionManager = session.web.OldWidget.extend({ } self.dialog_stop(); }, - error: session.webclient.crashmanager.on_rpc_error + error: instance.webclient.crashmanager.on_rpc_error }) }); }, @@ -216,16 +204,8 @@ session.web.ActionManager = session.web.OldWidget.extend({ } }); -session.web.ViewManager = session.web.OldWidget.extend(/** @lends session.web.ViewManager# */{ +instance.web.ViewManager = instance.web.Widget.extend({ template: "ViewManager", - /** - * @constructs session.web.ViewManager - * @extends session.web.OldWidget - * - * @param parent - * @param dataset - * @param views - */ init: function(parent, dataset, views, flags) { this._super(parent); this.model = dataset ? dataset.model : undefined; @@ -234,7 +214,7 @@ session.web.ViewManager = session.web.OldWidget.extend(/** @lends session.web.V this.active_view = null; this.views_src = _.map(views, function(x) { if (x instanceof Array) { - var View = session.web.views.get_object(x[1], true); + var View = instance.web.views.get_object(x[1], true); return { view_id: x[0], view_type: x[1], @@ -246,22 +226,16 @@ session.web.ViewManager = session.web.OldWidget.extend(/** @lends session.web.V }); this.views = {}; this.flags = flags || {}; - this.registry = session.web.views; + this.registry = instance.web.views; this.views_history = []; }, - render: function() { - return session.web.qweb.render(this.template, { - self: this, - prefix: this.element_id, - views: this.views_src}); - }, /** * @returns {jQuery.Deferred} initial view loading promise */ start: function() { this._super(); var self = this; - this.$element.find('.oe_vm_switch button').click(function() { + this.$element.find('.oe_view_manager_switch a').click(function() { self.on_mode_switch($(this).data('view-type')); }); var views_ids = {}; @@ -270,7 +244,9 @@ session.web.ViewManager = session.web.OldWidget.extend(/** @lends session.web.V deferred : $.Deferred(), controller : null, options : _.extend({ - sidebar_id : self.element_id + '_sidebar_' + view.view_type, + $buttons : self.$element.find('.oe_view_manager_buttons'), + $sidebar : self.$element.find('.oe_view_manager_sidebar'), + $pager : self.$element.find('.oe_view_manager_pager'), action : self.action, action_views_ids : views_ids }, self.flags, self.flags[view.view_type] || {}, view.options || {}) @@ -278,7 +254,7 @@ session.web.ViewManager = session.web.OldWidget.extend(/** @lends session.web.V views_ids[view.view_type] = view.view_id; }); if (this.flags.views_switcher === false) { - this.$element.find('.oe_vm_switch').hide(); + this.$element.find('.oe_view_manager_switch').hide(); } // If no default view defined, switch to the first one in sequence var default_view = this.flags.default_view || this.views_src[0].view_type; @@ -292,9 +268,9 @@ session.web.ViewManager = session.web.OldWidget.extend(/** @lends session.web.V * @returns {jQuery.Deferred} new view loading promise */ on_mode_switch: function(view_type, no_store) { - var self = this, - view = this.views[view_type], - view_promise; + var self = this; + var view = this.views[view_type]; + var view_promise; if(!view) return $.Deferred().reject(); @@ -306,13 +282,22 @@ session.web.ViewManager = session.web.OldWidget.extend(/** @lends session.web.V if (!view.controller) { // Lazy loading of views var controllerclass = this.registry.get_object(view_type); - var controller = new controllerclass(this, this.dataset, view.view_id, view.options); + var options = _.clone(view.options); + if (view_type === "form" && this.action) { + switch (this.action.target) { + case 'new': + case 'inline': + options.initial_mode = 'edit'; + break; + } + } + var controller = new controllerclass(this, this.dataset, view.view_id, options); if (view.embedded_view) { controller.set_embedded_view(view.embedded_view); } controller.do_switch_view.add_last(this.on_mode_switch); controller.do_prev_view.add_last(this.on_prev_view); - var container = $("#" + this.element_id + '_view_' + view_type); + var container = this.$element.find(".oe_view_manager_view_" + view_type); view_promise = controller.appendTo(container); this.views[view_type].controller = controller; this.views[view_type].deferred.resolve(view_type); @@ -335,9 +320,10 @@ session.web.ViewManager = session.web.OldWidget.extend(/** @lends session.web.V } this.$element - .find('.oe_vm_switch button').removeAttr('disabled') - .filter('[data-view-type="' + view_type + '"]') - .attr('disabled', true); + .find('.oe_view_manager_switch a').parent().removeClass('active') + this.$element + .find('.oe_view_manager_switch a').filter('[data-view-type="' + view_type + '"]') + .parent().addClass('active'); $.when(view_promise).then(function () { _.each(_.keys(self.views), function(view_name) { @@ -367,15 +353,16 @@ session.web.ViewManager = session.web.OldWidget.extend(/** @lends session.web.V * @returns {$.Deferred} switching end signal */ on_prev_view: function (options) { + options = options || {}; var current_view = this.views_history.pop(); var previous_view = this.views_history[this.views_history.length - 1] || options['default']; if (options.created && current_view === 'form' && previous_view === 'list') { // APR special case: "If creation mode from list (and only from a list), // after saving, go to page view (don't come back in list)" - return this.on_mode_switch('page'); + return this.on_mode_switch('form'); } else if (options.created && !previous_view && this.action && this.action.flags.default_view === 'form') { // APR special case: "If creation from dashboard, we have no previous view - return this.on_mode_switch('page'); + return this.on_mode_switch('form'); } return this.on_mode_switch(previous_view, true); }, @@ -390,12 +377,10 @@ session.web.ViewManager = session.web.OldWidget.extend(/** @lends session.web.V if (this.searchview) { this.searchview.destroy(); } - this.searchview = new session.web.SearchView( - this, this.dataset, - view_id, search_defaults, this.flags.search_view === false); + this.searchview = new instance.web.SearchView(this, this.dataset, view_id, search_defaults, this.flags.search_view === false); this.searchview.on_search.add(this.do_searchview_search); - return this.searchview.appendTo($("#" + this.element_id + "_search")); + return this.searchview.appendTo(this.$element.find(".oe_view_manager_view_search")); }, do_searchview_search: function(domains, contexts, groupbys) { var self = this, @@ -451,13 +436,13 @@ session.web.ViewManager = session.web.OldWidget.extend(/** @lends session.web.V } }); -session.web.ViewManagerAction = session.web.ViewManager.extend(/** @lends oepnerp.web.ViewManagerAction# */{ +instance.web.ViewManagerAction = instance.web.ViewManager.extend({ template:"ViewManagerAction", /** - * @constructs session.web.ViewManagerAction - * @extends session.web.ViewManager + * @constructs instance.web.ViewManagerAction + * @extends instance.web.ViewManager * - * @param {session.web.ActionManager} parent parent object/widget + * @param {instance.web.ActionManager} parent parent object/widget * @param {Object} action descriptor for the action this viewmanager needs to manage its views. */ init: function(parent, action) { @@ -482,7 +467,7 @@ session.web.ViewManagerAction = session.web.ViewManager.extend(/** @lends oepner this._super(parent, null, action.views, flags); this.session = parent.session; this.action = action; - var dataset = new session.web.DataSetSearch(this, action.res_model, action.context, action.domain); + var dataset = new instance.web.DataSetSearch(this, action.res_model, action.context, action.domain); if (action.res_id) { dataset.ids.push(action.res_id); dataset.index = 0; @@ -523,7 +508,7 @@ session.web.ViewManagerAction = session.web.ViewManager.extend(/** @lends oepner this.$element.find('.oe_debug_view').change(this.on_debug_changed); if (this.action.help && !this.flags.low_profile) { - var Users = new session.web.DataSet(self, 'res.users'), + var Users = new instance.web.DataSet(self, 'res.users'), $tips = this.$element.find('.oe_view_manager_menu_tips'); $tips.delegate('blockquote button', 'click', function() { var $this = $(this); @@ -547,17 +532,6 @@ session.web.ViewManagerAction = session.web.ViewManager.extend(/** @lends oepner } } - var $res_logs = this.$element.find('.oe-view-manager-logs:first'); - $res_logs.delegate('a.oe-more-logs', 'click', function () { - $res_logs.removeClass('oe-folded'); - return false; - }).delegate('a.oe-remove-everything', 'click', function () { - $res_logs.removeClass('oe-has-more').find('ul').empty(); - $res_logs.css('display','none'); - return false; - }); - $res_logs.css('display','none'); - return manager_ready; }, on_debug_changed: function (evt) { @@ -568,23 +542,26 @@ session.web.ViewManagerAction = session.web.ViewManager.extend(/** @lends oepner current_view = this.views[this.active_view].controller; switch (val) { case 'fvg': - var dialog = new session.web.Dialog(this, { title: _t("Fields View Get"), width: '95%' }).open(); - $('<pre>').text(session.web.json_node_to_xml(current_view.fields_view.arch, true)).appendTo(dialog.$element); + var dialog = new instance.web.Dialog(this, { title: _t("Fields View Get"), width: '95%' }).open(); + $('<pre>').text(instance.web.json_node_to_xml(current_view.fields_view.arch, true)).appendTo(dialog.$element); break; case 'perm_read': var ids = current_view.get_selected_ids(); if (ids.length === 1) { this.dataset.call('perm_read', [ids]).then(function(result) { - var dialog = new session.web.Dialog(this, { + var dialog = new instance.web.Dialog(this, { title: _.str.sprintf(_t("View Log (%s)"), self.dataset.model), width: 400 }, QWeb.render('ViewManagerDebugViewLog', { perm : result[0], - format : session.web.format_value + format : instance.web.format_value })).open(); }); } break; + case 'toggle_layout_outline': + current_view.rendering_engine.toggle_layout_debugging(); + break; case 'fields': this.dataset.call_and_eval( 'fields_get', [false, {}], null, 1).then(function (fields) { @@ -602,7 +579,7 @@ session.web.ViewManagerAction = session.web.ViewManager.extend(/** @lends oepner .append($('<dd style="white-space: pre-wrap;">').text(def)); }); }); - new session.web.Dialog(self, { + new instance.web.Dialog(self, { title: _.str.sprintf(_t("Model %s fields"), self.dataset.model), width: '95%'}, $root).open(); @@ -610,7 +587,7 @@ session.web.ViewManagerAction = session.web.ViewManager.extend(/** @lends oepner break; case 'manage_views': if (current_view.fields_view && current_view.fields_view.arch) { - var view_editor = new session.web.ViewEditor(current_view, current_view.$element, this.dataset, current_view.fields_view.arch); + var view_editor = new instance.web.ViewEditor(current_view, current_view.$element, this.dataset, current_view.fields_view.arch); view_editor.start(); } else { this.do_warn(_t("Manage Views"), @@ -661,8 +638,6 @@ session.web.ViewManagerAction = session.web.ViewManager.extend(/** @lends oepner return $.when(this._super(view_type, no_store)).then(function () { self.shortcut_check(self.views[view_type]); - self.$element.find('.oe-view-manager-logs:first').addClass('oe-folded').removeClass('oe-has-more').css('display','none').find('ul').empty(); - var controller = self.views[self.active_view].controller, fvg = controller.fields_view, view_id = (fvg && fvg.view_id) || '--'; @@ -744,127 +719,88 @@ session.web.ViewManagerAction = session.web.ViewManager.extend(/** @lends oepner } }); }, - /** - * Intercept do_action resolution from children views - */ - on_action_executed: function () { - return new session.web.DataSet(this, 'res.log') - .call('get', [], this.do_display_log); - }, - /** - * @param {Array<Object>} log_records - */ - do_display_log: function (log_records) { - var self = this; - var cutoff = 3; - var $logs = this.$element.find('.oe-view-manager-logs:first').addClass('oe-folded').css('display', 'block'); - var $logs_list = $logs.find('ul').empty(); - $logs.toggleClass('oe-has-more', log_records.length > cutoff); - _(log_records.reverse()).each(function (record) { - var context = {}; - if (record.context) { - try { context = py.eval(record.context); } - catch (e) { /* TODO: what do I do now? */ } - } - $(_.str.sprintf('<li><a href="#">%s</a></li>', record.name)) - .appendTo($logs_list) - .delegate('a', 'click', function () { - self.do_action({ - type: 'ir.actions.act_window', - res_model: record.res_model, - res_id: record.res_id, - // TODO: need to have an evaluated context here somehow - context: context, - views: [[context.view_id || false, 'form']] - }); - return false; - }); - }); - }, display_title: function () { return this.action.name; } }); -session.web.Sidebar = session.web.OldWidget.extend({ - init: function(parent, element_id) { - this._super(parent, element_id); - this.items = {}; - this.sections = {}; +instance.web.Sidebar = instance.web.Widget.extend({ + init: function(parent) { + this._super(parent); + var view = this.getParent(); + this.sections = [ + { 'name' : 'print', 'label' : _t('Print'), }, + { 'name' : 'files', 'label' : _t('Attachement'), }, + { 'name' : 'other', 'label' : _t('More'), } + ]; + this.items = { + 'print' : [], + 'files' : [], + 'other' : [ + { label: _t("Import"), callback: view.on_sidebar_import }, + { label: _t("Export"), callback: view.on_sidebar_export } + ] + } + if (this.session.uid === 1) { + var item = { label: _t("Translate"), callback: view.on_sidebar_translate, title: _t("Technical translation") }; + this.items.other.push(item); + } }, start: function() { - this._super(this); var self = this; - this.$element.html(session.web.qweb.render('Sidebar')); - this.$element.find(".toggle-sidebar").click(function(e) { - self.do_toggle(); + this._super(this); + this.redraw(); + this.$element.on('click','.oe_dropdown_toggle',function(event) { + $(this).parent().find('ul').toggle(); + return false; }); - }, - add_default_sections: function() { - var self = this, - view = this.getParent(), - view_manager = view.getParent(), - action = view_manager.action; - if (this.session.uid === 1) { - this.add_section(_t('Customize'), 'customize'); - this.add_items('customize', [{ - label: _t("Translate"), - callback: view.on_sidebar_translate, - title: _t("Technical translation") - }]); - } - - this.add_section(_t('Other Options'), 'other'); - this.add_items('other', [ - { - label: _t("Import"), - callback: view.on_sidebar_import - }, { - label: _t("Export"), - callback: view.on_sidebar_export + this.$element.on('click','.oe_dropdown_menu li a', function(event) { + var section = $(this).data('section'); + var index = $(this).data('index'); + $(this).closest('ul').hide(); + var item = self.items[section][index]; + if (item.callback) { + item.callback.apply(self, [item]); + } else if (item.action) { + self.on_item_action_clicked(item); + } else if (item.url) { + return true; } - ]); + return false; + }); + //this.$div.html(QWeb.render('FormView.sidebar.attachments', this)); + //this.$element.find('.oe-binary-file').change(this.on_attachment_changed); + //this.$element.find('.oe-sidebar-attachment-delete').click(this.on_attachment_delete); + }, + redraw: function() { + var self = this; + self.$element.html(QWeb.render('Sidebar', {widget: self})); + this.$element.find('ul').hide(); + }, + add_section: function() { + var self = this; }, - add_toolbar: function(toolbar) { var self = this; - _.each([['print', _t("Reports")], ['action', _t("Actions")], ['relate', _t("Links")]], function(type) { - var items = toolbar[type[0]]; - if (items.length) { + _.each(['print','action','relate'], function(type) { + var items = toolbar[type]; + if (items) { for (var i = 0; i < items.length; i++) { items[i] = { label: items[i]['name'], action: items[i], - classname: 'oe_sidebar_' + type[0] + classname: 'oe_sidebar_' + type } } - self.add_section(type[1], type[0]); - self.add_items(type[0], items); + self.add_items(type=='print' ? 'print' : 'other', items); } }); }, - - add_section: function(name, code) { - if(!code) code = _.str.underscored(name); - var $section = this.sections[code]; - - if(!$section) { - var section_id = _.uniqueId(this.element_id + '_section_' + code + '_'); - $section = $(session.web.qweb.render("Sidebar.section", { - section_id: section_id, - name: name, - classname: 'oe_sidebar_' + code - })); - $section.appendTo(this.$element.find('div.sidebar-actions')); - this.sections[code] = $section; - } - return $section; - }, /** * For each item added to the section: * * ``label`` - * will be used as the item's name in the sidebar + * will be used as the item's name in the sidebar, can be html * * ``action`` * descriptor for the action which will be executed, ``action`` and @@ -886,34 +822,10 @@ session.web.Sidebar = session.web.OldWidget.extend({ * @param {Array<{label, action | callback[, classname][, title]}>} items */ add_items: function(section_code, items) { - var self = this, - $section = this.add_section(_.str.titleize(section_code.replace('_', ' ')), section_code), - section_id = $section.attr('id'); - + var self = this; if (items) { - for (var i = 0; i < items.length; i++) { - items[i].element_id = _.uniqueId(section_id + '_item_'); - this.items[items[i].element_id] = items[i]; - } - - var $items = $(session.web.qweb.render("Sidebar.section.items", {items: items})); - - $items.find('a.oe_sidebar_action_a').click(function() { - var item = self.items[$(this).attr('id')]; - if (item.callback) { - item.callback.apply(self, [item]); - } - if (item.action) { - self.on_item_action_clicked(item); - } - return false; - }); - - var $ul = $section.find('ul'); - if(!$ul.length) { - $ul = $('<ul/>').appendTo($section); - } - $items.appendTo($ul); + this.items[section_code].push.apply(this.items[section_code],items); + this.redraw(); } }, on_item_action_clicked: function(item) { @@ -921,11 +833,7 @@ session.web.Sidebar = session.web.OldWidget.extend({ self.getParent().sidebar_context().then(function (context) { var ids = self.getParent().get_selected_ids(); if (ids.length == 0) { - //TODO: make prettier warning? - openerp.web.dialog($("<div />").text(_t("You must choose at least one record.")), { - title: _t("Warning"), - modal: true - }); + instance.web.dialog($("<div />").text(_t("You must choose at least one record.")), { title: _t("Warning"), modal: true }); return false; } var additional_context = _.extend({ @@ -948,18 +856,60 @@ session.web.Sidebar = session.web.OldWidget.extend({ }); }); }, - do_fold: function() { - this.$element.addClass('closed-sidebar').removeClass('open-sidebar'); + do_attachement_update: function(dataset, model_id) { + if (!model_id) { + this.on_attachments_loaded([]); + } else { + var dom = [ ['res_model', '=', dataset.model], ['res_id', '=', model_id], ['type', 'in', ['binary', 'url']] ]; + var ds = new instance.web.DataSetSearch(this, 'ir.attachment', dataset.get_context(), dom); + ds.read_slice(['name', 'url', 'type'], {}).then(this.on_attachments_loaded); + } }, - do_unfold: function() { - this.$element.addClass('open-sidebar').removeClass('closed-sidebar'); + on_attachments_loaded: function(attachments) { + var self = this; + var items = []; + // TODO: preprend: _s + + var prefix = '/web/binary/saveas?session_id=' + self.session.session_id + '&model=ir.attachment&field=datas&filename_field=name&id='; + _.each(attachments,function(a) { + a.label = a.name; + if(a.type === "binary") { + a.url = prefix + a.id + '&t=' + (new Date().getTime()); + } + }); + attachments.push( { label: _t("Add..."), callback: self.on_attachment_add } ); + self.items['files'] = attachments; + self.redraw(); + }, + on_attachment_add: function(e) { + this.$element.find('.oe_sidebar_add').show(); + }, + on_attachment_changed: function(e) { + return; + window[this.element_id + '_iframe'] = this.do_update; + var $e = $(e.target); + if ($e.val() != '') { + this.$element.find('form.oe-binary-form').submit(); + $e.parent().find('input[type=file]').prop('disabled', true); + $e.parent().find('button').prop('disabled', true).find('img, span').toggle(); + } }, - do_toggle: function() { - this.$element.toggleClass('open-sidebar closed-sidebar'); + on_attachment_delete: function(e) { + return; + var self = this, $e = $(e.currentTarget); + var name = _.str.trim($e.parent().find('a.oe-sidebar-attachments-link').text()); + if (confirm(_.str.sprintf(_t("Do you really want to delete the attachment %s?"), name))) { + this.rpc('/web/dataset/unlink', { + model: 'ir.attachment', + ids: [parseInt($e.attr('data-id'))] + }, function(r) { + $e.parent().remove(); + self.do_notify("Delete an attachment", "The attachment '" + name + "' has been deleted"); + }); + } } }); -session.web.TranslateDialog = session.web.Dialog.extend({ +instance.web.TranslateDialog = instance.web.Dialog.extend({ dialog_title: {toString: function () { return _t("Translations"); }}, init: function(view) { // TODO fme: should add the language to fields_view_get because between the fields view get @@ -979,14 +929,14 @@ session.web.TranslateDialog = session.web.Dialog.extend({ this.translatable_fields_keys = _.map(this.view.translatable_fields || [], function(i) { return i.name }); this.languages = null; this.languages_loaded = $.Deferred(); - (new session.web.DataSetSearch(this, 'res.lang', this.view.dataset.get_context(), + (new instance.web.DataSetSearch(this, 'res.lang', this.view.dataset.get_context(), [['translatable', '=', '1']])).read_slice(['code', 'name'], { sort: 'id' }).then(this.on_languages_loaded); }, start: function() { var self = this; this._super(); $.when(this.languages_loaded).then(function() { - self.$element.html(session.web.qweb.render('TranslateDialog', { widget: self })); + self.$element.html(instance.web.qweb.render('TranslateDialog', { widget: self })); self.$fields_form = self.$element.find('.oe_translation_form'); self.$fields_form.find('.oe_trad_field').change(function() { $(this).toggleClass('touched', ($(this).val() != $(this).attr('data-value'))); @@ -1075,29 +1025,61 @@ session.web.TranslateDialog = session.web.Dialog.extend({ } }); -session.web.View = session.web.Widget.extend(/** @lends session.web.View# */{ +instance.web.View = instance.web.Widget.extend({ template: "EmptyComponent", // name displayed in view switchers display_name: '', + /** + * Define a view type for each view to allow automatic call to fields_view_get. + */ + view_type: undefined, init: function(parent, dataset, view_id, options) { this._super(parent); this.dataset = dataset; this.view_id = view_id; this.set_default_options(options); }, + start: function() { + return this.load_view(); + }, + load_view: function() { + if (this.embedded_view) { + var def = $.Deferred(); + var self = this; + $.async_when().then(function() {def.resolve(self.embedded_view);}); + return def.pipe(this.on_loaded); + } else { + var context = new instance.web.CompoundContext(this.dataset.get_context()); + if (! this.view_type) + console.warn("view_type is not defined", this); + return this.rpc("/web/view/load", { + "model": this.dataset.model, + "view_id": this.view_id, + "view_type": this.view_type, + toolbar: !!this.options.$sidebar, + context: context + }).pipe(this.on_loaded); + } + }, + /** + * Called after a successful call to fields_view_get. + * Must return a promise. + */ + on_loaded: function(fields_view_get) { + }, set_default_options: function(options) { this.options = options || {}; _.defaults(this.options, { // All possible views options should be defaulted here + $sidebar: null, sidebar_id: null, - sidebar: true, action: null, action_views_ids: {} }); }, open_translate_dialog: function(field) { if (!this.translate_dialog) { - this.translate_dialog = new session.web.TranslateDialog(this).start(); + this.translate_dialog = new instance.web.TranslateDialog(this).start(); } this.translate_dialog.open(field); }, @@ -1109,7 +1091,7 @@ session.web.View = session.web.Widget.extend(/** @lends session.web.View# */{ * @param {String} [action_data.special=null] special action handlers (currently: only ``'cancel'``) * @param {String} [action_data.type='workflow'] the action type, if present, one of ``'object'``, ``'action'`` or ``'workflow'`` * @param {Object} [action_data.context=null] additional action context, to add to the current context - * @param {session.web.DataSet} dataset a dataset object used to communicate with the server + * @param {instance.web.DataSet} dataset a dataset object used to communicate with the server * @param {Object} [record_id] the identifier of the object on which the action is to be applied * @param {Function} on_closed callback to execute when dialog is closed or when the action does not generate any result (no new action) */ @@ -1121,12 +1103,12 @@ session.web.View = session.web.Widget.extend(/** @lends session.web.View# */{ return self.getParent().on_action_executed.apply(null, arguments); } }; - var context = new session.web.CompoundContext(dataset.get_context(), action_data.context || {}); + var context = new instance.web.CompoundContext(dataset.get_context(), action_data.context || {}); var handler = function (r) { var action = r.result; if (action && action.constructor == Object) { - var ncontext = new session.web.CompoundContext(context); + var ncontext = new instance.web.CompoundContext(context); if (record_id) { ncontext.add({ active_id: record_id, @@ -1175,13 +1157,12 @@ session.web.View = session.web.Widget.extend(/** @lends session.web.View# */{ /** * Directly set a view to use instead of calling fields_view_get. This method must * be called before start(). When an embedded view is set, underlying implementations - * of session.web.View must use the provided view instead of any other one. + * of instance.web.View must use the provided view instead of any other one. * * @param embedded_view A view. */ set_embedded_view: function(embedded_view) { this.embedded_view = embedded_view; - this.options.sidebar = false; }, do_show: function () { this.$element.show(); @@ -1214,15 +1195,12 @@ session.web.View = session.web.Widget.extend(/** @lends session.web.View# */{ }, do_search: function(view) { }, - set_common_sidebar_sections: function(sidebar) { - sidebar.add_default_sections(); - }, on_sidebar_import: function() { - var import_view = new session.web.DataImport(this, this.dataset); + var import_view = new instance.web.DataImport(this, this.dataset); import_view.start(); }, on_sidebar_export: function() { - var export_view = new session.web.DataExport(this, this.dataset); + var export_view = new instance.web.DataExport(this, this.dataset); export_view.start(); }, on_sidebar_translate: function() { @@ -1247,7 +1225,29 @@ session.web.View = session.web.Widget.extend(/** @lends session.web.View# */{ } }); -session.web.json_node_to_xml = function(node, human_readable, indent) { +instance.web.xml_to_json = function(node) { + switch (node.nodeType) { + case 3: + case 4: + return node.data; + break; + case 1: + var attrs = $(node).getAttributes(); + _.each(['domain', 'filter_domain', 'context', 'default_get'], function(key) { + if (attrs[key]) { + try { + attrs[key] = JSON.parse(attrs[key]); + } catch(e) { } + } + }); + return { + tag: node.tagName.toLowerCase(), + attrs: attrs, + children: _.map(node.childNodes, instance.web.xml_to_json) + } + } +} +instance.web.json_node_to_xml = function(node, human_readable, indent) { // For debugging purpose, this function will convert a json node back to xml // Maybe useful for xml view editor indent = indent || 0; @@ -1276,7 +1276,7 @@ session.web.json_node_to_xml = function(node, human_readable, indent) { r += '>' + cr; var childs = []; for (var i = 0, ii = node.children.length; i < ii; i++) { - childs.push(session.web.json_node_to_xml(node.children[i], human_readable, indent + 1)); + childs.push(instance.web.json_node_to_xml(node.children[i], human_readable, indent + 1)); } r += childs.join(cr); r += cr + sindent + '</' + node.tag + '>'; @@ -1285,6 +1285,43 @@ session.web.json_node_to_xml = function(node, human_readable, indent) { return r + '/>'; } } +instance.web.xml_to_str = function(node) { + if (window.ActiveXObject) { + return node.xml; + } else { + return (new XMLSerializer()).serializeToString(node); + } +} +instance.web.str_to_xml = function(s) { + if (window.DOMParser) { + var dp = new DOMParser(); + var r = dp.parseFromString(s, "text/xml"); + if (r.body && r.body.firstChild && r.body.firstChild.nodeName == 'parsererror') { + throw new Error("Could not parse string to xml"); + } + return r; + } + var xDoc; + try { + xDoc = new ActiveXObject("MSXML2.DOMDocument"); + } catch (e) { + throw new Error("Could not find a DOM Parser: " + e.message); + } + xDoc.async = false; + xDoc.preserveWhiteSpace = true; + xDoc.loadXML(s); + return xDoc; +} + +/** + * Registry for all the client actions key: tag value: widget + */ +instance.web.client_actions = new instance.web.Registry(); + +/** + * Registry for all the main views + */ +instance.web.views = new instance.web.Registry(); }; diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 894f7353c1a..0e1edb89969 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -2,6 +2,14 @@ <!-- vim:fdl=1: --> <templates id="template" xml:space="preserve"> +<t t-name="EmptyComponent"> + <div></div> +</t> +<t t-name="Loading"> + <div class="oe_loading"> + Loading... + </div> +</t> <t t-name="Notification"> <div class="oe_notification" t-translation="off"> <div id="oe_notification_default"> @@ -17,208 +25,8 @@ </div> </div> </t> -<t t-name="WebClient"> - <div class="oe_webclient"> - <div class="oe_topbar"> - <div class="oe_menu_placeholder"/> - <div class="oe_user_menu_placeholder"/> - <div class="oe_systray"/> - </div> - <div class="oe_leftbar"> - <a href="#" class="oe_logo"><img t-att-src='_s + "/web/static/src/img/logo.png"'/></a> - <div class="oe_secondary_menus_container"/> - - <div class="oe_footer"> - Powered by <a href="http://www.openerp.com"><span>Open</span>ERP</a> - </div> - </div> - <div class="oe_application"/> - </div> -</t> -<t t-name="Loading"> - <div id="oe_loading"> - <div class="loading"> - Loading... - </div> - </div> -</t> -<t t-name="Database.CreateDB"> - <form name="create_db_form" class="oe_forms" method="POST"> - <table width="100%"> - <tr> - <td class="option_string"> - CREATE DATABASE - </td> - </tr> - </table> - <table align="center" class="db_option_table"> - <tr> - <td><label for="super_admin_pwd">Master password:</label></td> - <td><input type="password" name="super_admin_pwd" class="required" value="admin"/></td> - </tr> - <tr> - <td><label for="db_name">New database name:</label></td> - <td><input type="text" name="db_name" - class="required" matches="^[a-zA-Z][a-zA-Z0-9_]+$"/></td> - </tr> - <tr> - <td><label for="demo_data">Load Demonstration data:</label></td> - <td><input type="checkbox" name="demo_data"/></td> - </tr> - <tr> - <td><label for="db_lang">Default language:</label></td> - <td> - <select name="db_lang" t-if="lang_list"> - <t t-foreach="lang_list" t-as="lang"> - <option t-att-value="lang[0]" t-att-selected="lang[0] === 'en_US' ? 'selected' : undefined"><t t-esc="lang[1]"/></option> - </t> - </select> - </td> - </tr> - <tr> - <td><label for="create_admin_pwd">Admin password:</label></td> - <td><input type="password" name="create_admin_pwd" class="required"/></td> - </tr> - <tr> - <td><label for="create_confirm_pwd">Confirm password:</label></td> - <td><input type="password" name="create_confirm_pwd" class="required" - equalTo="input[name=create_admin_pwd]"/></td> - </tr> - <tr> - <td colspan="2" align="right"><button class="oe_button">Create</button></td> - </tr> - </table> - </form> -</t> -<t t-name="DropDB"> - <form name="drop_db_form" class="oe_forms" method="POST"> - <table width="100%"> - <tr> - <td class="option_string"> - DROP DATABASE - </td> - </tr> - </table> - <table align="center" class="db_option_table"> - <tr> - <td><label for="drop_db">Database:</label></td> - <td> - <select t-if="db_list" name="drop_db" autofocus="autofocus"> - <t t-foreach="db_list" t-as="db"> - <option t-att-value="db"><t t-esc="db"/></option> - </t> - </select> - <input t-if="!db_list" name="drop_db" class="required" - type="text" autofocus="autofocus"/> - </td> - </tr> - <tr> - <td><label for="drop_password">Master Password:</label></td> - <td><input type="password" name="drop_pwd" class="required"/></td> - </tr> - <tr> - <td colspan="2" align="right"><button class="oe_button">Drop</button></td> - </tr> - </table> - </form> -</t> -<t t-name="BackupDB"> - <form name="backup_db_form" class="oe_forms" method="POST" target="backup-target" - action="/web/database/backup"> - <input type="hidden" name="token"/> - <table width="100%"> - <tr> - <td class="option_string"> - BACKUP DATABASE - </td> - </tr> - </table> - <table align="center" class="db_option_table"> - <tr> - <td><label for="backup_db">Database:</label></td> - <td> - <select t-if="db_list" name="backup_db" autofocus="autofocus"> - <t t-foreach="db_list" t-as="db"> - <option t-att-value="db"><t t-esc="db"/></option> - </t> - </select> - <input t-if="!db_list" name="backup_db" class="required" - type="text" autofocus="autofocus"/> - </td> - </tr> - <tr> - <td><label for="backup_pwd">Master Password:</label></td> - <td><input type="password" name="backup_pwd" class="required"/></td> - </tr> - <tr> - <td colspan="2" align="right"><button class="oe_button">Backup</button></td> - </tr> - </table> - </form> -</t> -<t t-name="RestoreDB"> - <form name="restore_db_form" class="oe_forms" method="POST"> - <table width="100%"> - <tr> - <td class="option_string"> - RESTORE DATABASE - </td> - </tr> - </table> - <table align="center" class="db_option_table"> - <tr> - <td><label for="restore_db">File:</label></td> - <td><input type="file" name="db_file" class="required" - autofocus="autofocus"/></td> - </tr> - <tr> - <td><label for="restore_pwd">Master Password:</label></td> - <td><input type="password" name="restore_pwd" class="required"/></td> - </tr> - <tr> - <td><label for="new_db">New database name:</label></td> - <td><input type="text" name="new_db" class="required"/></td> - </tr> - <tr> - <td colspan="2" align="right"><button class="oe_button">Restore</button></td> - </tr> - </table> - </form> -</t> -<t t-name="Change_DB_Pwd"> - <form name="change_pwd_form" class="oe_forms" method="POST"> - <table width="100%"> - <tr> - <td class="option_string"> - CHANGE MASTER PASSWORD - </td> - </tr> - </table> - <table align="center" class="db_option_table"> - <tr> - <td><label for="old_pwd">Master password:</label></td> - <td><input type="password" name="old_pwd" class="required" - minlength="1" autofocus="autofocus"/></td> - </tr> - <tr> - <td><label for="new_pwd">New master password:</label></td> - <td><input type="password" name="new_pwd" class="required" - minlength="1"/></td> - </tr> - <tr> - <td><label for="confirm_pwd">Confirm new master password:</label></td> - <td><input type="password" name="confirm_pwd" class="required" - equalTo="input[name=new_pwd]" minlength="1"/></td> - </tr> - <tr> - <td colspan="2" align="right"><button class="oe_button">Change Password</button></td> - </tr> - </table> - </form> -</t> - -<t t-name="CrashManagerWarning"> +<t t-name="CrashManager.warning"> <table cellspacing="0" cellpadding="0" border="0" class="oe-dialog-warning"> <tr> <td><img t-att-src='_s + "/web/static/src/img/warning.png"' class="oe-dialog-icon"/></td> @@ -235,7 +43,7 @@ </tr> </table> </t> -<t t-name="CrashManagerError"> +<t t-name="CrashManager.error"> <t t-if="!session.openerp_entreprise"> <span>Your version of OpenERP is unsupported. Support & maintenance services are available here: <a href="http://www.openerp.com/support-or-publisher-warranty-contract" target="_blank">OpenERP Entreprise</a>.</span> </t> @@ -266,7 +74,33 @@ </div> </t> -<t t-name="Login_dblist"> +<t t-name="Login"> + <div class="oe_login"> + <div class="oe_login_bottom"> </div> + <div class="oe_login_error_message">Invalid username or password</div> + <div class="oe_login_pane"> + <div class="oe_login_logo"><img src='/web/static/src/img/logo2.png'/></div> + <form action="" method="post"> + <div class="oe_login_dbpane" > + Database: + <input name="db" t-att-value="widget.selected_db || ''"/> + </div> + <ul> + <li>Username</li> + <li><input type="text" name="login" t-att-value="widget.selected_login || ''" autofocus="autofocus"/></li> + <li>Password</li> + <li><input type="password" name="password" t-att-value="widget.selected_password || ''"/></li> + <li><button name="submit">Log in</button></li> + </ul> + </form> + <div class="oe_login_footer"> + <a href="#" class="oe_login_manage_db">Manage Databases</a> | + <a href="http://www.openerp.com">Powered by <span>OpenERP</span></a> + </div> + </div> + </div> +</t> +<t t-name="Login.dblist"> <select name="db"> <t t-foreach="db_list" t-as="db"> <t t-if="selected_db === db"> @@ -280,51 +114,179 @@ </select> </t> -<t t-name="Login"> - <div class="login"> - <div class="bottom"> </div> - <div class="login_error_message">Invalid username or password</div> - <div class="pane"> - <div id="logo"><img src='/web/static/src/img/logo2.png'/></div> - <form action="" method="post"> - <div class="dbpane" > - Database: - <input name="db" t-att-value="widget.selected_db || ''"/> - </div> - <ul> - <li>Username</li> - <li><input type="text" name="login" t-att-value="widget.selected_login || ''" autofocus="autofocus"/></li> - <li>Password</li> - <li><input type="password" name="password" t-att-value="widget.selected_password || ''"/></li> - <li><button name="submit">Log in</button></li> - </ul> - </form> - <div class="footer"> - <a href="#" id="oe-db-config">Manage Databases</a> | - <a href="http://www.openerp.com">Powered by <span class="openerp">OpenERP</span></a> - </div> - </div> - </div> -</t> <t t-name="DatabaseManager"> - <div class="oe-database-manager"> - <div class="database"> - <a class="company_logo_link" href="/?"> - <div class="company_logo"></div> - </a> - <ul class="db_options"> - <li id="db-create">Create</li> - <li id="db-drop">Drop</li> - <li id="db-backup">Backup</li> - <li id="db-restore">Restore</li> - <li id="db-change-password">Password</li> - <li id="back-to-login">Back to Login</li> + <div class="oe_database_manager"> + <div class="oe_database_manager_menu"> + <ul class="oe_form_notebook"> + <li><a href="#db_create">Create</a></li> + <li><a href="#db_drop">Drop</a></li> + <li><a href="#db_backup">Backup</a></li> + <li><a href="#db_restore">Restore</a></li> + <li><a href="#db_change_password">Password</a></li> + <li><a id="back_to_login" href="#">Back to Login</a></li> </ul> + <form id="db_create" name="create_db_form" class="oe_forms" method="POST"> + <table align="center" class="db_option_table"> + <tr> + <th colspan="2" class="option_string"> CREATE DATABASE </th> + </tr> + <tr> + <td><label for="super_admin_pwd">Master password:</label></td> + <td><input type="password" name="super_admin_pwd" class="required" value="admin"/></td> + </tr> + <tr> + <td><label for="db_name">New database name:</label></td> + <td><input type="text" name="db_name" class="required" matches="^[a-zA-Z][a-zA-Z0-9_]+$"/></td> + </tr> + <tr> + <td><label for="demo_data">Load Demonstration data:</label></td> + <td><input type="checkbox" name="demo_data"/></td> + </tr> + <tr> + <td><label for="db_lang">Default language:</label></td> + <td> + <select name="db_lang" t-if="widget.lang_list"> + <t t-foreach="widget.lang_list" t-as="lang"> + <option t-att-value="lang[0]" t-att-selected="lang[0] === 'en_US' ? 'selected' : undefined"><t t-esc="lang[1]"/></option> + </t> + </select> + </td> + </tr> + <tr> + <td><label for="create_admin_pwd">Admin password:</label></td> + <td><input type="password" name="create_admin_pwd" class="required"/></td> + </tr> + <tr> + <td><label for="create_confirm_pwd">Confirm password:</label></td> + <td><input type="password" name="create_confirm_pwd" class="required" equalTo="input[name=create_admin_pwd]"/></td> + </tr> + <tr> + <td colspan="2" align="right"><button class="oe_button">Create</button></td> + </tr> + </table> + </form> + <form id="db_drop" name="drop_db_form" class="oe_forms" method="POST"> + <table align="center" class="db_option_table"> + <tr> + <th colspan="2" class="option_string"> DROP DATABASE </th> + </tr> + <tr> + <td><label for="drop_db">Database:</label></td> + <td> + <select t-if="widget.db_list" name="drop_db" autofocus="autofocus"> + <t t-foreach="widget.db_list" t-as="db"> + <option t-att-value="db"><t t-esc="db"/></option> + </t> + </select> + <input t-if="!widget.db_list" name="drop_db" class="required" type="text" autofocus="autofocus"/> + </td> + </tr> + <tr> + <td><label for="drop_password">Master Password:</label></td> + <td><input type="password" name="drop_pwd" class="required"/></td> + </tr> + <tr> + <td colspan="2" align="right"><button class="oe_button">Drop</button></td> + </tr> + </table> + </form> + <form id="db_backup" name="backup_db_form" class="oe_forms" method="POST" target="backup-target" action="/web/database/backup"> + <input type="hidden" name="token"/> + <table align="center" class="db_option_table"> + <tr> + <th colspan="2" class="option_string"> BACKUP DATABASE </th> + </tr> + <tr> + <td><label for="backup_db">Database:</label></td> + <td> + <select t-if="widget.db_list" name="backup_db" autofocus="autofocus"> + <t t-foreach="widget.db_list" t-as="db"> + <option t-att-value="db"><t t-esc="db"/></option> + </t> + </select> + <input t-if="!widget.db_list" name="backup_db" class="required" type="text" autofocus="autofocus"/> + </td> + </tr> + <tr> + <td><label for="backup_pwd">Master Password:</label></td> + <td><input type="password" name="backup_pwd" class="required"/></td> + </tr> + <tr> + <td colspan="2" align="right"><button class="oe_button">Backup</button></td> + </tr> + </table> + </form> + <form id="db_restore" name="restore_db_form" class="oe_forms" method="POST"> + <table align="center" class="db_option_table"> + <tr> + <th colspan="2" class="option_string"> RESTORE DATABASE </th> + </tr> + <tr> + <td><label for="restore_db">File:</label></td> + <td><input type="file" name="db_file" class="required" autofocus="autofocus"/></td> + </tr> + <tr> + <td><label for="restore_pwd">Master Password:</label></td> + <td><input type="password" name="restore_pwd" class="required"/></td> + </tr> + <tr> + <td><label for="new_db">New database name:</label></td> + <td><input type="text" name="new_db" class="required"/></td> + </tr> + <tr> + <td colspan="2" align="right"><button class="oe_button">Restore</button></td> + </tr> + </table> + </form> + <form id="db_change_password" name="change_pwd_form" class="oe_forms" method="POST"> + <table align="center" class="db_option_table"> + <tr> + <th colspan="2" class="option_string"> CHANGE MASTER PASSWORD </th> + </tr> + <tr> + <td><label for="old_pwd">Master password:</label></td> + <td><input type="password" name="old_pwd" class="required" minlength="1" autofocus="autofocus"/></td> + </tr> + <tr> + <td><label for="new_pwd">New master password:</label></td> + <td><input type="password" name="new_pwd" class="required" minlength="1"/></td> + </tr> + <tr> + <td><label for="confirm_pwd">Confirm new master password:</label></td> + <td><input type="password" name="confirm_pwd" class="required" equalTo="input[name=new_pwd]" minlength="1"/></td> + </tr> + <tr> + <td colspan="2" align="right"><button class="oe_button">Change Password</button></td> + </tr> + </table> + </form> </div> - <div id="oe_db_options" class="oe_db_options"></div> </div> </t> +<t t-name="WebClient"> + <table class="oe_webclient"> + <tr> + <td colspan="2" class="oe_topbar"> + <div class="oe_menu_placeholder"/> + <div class="oe_user_menu_placeholder"/> + <div class="oe_systray"/> + </td> + </tr> + <tr> + <td class="oe_leftbar" valign="top"> + <a href="#" class="oe_logo"><img t-att-src='_s + "/web/static/src/img/logo.png"'/></a> + <div class="oe_secondary_menus_container"/> + <div class="oe_footer"> + Powered by <a href="http://www.openerp.com"><span>Open</span>ERP</a> + </div> + </td> + <td class="oe_application"> + </td> + </tr> + </table> +</t> + <t t-name="Menu"> <ul class="oe_menu" t-if="widget.data"> <li t-foreach="widget.data.data.children" t-as="menu"> @@ -362,12 +324,17 @@ </ul> </t> <t t-name="Menu.secondary.link"> - <a href="#" + <a t-attf-href="#menu_id=#{menu.id}&action_id=#{menu.action ? menu.action.split(',')[1] : ''}" t-att-class="menu.children.length ? 'oe_menu_toggler' : 'oe_menu_leaf'" t-att-data-menu="menu.id" t-att-data-action-model="menu.action ? menu.action.split(',')[0] : ''" t-att-data-action-id="menu.action ? menu.action.split(',')[1] : ''"> <t t-esc="menu.name"/> + <t t-if="menu.needaction_enabled"> + <div class="oe_menu_counter"> + <t t-esc="menu.needaction_counter"/> + </div> + </t> </a> </t> @@ -396,73 +363,96 @@ </a> </li> </t> +<t t-name="About-Page"> + <div> + <a class="oe_activate_debug_mode" href="?debug" style="float:right; font-size: 80%;">Activate the developper mode</a> + <h1 style="margin:0;">OpenERP</h1> + <h3 style="margin:15px 0;padding:0;">Version <t t-esc="version_info.version"/></h3> + <p> + Copyright © 2004-TODAY OpenERP SA. All Rights Reserved.<br /> + OpenERP is a trademark of the <a target="_blank" href="http://openerp.com/" style="text-decoration: underline;">OpenERP SA Company</a>. + </p> + <p> + Licenced under the terms of <a target="_blank" href="http://www.gnu.org/licenses/agpl.html" style="text-decoration: underline;">GNU Affero General Public License</a> + </p> + <p> + For more information visit <a target="_blank" href="http://openerp.com/" style="text-decoration: underline;">OpenERP.com</a> + </p> + </div> +</t> +<t t-name="Change_Pwd"> + <form name="change_password_form" class="oe_forms" method="POST"> + <table align="center"> + <tr> + <td><label for="old_pwd">Old Password:</label></td> + <td><input type="password" name="old_pwd" + minlength="1" autofocus="autofocus"/></td> + </tr> + <tr> + <td><label for="new_password">New Password:</label></td> + <td><input type="password" name="new_password" + minlength="1" autofocus="autofocus"/></td> + </tr> + <tr> + <td><label for="confirm_pwd">Confirm Password:</label></td> + <td><input type="password" name="confirm_pwd" + minlength="1"/></td> + </tr> + <tr> + <td colspan="2" align="right"><button class="oe_button">Change Password</button></td> + </tr> + </table> + </form> +</t> <t t-name="ViewManager"> - <table class="view-manager-main-table" cellpadding="0" cellspacing="0"> - <tbody> - <tr> - <td class="view-manager-main-content"> - <div class="oe-view-manager-header"> - <h2 class="oe_view_title" t-if="self.flags.display_title !== false"> - <span class="oe_view_title_text"><t t-esc="self.display_title()"/></span> - </h2> - <div class="oe_vm_switch"> - <t t-if="views.length != 1" t-foreach="views" t-as="view"> - - <button type="button" t-attf-class="oe_vm_switch_#{view.view_type}" t-att-data-view-type="view.view_type" t-att-title="view.label || view.view_type"> - <span><t t-esc="view.label || view.view_type"/></span> - </button> - </t> - </div> - </div> - <div t-attf-id="#{prefix}_search" t-opentag="true"/> - <t t-foreach="views" t-as="view"> - <div t-attf-id="#{prefix}_view_#{view.view_type}"/> - </t> - </td> - <td class="view-manager-main-sidebar" height="100%"> - <t t-foreach="views" t-as="view"> - <div t-attf-id="#{prefix}_sidebar_#{view.view_type}" class="sidebar-main-div closed-sidebar" style="display: none"/> + <div class="oe_view_manager"> + <table class="oe_view_manager_header2"> + <col width="33%"/> + <col width="33%"/> + <col width="34%"/> + <tr> + <td colspan="2"> + <h2 class="oe_view_title" t-if="widget.flags.display_title !== false"> + <span class="oe_view_title_text"><t t-esc="widget.display_title()"/></span> + </h2> + </td> + <td> <div class="oe_view_manager_view_search" t-opentag="true"/> </td> + </tr> + <tr> + <td> <div class="oe_view_manager_buttons"/> </td> + <td> <div class="oe_view_manager_sidebar"/> </td> + <td> + <ul class="oe_view_manager_switch oe_button_group oe_right"> + <t t-if="widget.views_src.length != 1" t-foreach="widget.views_src" t-as="view"> + <li><a t-attf-class="oe_vm_switch_#{view.view_type} oe_i" t-att-data-view-type="view.view_type" t-att-title="view.label || view.view_type"> + </a></li> + </t> + </ul> + <div class="oe_view_manager_pager oe_right"/> + </td> + </tr> + </table> + <div class="oe_view_manager_body"> + <t t-foreach="widget.views_src" t-as="view"> + <div t-attf-class="oe_view_manager_view_#{view.view_type}"/> </t> - </td> - </tr> - </tbody> - </table> + </div> + </div> </t> - -<t t-extend="ViewManager" t-name="ViewManagerAction"> - <t t-jquery=".view-manager-main-table tbody" t-operation="prepend"> - <tr> - <td class="oe_view_manager_menu_tips" colspan="2"> - <blockquote t-if="self.action.help and !self.flags.low_profile - and !(self.action.id in self.session.hidden_menutips)"> - <p><t t-esc="self.action.help"/></p> - <div> - <button type="button" name="hide">Hide this tip</button> - <button type="button" name="disable">Disable all tips</button> - </div> - </blockquote> - </td> - </tr> - </t> +<t t-name="ViewManagerAction" t-extend="ViewManager"> <t t-jquery="h2.oe_view_title" t-operation="prepend"> - <a t-if="self.flags.display_title !== false" class="oe-shortcut-toggle" title="Add / Remove Shortcut..." + <a t-if="widget.flags.display_title !== false" class="oe-shortcut-toggle" title="Add / Remove Shortcut..." href="javascript: void(0)"> </a> </t> <t t-jquery="h2.oe_view_title" t-operation="after"> - <select t-if="self.session.debug" class="oe_debug_view"/> - </t> - <t t-jquery=".oe-view-manager-header" t-operation="after"> - <div class="oe-view-manager-logs oe-folded"> - <ul></ul> - <a class="oe-more-logs" href="#">More…</a> - <a class="oe-remove-everything ui-icon ui-icon-closethick"/> - </div> + <select t-if="widget.session.debug" class="oe_debug_view"/> </t> </t> <t t-name="ViewManagerDebug"> <option value="">Debug View#<t t-esc="view.fields_view.view_id"/></option> - <option t-if="_.indexOf(['form', 'page'], view_manager.active_view) gt -1" value="perm_read" data-views="form,page">View Log (perm_read)</option> + <option t-if="view_manager.active_view === 'form'" value="perm_read" data-views="form,page">View Log (perm_read)</option> + <option t-if="view_manager.active_view === 'form'" value="toggle_layout_outline">Toggle Form Layout Outline</option> <option value="fields">View Fields</option> <option value="fvg">Fields View Get</option> <t t-if="view_manager.session.uid === 1"> @@ -494,55 +484,84 @@ <span><t t-esc="format(perm.write_date, { 'type' : 'datetime' }, '/')"/></span> </div> </t> -<t t-extend="ViewManager" t-name="One2Many.viewmanager"> - <t t-jquery="span.oe_view_title_text" t-operation="replace"/> - - <t t-jquery=".oe-view-manager-header"> - this.attr('t-if', 'views.length != 1'); - </t> +<t t-name="ViewPager"> + <div class="oe_pager_value"> + <t t-raw="__content__"/> + </div> + <ul class="oe_pager_group"> + <!-- + <button class="oe_button oe_button_pager" type="button" data-pager-action="first"> + <img t-att-src='_s + "/web/static/src/img/pager_first.png"'/> + </button> + --> + <li> + <a class="oe_i" type="button" data-pager-action="previous">(</a> + </li> + <li> + <a class="oe_i" type="button" data-pager-action="next">)</a> + </li> + <!-- + <button class="oe_button oe_button_pager" type="button" data-pager-action="last"> + <img t-att-src='_s + "/web/static/src/img/pager_last.png"'/> + </button> + --> + </ul> </t> <t t-name="Sidebar"> - <a class="toggle-sidebar"></a> - <div class="sidebar-content"> - <div class="sidebar-actions"> - </div> + <div class="oe_sidebar"> + <t t-foreach="widget.sections" t-as="section"> + <div class="oe_form_dropdown_section"> + <button class="oe_dropdown_toggle"><t t-esc="section.label"/></button> + <ul class="oe_dropdown_menu"> + <li t-foreach="widget.items[section.name]" t-as="item" t-att-class="item.classname"> + <a class="oe_sidebar_action_a" t-att-title="item.title" t-att-data-section="section.name" t-att-data-index="item_index" t-att-href="item.url" target="_blank"> + <t t-raw="item.label"/> + </a> + </li> + <li t-if="section.name == 'files'" class="oe_sidebar_add" style="display:none;"> + Input type file stuff + </li> + </ul> + </div> + </t> </div> </t> -<t t-name="Sidebar.section"> - <div t-att-id="section_id" t-att-class="classname"> - <h2><t t-esc="name"/></h2> +<t t-name="Sidebar.attachments"> + <div class="oe-sidebar-attachments-toolbar"> + <div class="oe-binary-file-set" style="float: right"> + <form class="oe-binary-form" t-attf-target="#{element_id}_iframe" + method="post" enctype="multipart/form-data" action="/web/binary/upload_attachment"> + <input type="hidden" name="session_id" t-att-value="session.session_id"/> + <input type="hidden" name="callback" t-attf-value="#{element_id}_iframe"/> + <input type="hidden" name="model" t-att-value="view.dataset.model"/> + <input type="hidden" name="id" t-att-value="view.datarecord.id"/> + <button class="oe_button" type="button"> + <img t-att-src='_s + "/web/static/src/img/throbber.gif"' width="16" height="16" style="display: none"/> + <span>Add</span> + </button> + <input type="file" class="oe-binary-file" name="ufile" title="Add attachment" + t-att-onclick="view.datarecord.id ? null : 'alert(\'No record selected ! You can only attach to existing record.\'); return false;'"/> + </form> + <iframe t-attf-id="#{element_id}_iframe" t-attf-name="#{element_id}_iframe" style="display: none"/> + </div> </div> -</t> -<t t-name="Sidebar.section.items"> - <li t-foreach="items" t-as="item" t-att-class="item.classname"> - <a class="oe_sidebar_action_a" t-att-id="item.element_id" t-att-title="item.title" href="#"> - <t t-esc="item.label"/> - </a> - </li> + <br style="clear: both"/> + <ul class="oe-sidebar-attachments-items"> + <li t-foreach="attachments" t-as="attachment"> + <t t-if="attachment.type == 'binary'" t-set="attachment.url" t-value="_s + '/web/binary/saveas?session_id=' + + session.session_id + '&model=ir.attachment&id=' + attachment.id + + '&field=datas&filename_field=name&t=' + (new Date().getTime())"/> + <a class="oe-sidebar-attachments-link" t-att-href="attachment.url" target="_blank"> + <t t-esc="attachment.name"/> + </a> + <a href="#" class="oe-sidebar-attachment-delete" t-att-data-id="attachment.id" t-attf-title="Delete the attachment #{attachment.name}"> + <img t-att-src='_s + "/web/static/src/img/attachments-close.png"' width="15" height="15" border="0"/> + </a> + </li> + </ul> </t> -<t t-name="TranslateDialog"> - <table t-if="widget.view.translatable_fields" class="oe_frame oe_forms oe_translation_form" border="0" cellpadding="0" cellspacing="0" width="100%"> - <tr> - <td class="oe_form_separator" width="1%" nowrap="nowrap"> - <div class="separator horizontal">Field</div> - </td> - <th t-foreach="widget.languages" align="left"> - <div class="separator horizontal"><t t-esc="name"/></div> - </th> - </tr> - <tr t-foreach="widget.view.translatable_fields" t-as="field" t-att-data-field="field.name"> - <td class="oe_form_frame_cell" width="1%" nowrap="nowrap"> - <label class="oe_label"><t t-esc="field.string"/>:</label> - </td> - <td t-foreach="widget.languages" t-as="lg" class="oe_form_frame_cell"> - <input t-if="field.type == 'char'" type="text" t-attf-name="#{lg.code}-#{field.name}" value="" data-value="" class="oe_trad_field" style="width: 100%"/> - <textarea t-if="field.type == 'text'" t-attf-name="#{lg.code}-#{field.name}" data-value="" class="oe_trad_field" style="width: 100%"></textarea> - </td> - </tr> - </table> -</t> <t t-name="TreeView"> <select t-if="toolbar" style="width: 30%"> </select> @@ -585,44 +604,14 @@ </td> </tr> -<t t-name="ViewPager"> - <button class="oe_button oe_button_pager" type="button" data-pager-action="first"> - <img t-att-src='_s + "/web/static/src/img/pager_first.png"'/> - </button> - <button class="oe_button oe_button_pager" type="button" data-pager-action="previous"> - <img t-att-src='_s + "/web/static/src/img/pager_previous.png"'/> - </button> - - <t t-raw="__content__"/> - - <button class="oe_button oe_button_pager" type="button" data-pager-action="next"> - <img t-att-src='_s + "/web/static/src/img/pager_next.png"'/> - </button> - <button class="oe_button oe_button_pager" type="button" data-pager-action="last"> - <img t-att-src='_s + "/web/static/src/img/pager_last.png"'/> - </button> -</t> - <table t-name="ListView" class="oe-listview-content"> <t t-set="columns_count" t-value="visible_columns.length + (options.selectable ? 1 : 0) + (options.deletable ? 1 : 0) + (options.isClarkGable ? 1 : 0)"/> - <thead class="ui-widget-header"> + <thead> <tr t-if="options.action_buttons !== false or options.pager !== false"> <th t-att-colspan="columns_count"> - <table> - <tr> - <td t-if="!no_leaf and options.action_buttons !== false" class="oe-actions"> - <button type="button" class="oe_button oe-list-add" - t-if="options.addable"> - <t t-esc="options.addable"/> - </button> - <button type="button" class="oe_button oe-list-delete" - t-if="options.selectable and options.deletable"> - Delete - </button> - </td> - <t t-call="Listview.navigation.button"/> - </tr> - </table> + <div class="oe_list_buttons"/> + <div class="oe_list_sidebar"/> + <div class="oe_list_pager"/> </th> </tr> <tr t-if="options.header" class="oe-listview-header-columns"> @@ -632,19 +621,19 @@ </th> </t> <th t-if="options.selectable" width="1" > - <input type="checkbox" class="all-record-selector"/> </th> - <th t-if="options.isClarkGable" width="1"> </th> + <input type="checkbox" class="all-record-selector"/> + </th> + <th t-if="options.isClarkGable" width="1"></th> <t t-foreach="columns" t-as="column"> <th t-if="!column.meta and column.invisible !== '1'" t-att-data-id="column.id" t-att-class="((options.sortable and column.tag !== 'button') ? 'oe-sortable' : null)"> - <t t-if="column.tag !== 'button'" - ><t t-esc="column.string"/></t> + <t t-if="column.tag !== 'button'"><t t-esc="column.string"/></t> </th> </t> <th t-if="options.deletable" width="1"/> </tr> </thead> - <tfoot class="ui-widget-header"> + <tfoot> <tr> <td t-if="options.selectable"/> <td t-if="options.isClarkGable"/> @@ -653,32 +642,26 @@ </td> <td t-if="options.deletable"/> </tr> - <tr> - <t t-call="Listview.navigation.button"/> - </tr> </tfoot> </table> -<t t-extend="ListView" t-name="One2Many.listview"> - <t t-jquery="thead.ui-widget-header > tr:first"> - this.removeAttr('t-if'); - </t> - - <t t-jquery="tfoot > tr:last-child" t-operation="replace"/> - - <t t-jquery="td.oe-actions"> - this.removeAttr('t-if'); - </t> - <t t-jquery="td.oe-actions" t-operation="prepend"> - <h3 class="oe_view_title"><t t-esc="fields_view.arch.attrs.string"/></h3> +<div t-name="ListView.buttons" class="oe_list_buttons"> + <t t-if="!widget.no_leaf and widget.options.action_buttons !== false"> + <button type="button" class="oe_button oe_list_add" t-if="widget.options.addable"> + <t t-esc="widget.options.addable"/> + </button> + <button type="button" class="oe_button oe_list_delete" t-if="widget.options.selectable and widget.options.deletable"> + Delete + </button> </t> +</div> +<t t-name="ListView.pager"> + <div class="oe_list_pager" t-att-colspan="widget.columns_count"> + <t t-if="!widget.no_leaf and widget.options.pager !== false" t-call="ViewPager"> + <span class="oe-pager-state"> + </span> + </t> + </div> </t> -<th t-name="Listview.navigation.button" t-if="!no_leaf and options.pager !== false" - class="oe-list-pager" t-att-colspan="columns_count"> - <t t-call="ViewPager"> - <span class="oe-pager-state"> - </span> - </t> -</th> <t t-name="ListView.rows" t-foreach="records.length" t-as="index"> <t t-call="ListView.row"> <t t-set="record" t-value="records.at(index)"/> @@ -711,73 +694,38 @@ </td> </t> <td t-if="options.deletable" class='oe-record-delete' width="1"> - <button type="button" name="delete"></button> + <button type="button" name="delete" class="oe_i">d</button> </td> </tr> -<t t-name="ListView.row.form"> - <th t-if="widget.editable_list.options.selectable"></th> - <th t-if="widget.editable_list.options.isClarkGable"></th> -</t> <t t-name="FormView"> - <div class="oe_form_header"> - <div class="oe_form_buttons" t-if="widget.options.action_buttons !== false"> - <button type="button" class="oe_button oe_form_button_save">Save</button> - <button type="button" class="oe_button oe_form_button_cancel">Cancel</button> - </div> - <div class="oe_form_pager" t-if="widget.options.pager !== false"> - <t t-call="ViewPager"> - <span class="oe_pager_index">0</span><span class="oe_pager_separator"> / </span><span class="oe_pager_count">0</span> - </t> - </div> + <div class="oe_formview"> + <div class="oe_form_buttons"/> + <div class="oe_form_sidebar"/> + <div class="oe_form_pager"/> + <div class="oe_form_container"/> </div> </t> -<t t-name="One2Many.formview" t-extend="FormView"> - <t t-jquery=".oe_form_buttons" t-operation="inner"> - <button type="button" class="oe_button oe_form_button_create">Add</button> - </t> -</t> -<t t-name="PageView" t-extend="FormView"> - <t t-jquery=".oe_form_buttons" t-operation="inner"> - <button type="button" class="oe_button oe_form_button_edit">Edit</button> - <button type="button" class="oe_button oe_form_button_create">Create</button> - <button type="button" class="oe_button oe_form_button_duplicate">Duplicate</button> - <button type="button" class="oe_button oe_form_button_delete">Delete</button> +<div t-name="FormView.buttons" class="oe_form_buttons"> + <t t-if="widget.options.action_buttons !== false"> + <span class="oe_form_buttons_view"> + <button type="button" class="oe_button oe_form_button_edit">Edit</button> + <button type="button" class="oe_button oe_form_button_create">Create</button> + <button type="button" class="oe_button oe_form_button_duplicate">Duplicate</button> + <button type="button" class="oe_button oe_form_button_delete">Delete</button> + </span> + <span class="oe_form_buttons_edit"> + <button type="button" class="oe_button oe_form_button_save">Save</button> + <button type="button" class="oe_button oe_form_button_cancel">Cancel</button> + </span> </t> -</t> -<t t-name="FormView.sidebar.attachments"> - <div class="oe-sidebar-attachments-toolbar"> - <div class="oe-binary-file-set" style="float: right"> - <form class="oe-binary-form" t-attf-target="#{element_id}_iframe" - method="post" enctype="multipart/form-data" action="/web/binary/upload_attachment"> - <input type="hidden" name="session_id" t-att-value="session.session_id"/> - <input type="hidden" name="callback" t-attf-value="#{element_id}_iframe"/> - <input type="hidden" name="model" t-att-value="view.dataset.model"/> - <input type="hidden" name="id" t-att-value="view.datarecord.id"/> - <button class="oe_button" type="button"> - <img t-att-src='_s + "/web/static/src/img/throbber.gif"' width="16" height="16" style="display: none"/> - <span>Add</span> - </button> - <input type="file" class="oe-binary-file" name="ufile" title="Add attachment" - t-att-onclick="view.datarecord.id ? null : 'alert(\'No record selected ! You can only attach to existing record.\'); return false;'"/> - </form> - <iframe t-attf-id="#{element_id}_iframe" t-attf-name="#{element_id}_iframe" style="display: none"/> - </div> +</div> +<t t-name="FormView.pager"> + <div class="oe_form_pager"> + <t t-if="widget.options.pager !== false" t-call="ViewPager"> + <span class="oe_pager_index">0</span><span class="oe_pager_separator"> / </span><span class="oe_pager_count">0</span> + </t> </div> - <br style="clear: both"/> - <ul class="oe-sidebar-attachments-items"> - <li t-foreach="attachments" t-as="attachment"> - <t t-if="attachment.type == 'binary'" t-set="attachment.url" t-value="_s + '/web/binary/saveas?session_id=' - + session.session_id + '&model=ir.attachment&id=' + attachment.id - + '&field=datas&filename_field=name&t=' + (new Date().getTime())"/> - <a class="oe-sidebar-attachments-link" t-att-href="attachment.url" target="_blank"> - <t t-esc="attachment.name"/> - </a> - <a href="#" class="oe-sidebar-attachment-delete" t-att-data-id="attachment.id" t-attf-title="Delete the attachment #{attachment.name}"> - <img t-att-src='_s + "/web/static/src/img/attachments-close.png"' width="15" height="15" border="0"/> - </a> - </li> - </ul> </t> <form t-name="FormView.set_default" class="oe_forms oe_frame"> <t t-set="args" t-value="widget.dialog_options.args"/> @@ -789,7 +737,7 @@ Default: </label> </td> - <td class="required"> + <td class="oe_form_required"> <select id="formview_default_fields"> <option value=""/> <option t-foreach="args.fields" t-as="field" @@ -835,86 +783,69 @@ </tr> </table> </form> -<t t-name="Widget"> - Unhandled widget - <t t-js="dict">console.warn('Unhandled widget', dict.widget);</t> -</t> -<t t-name="WidgetFrame"> - <table border="0" width="100%" cellpadding="0" cellspacing="0" class="oe_frame oe_forms"> - <tr t-foreach="widget.table" t-as="row"> - <t t-foreach="row" t-as="td"> - <td t-att-colspan="td.colspan gt 1 ? td.colspan : undefined" - t-att-width="td.width" - t-att-valign="td.table ? 'top' : undefined" - t-attf-class="oe_form_frame_cell #{td.classname} #{td.element_class}" - > - <t t-raw="td.render()"/> - </td> - </t> - </tr> - </table> + +<t t-name="FormRenderingForm.manual"> + <div class="oe_form_manual_layout"/> </t> -<t t-name="WidgetFrame.readonly" t-extend="WidgetFrame"> - <t t-jquery=">table"> - this.attr('class', this.attr('class')+' oe_form_readonly'); - </t> +<t t-name="FormRenderingForm"> + <div class="oe_form_sheetbg"> + <div t-attf-class="oe_form_sheet oe_form_sheet_width #{classnames}"> + <group col="4"/> + </div> + </div> </t> -<t t-name="WidgetGroup"> - <t t-if="widget.string"> - <fieldset class="oe_group_box"> - <legend><t t-esc="widget.string"/></legend> - <t t-call="WidgetFrame"/> - </fieldset> +<t t-name="FormRenderingGroup"> + <t t-set="table"> + <table border="0" cellpadding="0" cellspacing="0" width="100%" t-att-class="classnames"/> </t> - <t t-if="!widget.string"> - <t t-call="WidgetFrame"/> + <t t-if="string"> + <fieldset class="oe_group_box"> + <legend><t t-esc="string"/></legend> + <t t-raw="table"/> + </fieldset> </t> -</t> -<t t-name="WidgetNotebook"> - <ul> - <li t-foreach="widget.pages" t-as="page"> - <a href="#"> - <t t-esc="page.string"/> - </a> - </li> - </ul> - <t t-foreach="widget.pages" t-as="page"> - <div t-att-class="page.element_class"> - </div> + <t t-if="!string"> + <t t-raw="table"/> </t> </t> -<t t-name="WidgetNotebook.tooltip"> - <t t-foreach="widget.pages" t-as="page"> - <div class="oe_tooltip_string"> - Notebook Page "<t t-esc="page.string"/>" - </div> - <ul class="oe_tooltip_technical"> - <li data-item="modifiers"> - <span class="oe_tooltip_technical_title">Modifiers:</span> - <t t-esc="page.node.attrs.modifiers"/> +<t t-name="FormRenderingNotebook"> + <div> + <ul t-attf-class="oe_form_notebook #{classnames}"> + <li t-foreach="pages" t-as="page" t-att-modifiers="page.modifiers"> + <a t-attf-href="##{page.id}"> + <t t-esc="page.string"/> + </a> </li> </ul> - </t> + </div> </t> -<t t-name="WidgetNotebookPage"> - <t t-call="WidgetFrame"/> +<t t-name="FormRenderingNotebookPage"> + <div t-attf-class="oe_form_notebook_page #{classnames}" t-att-id="id"> + <group t-if="layout === 'auto'" col="4"/> + </div> </t> -<t t-name="WidgetSeparator"> - <div t-if="widget.orientation !== 'vertical'" t-att-class="'separator ' + widget.orientation"> - <t t-esc="widget.string"/> +<t t-name="FormRenderingSeparator"> + <div t-attf-class="oe_horizontal_separator #{classnames}"> + <t t-esc="string"/> </div> </t> -<t t-name="WidgetLabel"> - <label t-att-for="widget.element_id" - t-attf-class="oe_label#{widget.help ? '_help' : ''} oe_align_#{widget.align}"> - <t t-esc="widget.string"/> - <span t-if="widget.help">?</span> - <t t-if="widget.string and widget.node.tag != 'label'">:</t> +<t t-name="FormRenderingLabel"> + <label t-att-for="_for" + t-att-title="help" + t-attf-class="#{classnames} oe_form_label#{help ? '_help' : ''} oe_align_#{align}"> + <t t-esc="string"/> + <span t-if="help">?</span> + : </label> </t> + +<t t-name="Widget"> + Unhandled widget + <t t-js="dict">console.warn('Unhandled widget', dict.widget);</t> +</t> <t t-name="WidgetLabel.tooltip"> <div class="oe_tooltip_string" t-if="widget.string"> - <t t-esc="widget.string"/> <t t-if="debug and widget.nolabel">(nolabel)</t> + <t t-esc="widget.node.attrs.string"/> <t t-if="debug and widget.nolabel">(nolabel)</t> </div> <p t-if="widget.help" class="oe_tooltip_help"><t t-esc="widget.help"/></p> <ul t-if="debug" class="oe_tooltip_technical"> @@ -974,68 +905,88 @@ </li> </ul> </t> -<t t-name="WidgetParagraph"> - <p t-attf-class="oe_form_paragraph oe_align_#{widget.align} #{widget.multilines ? 'oe_multilines' : ''}"><t t-esc="widget.string"/></p> -</t> <t t-name="FieldChar"> - <input t-att-type="widget.password ? 'password' : 'text'" size="1" - t-att-name="widget.name" - t-att-id="widget.element_id" - t-attf-class="field_#{widget.type} #{_(['integer', 'float', 'float_time']).contains(widget.type) ? 'oe-number' : ''}" - style="width: 100%" - /><img class="oe_field_translate oe_input_icon" t-if="widget.field.translate" t-att-src='_s + "/web/static/src/img/icons/terp-translate.png"' width="16" height="16" border="0"/> -</t> -<t t-name="FieldChar.readonly"> - <div - t-att-id="widget.element_id" - t-attf-class="field_#{widget.type} #{_(['integer', 'float', 'float_time']).contains(widget.type) ? 'oe-number' : ''}"> - </div> -</t> -<t t-name="FieldURI.readonly"> - <div> - <a href="#" class="oe_form_uri"/> - </div> + <t t-if="!widget.get('effective_readonly')"> + <div class="oe_form_field_char"> + <input t-att-type="widget.password ? 'password' : 'text'" + t-att-id="widget.id_for_label" + t-att-tabindex="widget.node.attrs.tabindex" + t-att-autofocus="widget.node.attrs.autofocus" + t-attf-class="field_char #{widget.is_field_number ? 'oe-number' : ''}" + /><img class="oe_field_translate oe_input_icon" t-if="widget.field.translate" t-att-src='_s + "/web/static/src/img/icons/terp-translate.png"' width="16" height="16" border="0"/> + </div> + </t> + <t t-if="widget.get('effective_readonly')"> + <div t-attf-class="oe_form_field_char #{widget.is_field_number ? 'oe-number' : ''}"> + </div> + </t> </t> <t t-name="FieldEmail"> - <table cellpadding="0" cellspacing="0" border="0" width="100%"> - <tr> - <td width="100%"> - <t t-call="FieldChar"/> - </td> - <td width="16"> - <button class="oe_button oe_field_button" type="button" title="Send an e-mail with your default e-mail client"> - <img t-att-src='_s + "/web/static/src/img/icons/terp-mail-message-new.png"'/> - </button> - </td> - </tr> - </table> + <t t-if="!widget.get('effective_readonly')"> + <table class="oe_form_field_email" cellpadding="0" cellspacing="0" border="0"> + <tr> + <td> + <input type="text" + t-att-id="widget.id_for_label" + t-att-tabindex="widget.node.attrs.tabindex" + t-att-autofocus="widget.node.attrs.autofocus" + /> + </td> + <td width="16"> + <button class="oe_button oe_field_button" tabindex="-1" type="button" title="Send an e-mail with your default e-mail client"> + <img t-att-src='_s + "/web/static/src/img/icons/terp-mail-message-new.png"'/> + </button> + </td> + </tr> + </table> + </t> + <t t-if="widget.get('effective_readonly')"> + <div> + <a href="#" class="oe_form_uri"/> + </div> + </t> </t> <t t-name="FieldUrl"> - <table cellpadding="0" cellspacing="0" border="0" width="100%"> - <tr> - <td width="100%"> - <t t-call="FieldChar"/> - </td> - <td width="16"> - <button class="oe_button oe_field_button" type="button" title="Open this resource"> - <img t-att-src='_s + "/web/static/src/img/icons/gtk-ok.png"'/> - </button> - </td> - </tr> - </table> + <t t-if="!widget.get('effective_readonly')"> + <table class="oe_form_field_url" cellpadding="0" cellspacing="0" border="0"> + <tr> + <td> + <input type="text" + t-att-id="widget.id_for_label" + t-att-tabindex="widget.node.attrs.tabindex" + t-att-autofocus="widget.node.attrs.autofocus" + /> + </td> + <td width="16"> + <button class="oe_button oe_field_button" tabindex="-1" type="button" title="Open this resource"> + <img t-att-src='_s + "/web/static/src/img/icons/gtk-ok.png"'/> + </button> + </td> + </tr> + </table> + </t> + <t t-if="widget.get('effective_readonly')"> + <div> + <a href="#" class="oe_form_uri"/> + </div> + </t> </t> <t t-name="FieldText"> - <textarea rows="6" - t-att-name="widget.name" - t-att-id="widget.element_id" - t-attf-class="field_#{widget.type}" - style="width: 100%" - ></textarea><img class="oe_field_translate oe_input_icon" t-if="widget.field.translate" t-att-src='_s + "/web/static/src/img/icons/terp-translate.png"' width="16" height="16" border="0"/> + <div class="oe_form_field_text"> + <textarea rows="6" + t-att-name="widget.name" + class="field_text" + t-att-tabindex="widget.node.attrs.tabindex" + t-att-autofocus="widget.node.attrs.autofocus" + ></textarea> + <img class="oe_field_translate oe_input_icon" t-if="widget.field.translate" + t-att-src='_s + "/web/static/src/img/icons/terp-translate.png"' width="16" height="16" border="0"/> + </div> </t> <t t-name="web.datetimepicker"> - <div class="oe_datepicker_root"> - <input type="text" size="1" class="oe_datepicker_container" disabled="disabled" style="display: none;"/> - <input type="text" size="1" style="width: 100%" + <div class="oe_datepicker_root oe_form_field_datetime"> + <input type="text" class="oe_datepicker_container" disabled="disabled" style="display: none;"/> + <input type="text" t-att-name="widget.name" t-attf-class="oe_datepicker_master field_#{widget.type_of_date}" /><img class="oe_input_icon oe_datepicker_trigger" t-att-src='_s + "/web/static/src/img/ui/field_calendar.png"' @@ -1043,86 +994,107 @@ </div> </t> <t t-name="FieldSelection"> - <select - t-att-name="widget.name" - t-att-id="widget.element_id" - t-attf-class="field_#{widget.type}" - style="width: 100%"> - <t t-foreach="widget.values" t-as="option"> - <option><t t-esc="option[1]"/></option> - </t> - </select> + <t t-if="!widget.get('effective_readonly')"> + <div class="oe_form_field_selection"> + <select + t-att-name="widget.name" + t-att-tabindex="widget.node.attrs.tabindex" + t-att-autofocus="widget.node.attrs.autofocus" + t-att-id="widget.id_for_label"> + <t t-foreach="widget.values" t-as="option"> + <option><t t-esc="option[1]"/></option> + </t> + </select> + </div> + </t> + <t t-if="widget.get('effective_readonly')"> + <div class="field_selection"> + </div> + </t> </t> <t t-name="FieldMany2One"> - <table cellpadding="0" cellspacing="0" border="0" width="100%"> - <tr> - <td width="100%" valign="top"> - <t t-call="FieldChar"/> - <span class="oe-m2o-drop-down-button"> - <img t-att-src='_s + "/web/static/src/img/down-arrow.png"' /></span> - </td> - <td> - <button class="oe_button oe_field_button oe-m2o-cm-button" t-att-id="widget.name + '_open'"> - <img t-att-src='_s + "/web/static/src/img/icons/STOCK_DIRECTORY_MENU.png"'/> - </button> - <div t-att-id="widget.cm_id" class="contextMenu" style="display:none"> - </div> - </td> - </tr> - </table> -</t> -<t t-name="FieldMany2One.context_menu"> - <ul> - <li t-att-id="widget.cm_id + '_open'" style="color:grey" class="oe_m2o_menu_item_mandatory">Open...</li> - <li t-att-id="widget.cm_id + '_create'" class="oe_m2o_menu_item_noreadonly">Create...</li> - <li t-att-id="widget.cm_id + '_search'" class="oe_m2o_menu_item_noreadonly">Search...</li> - <t t-set="i" t-value="0"/> - <t t-foreach="widget.related_entries" t-as="entry"> - <li t-att-id="widget.cm_id + '_related_' + i" style="color:grey" class="oe_m2o_menu_item_mandatory"> - ... <t t-esc="(entry[2] || {})['name'] || ''"/> - </li> - <t t-set="i" t-value="i+1"/> - </t> - </ul> -</t> -<t t-name="FieldOne2Many"> -</t> -<t t-name="FieldMany2Many"> - <div t-att-id="widget.list_id"></div> + <t t-if="!widget.get('effective_readonly')"> + <table class="oe_form_field_many2one" cellpadding="0" cellspacing="0" border="0"> + <tr> + <td valign="top"> + <input type="text" + t-att-id="widget.id_for_label" + t-att-tabindex="widget.node.attrs.tabindex" + t-att-autofocus="widget.node.attrs.autofocus" + /> + <span class="oe-m2o-drop-down-button"> + <img t-att-src='_s + "/web/static/src/img/down-arrow.png"' /></span> + </td> + <td width="16"> + <button class="oe_button oe_field_button oe-m2o-cm-button"> + <img t-att-src='_s + "/web/static/src/img/icons/gtk-go-forward.png"'/> + </button> + </td> + </tr> + </table> + </t> + <t t-if="widget.get('effective_readonly')"> + <div> + <a href="#" class="oe_form_uri"/> + </div> + </t> </t> <t t-name="FieldReference"> - <table border="0" width="100%" cellpadding="0" cellspacing="0" class="oe_frame oe_forms"> - <tr> - <td t-attf-class="oe_form_frame_cell oe_form_selection oe_form_view_reference_selection"> - </td> - <td t-attf-class="oe_form_frame_cell oe_form_many2one oe_form_view_reference_m2o" nowrap="true" style="display: none"> - </td> - </tr> - </table> + <t t-if="!widget.get('effective_readonly')"> + <table class="oe_form_field_reference oe_frame oe_forms" border="0" cellpadding="0" cellspacing="0"> + <tr> + <td t-attf-class="oe_form_frame_cell oe_form_selection oe_form_view_reference_selection"> + </td> + <td t-attf-class="oe_form_frame_cell oe_form_many2one oe_form_view_reference_m2o" nowrap="true" style="display: none"> + </td> + </tr> + </table> + </t> + <t t-if="widget.get('effective_readonly')"> + <a href="#" class="oe_form_uri"/> + </t> </t> <t t-name="FieldBoolean"> - <input type="checkbox" - t-att-name="widget.name" - t-att-id="widget.element_id" - t-attf-class="field_#{widget.type}"/> + <span class="oe_form_field_boolean"> + <input type="checkbox" + t-att-id="widget.id_for_label" + t-att-name="widget.name" + t-att-tabindex="widget.node.attrs.tabindex" + t-att-autofocus="widget.node.attrs.autofocus" + class="field_boolean"/> + </span> </t> <t t-name="FieldProgressBar"> - <div t-opentag="true" class="oe-progressbar"> + <div class="oe_form_field_progressbar" t-opentag="true"> <span></span> </div> </t> +<t t-name="FieldStatus.content"> + <ul class="oe-arrow-list"> + <t t-set="size" t-value="widget.to_show.length"/> + <t t-foreach="_.range(size)" t-as="i"> + <li t-att-class="widget.to_show[i][0] === widget.selected_value ? 'oe-arrow-list-selected' : ''"> + <span class="oe-arrow-list-before" t-if="i > 0"></span><span><t t-esc="widget.to_show[i][1]"/></span><span class="oe-arrow-list-after" t-if="i < size - 1"></span> + </li> + </t> + </ul> +</t> +<t t-name="FieldStatus.content"> + <ul class="oe_form_steps"> + <t t-set="size" t-value="widget.to_show.length"/> + <t t-foreach="_.range(size)" t-as="i"> + <li t-att-class="widget.to_show[i][0] === widget.selected_value ? 'oe_form_steps_active' : ''"> + <t t-esc="widget.to_show[i][1]"/> + <img t-att-src='_s + "/web/static/src/img/form_steps.png"' class="oe_form_steps_arrow" t-if="i < size - 1"/> + </li> + </t> + </ul> +</t> <t t-name="FieldBinaryImage"> <table cellpadding="0" cellspacing="0" border="0" width="100%"> <tr> - <td align="center"> - <img t-att-src='_s + "/web/static/src/img/placeholder.png"' class="oe-binary-image" - t-att-border="widget.readonly ? 0 : 1" - t-att-id="widget.element_id + '_field'" - t-att-name="widget.name" - t-attf-class="field_#{widget.type}" - t-att-width="widget.node.attrs.img_width || widget.node.attrs.width" - t-att-height="widget.node.attrs.img_height || widget.node.attrs.height" - /> + <td class="oe_form_field-binary-image-placeholder" align="center"> + </td> </tr> <tr> @@ -1131,7 +1103,7 @@ <table cellspacing="0" cellpadding="0" border="0"> <tr> <td> - <div class="oe-binary-file-set" style="width: 40px; height:22px;"> + <div class="oe-binary-file-set"> <form class="oe-binary-form" t-att-target="widget.iframe" method="post" enctype="multipart/form-data" action="/web/binary/upload"> <input type="hidden" name="session_id" value=""/> @@ -1139,7 +1111,10 @@ <button class="oe_button" type="button" title="Set Image"> <img t-att-src='_s + "/web/static/src/img/icons/STOCK_DIRECTORY.png"'/> </button> - <input type="file" class="oe-binary-file" name="ufile"/> + <input type="file" class="oe-binary-file" name="ufile" + t-att-tabindex="widget.node.attrs.tabindex" + t-att-autofocus="widget.node.attrs.autofocus" + /> </form> </div> </td> @@ -1160,58 +1135,77 @@ </tr> </table> </t> +<t t-name="FieldBinaryImage-img"> + <img t-att-src='url' class="oe-binary-image field_image" + t-att-border="widget.readonly ? 0 : 1" + t-att-name="widget.name" + t-att-width="widget.node.attrs.img_width || widget.node.attrs.width" + t-att-height="widget.node.attrs.img_height || widget.node.attrs.height" + t-att-style="'max-width: ' + (widget.$element.width() > 60 ? widget.$element.width() : 60) + 'px'" + /> +</t> <t t-name="FieldBinaryFile"> - <table cellpadding="0" cellspacing="0" border="0" width="100%"> - <tr> - <td width="100%"> - <input type="text" size="1" readonly="readonly" - t-att-name="widget.name" - t-att-id="widget.element_id + '_field'" - t-attf-class="field_#{widget.type}" style="width: 100%" - /> - </td> - <td class="oe-binary" nowrap="true"> - <table cellspacing="0" cellpadding="0" border="0"> - <tr> - <td> - <div class="oe-binary-file-set" style="width: 80px; height:22px;"> - <form class="oe-binary-form" t-att-target="widget.iframe" - method="post" enctype="multipart/form-data" action="/web/binary/upload"> - <input type="hidden" name="session_id" value=""/> - <input type="hidden" name="callback" t-att-value="widget.iframe"/> - <button class="oe_button oe_field_button" type="button" title="Set Image"> - <img t-att-src='_s + "/web/static/src/img/icons/STOCK_DIRECTORY.png"'/> - <span>Select</span> - </button> - <input type="file" class="oe-binary-file" name="ufile"/> - </form> - </div> - </td> - <td> - <button class="oe_button oe-binary-file-save" type="button" title="Save As"> - <img t-att-src='_s + "/web/static/src/img/icons/gtk-save.png"'/> - <span>Save As</span> - </button> - </td> - <td> - <button class="oe_button oe-binary-file-clear" type="button" title="Clear"> - <img t-att-src='_s + "/web/static/src/img/icons/STOCK_MISSING_IMAGE.png"'/> - <span>Clear</span> - </button> - </td> - </tr> - </table> - </td> - <td class="oe-binary-progress" style="display: none" nowrap="true"> - <img t-att-src='_s + "/web/static/src/img/throbber.gif"' width="16" height="16"/> - <b>Uploading ...</b> - <iframe t-att-id="widget.iframe" t-att-name="widget.iframe" style="display: none"/> - </td> - </tr> - </table> + <t t-if="!widget.get('effective_readonly')"> + <table class="oe_form_field_binary" cellpadding="0" cellspacing="0" border="0"> + <tr> + <td> + <input type="text" size="1" readonly="readonly" + t-att-name="widget.name" + t-att-tabindex="widget.node.attrs.tabindex" + t-att-autofocus="widget.node.attrs.autofocus" + class="field_binary" + /> + </td> + <td class="oe-binary" nowrap="true"> + <table cellspacing="0" cellpadding="0" border="0"> + <tr> + <td> + <div class="oe-binary-file-set" style="width: 80px; height:22px;"> + <form class="oe-binary-form" t-att-target="widget.iframe" + method="post" enctype="multipart/form-data" action="/web/binary/upload"> + <input type="hidden" name="session_id" value=""/> + <input type="hidden" name="callback" t-att-value="widget.iframe"/> + <button class="oe_button oe_field_button" type="button" title="Set Image"> + <img t-att-src='_s + "/web/static/src/img/icons/STOCK_DIRECTORY.png"'/> + <span>Select</span> + </button> + <input type="file" class="oe-binary-file" name="ufile"/> + </form> + </div> + </td> + <td> + <button class="oe_button oe-binary-file-save" type="button" title="Save As"> + <img t-att-src='_s + "/web/static/src/img/icons/gtk-save.png"'/> + <span>Save As</span> + </button> + </td> + <td> + <button class="oe_button oe-binary-file-clear" type="button" title="Clear"> + <img t-att-src='_s + "/web/static/src/img/icons/STOCK_MISSING_IMAGE.png"'/> + <span>Clear</span> + </button> + </td> + </tr> + </table> + </td> + <td class="oe-binary-progress" style="display: none" nowrap="true"> + <img t-att-src='_s + "/web/static/src/img/throbber.gif"' width="16" height="16"/> + <b>Uploading ...</b> + <iframe t-att-id="widget.iframe" t-att-name="widget.iframe" style="display: none"/> + </td> + </tr> + </table> + </t> + <t t-if="widget.get('effective_readonly')"> + <div> + <a href="javascript:void(0)" class="oe_form_uri"/> + </div> + </t> </t> <t t-name="WidgetButton"> - <button type="button" class="oe_button"> + <button type="button" class="oe_button oe_form_button" + t-att-tabindex="widget.node.attrs.tabindex" + t-att-autofocus="widget.node.attrs.autofocus"> <img t-if="widget.node.attrs.icon" t-att-src="_s + '/web/static/src/img/icons/' + widget.node.attrs.icon + '.png'" width="16" height="16"/> <span t-if="widget.string"><t t-esc="widget.string"/></span> </button> @@ -1248,17 +1242,57 @@ </t> </t> -<t t-name="SearchView"> - <form class="oe_forms"> - <t t-call="SearchView.render_lines"/> - <div class="oe_search-view-buttons"> - <button class="oe_button">Search</button> - <button class="oe_button" type="reset">Clear</button> - <select class="oe_search-view-filters-management"> - </select> - </div> - </form> +<t t-name="TranslateDialog"> + <table t-if="widget.view.translatable_fields" class="oe_form oe_translation_form" border="0" cellpadding="0" cellspacing="0" width="100%"> + <tr> + <td class="oe_form_separator" width="1%" nowrap="nowrap"> + <div class="oe_horizontal_separator">Field</div> + </td> + <th t-foreach="widget.languages" align="left"> + <div class="oe_horizontal_separator"><t t-esc="name"/></div> + </th> + </tr> + <tr t-foreach="widget.view.translatable_fields" t-as="field" t-att-data-field="field.name"> + <td class="oe_form_group_cell" width="1%" nowrap="nowrap"> + <label class="oe_label"><t t-esc="field.node.attrs.string"/>:</label> + </td> + <td t-foreach="widget.languages" t-as="lg" class="oe_form_group_cell"> + <input t-if="field.type == 'char'" type="text" t-attf-name="#{lg.code}-#{field.name}" value="" data-value="" class="oe_trad_field" style="width: 100%"/> + <textarea t-if="field.type == 'text'" t-attf-name="#{lg.code}-#{field.name}" data-value="" class="oe_trad_field" style="width: 100%"></textarea> + </td> + </tr> + </table> +</t> + +<t t-name="One2Many.viewmanager" t-extend="ViewManager"> + <t t-jquery="span.oe_view_title_text" t-operation="replace"/> + + <t t-jquery=".oe-view-manager-header"> + this.attr('t-if', 'views.length != 1'); + </t> +</t> +<t t-name="One2Many.formview" t-extend="FormView"> + <t t-jquery=".oe_form_buttons" t-operation="inner"> + <button type="button" class="oe_button oe_form_button_create">Add</button> + </t> +</t> +<t t-name="One2Many.listview" t-extend="ListView"> + <t t-jquery="thead.ui-widget-header > tr:first"> + this.removeAttr('t-if'); + </t> + + <t t-jquery="tfoot > tr:last-child" t-operation="replace"/> + + <t t-jquery="td.oe-actions"> + this.removeAttr('t-if'); + </t> + <t t-jquery="td.oe-actions" t-operation="prepend"> + <h3 class="oe_view_title"><t t-esc="fields_view.arch.attrs.string"/></h3> + </t> </t> + +<div t-name="SearchView" class="oe_searchview"> +</div> <t t-name="SearchView.managed-filters"> <option class="oe-filters-title" value="">Filters</option> <optgroup label="-- Filters --"> @@ -1314,10 +1348,28 @@ <br t-if="attrs.string"/> <t t-esc="attrs.string"/> </button> -<span t-name="SearchView.filters" class="filter_label_group" - ><t t-foreach="filters" t-as="filter" - ><t t-raw="filter.render(defaults)"/></t -></span> +<ul t-name="SearchView.filters"> + <li t-foreach="widget.filters" t-as="filter"> + <t t-esc="filter.attrs.string or filter.attrs.name or 'Ω'"/> + </li> +</ul> +<t t-name="SearchView.filters.facet"> + <div class="category oe_filter_category"><t t-esc="facet.get('category')"/></div> + + <t t-set="val" t-value="facet.get('json')"/> + + <div t-if="!(val instanceof Array)" class="search_facet_input_container"> + <t t-esc="facet.get('value')"/> + </div> + <t t-if="val instanceof Array"> + <div class="search_facet_input_container" + t-foreach="facet.get('json')" t-as="filter"> + <t t-esc="filter.attrs.string || filter.attrs.name"/> + </div> + </t> + + <div class="search_facet_remove VS-icon VS-icon-cancel"/> +</t> <t t-name="SearchView.field"> <label t-att-class="'oe_label' + (attrs.help ? '_help' : '')" t-att-title="attrs.help" @@ -1375,8 +1427,7 @@ </div> </t> <t t-name="SearchView.util.expand"> - <div t-att-class="'searchview_group ' + (expand == '0' ? 'folded' : 'expanded')" - t-att-id="element_id"> + <div t-att-class="'searchview_group ' + (expand == '0' ? 'folded' : 'expanded')"> <a t-if="label" class="searchview_group_string" href="#"> <t t-esc="label"/> </a> @@ -1394,34 +1445,18 @@ </t> </t> </t> -<t t-name="SearchView.extended_search"> - <t t-call="SearchView.util.expand"> - <t t-set="expand" t-value="false"/> - <t t-set="label">Advanced Filters</t> - <t t-set="content"> - <div class="searchview_extended_groups_list"> - </div> - </t> - </t> -</t> -<t t-name="SearchView.extended_search.group"> - <div t-att-id="widget.element_id" class="searchview_extended_group"> - <select class="searchview_extended_group_choice"> - <option value="any">Any of the following conditions must match</option> - <option value="all">All the following conditions must match</option> - <option value="none">None of the following conditions must match</option> - </select> - <a class="searchview_extended_delete_group" - href="javascript:void(0)"><span></span></a> - <div class="searchview_extended_propositions_list"> - </div> - <a class="searchview_extended_add_proposition" href="javascript:void(0)"> - <span>Add condition</span></a> - <div class="oe_adv_filters_and"><span>and</span></div> - </div> -</t> +<div t-name="SearchView.advanced" class="oe_searchview_advanced"> + <h4>Advanced Search...</h4> + <form> + <ul> + + </ul> + <button class="oe_add_condition" type="button">Add a condition</button> + <button class="oe_apply" type="submit">Apply</button> + </form> +</div> <t t-name="SearchView.extended_search.proposition"> - <div t-att-id="widget.element_id"> + <li> <select class="searchview_extended_prop_field"> <t t-foreach="widget.attrs.fields" t-as="field"> <option t-if="typeof field.store === 'undefined' || field.store || field.fnct_search" @@ -1435,7 +1470,7 @@ <span class="searchview_extended_prop_value"/> <a class="searchview_extended_delete_prop" href="javascript:void(0)"><span> </span></a> - </div> + </li> </t> <t t-name="SearchView.extended_search.proposition.char"> <input t-att-id="widget.element_id" class="field_char"/> @@ -1459,20 +1494,20 @@ </select> </t> <t t-name="SelectCreatePopup"> - <div t-att-id="widget.element_id"> + <div> <table style="width:100%"> <tr style="width:100%"> <td style="width:100%"> - <div t-att-id="widget.element_id + '_search'" style="width:100%"></div> + <div class="oe-select-create-popup-view-search" style="width:100%"></div> </td> </tr> <tr style="width:100%"> <td style="width:100%"> - <div t-att-id="widget.element_id + '_view_list'" style="width:100%"></div> + <div class="oe-select-create-popup-view-list" style="width:100%"></div> </td> </tr> </table> - <div t-att-id="widget.element_id + '_view_form'" style="width:100%"></div> + <div class="oe-select-create-popup-view-form" style="width:100%"></div> </div> </t> <t t-name="SelectCreatePopup.search.buttons"> @@ -1490,8 +1525,8 @@ <button type="button" class="oe_button oe_selectcreatepopup-form-close">Cancel</button> </t> <t t-name="FormOpenPopup"> - <div t-att-id="widget.element_id"> - <div t-att-id="widget.element_id + '_view_form'" style="width:100%"></div> + <div> + <div class="oe-form-open-popup-form-view" style="width:100%"></div> </div> </t> <t t-name="FormOpenPopup.form.buttons"> @@ -1507,21 +1542,7 @@ <td t-if="edited and !options.deletable" class="oe-listview-padding"/> </t> </t> -<t t-name="ListView.row.frame"> - <t t-foreach="widget.table" t-as="row"> - <t t-foreach="row" t-as="td"> - <td t-att-colspan="td.colspan gt 1 ? td.colspan : undefined" - t-att-valign="td.table ? 'top' : undefined" - t-attf-class="oe_form_frame_cell #{td.classname} #{td.element_class} oe-field-cell" - > - <t t-raw="td.render()"/> - </td> - </t> - </t> - <td> - <button class="oe-edit-row-save" type="button"> </button> - </td> -</t> + <t t-name="view_editor"> <table class="oe_view_editor"> <t t-call="view_editor.row"/> @@ -1585,6 +1606,7 @@ <t t-name="vieweditor_boolean"> <input type="checkbox" t-att-id="widget.name"/> </t> + <t t-name="ExportView"> <a id="exportview" href="javascript: void(0)" style="text-decoration: none;color: #3D3D3D;">Export</a> </t> @@ -1636,7 +1658,6 @@ </td> </tr> </table> - <table t-name="ExportTreeView-Secondary" id="field-tree-structure" class="oe_export_fields_selector_export" cellspacing="0" cellpadding="0"> @@ -1670,7 +1691,6 @@ </table> </td> </tr> - <t t-name="ExportNewList"> <tr> <th><label>Save as:</label></th> @@ -1678,7 +1698,6 @@ <td><button class="oe_button oe_export_button_export" id="add_export_list">Ok</button></td> </tr> </t> - <t t-name="Exists.ExportList"> <label for="saved_export_list">Saved exports:</label> @@ -1690,30 +1709,6 @@ </select> <button class="oe_button oe_export_button_export" id="delete_export_list" type="button">Delete</button> </t> -<t t-name="Change_Pwd"> - <form name="change_password_form" class="oe_forms" method="POST"> - <table align="center"> - <tr> - <td><label for="old_pwd">Old Password:</label></td> - <td><input type="password" name="old_pwd" - minlength="1" autofocus="autofocus"/></td> - </tr> - <tr> - <td><label for="new_password">New Password:</label></td> - <td><input type="password" name="new_password" - minlength="1" autofocus="autofocus"/></td> - </tr> - <tr> - <td><label for="confirm_pwd">Confirm Password:</label></td> - <td><input type="password" name="confirm_pwd" - minlength="1"/></td> - </tr> - <tr> - <td colspan="2" align="right"><button class="oe_button">Change Password</button></td> - </tr> - </table> - </form> -</t> <t t-name="ImportView"> <a id="importview" href="javascript: void(0)" style="text-decoration: none;color: #3D3D3D;">Import</a> @@ -1790,34 +1785,4 @@ </t> </t> -<t t-name="About-Page"> - <div> - <a class="oe_activate_debug_mode" href="?debug" style="float:right; font-size: 80%;">Activate the developper mode</a> - <h1 style="margin:0;">OpenERP</h1> - <h3 style="margin:15px 0;padding:0;">Version <t t-esc="version_info.version"/></h3> - <p> - Copyright © 2004-TODAY OpenERP SA. All Rights Reserved.<br /> - OpenERP is a trademark of the <a target="_blank" href="http://openerp.com/" style="text-decoration: underline;">OpenERP SA Company</a>. - </p> - <p> - Licenced under the terms of <a target="_blank" href="http://www.gnu.org/licenses/agpl.html" style="text-decoration: underline;">GNU Affero General Public License</a> - </p> - <p> - For more information visit <a target="_blank" href="http://openerp.com/" style="text-decoration: underline;">OpenERP.com</a> - </p> - </div> -</t> -<t t-name="FieldStatus.content"> - <ul class="oe-arrow-list"> - <t t-set="size" t-value="widget.to_show.length"/> - <t t-foreach="_.range(size)" t-as="i"> - <li t-att-class="widget.to_show[i][0] === widget.selected_value ? 'oe-arrow-list-selected' : ''"> - <span class="oe-arrow-list-before" t-if="i > 0"></span><span><t t-esc="widget.to_show[i][1]"/></span><span class="oe-arrow-list-after" t-if="i < size - 1"></span> - </li> - </t> - </ul> -</t> -<t t-name="EmptyComponent"> - <div></div> -</t> </templates> |
