diff --git a/view/view_apps_renderer.anubis b/view/view_apps_renderer.anubis
index 4c83ad4..6dbe5b2 100644
--- a/view/view_apps_renderer.anubis
+++ b/view/view_apps_renderer.anubis
@@ -22,6 +22,7 @@ define List(HTML_Row(HTML_Off_Form))
(
SQLite3DataBase db,
List(HK_App) apps,
+ String dialog_uid,
(String) -> String _T,
)=
map_append((HK_App app) |-> since app is hk_app(app_name, icon, help, tables),
@@ -34,8 +35,8 @@ define List(HTML_Row(HTML_Off_Form))
[ cell(text(_table_name)),
cell(
sequence([
- partial(img_button(icn16_add, [event(onclick, jq_dialog_open(dialog_id("edit_table_dlg_ajax"), "'table_name="+table_name+"'"))], "", [])),
- actioner(link([core_attrs([event(onclick, jq_dialog_open(dialog_id("edit_table_dlg_ajax"), "'table_name="+table_name+"'"))])],_T("ADD")), no_action, [])
+ partial(img_button(icn16_add, [event(onclick, jq_dialog_open(dialog_id(dialog_uid), "'table_name="+table_name+"'"))], "", [])),
+ actioner(link([core_attrs([event(onclick, jq_dialog_open(dialog_id(dialog_uid), "'table_name="+table_name+"'"))])],_T("ADD")), no_action, [])
])
),
cell(
@@ -70,11 +71,12 @@ public define Maybe(HTML_Partial_Content)
String lang,
String web_site_directory
) =
+ with dialog_uid = "_"+generate_random_string(15)+"_cxm_dlg",
success(partial_content([css(css_file("hayamiki/hayamiki.css")),]+
jq_dialog_init + jq_datetimepicker_init(lang),
sequence([
- partial(jq_dialog_create(dialog_id("edit_table_dlg_ajax"), ajax(controller_action("hk_c", "ajax_edit_table")))), /*&table_name="+rows.table_name*/
- table([class("hk_table_apps")], generate_apps(db, hk_db.apps, _T))
+ partial(jq_dialog_create(dialog_id(dialog_uid), ajax(controller_action("hk_c", "ajax_edit_table&dialog_id="+dialog_uid)))), /*&table_name="+rows.table_name*/
+ table([class("hk_table_apps")], generate_apps(db, hk_db.apps, dialog_uid, _T))
])
))
.
diff --git a/view/view_table_manager_types.anubis b/view/view_table_manager_types.anubis
index a5802aa..4452259 100644
--- a/view/view_table_manager_types.anubis
+++ b/view/view_table_manager_types.anubis
@@ -45,8 +45,8 @@ public type HK_Table_controller:
(SQLite3DataBase db, List(Web_arg) lwa) -> Maybe(One) write_from_fields,
//Read
(SQLite3DataBase db, String clause) -> Int get_count,
- (SQLite3DataBase db, List(String) columns, DB_id) -> JsonValue get_one_json,
- (SQLite3DataBase db, List(String) columns, HK_Referer referer, String clause, String order_by) -> JsonValue get_rows_json,
+ (SQLite3DataBase db, List(String) columns, Bool resolve_fk, DB_id) -> JsonValue get_one_json,
+ (SQLite3DataBase db, List(String) columns, Bool resolve_fk, HK_Referer referer, String clause, String order_by) -> JsonValue get_rows_json,
//Update
//Delete
(SQLite3DataBase db, List(Int) l_int) -> Maybe(One) delete,
diff --git a/view/view_table_renderer.anubis b/view/view_table_renderer.anubis
index 4260bab..facc5cd 100644
--- a/view/view_table_renderer.anubis
+++ b/view/view_table_renderer.anubis
@@ -114,6 +114,28 @@ public define HK_Filter
get_filter_from_name(filters, filter_name)
.
+define String
+ ajax_combo_init_parameter
+ (
+ String combo_uid,
+ String f_table, //db table name
+ String c_name, //column name in the table
+ List(String) s_fields //search fields
+ )=
+"$('#"+combo_uid+"').ajaxComboBox( '?aws_controller=hk_c&aws_action=combo_search',
+ {
+ lang: 'fr',
+ per_page: 15,
+ db_table: '"+f_table+"',
+ button_img: 'ajax-combobox/btn.png',
+ field: '"+force_nth(0, s_fields, "name")+"',
+ search_field: '"+join(", ", s_fields)+"',
+ hidden_name: '"+c_name+"',
+ show_string: '"+join(", ", s_fields)+"',
+ and_or: 'OR'
+ });"
+.
+
define HTML_In_Form
/* create a selector for choosing the view name.
* The selector create an immediate action. This means when the entry is selected, the action is fired.
@@ -458,7 +480,7 @@ public define HTML_Partial_Content
with referer_arg = "&"+to_Web_arg(table_referer, ""),
- with uid = generate_random_string(20)+"_",
+ with uid = "_"+generate_random_string(15)+"_",
partial_content([ js(js_file("js/jscolor.min.js"))
]+jq_dialog_init + jq_datetimepicker_init(lang),
[html_wave_box_wide(_T(to_upper(current_view.table_name)),
@@ -583,7 +605,7 @@ define CXM_Form_Field
//Edit table part
-public define List(CXM_Form_Field)
+public define List(CXM_Form_Field)
edit_form_lines
(
SQLite3DataBase db,
@@ -655,13 +677,19 @@ public define List(CXM_Form_Field)
// ])
// ],
- foreign_text_search(s_name, c_name, select, foreign_table_name, fields, help) then
- with uid = "_"+generate_random_string(20)+"_",
+ foreign_text_search(s_name, c_name, select, foreign_table_name, s_fields, help) then
+ with uid = "_"+generate_random_string(20)+"_",
+ with combo_init = ajax_combo_init_parameter(uid, foreign_table_name, c_name, s_fields),
[
- partial(jq_dialog_create(dialog_id(uid+foreign_table_name+"_dlg_ajax"), ajax(controller_action("hk_c", "ajax_edit_table&dialog_id="+uid+foreign_table_name+"_dlg_ajax&sub_dialog=true&table_name="+foreign_table_name)))),
+
+ partial(partial_content([ js(js_file("ajax-combobox/js/jquery.ajax-combobox.js")),
+ css(css_file("ajax-combobox/css/jquery.ajax-combobox.css")),
+ js_inline(jquery_ready(combo_init))],
+ jq_dialog_create(dialog_id(uid+foreign_table_name+"_dlg_ajax"), ajax(controller_action("hk_c", "ajax_edit_table&dialog_id="+uid+foreign_table_name+"_dlg_ajax&sub_dialog=true&table_name="+foreign_table_name))))),
one_line_fields([
+
message(to_String(format_label(label(_T(s_name), no_help), html_Id("")))+
- ""),
+ ""),
partial(img_button(icn16_add, [event(onclick, jq_dialog_open(dialog_id(uid+foreign_table_name+"_dlg_ajax")))], "", [])),
]),
help_line(help, _T)
@@ -669,9 +697,24 @@ public define List(CXM_Form_Field)
choices_text(s_name, c_name, value, select, help) then
[selector_c([],label(_T(s_name), no_help), html_Id(""), wan(c_name), 1, select.get(db, no_referer, ""), success(init(value)), mandatory), help_line(help, _T)],
+
text_area(s_name, c_name, value, txt_editor, help) then
- with id = if txt_editor is { none then "", rich_editor then c_name+"_editor"},
- [text_area([],label(_T(s_name), no_help), html_Id(id), wan(c_name), init(not_null_String(value)), narrow, (Int)5, mandatory), help_line(help, _T)],
+ with uid = "_"+generate_random_string(20),
+ with id = if txt_editor is { none then "", rich_editor then uid+"_editor"},
+ [
+ if txt_editor is {
+ none then empty,
+ rich_editor then partial(partial_content(
+ [ js(js_file("ckeditor/ckeditor.js")),
+ js_inline(jquery_ready(
+ " var cke_instance = CKEDITOR.replace( '"+id+"' );
+ //cxm_cke('"+id+"');
+ cke_instance.on('blur', function() { cke_instance.updateElement(); });
+ "))],empty))
+ },
+ text_area([],label(_T(s_name), no_help), html_Id(id), wan(c_name), init(not_null_String(value)), narrow, (Int)5, mandatory), help_line(help, _T)
+ ],
+
boolean(s_name, c_name, value, help) then
[checkboxr([],label(_T(s_name), no_help), html_Id(""), wan(c_name), wav("true"),value, mandatory), help_line(help, _T)],
integer(s_name, c_name, value, help) then
@@ -696,166 +739,147 @@ public define List(CXM_Form_Field)
edit_form_lines(db, fk_clause, table_referer, _T, t, new_entry, reverse(line)+so_far)
}.
-define List(String)
- get_rich_editor_inline
- (
- List(HK_Form_Entry) list
- )=
- map_select((HK_Form_Entry entry) |->
- if entry is text_area(s_name, c_name, value, txt_editor, help) then
- if txt_editor is { none then failure,
- rich_editor then success(" CKEDITOR.replace( '"+c_name+"_editor' );")
- }
- else
- failure, list)
-.
+//define List(String)
+// get_rich_editor_inline
+// (
+// List(HK_Form_Entry) list
+// )=
+// map_select((HK_Form_Entry entry) |->
+// if entry is text_area(s_name, c_name, value, txt_editor, help) then
+// if txt_editor is { none then failure,
+// rich_editor then success(" CKEDITOR.replace( '"+c_name+"_editor' );")
+// }
+// else
+// failure, list)
+//.
-define List(HTML_Head_Tag)
- get_editor_header
- (
- List(HK_Form_Entry) _list
- )=
- with result = get_rich_editor_inline(_list),
- if length(result) > 0 then
- [js(js_file("ckeditor/ckeditor.js")),
- js_inline(jquery_ready(join("",result)))
- ]
- else
- []
- .
+// define List(HTML_Head_Tag)
+// get_editor_header
+// (
+// List(HK_Form_Entry) _list
+// )=
+// with result = get_rich_editor_inline(_list),
+// if length(result) > 0 then
+// [
+// js_inline(jquery_ready(join("",result)))
+// ]
+// else
+// []
+// .
-define List(String)
- _get_editor_header
- (
- List(HK_Form_Table_Line) _lines
- )=
- map_append((HK_Form_Table_Line line)
- |->
- map_append((HK_Form_Table_Cell cell)
- |->
- if cell is
- {
- hk_form_table_cell(_, form_entry) then
- if form_entry is text_area(s_name, c_name, value, txt_editor, help) then
- if txt_editor is {
- none then [],
- rich_editor then [" CKEDITOR.replace( '"+c_name+"_editor' );"]
- }
- else
- [],
- hk_form_table_cell_table(_, t_lines) then
- _get_editor_header(t_lines)
- },
- line.form_table_cells),
- _lines)
-.
+//define List(String)
+// _get_editor_header
+// (
+// List(HK_Form_Table_Line) _lines
+// )=
+// map_append((HK_Form_Table_Line line)
+// |->
+// map_append((HK_Form_Table_Cell cell)
+// |->
+// if cell is
+// {
+// hk_form_table_cell(_, form_entry) then
+// if form_entry is text_area(s_name, c_name, value, txt_editor, help) then
+// if txt_editor is {
+// none then [],
+// rich_editor then [" CKEDITOR.replace( '"+c_name+"_editor' );"]
+// }
+// else
+// [],
+// hk_form_table_cell_table(_, t_lines) then
+// _get_editor_header(t_lines)
+// },
+// line.form_table_cells),
+// _lines)
+//.
-define String
- ajax_combo_init_parameter
- (
- String f_table, //db table name
- String c_name, //column name in the table
- List(String) s_fields //search fields
- )=
-"$('#acb_"+c_name+"').ajaxComboBox( '?aws_controller=hk_c&aws_action=combo_search',
- {
- lang: 'fr',
- per_page: 15,
- db_table: '"+f_table+"',
- button_img: 'ajax-combobox/btn.png',
- field: '"+force_nth(0, s_fields, "name")+"',
- search_field: '"+join(", ", s_fields)+"',
- hidden_name: '"+c_name+"',
- show_string: '"+join(", ", s_fields)+"',
- and_or: 'OR'
- });"
-.
-define List(String)
- _get_combo_header
- (
- String table_name,
- List(HK_Form_Table_Line) _lines
- )=
- map_append((HK_Form_Table_Line line)
- |->
- map_append((HK_Form_Table_Cell cell)
- |->
- if cell is
- {
- hk_form_table_cell(_, form_entry) then
- if form_entry is foreign_text_search(s_name, c_name, select, f_table, s_fields, help) then
- [ajax_combo_init_parameter(f_table, c_name, s_fields)]
- else
- [],
- hk_form_table_cell_table(_, t_lines) then
- _get_combo_header(table_name, t_lines)
- },
- line.form_table_cells),
- _lines)
-.
-define List(HTML_Head_Tag)
- get_editor_header
- (
- List(HK_Form_Table_Line) _lines
- )=
- with result = _get_editor_header(_lines),
- if length(result) > 0 then
- [js(js_file("ckeditor/ckeditor.js")),
- js_inline(jquery_ready(join("",result)))
- ]
- else
- []
- .
+//define List(String)
+// _get_combo_header
+// (
+// String table_name,
+// List(HK_Form_Table_Line) _lines
+// )=
+// map_append((HK_Form_Table_Line line)
+// |->
+// map_append((HK_Form_Table_Cell cell)
+// |->
+// if cell is
+// {
+// hk_form_table_cell(_, form_entry) then
+// if form_entry is foreign_text_search(s_name, c_name, select, f_table, s_fields, help) then
+// [ajax_combo_init_parameter(f_table, c_name, s_fields)]
+// else
+// [],
+// hk_form_table_cell_table(_, t_lines) then
+// _get_combo_header(table_name, t_lines)
+// },
+// line.form_table_cells),
+// _lines)
+//.
+
+//define List(HTML_Head_Tag)
+// get_editor_header
+// (
+// List(HK_Form_Table_Line) _lines
+// )=
+// with result = _get_editor_header(_lines),
+// if length(result) > 0 then
+// [js(js_file("ckeditor/ckeditor.js")),
+// js_inline(jquery_ready(join("",result)))
+// ]
+// else
+// []
+// .
-define List(HTML_Head_Tag)
- get_combo_header
- (
- String table_name,
- List(HK_Form_Table_Line) _lines
- )=
- with result = _get_combo_header(table_name, _lines),
- if length(result) > 0 then
- [js(js_file("ajax-combobox/js/jquery.ajax-combobox.js")),
- css(css_file("ajax-combobox/css/jquery.ajax-combobox.css")),
- js_inline(jquery_ready(join("",result)))
- ]
- else
- []
- .
+//define List(HTML_Head_Tag)
+// get_combo_header
+// (
+// String table_name,
+// List(HK_Form_Table_Line) _lines
+// )=
+// with result = _get_combo_header(table_name, _lines),
+// if length(result) > 0 then
+// [js(js_file("ajax-combobox/js/jquery.ajax-combobox.js")),
+// css(css_file("ajax-combobox/css/jquery.ajax-combobox.css")),
+// js_inline(jquery_ready(join("",result)))
+// ]
+// else
+// []
+// .
-define List(String)
- _get_combo_header
- (
- String table_name,
- List(HK_Form_Entry) _entries
- )=
- map_append((HK_Form_Entry entry)
- |->
- if entry is foreign_text_search(s_name, c_name, select, f_table, s_fields, help) then
- [ajax_combo_init_parameter(f_table, c_name, s_fields)]
- else
- []
- ,
- _entries)
-.
+//define List(String)
+// _get_combo_header
+// (
+// String table_name,
+// List(HK_Form_Entry) _entries
+// )=
+// map_append((HK_Form_Entry entry)
+// |->
+// if entry is foreign_text_search(s_name, c_name, select, f_table, s_fields, help) then
+// []
+// else
+// []
+// ,
+// _entries)
+//.
-define List(HTML_Head_Tag)
- get_combo_header
- (
- String table_name,
- List(HK_Form_Entry) _lines
- )=
- with result = _get_combo_header(table_name, _lines),
- if length(result) > 0 then
- [js(js_file("ajax-combobox/js/jquery.ajax-combobox.js")),
- css(css_file("ajax-combobox/css/jquery.ajax-combobox.css")),
- js_inline(jquery_ready(join("",result)))
- ]
- else
- []
- .
+//define List(HTML_Head_Tag)
+// get_combo_header
+// (
+// String table_name,
+// List(HK_Form_Entry) _lines
+// )=
+// with result = _get_combo_header(table_name, _lines),
+// if length(result) > 0 then
+// [js(js_file("ajax-combobox/js/jquery.ajax-combobox.js")),
+// css(css_file("ajax-combobox/css/jquery.ajax-combobox.css")),
+// js_inline(jquery_ready(join("",result)))
+// ]
+// else
+// []
+// .
public define HTML_Partial_Content
renderer_edit_list_form
@@ -898,9 +922,9 @@ public define HTML_Partial_Content
};
$('#my-awesome-dropzone').dropzone(); ")),
]+
- jq_datetimepicker_init(lang) +
- get_editor_header(list_entries)+ //add editor header if need. Like init ck_editor
- get_combo_header(table_name, list_entries),
+ jq_datetimepicker_init(lang),
+ //get_editor_header(list_entries)+ //add editor header if need. Like init ck_editor
+ //get_combo_header(table_name, list_entries),
sequence([
cxm_form( table_name, lwa,
(if length(list_entries) > 19 then
@@ -1000,28 +1024,47 @@ public define List(CXM_Form_Field)
partial(jq_dialog_create(dialog_id(uid+foreign_table_name+"_dlg_ajax"), ajax(controller_action("hk_c", "ajax_edit_table&dialog_id="+uid+foreign_table_name+"_dlg_ajax&sub_dialog=true&table_name="+foreign_table_name)))),
one_line_fields([
partial(partial_content([js_inline(load_script)],empty)),
- selector_c([],label(_T(s_name), no_help), html_Id(uid+foreign_table_name+"_"+c_name+"_selector"), wan(c_name), 1, select.get(db, table_referer, fk_clause), success(init(to_String(value))), mandatory), help_line(help, _T),
+ selector_c([],label(_T(s_name), no_help), html_Id(uid+foreign_table_name+"_"+c_name+"_selector"), wan(c_name), 1, /*select.get(db, table_referer, fk_clause)*/ [], success(init("")), mandatory), help_line(help, _T),
partial(img_button(icn16_add, [event(onclick, jq_dialog_open(dialog_id(uid+foreign_table_name+"_dlg_ajax")))], "", [])),
])
],
-
+
foreign_text_search(s_name, c_name, select, foreign_table_name, s_fields, help) then
//[raw_in_form(literal(""))]
- with uid = "_"+generate_random_string(20)+"_",
+ with uid = "_"+generate_random_string(20)+"_",
+ with combo_init = ajax_combo_init_parameter(uid, foreign_table_name, c_name, s_fields),
[
- partial(jq_dialog_create(dialog_id(uid+foreign_table_name+"_dlg_ajax"), ajax(controller_action("hk_c", "ajax_edit_table&dialog_id="+uid+foreign_table_name+"_dlg_ajax&sub_dialog=true&table_name="+foreign_table_name)))),
+ partial(partial_content([
+ js(js_file("ajax-combobox/js/jquery.ajax-combobox.js")),
+ css(css_file("ajax-combobox/css/jquery.ajax-combobox.css")),
+ js_inline(jquery_ready(combo_init))],
+ jq_dialog_create(dialog_id(uid+foreign_table_name+"_dlg_ajax"), ajax(controller_action("hk_c", "ajax_edit_table&dialog_id="+uid+foreign_table_name+"_dlg_ajax&sub_dialog=true&table_name="+foreign_table_name))))),
one_line_fields([
- message(""),
+ message(""),
partial(img_button(icn16_add, [event(onclick, jq_dialog_open(dialog_id(uid+foreign_table_name+"_dlg_ajax")))], "", [])),
]),
help_line(help, _T)
]
-
+
choices_text(s_name, c_name, value, select, help) then
[selector_c([],no_label, html_Id(""), wan(c_name), 1, select.get(db, no_referer, ""), success(init(value)), mandatory), help_line(help, _T)],
+
text_area(s_name, c_name, value, txt_editor, help) then
- with id = if txt_editor is { none then "", rich_editor then c_name+"_editor"},
- [text_area([],no_label, html_Id(id), wan(c_name), init(not_null_String(value)), narrow, (Int)5, mandatory), help_line(help, _T)],
+ with uid = "_"+generate_random_string(20),
+ with id = if txt_editor is { none then "", rich_editor then uid+"_editor"},
+ [
+ if txt_editor is {
+ none then empty,
+ rich_editor then partial(partial_content(
+ [js(js_file("ckeditor/ckeditor.js")),
+ js_inline(jquery_ready(
+ " var cke_instance = CKEDITOR.replace( '"+id+"' );
+ //cxm_cke('"+id+"');
+ cke_instance.on('blur', function() { cke_instance.updateElement(); });
+ "))],empty))
+ },
+ text_area([],no_label, html_Id(id), wan(c_name), init(not_null_String(value)), narrow, (Int)5, mandatory), help_line(help, _T)],
+
boolean(s_name, c_name, value, help) then
[checkboxr([],no_label, html_Id(""), wan(c_name), wav("true"),value, mandatory), help_line(help, _T)],
integer(s_name, c_name, value, help) then
@@ -1123,23 +1166,30 @@ public define HTML_Partial_Content
//true if need to create row or false for updating
with create = if get_String(lwa, "id","none") = "none" then true else false,
with id = get_String(lwa, "id","none"),
- with button_name = if create then _T("CREATE") else _T("UPDATE"),
+ with sub_dialog = get_String(lwa, "sub_dialog", "false"),
+ with dialog_id = get_String(lwa, "dialog_id", ""),
+ with after_close_cb = get_String(lwa, "after_close_cb", ""), //call back
+ with button_label = if create then _T("CREATE") else _T("UPDATE"),
+ with button_name = if create then "create" else "update",
with button_name_and_new = if create then _T("CREATE_AND_EDIT_NEW") else _T("UPDATE_AND_EDIT_NEW"),
partial_content(
[ js(js_file("js/jscolor.min.js"))
]+
- jq_datetimepicker_init(lang) +
- get_editor_header(table_form.form_lines)+ //add editor header if need. Like init ck_editor
- get_combo_header(table_name, table_form.form_lines), //add editor header if need. Like init ck_editor
+ jq_datetimepicker_init(lang),
+ //get_editor_header(table_form.form_lines)+ //add editor header if need. Like init ck_editor
+ //get_combo_header(table_name, table_form.form_lines), //add editor header if need. Like init ck_editor
sequence([
cxm_form( table_name, [], lwa,
get_hidden(table_form.table_attr)+
[construct_edit_form_table(db, table_referer, _T, lwa, table_form.form_lines, create)]+
[ one_line_fields([
- submit(if create then hkc_save_row else hkc_update_row, failure, button_name, [("table_name", table_name)]),
- submit(if create then hkc_save_row_and_new else hkc_update_row_and_new, failure, button_name_and_new, [("table_name", table_name)]),
- submit(hkc_cancel, failure, _T("CANCEL"))
+// submit(if create then hkc_save_row else hkc_update_row, failure, button_name, [("table_name", table_name)]),
+// submit(if create then hkc_save_row_and_new else hkc_update_row_and_new, failure, button_name_and_new, [("table_name", table_name)]),
+// submit(hkc_cancel, failure, _T("CANCEL"))
+ partial(jquery_button(jQuery_button(button, dialog_id+button_name, button_name, button_label, jQuery_actioner(same, jqscript(jq_form_submit_and_close(dialog_id, table_name, hkc_update_row, [("table_name", table_name), ("sub_dialog", sub_dialog)], after_close_cb)))))),
+ partial(jquery_button(jQuery_button(button, dialog_id+"cancel", "cancel", _T("CANCEL"), jQuery_actioner(same, jqscript("$('#"+dialog_id+"').dialog('close')"))))),
+
])
],
"form_field table",
diff --git a/view/web_action.anubis b/view/web_action.anubis
index 623cf9c..64df965 100644
--- a/view/web_action.anubis
+++ b/view/web_action.anubis
@@ -16,6 +16,7 @@ public define WEB_Action_Name hkc_grid_view = controller_action("hk_c"
public define WEB_Action_Name hkc_grid_action = controller_action("hk_c", "grid_action").
public define WEB_Action_Name hkc_combo_search = controller_action("hk_c", "combo_search").
public define WEB_Action_Name hkc_selector = controller_action("hk_c", "selector"). //get dropdown HMTL content
+public define WEB_Action_Name hkc_query = controller_action("hk_c", "query"). //CRUD action
public define WEB_Action_Name hkc_list_view = controller_action("hk_c", "view_table").
public define WEB_Action_Name hkc_list_view_name = controller_action("hk_c", "hkc_list_view_name").
--
libgit2 0.21.4