first push message

This commit is contained in:
2026-07-01 14:41:49 +07:00
parent 6667dec2bf
commit 58b5f46cc4
2951 changed files with 316619 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
# from . import controllers
from . import models
+36
View File
@@ -0,0 +1,36 @@
{
'name': "sh_survey_extra_fields",
'summary': "Short (1 phrase/line) summary of the module's purpose",
'description': """
Long description of module's purpose
""",
'author': "My Company",
'website': "https://www.yourcompany.com",
# Categories can be used to filter modules in modules listing
# Check https://github.com/odoo/odoo/blob/15.0/odoo/addons/base/data/ir_module_category_data.xml
# for the full list
'category': 'Uncategorized',
'version': '0.1',
# any module necessary for this one to work correctly
'depends': ["survey","address_kh"],
# always loaded
'data': [
"security/ir.model.access.csv",
# "views/survey_views.xml",
# "views/survey_templates.xml",
# "views/survey_survey_views.xml",
# "data/data.xml",
'views/other_views.xml',
],
# only loaded in demonstration mode
'demo': [
'demo/demo.xml',
],
}
@@ -0,0 +1 @@
from . import controllers
@@ -0,0 +1,164 @@
# Part of Softhealer Technologies.
from odoo import http
from odoo.http import request
from odoo.exceptions import AccessError, MissingError
class SurveyController(http.Controller):
@http.route(['/survey/get_many2many_field_data'], type='json', auth="public", methods=['POST'])
def get_many2many_field_data(self, **kw):
records = []
rec_name = 'display_name' # Default fallback
model_name = False
if kw.get("model_id"):
try:
model_record = request.env["ir.model"].sudo().browse(int(kw.get("model_id")))
if model_record.exists():
model_name = model_record.model
# Use the model's defined _rec_name, fallback to display_name
rec_name = model_record._rec_name or 'display_name'
# Search records. Note: Using sudo() because this is a public route.
# Ensure the model has appropriate access rights for public users if possible.
records = request.env[model_name].sudo().search_read(
[],
fields=[rec_name, 'id'],
order=rec_name # Optional: Order by name for better UX
)
# Rename the rec_name field to 'name' for JS consistency
if records:
for record in records:
record['name'] = record.pop(rec_name, '')
except Exception as e:
# Log error in production, return empty in dev
pass
return {
'records': records,
'rec_name': rec_name,
'model_name': model_name,
}
@http.route(['/survey/get_many2one_field_data'], type='json', auth="public", methods=['POST'])
def get_many2one_field_data(self, **kw):
records = []
rec_name = 'display_name' # Default fallback
model_name = False
if kw.get("model_id"):
try:
model_record = request.env["ir.model"].sudo().browse(int(kw.get("model_id")))
if model_record.exists():
model_name = model_record.model
rec_name = model_record._rec_name or 'display_name'
records = request.env[model_name].sudo().search_read(
[],
fields=[rec_name, 'id'],
order=rec_name
)
if records:
for record in records:
record['name'] = record.pop(rec_name, '')
except Exception as e:
pass
return {
'records': records,
'rec_name': rec_name,
'model_name': model_name,
}
@http.route(['/survey/get_countries'], type='json', auth="public", methods=['POST'], csrf=False)
def get_countries(self, **kw):
"""
Returns list of countries and their states.
Optimized to fetch states in one go instead of per-country.
"""
countries = request.env["res.country"].sudo().search_read(
[],
fields=['name', 'id', 'code'],
order='name'
)
# Fetch all states at once for performance
all_states = request.env["res.country.state"].sudo().search_read(
[],
fields=['name', 'id', 'country_id', 'code'],
order='name'
)
# Group states by country_id for easier JS consumption
states_by_country = {}
for state in all_states:
country_id = state.get('country_id', [False])[0] if isinstance(state.get('country_id'),
list) else state.get('country_id')
if country_id:
if country_id not in states_by_country:
states_by_country[country_id] = []
states_by_country[country_id].append({
'id': state['id'],
'name': state['name'],
'code': state.get('code', '')
})
return {
'countries': countries,
'states_by_country': states_by_country,
}
@http.route(['/survey/get_country_info/<model("res.country"):country>'], type='json', auth="public",
methods=['POST'], csrf=False)
def get_country_info(self, country, **kw):
"""
Get details for a specific country including its states.
"""
if not country.exists():
return {'error': 'Country not found'}
states = []
for st in country.sudo().state_ids:
states.append({
'id': st.id,
'name': st.name,
'code': st.code or ''
})
return {
'states': states,
'phone_code': country.phone_code or '',
'zip_required': country.zip_required,
'state_required': country.state_required,
}
@http.route('/survey/download/<model("survey.user_input.line"):answer_line>/<string:answer_token>', type='http',
auth='public', website=True, csrf=False)
def survey_download_file(self, answer_line, answer_token, **kw):
"""
Securely download a file attached to a survey answer.
"""
if not answer_line.exists():
return request.not_found()
# Verify the answer token matches the user input line's token
# This ensures that only people with the correct link can download
if answer_line.user_input_id.access_token != answer_token:
return request.not_found()
if answer_line.answer_type == "ans_sh_file" and answer_line.value_ans_sh_file_fname:
try:
# Generate the secure download URL using Odoo's built-in content controller
base_url = request.httprequest.url_root.rstrip('/')
download_url = f"{base_url}/web/content/{answer_line._name}/{answer_line.id}/value_ans_sh_file/{answer_line.value_ans_sh_file_fname}?download=true&access_token={answer_token}"
return request.redirect(download_url, code=301)
except Exception:
return request.not_found()
return request.not_found()
+79
View File
@@ -0,0 +1,79 @@
<odoo>
<data>
<record id="view_survey_1" model="address.survey">
<field name="name">បន្ទាយមានជ័យ</field>
</record>
<record id="view_survey_2" model="address.survey">
<field name="name">បាត់ដំបង</field>
</record>
<record id="view_survey_3" model="address.survey">
<field name="name">កំពង់ចាម</field>
</record>
<record id="view_survey_4" model="address.survey">
<field name="name">កំពង់ឆ្នាំង</field>
</record>
<record id="view_survey_5" model="address.survey">
<field name="name">កំពង់ស្ពឺ</field>
</record>
<record id="view_survey_6" model="address.survey">
<field name="name">កំពង់ធំ</field>
</record>
<record id="view_survey_7" model="address.survey">
<field name="name">កំពត</field>
</record>
<record id="view_survey_8" model="address.survey">
<field name="name">កណ្តាល</field>
</record>
<record id="view_survey_9" model="address.survey">
<field name="name">កោះកុង</field>
</record>
<record id="view_survey_10" model="address.survey">
<field name="name">មណ្ឌលគីរី</field>
</record>
<record id="view_survey_11" model="address.survey">
<field name="name">ភ្នំពេញ</field>
</record>
<record id="view_survey_12" model="address.survey">
<field name="name">ព្រះវិហារ</field>
</record>
<record id="view_survey_13" model="address.survey">
<field name="name">ព្រៃវែង</field>
</record>
<record id="view_survey_14" model="address.survey">
<field name="name">ពោធិ៍សាត់</field>
</record>
<record id="view_survey_15" model="address.survey">
<field name="name">រតនៈគីរី</field>
</record>
<record id="view_survey_16" model="address.survey">
<field name="name">សៀមរាប</field>
</record>
<record id="view_survey_17" model="address.survey">
<field name="name">ព្រះសីហនុ</field>
</record>
<record id="view_survey_18" model="address.survey">
<field name="name">ស្ទឹងត្រែង</field>
</record>
<record id="view_survey_19" model="address.survey">
<field name="name">ស្វាយរៀង</field>
</record>
<record id="view_survey_20" model="address.survey">
<field name="name">តាកែវ</field>
</record>
<record id="view_survey_21" model="address.survey">
<field name="name">ឧត្តរមានជ័យ</field>
</record>
<record id="view_survey_22" model="address.survey">
<field name="name">កែប</field>
</record>
<record id="view_survey_23" model="address.survey">
<field name="name">ប៉ៃលិន</field>
</record>
<record id="view_survey_24" model="address.survey">
<field name="name">ត្បូងឃ្មុំ</field>
</record>
<record id="view_survey_25" model="address.survey">
<field name="name">រតនៈគីរី</field>
</record>
</data>
</odoo>
+30
View File
@@ -0,0 +1,30 @@
<odoo>
<data>
<!--
<record id="object0" model="sh_survey_extra_fields.sh_survey_extra_fields">
<field name="name">Object 0</field>
<field name="value">0</field>
</record>
<record id="object1" model="sh_survey_extra_fields.sh_survey_extra_fields">
<field name="name">Object 1</field>
<field name="value">10</field>
</record>
<record id="object2" model="sh_survey_extra_fields.sh_survey_extra_fields">
<field name="name">Object 2</field>
<field name="value">20</field>
</record>
<record id="object3" model="sh_survey_extra_fields.sh_survey_extra_fields">
<field name="name">Object 3</field>
<field name="value">30</field>
</record>
<record id="object4" model="sh_survey_extra_fields.sh_survey_extra_fields">
<field name="name">Object 4</field>
<field name="value">40</field>
</record>
-->
</data>
</odoo>
@@ -0,0 +1,2 @@
from . import address
from . import other_table
+9
View File
@@ -0,0 +1,9 @@
from odoo import models, fields, api, _
class AddressSurvey(models.Model):
_name = 'address.survey'
_description = "ទីកន្លែង"
_rec_name = 'name'
name = fields.Char()
@@ -0,0 +1,88 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
class Years(models.Model):
_name = 'year.year'
_description = "ឆ្នាំទទួលបានអាហារូបករណ៍"
_rec_name = 'name'
name = fields.Char()
class SchoolStudent(models.Model):
_name = 'school.student'
_description = "គ្រឺះស្ថានផ្ដល់អាហារូបករណ៍"
_rec_name = 'name'
name = fields.Char()
class SkillStudent(models.Model):
_name = 'skill.student'
_description = "ជំនាញ"
_rec_name = 'name'
name = fields.Char()
class GradeStudent(models.Model):
_name = 'grade.student'
_description = "កម្រិតសិក្សាចុងក្រោយ"
_rec_name = 'name'
name = fields.Char()
class LastSkill(models.Model):
_name = 'last.skill'
_description = "ជំនាញចុងក្រោយ"
_rec_name = 'name'
name = fields.Char()
class LevelScore(models.Model):
_name = 'level.score'
_description = "កម្រិត"
_rec_name = 'name'
name = fields.Char()
class OtherLevel(models.Model):
_name = 'other.level'
_description = "កម្រិតផ្សេងទៀត"
_rec_name = 'name'
name = fields.Char()
class TeamWork(models.Model):
_name = 'team.work'
_description = "ក្រុមការងារ"
_rec_name = 'name'
name = fields.Char()
class MultiChoose(models.Model):
_name = 'multi.choose'
_description = "ជម្រើសច្រើន"
_rec_name = 'name'
name = fields.Char()
class OtherPlan(models.Model):
_name = 'other.plan'
_description = "ជម្រើសច្រើន"
_rec_name = 'name'
name = fields.Char()
class Connection(models.Model):
_name = 'con.con'
_description = "កម្មពិធីទំនាក់ទំនង"
_rec_name = 'name'
name = fields.Char()
class PositionFamily(models.Model):
_name = 'position.family'
_description = "ត្រូវជាអ្វីជាមួយនឹងនិស្សិត"
_rec_name = 'name'
name = fields.Char()
@@ -0,0 +1,14 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_address_survey,access_address_survey,model_address_survey,base.group_user,1,1,1,0
access_year_year,access_year_year,model_year_year,base.group_user,1,1,1,0
access_school_student,access_school_student,model_school_student,base.group_user,1,1,1,0
access_skill_student,access_skill_student,model_skill_student,base.group_user,1,1,1,0
access_grade_student,access_grade_student,model_grade_student,base.group_user,1,1,1,0
access_last_skill,access_last_skill,model_last_skill,base.group_user,1,1,1,0
access_level_score,access_level_score,model_level_score,base.group_user,1,1,1,0
access_other_level,access_other_level,model_other_level,base.group_user,1,1,1,0
access_team_work,access_team_work,model_team_work,base.group_user,1,1,1,0
access_multi_choose,access_multi_choose,model_multi_choose,base.group_user,1,1,1,0
access_other_plan,access_other_plan,model_other_plan,base.group_user,1,1,1,0
access_con_con,access_con_con,model_con_con,base.group_user,1,1,1,0
access_position_family,access_position_family,model_position_family,base.group_user,1,1,1,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_address_survey access_address_survey model_address_survey base.group_user 1 1 1 0
3 access_year_year access_year_year model_year_year base.group_user 1 1 1 0
4 access_school_student access_school_student model_school_student base.group_user 1 1 1 0
5 access_skill_student access_skill_student model_skill_student base.group_user 1 1 1 0
6 access_grade_student access_grade_student model_grade_student base.group_user 1 1 1 0
7 access_last_skill access_last_skill model_last_skill base.group_user 1 1 1 0
8 access_level_score access_level_score model_level_score base.group_user 1 1 1 0
9 access_other_level access_other_level model_other_level base.group_user 1 1 1 0
10 access_team_work access_team_work model_team_work base.group_user 1 1 1 0
11 access_multi_choose access_multi_choose model_multi_choose base.group_user 1 1 1 0
12 access_other_plan access_other_plan model_other_plan base.group_user 1 1 1 0
13 access_con_con access_con_con model_con_con base.group_user 1 1 1 0
14 access_position_family access_position_family model_position_family base.group_user 1 1 1 0
@@ -0,0 +1,223 @@
/*!
* Multiple select dropdown with filter jQuery plugin.
* Copyright (C) 2020 Andrew Wagner github.com/andreww1011
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
:root {
--fms-badge-text-color: white;
--fms-badge-color: var(--primary)
}
.filter-multi-select.dropup, .filter-multi-select.dropdown {
position: relative;
border:1px solid #714B67;
}
.filter-multi-select .dropdown-toggle::after {
all: unset;
}
.filter-multi-select .dropdown-toggle:empty::after {
all: unset;
}
.filter-multi-select > .dropdown-toggle::before {
display: inline-block;
margin-right: 0.255em;
vertical-align: middle;
content: "";
border-top: 0.3em solid;
border-right: 0.3em solid transparent;
border-bottom: 0;
border-left: 0.3em solid transparent;
}
.filter-multi-select > .dropdown-toggle:empty::before {
margin-right: 0.255em;
}
.filter-multi-select > .viewbar {
white-space: normal;
font-size: 0.875rem;
font-weight: 400;
height: auto;
cursor: pointer;
}
.filter-multi-select > .viewbar > .selected-items > .item {
margin: .125rem .25rem .125rem 0;
padding: 2px 0px 2px 8px !important;
display: inline-flex;
height: auto !important;
color: var(--fms-badge-text-color);
background-color: var(--fms-badge-color);
border-radius: 1.1em;
align-items: center;
vertical-align: baseline;
}
.filter-multi-select > .viewbar > .selected-items > .item > button {
background-color: transparent;
color: var(--fms-badge-text-color);
border: 0;
font-weight: 900;
cursor: pointer;
}
.filter-multi-select > .viewbar > .selected-items > .item > button:hover {
filter: contrast(50%);
}
.filter-multi-select > .viewbar > .selected-items > .item.disabled {
display: inline-flex;
padding: 0px .5em 0px .5em;
filter: grayscale(80%) brightness(150%);
}
.filter-multi-select > .viewbar > .selected-items > .item.disabled > button {
display: none;
}
.filter-multi-select > .dropdown-menu {
position: absolute;
top: 100%;
left: 0%;
z-index: 1000;
display: none;
float: left;
max-height: 50vh;
min-width: 10rem;
overflow-y: auto;
padding: 0.5rem 0;
margin: 0.125rem 0 0;
font-size: 0.875rem;
text-align: left;
list-style: none;
background-color: #FFFFFF;
background-clip: padding-box;
border: 1px solid rgba(0, 0, 0, 0.15);
border-radius: 0.25rem;
}
.filter-multi-select > .dropdown-menu.show {
display: block;
}
.filter-multi-select > .dropdown-menu > .filter > input {
font-size: 0.875rem;
}
.filter-multi-select > .dropdown-menu > .filter > button {
position: absolute;
border: 0;
background-color: transparent;
font-weight: 900;
color: #ccc;
right: 2rem;
top: 1rem;
}
.filter-multi-select > .dropdown-menu > .filter > button:hover {
color: #aaa;
}
.filter-multi-select .dropdown-item {
display: block;
width: 100%;
padding: 0.25rem 1.5rem;
clear: both;
font-weight: 400;
color: #212529;
text-align: inherit;
white-space: nowrap;
background-color: transparent;
border: 0;
}
.filter-multi-select .dropdown-item.disabled, .filter-multi-select .dropdown-item:disabled {
color: #6c757d;
pointer-events: none;
background-color: transparent;
}
.filter-multi-select .dropdown-item:hover, .filter-multi-select .dropdown-item:focus {
background-color: inherit;
}
.filter-multi-select .dropdown-item.active, .filter-multi-select .dropdown-item:active {
color: inherit;
}
.filter-multi-select .dropdown-item .custom-control-input {
position: absolute;
z-index: -1;
opacity: 0;
}
.filter-multi-select .dropdown-item .custom-control-label {
position: relative;
margin-bottom: 0;
vertical-align: top;
display: inline-block;
}
.filter-multi-select .dropdown-item .custom-control-label::before {
border-radius: 0.25rem;
transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
position: absolute;
top: 0.15625rem;
left: -1.5rem;
display: block;
width: 1rem;
height: 1rem;
pointer-events: none;
content: "";
background-color: #FFFFFF;
border: #adb5bd solid 1px
}
.filter-multi-select .dropdown-item .custom-control-label::after {
position: absolute;
top: 0.15625rem;
left: -1.5rem;
display: block;
width: 1rem;
height: 1rem;
content: "";
background: no-repeat 50% / 50% 50%;
}
.filter-multi-select .dropdown-item .custom-checkbox:checked ~ .custom-control-label::before,
.filter-multi-select .dropdown-item .custom-checkbox:indeterminate ~ .custom-control-label::before {
border-color: var(--fms-badge-color);
background-color: var(--fms-badge-color);
}
.filter-multi-select .dropdown-item .custom-checkbox:checked:disabled ~ .custom-control-label::before,
.filter-multi-select .dropdown-item .custom-checkbox:indeterminate:disabled ~ .custom-control-label::before {
border-color: var(--fms-badge-color);
background-color: var(--fms-badge-color);
filter: grayscale(80%) brightness(150%);
}
.filter-multi-select .dropdown-item .custom-checkbox:checked ~ .custom-control-label::after {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23FFFFFF' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3e%3c/svg%3e");
}
.filter-multi-select .dropdown-item .custom-checkbox:indeterminate ~ .custom-control-label::after {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3e%3cpath stroke='%23FFFFFF' d='M0 2h4'/%3e%3c/svg%3e");
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

@@ -0,0 +1,397 @@
/** @odoo-module **/
import { patch } from "@web/core/utils/patch";
import { _t } from "@web/core/l10n/translation";
import { rpc } from "@web/core/network/rpc_service";
import {
parseDateTime,
parseDate,
serializeDateTime,
serializeDate,
} from "@web/core/l10n/dates";
// In Odoo 17+, the survey form is an OWL component.
// Ensure this import path matches your specific Odoo 19 tree structure.
import { SurveyForm } from "@survey/components/survey_form";
patch(SurveyForm.prototype, {
/**
* @override
* OWL lifecycle method replacing legacy init()
*/
setup() {
super.setup(...arguments);
this.shFileDataDictionary = {};
// Delegate events to the document since we are patching the component
// and custom inputs might be rendered dynamically.
this.onDomChange = this.onDomChange.bind(this);
this.onDomInput = this.onDomInput.bind(this);
this.onDomClick = this.onDomClick.bind(this);
document.addEventListener("change", this.onDomChange);
document.addEventListener("input", this.onDomInput);
document.addEventListener("click", this.onDomClick);
},
//--------------------------------------------------------------------------
// Event Handlers (Converted to Vanilla JS)
//--------------------------------------------------------------------------
onDomChange(ev) {
const target = ev.target;
if (target.classList.contains("js_cls_country_id")) {
this._onChangeCountry(target);
} else if (target.classList.contains("sh_file_input")) {
this._onChangeFileInput(target);
}
},
onDomInput(ev) {
const target = ev.target;
if (target.matches("input[type='range']")) { // Adjust selector if class changed in v19
this._onInputRangeValueChange(target);
}
},
onDomClick(ev) {
const target = ev.target;
if (target.closest(".js_cls_sh_signature_clear_btn")) {
this._onClickSignatureClearButton(target);
}
},
_onInputRangeValueChange(input) {
const nextLabel = input.nextElementSibling;
if (nextLabel && nextLabel.tagName === 'LABEL') {
nextLabel.textContent = input.value;
}
},
async _onChangeFileInput(fileInput) {
const FILE_LIST = [];
const toBase64 = file => new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = error => reject(error);
reader.readAsDataURL(file);
});
for (const file of fileInput.files) {
try {
const result = await toBase64(file);
const result_list = result.split(',');
if (result_list.length === 2) {
FILE_LIST.push({
fname: file.name,
type: file.type,
datas: result_list[1]
});
}
} catch (e) {
console.error("Error reading file:", e);
}
}
if (FILE_LIST.length > 0) {
this.shFileDataDictionary[fileInput.name] = FILE_LIST;
}
},
_validateURL(URL) {
const expression = /(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})/gi;
return new RegExp(expression).test(URL);
},
//--------------------------------------------------------------------------
// Validation (Adapted for OWL)
//--------------------------------------------------------------------------
_validateForm(formEl, formData) {
// Call original validation first
let isValid = true;
if (super._validateForm) {
isValid = super._validateForm(formEl, formData);
}
if (!isValid) return false;
const errors = {};
const validationEmailMsg = _t("This answer must be an email address.");
const validationUrlMsg = _t("This answer must be a valid URL.");
// Query custom question wrappers
const questionWrappers = formEl.querySelectorAll('.js_question-wrapper[data-question-type]');
questionWrappers.forEach(wrapper => {
const questionType = wrapper.dataset.questionType;
const questionId = wrapper.id;
const isRequired = wrapper.dataset.required !== undefined;
const constrErrorMsg = wrapper.dataset.constrErrorMsg || _t("This question requires an answer.");
if (questionType === 'que_sh_email') {
const input = wrapper.querySelector('input');
if (input) {
if (isRequired && !input.value.trim()) errors[questionId] = constrErrorMsg;
else if (input.value && !this._validateEmail(input.value)) errors[questionId] = validationEmailMsg;
}
} else if (questionType === 'que_sh_url') {
const input = wrapper.querySelector('input');
if (input) {
if (isRequired && !input.value.trim()) errors[questionId] = constrErrorMsg;
else if (input.value && !this._validateURL(input.value)) errors[questionId] = validationUrlMsg;
}
} else if (questionType === 'que_sh_file') {
const input = wrapper.querySelector('input[type="file"]');
if (isRequired && input && input.files.length === 0) {
errors[questionId] = constrErrorMsg;
}
}
// ... Add other switch cases for time, week, month, password, signature, etc. ...
});
if (Object.keys(errors).length > 0) {
this._showErrors(errors);
return false;
}
return true;
},
//--------------------------------------------------------------------------
// Data Fetching (Using modern RPC & Vanilla JS)
//--------------------------------------------------------------------------
async getCountries() {
const countrySelects = document.querySelectorAll(".js_cls_country_id");
if (countrySelects.length) {
try {
const data = await rpc("/survey/get_countries", {});
countrySelects.forEach(select => {
data.countries.forEach(country => {
const opt = document.createElement("option");
opt.textContent = country.name;
opt.value = country.id;
select.appendChild(opt);
});
});
} catch (error) {
console.error("Error fetching countries:", error);
}
}
},
async getMany2oneFieldData() {
const m2oSelects = document.querySelectorAll(".js_cls_sh_many2one_select");
for (const select of m2oSelects) {
const modelId = parseInt(select.dataset.model_id);
if (modelId) {
try {
const data = await rpc("/survey/get_many2one_field_data", { model_id: modelId });
data.records.forEach(record => {
const opt = document.createElement("option");
opt.textContent = record.name;
opt.value = record.name;
select.appendChild(opt);
});
} catch (error) {
console.error("Error fetching M2O data:", error);
}
}
}
},
async getMany2manyFieldData() {
const m2mSelects = document.querySelectorAll(".js_cls_sh_many2many_select");
for (const select of m2mSelects) {
const modelId = parseInt(select.dataset.model_id);
if (modelId) {
try {
const data = await rpc("/survey/get_many2many_field_data", { model_id: modelId });
data.records.forEach(record => {
const opt = document.createElement("option");
opt.textContent = record.name;
opt.value = record.name;
select.appendChild(opt);
});
// Initialize multi-select plugin if still available globally
if (window.jQuery && window.jQuery.fn.filterMultiSelect) {
window.jQuery(select).filterMultiSelect();
}
} catch (error) {
console.error("Error fetching M2M data:", error);
}
}
}
},
getSignatureFieldData() {
const signPads = document.querySelectorAll(".js_cls_sh_signature_pad");
signPads.forEach(pad => {
if (window.jQuery && window.jQuery.fn.jSignature) {
window.jQuery(pad).jSignature({
"background-color": "#808080",
'width': 1102,
'height': 276
});
}
});
},
_focusOnFirstInput() {
if (super._focusOnFirstInput) super._focusOnFirstInput();
if (document.querySelector(".js_cls_country_id")) this.getCountries();
if (document.querySelector(".js_cls_sh_many2one_select")) this.getMany2oneFieldData();
if (document.querySelector(".js_cls_sh_many2many_select")) this.getMany2manyFieldData();
if (document.querySelector(".js_cls_sh_signature_wrapper")) this.getSignatureFieldData();
},
_onClickSignatureClearButton(btn) {
const wrapper = btn.closest(".js_cls_sh_signature_wrapper");
if (wrapper) {
const pad = wrapper.querySelector(".js_cls_sh_signature_pad");
if (window.jQuery && window.jQuery.fn.jSignature) {
window.jQuery(pad).jSignature("reset");
}
}
},
async _onChangeCountry(selectEl) {
if (!selectEl.value) return;
// Fixed typo from original: get_ountry_info -> get_country_info
const url = `/survey/get_country_info/${selectEl.value}`;
try {
const data = await rpc(url, {});
const addressWrapper = selectEl.closest(".js_cls_sh_address_wrapper");
if (!addressWrapper) return;
const stateSelect = addressWrapper.querySelector(".js_cls_state_id");
if (stateSelect) {
if (stateSelect.dataset.init === "0" || stateSelect.options.length <= 1) {
if (data.states.length || data.state_required) {
stateSelect.innerHTML = "";
data.states.forEach(state => {
const opt = document.createElement("option");
opt.textContent = state[1];
opt.value = state[0];
opt.dataset.code = state[2];
stateSelect.appendChild(opt);
});
stateSelect.parentElement.style.display = "block";
} else {
stateSelect.value = "";
stateSelect.parentElement.style.display = "none";
}
stateSelect.dataset.init = "0";
}
}
} catch (error) {
console.error("Error fetching country info:", error);
}
},
//--------------------------------------------------------------------------
// Submit Preparation
//--------------------------------------------------------------------------
_prepareSubmitAnswersAddress(params, questionId, addressEle) {
const addressWrapper = addressEle.closest(".js_cls_sh_address_wrapper");
if (!addressWrapper) return params;
const getVal = (cls) => addressWrapper.querySelector(cls)?.value || "";
const street = getVal(".js_cls_street");
const street2 = getVal(".js_cls_street2");
const city = getVal(".js_cls_city");
const zip = getVal(".js_cls_zip");
const country_id = getVal(".js_cls_country_id");
const state_id = getVal(".js_cls_state_id");
if (street || street2 || city || zip || country_id || state_id) {
// Use modern URLSearchParams instead of manual string concatenation
const queryParams = new URLSearchParams({
street, street2, city, zip, country_id, state_id
}).toString();
params[questionId] = queryParams;
}
return params;
},
_prepareSubmitAnswersMany2many(params, m2mSelectEle, questionId) {
const checkedInputs = m2mSelectEle.querySelectorAll("input:checked");
checkedInputs.forEach(input => {
if (input.value !== "") {
if (!params[questionId]) params[questionId] = [];
if (Array.isArray(params[questionId])) {
params[questionId].push(input.value);
}
}
});
return params;
},
_prepareSubmitAnswersSignature(params, questionId, signatureEle) {
const wrapper = signatureEle.closest(".js_cls_sh_signature_wrapper");
if (wrapper) {
const pad = wrapper.querySelector(".js_cls_sh_signature_pad");
if (window.jQuery && window.jQuery.fn.jSignature) {
const datapair = window.jQuery(pad).jSignature("getData", "image");
params[questionId] = datapair[1];
}
}
return params;
},
_prepareSubmitValues(formData, params) {
if (super._prepareSubmitValues) {
super._prepareSubmitValues(formData, params);
}
const questionElements = document.querySelectorAll("[data-question-type]");
questionElements.forEach(el => {
const questionType = el.dataset.questionType;
const name = el.name;
switch (questionType) {
case 'text_box':
case 'char_box':
case 'numerical_box':
case "que_sh_color":
case "que_sh_email":
case "que_sh_url":
case "que_sh_time":
case "que_sh_range":
case "que_sh_week":
case "que_sh_month":
case "que_sh_password":
params[name] = el.value;
break;
case 'date':
case 'datetime': {
const [parse, serialize] = questionType === "date"
? [parseDate, serializeDate]
: [parseDateTime, serializeDateTime];
const date = parse(el.value);
params[name] = date ? serialize(date) : "";
break;
}
case "que_sh_file":
params[name] = this.shFileDataDictionary[name] || [];
break;
case "que_sh_address":
this._prepareSubmitAnswersAddress(params, name, el);
break;
case "que_sh_many2one": {
const hiddenInput = el.parentElement.querySelector("input[type='hidden']") || el.parentElement.querySelector("input");
params[name] = hiddenInput ? hiddenInput.value : el.value;
break;
}
case "que_sh_many2many":
this._prepareSubmitAnswersMany2many(params, el, name);
break;
case "que_sh_signature":
this._prepareSubmitAnswersSignature(params, name, el);
break;
}
});
}
});
@@ -0,0 +1,927 @@
(function (root, factory) {
// check to see if 'knockout' AMD module is specified if using requirejs
if (typeof define === 'function' && define.amd &&
typeof require === 'function' && typeof require.specified === 'function' && require.specified('knockout')) {
// AMD. Register as an anonymous module.
define(['jquery', 'knockout'], factory);
} else {
// Browser globals (Safe check for window in strict ES6 modules)
factory(typeof window !== 'undefined' ? window.jQuery : root.jQuery, root.ko);
}
})(typeof window !== 'undefined' ? window : this, function ($, ko) {
"use strict";// jshint ;_;
if (typeof ko !== 'undefined' && ko.bindingHandlers && !ko.bindingHandlers.multiselect) {
ko.bindingHandlers.multiselect = {
after: ['options', 'value', 'selectedOptions', 'enable', 'disable'],
init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
var $element = $(element);
var config = ko.toJS(valueAccessor());
$element.multiselect(config);
if (allBindings.has('options')) {
var options = allBindings.get('options');
if (ko.isObservable(options)) {
ko.computed({
read: function() {
options();
setTimeout(function() {
var ms = $element.data('multiselect');
if (ms)
ms.updateOriginalOptions();
$element.multiselect('rebuild');
}, 1);
},
disposeWhenNodeIsRemoved: element
});
}
}
if (allBindings.has('value')) {
var value = allBindings.get('value');
if (ko.isObservable(value)) {
ko.computed({
read: function() {
value();
setTimeout(function() {
$element.multiselect('refresh');
}, 1);
},
disposeWhenNodeIsRemoved: element
}).extend({ rateLimit: 100, notifyWhenChangesStop: true });
}
}
if (allBindings.has('selectedOptions')) {
var selectedOptions = allBindings.get('selectedOptions');
if (ko.isObservable(selectedOptions)) {
ko.computed({
read: function() {
selectedOptions();
setTimeout(function() {
$element.multiselect('refresh');
}, 1);
},
disposeWhenNodeIsRemoved: element
}).extend({ rateLimit: 100, notifyWhenChangesStop: true });
}
}
var setEnabled = function (enable) {
setTimeout(function () {
if (enable)
$element.multiselect('enable');
else
$element.multiselect('disable');
});
};
if (allBindings.has('enable')) {
var enable = allBindings.get('enable');
if (ko.isObservable(enable)) {
ko.computed({
read: function () {
setEnabled(enable());
},
disposeWhenNodeIsRemoved: element
}).extend({ rateLimit: 100, notifyWhenChangesStop: true });
} else {
setEnabled(enable);
}
}
if (allBindings.has('disable')) {
var disable = allBindings.get('disable');
if (ko.isObservable(disable)) {
ko.computed({
read: function () {
setEnabled(!disable());
},
disposeWhenNodeIsRemoved: element
}).extend({ rateLimit: 100, notifyWhenChangesStop: true });
} else {
setEnabled(!disable);
}
}
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
$element.multiselect('destroy');
});
},
update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
var $element = $(element);
var config = ko.toJS(valueAccessor());
$element.multiselect('setOptions', config);
$element.multiselect('rebuild');
}
};
}
function forEach(array, callback) {
for (var index = 0; index < array.length; ++index) {
callback(array[index], index);
}
}
/**
* Constructor to create a new multiselect using the given select.
*/
function Multiselect(select, options) {
this.$select = $(select);
this.options = this.mergeOptions($.extend({}, options, this.$select.data()));
if (this.$select.attr("data-placeholder")) {
this.options.nonSelectedText = this.$select.data("placeholder");
}
this.originalOptions = this.$select.clone()[0].options;
this.query = '';
this.searchTimeout = null;
this.lastToggledInput = null;
this.options.multiple = this.$select.attr('multiple') === "multiple";
this.options.onChange = $.proxy(this.options.onChange, this);
this.options.onSelectAll = $.proxy(this.options.onSelectAll, this);
this.options.onDeselectAll = $.proxy(this.options.onDeselectAll, this);
this.options.onDropdownShow = $.proxy(this.options.onDropdownShow, this);
this.options.onDropdownHide = $.proxy(this.options.onDropdownHide, this);
this.options.onDropdownShown = $.proxy(this.options.onDropdownShown, this);
this.options.onDropdownHidden = $.proxy(this.options.onDropdownHidden, this);
this.options.onInitialized = $.proxy(this.options.onInitialized, this);
this.options.onFiltering = $.proxy(this.options.onFiltering, this);
this.buildContainer();
this.buildButton();
this.buildDropdown();
this.buildReset();
this.buildSelectAll();
this.buildDropdownOptions();
this.buildFilter();
this.updateButtonText();
this.updateSelectAll(true);
if (this.options.enableClickableOptGroups && this.options.multiple) {
this.updateOptGroups();
}
this.options.wasDisabled = this.$select.prop('disabled');
if (this.options.disableIfEmpty && $('option', this.$select).length <= 0) {
this.disable();
}
this.$select.wrap('<span class="multiselect-native-select" />').after(this.$container);
this.options.onInitialized(this.$select, this.$container);
}
Multiselect.prototype = {
defaults: {
buttonText: function(options, select) {
if (this.disabledText.length > 0 && (select.prop('disabled') || (options.length == 0 && this.disableIfEmpty))) {
return this.disabledText;
} else if (options.length === 0) {
return this.nonSelectedText;
} else if (this.allSelectedText && options.length === $('option', $(select)).length && $('option', $(select)).length !== 1 && this.multiple) {
if (this.selectAllNumber) {
return this.allSelectedText + ' (' + options.length + ')';
} else {
return this.allSelectedText;
}
} else if (this.numberDisplayed != 0 && options.length > this.numberDisplayed) {
return options.length + ' ' + this.nSelectedText;
} else {
var selected = '';
var delimiter = this.delimiterText;
options.each(function() {
var label = ($(this).attr('label') !== undefined) ? $(this).attr('label') : $(this).text();
selected += label + delimiter;
});
return selected.substring(0, selected.length - this.delimiterText.length);
}
},
buttonTitle: function(options, select) {
if (options.length === 0) {
return this.nonSelectedText;
} else {
var selected = '';
var delimiter = this.delimiterText;
options.each(function () {
var label = ($(this).attr('label') !== undefined) ? $(this).attr('label') : $(this).text();
selected += label + delimiter;
});
return selected.substring(0, selected.length - this.delimiterText.length);
}
},
checkboxName: function(option) { return false; },
optionLabel: function(element){ return $(element).attr('label') || $(element).text(); },
optionClass: function(element) { return $(element).attr('class') || ''; },
onChange : function(option, checked) {},
onDropdownShow: function(event) {},
onDropdownHide: function(event) {},
onDropdownShown: function(event) {},
onDropdownHidden: function(event) {},
onSelectAll: function() {},
onDeselectAll: function() {},
onInitialized: function($select, $container) {},
onFiltering: function($filter) {},
enableHTML: false,
buttonClass: 'btn btn-secondary', // Updated for BS5
inheritClass: false,
buttonWidth: 'auto',
buttonContainer: '<div class="btn-group" />',
dropRight: false,
dropUp: false,
selectedClass: 'active',
maxHeight: false,
includeSelectAllOption: false,
includeSelectAllIfMoreThan: 0,
selectAllText: ' Select all',
selectAllValue: 'multiselect-all',
selectAllName: false,
selectAllNumber: true,
selectAllJustVisible: true,
enableFiltering: false,
enableCaseInsensitiveFiltering: false,
enableFullValueFiltering: false,
enableClickableOptGroups: false,
enableCollapsibleOptGroups: false,
collapseOptGroupsByDefault: false,
filterPlaceholder: 'Search',
filterBehavior: 'text',
includeFilterClearBtn: true,
preventInputChangeEvent: false,
nonSelectedText: 'None selected',
nSelectedText: 'selected',
allSelectedText: 'All selected',
numberDisplayed: 3,
disableIfEmpty: false,
disabledText: '',
delimiterText: ', ',
includeResetOption: false,
includeResetDivider: false,
resetText: 'Reset',
templates: {
// Updated templates for Bootstrap 5 compatibility
button: '<button type="button" class="multiselect dropdown-toggle" data-bs-toggle="dropdown"><span class="multiselect-selected-text"></span></button>',
ul: '<ul class="multiselect-container dropdown-menu"></ul>',
filter: '<li class="multiselect-item multiselect-filter"><div class="input-group"><span class="input-group-text"><i class="fa fa-search"></i></span><input class="form-control multiselect-search" type="text" /></div></li>',
filterClearBtn: '<button class="btn btn-outline-secondary multiselect-clear-filter" type="button"><i class="fa fa-times-circle"></i></button>',
li: '<li><a tabindex="0" class="dropdown-item"><label class="form-check-label"></label></a></li>',
divider: '<li class="multiselect-item divider"><hr class="dropdown-divider"/></li>',
liGroup: '<li class="multiselect-item multiselect-group"><label class="dropdown-header"></label></li>',
resetButton: '<li class="multiselect-reset text-center"><div class="input-group"><a class="btn btn-secondary w-100"></a></div></li>'
}
},
constructor: Multiselect,
buildContainer: function() {
this.$container = $(this.options.buttonContainer);
this.$container.on('show.bs.dropdown', this.options.onDropdownShow);
this.$container.on('hide.bs.dropdown', this.options.onDropdownHide);
this.$container.on('shown.bs.dropdown', this.options.onDropdownShown);
this.$container.on('hidden.bs.dropdown', this.options.onDropdownHidden);
},
buildButton: function() {
this.$button = $(this.options.templates.button).addClass(this.options.buttonClass);
if (this.$select.attr('class') && this.options.inheritClass) {
this.$button.addClass(this.$select.attr('class'));
}
if (this.$select.prop('disabled')) { this.disable(); } else { this.enable(); }
if (this.options.buttonWidth && this.options.buttonWidth !== 'auto') {
this.$button.css({ 'width' : '100%', 'overflow' : 'hidden', 'text-overflow' : 'ellipsis' });
this.$container.css({ 'width': this.options.buttonWidth });
}
var tabindex = this.$select.attr('tabindex');
if (tabindex) { this.$button.attr('tabindex', tabindex); }
this.$container.prepend(this.$button);
},
buildDropdown: function() {
this.$ul = $(this.options.templates.ul);
if (this.options.dropRight) {
this.$ul.addClass('dropdown-menu-end'); // Updated for BS5
}
if (this.options.maxHeight) {
this.$ul.css({ 'max-height': this.options.maxHeight + 'px', 'overflow-y': 'auto', 'overflow-x': 'hidden' });
}
if (this.options.dropUp) {
var height = Math.min(this.options.maxHeight, $('option[data-role!="divider"]', this.$select).length*26 + $('option[data-role="divider"]', this.$select).length*19 + (this.options.includeSelectAllOption ? 26 : 0) + (this.options.enableFiltering || this.options.enableCaseInsensitiveFiltering ? 44 : 0));
var moveCalc = height + 34;
this.$ul.css({ 'max-height': height + 'px', 'overflow-y': 'auto', 'overflow-x': 'hidden', 'margin-top': "-" + moveCalc + 'px' });
}
this.$container.append(this.$ul);
},
buildDropdownOptions: function() {
this.$select.children().each($.proxy(function(index, element) {
var $element = $(element);
var tag = $element.prop('tagName').toLowerCase();
if ($element.prop('value') === this.options.selectAllValue) return;
if (tag === 'optgroup') { this.createOptgroup(element); }
else if (tag === 'option') {
if ($element.data('role') === 'divider') { this.createDivider(); }
else { this.createOptionValue(element); }
}
}, this));
$(this.$ul).off('change', 'li:not(.multiselect-group) input[type="checkbox"], li:not(.multiselect-group) input[type="radio"]');
$(this.$ul).on('change', 'li:not(.multiselect-group) input[type="checkbox"], li:not(.multiselect-group) input[type="radio"]', $.proxy(function(event) {
var $target = $(event.target);
var checked = $target.prop('checked') || false;
var isSelectAllOption = $target.val() === this.options.selectAllValue;
if (this.options.selectedClass) {
if (checked) { $target.closest('li').addClass(this.options.selectedClass); }
else { $target.closest('li').removeClass(this.options.selectedClass); }
}
var value = $target.val();
var $option = this.getOptionByValue(value);
var $optionsNotThis = $('option', this.$select).not($option);
var $checkboxesNotThis = $('input', this.$container).not($target);
if (isSelectAllOption) {
if (checked) { this.selectAll(this.options.selectAllJustVisible, true); }
else { this.deselectAll(this.options.selectAllJustVisible, true); }
} else {
if (checked) {
$option.prop('selected', true);
if (this.options.multiple) { $option.prop('selected', true); }
else {
if (this.options.selectedClass) { $($checkboxesNotThis).closest('li').removeClass(this.options.selectedClass); }
$($checkboxesNotThis).prop('checked', false);
$optionsNotThis.prop('selected', false);
this.$button.click();
}
if (this.options.selectedClass === "active") { $optionsNotThis.closest("a").css("outline", ""); }
} else { $option.prop('selected', false); }
this.options.onChange($option, checked);
this.updateSelectAll();
if (this.options.enableClickableOptGroups && this.options.multiple) { this.updateOptGroups(); }
}
this.$select.change();
this.updateButtonText();
if(this.options.preventInputChangeEvent) { return false; }
}, this));
$('li a', this.$ul).on('mousedown', function(e) { if (e.shiftKey) { return false; } });
$(this.$ul).on('touchstart click', 'li a', $.proxy(function(event) {
event.stopPropagation();
var $target = $(event.target);
if (event.shiftKey && this.options.multiple) {
if($target.is("label")){
event.preventDefault();
$target = $target.find("input");
$target.prop("checked", !$target.prop("checked"));
}
var checked = $target.prop('checked') || false;
if (this.lastToggledInput !== null && this.lastToggledInput !== $target) {
var from = this.$ul.find("li:visible").index($target.parents("li"));
var to = this.$ul.find("li:visible").index(this.lastToggledInput.parents("li"));
if (from > to) { var tmp = to; to = from; from = tmp; }
++to;
var range = this.$ul.find("li").not(".multiselect-filter-hidden").slice(from, to).find("input");
range.prop('checked', checked);
if (this.options.selectedClass) { range.closest('li').toggleClass(this.options.selectedClass, checked); }
for (var i = 0, j = range.length; i < j; i++) {
var $checkbox = $(range[i]);
var $option = this.getOptionByValue($checkbox.val());
$option.prop('selected', checked);
}
}
$target.trigger("change");
}
if($target.is("input") && !$target.closest("li").is(".multiselect-item")){ this.lastToggledInput = $target; }
$target.blur();
}, this));
// Keyboard support (Updated for BS5 'show' class instead of 'open')
this.$container.off('keydown.multiselect').on('keydown.multiselect', $.proxy(function(event) {
if ($('input[type="text"]', this.$container).is(':focus')) return;
if (event.keyCode === 9 && this.$ul.hasClass('show')) { // BS5 uses 'show' instead of 'open'
this.$button.click();
} else {
var $items = $(this.$container).find("li:not(.divider):not(.disabled) a").filter(":visible");
if (!$items.length) return;
var index = $items.index($items.filter(':focus'));
if (event.keyCode === 38 && index > 0) { index--; }
else if (event.keyCode === 40 && index < $items.length - 1) { index++; }
else if (!~index) { index = 0; }
var $current = $items.eq(index);
$current.focus();
if (event.keyCode === 32 || event.keyCode === 13) {
var $checkbox = $current.find('input');
$checkbox.prop("checked", !$checkbox.prop("checked"));
$checkbox.change();
}
event.stopPropagation();
event.preventDefault();
}
}, this));
if (this.options.enableClickableOptGroups && this.options.multiple) {
$("li.multiselect-group input", this.$ul).on("change", $.proxy(function(event) {
event.stopPropagation();
var $target = $(event.target);
var checked = $target.prop('checked') || false;
var $li = $(event.target).closest('li');
var $group = $li.nextUntil("li.multiselect-group").not('.multiselect-filter-hidden').not('.disabled');
var $inputs = $group.find("input");
var values = [];
var $options = [];
if (this.options.selectedClass) {
if (checked) { $li.addClass(this.options.selectedClass); }
else { $li.removeClass(this.options.selectedClass); }
}
$.each($inputs, $.proxy(function(index, input) {
var value = $(input).val();
var $option = this.getOptionByValue(value);
if (checked) {
$(input).prop('checked', true);
$(input).closest('li').addClass(this.options.selectedClass);
$option.prop('selected', true);
} else {
$(input).prop('checked', false);
$(input).closest('li').removeClass(this.options.selectedClass);
$option.prop('selected', false);
}
$options.push(this.getOptionByValue(value));
}, this));
this.options.onChange($options, checked);
this.$select.change();
this.updateButtonText();
this.updateSelectAll();
}, this));
}
if (this.options.enableCollapsibleOptGroups && this.options.multiple) {
$("li.multiselect-group .caret-container", this.$ul).on("click", $.proxy(function(event) {
var $li = $(event.target).closest('li');
var $inputs = $li.nextUntil("li.multiselect-group").not('.multiselect-filter-hidden');
var visible = true;
$inputs.each(function() { visible = visible && !$(this).hasClass('multiselect-collapsible-hidden'); });
if (visible) { $inputs.hide().addClass('multiselect-collapsible-hidden'); }
else { $inputs.show().removeClass('multiselect-collapsible-hidden'); }
}, this));
$("li.multiselect-all", this.$ul).css('background', '#f3f3f3').css('border-bottom', '1px solid #eaeaea');
$("li.multiselect-all > a > label.checkbox", this.$ul).css('padding', '3px 20px 3px 35px');
$("li.multiselect-group > a > input", this.$ul).css('margin', '4px 0px 5px -20px');
}
},
createOptionValue: function(element) {
var $element = $(element);
if ($element.is(':selected')) { $element.prop('selected', true); }
var label = this.options.optionLabel(element);
var classes = this.options.optionClass(element);
var value = $element.val();
var inputType = this.options.multiple ? "checkbox" : "radio";
var $li = $(this.options.templates.li);
var $label = $('label', $li);
$label.addClass(inputType);
$label.attr("title", label);
$li.addClass(classes);
if (this.options.collapseOptGroupsByDefault && $(element).parent().prop("tagName").toLowerCase() === "optgroup") {
$li.addClass("multiselect-collapsible-hidden");
$li.hide();
}
if (this.options.enableHTML) { $label.html(" " + label); }
else { $label.text(" " + label); }
var $checkbox = $('<input/>').attr('type', inputType);
var name = this.options.checkboxName($element);
if (name) { $checkbox.attr('name', name); }
$label.prepend($checkbox);
var selected = $element.prop('selected') || false;
$checkbox.val(value);
if (value === this.options.selectAllValue) {
$li.addClass("multiselect-item multiselect-all");
$checkbox.parent().parent().addClass('multiselect-all');
}
$label.attr('title', $element.attr('title'));
this.$ul.append($li);
if ($element.is(':disabled')) {
$checkbox.attr('disabled', 'disabled').prop('disabled', true).closest('a').attr("tabindex", "-1").closest('li').addClass('disabled');
}
$checkbox.prop('checked', selected);
if (selected && this.options.selectedClass) { $checkbox.closest('li').addClass(this.options.selectedClass); }
},
createDivider: function(element) {
var $divider = $(this.options.templates.divider);
this.$ul.append($divider);
},
createOptgroup: function(group) {
var label = $(group).attr("label");
var value = $(group).attr("value");
var $li = $('<li class="multiselect-item multiselect-group"><a href="javascript:void(0);"><label><b></b></label></a></li>');
var classes = this.options.optionClass(group);
$li.addClass(classes);
if (this.options.enableHTML) { $('label b', $li).html(" " + label); }
else { $('label b', $li).text(" " + label); }
if (this.options.enableCollapsibleOptGroups && this.options.multiple) {
$('a', $li).append('<span class="caret-container"><i class="fa fa-caret-down"></i></span>');
}
if (this.options.enableClickableOptGroups && this.options.multiple) {
$('a label', $li).prepend('<input type="checkbox" value="' + value + '"/>');
}
if ($(group).is(':disabled')) { $li.addClass('disabled'); }
this.$ul.append($li);
$("option", group).each($.proxy(function($, group) { this.createOptionValue(group); }, this));
},
buildReset: function() {
if (this.options.includeResetOption) {
if (this.options.includeResetDivider) { this.$ul.prepend($(this.options.templates.divider)); }
var $resetButton = $(this.options.templates.resetButton);
if (this.options.enableHTML) { $('a', $resetButton).html(this.options.resetText); }
else { $('a', $resetButton).text(this.options.resetText); }
$('a', $resetButton).click($.proxy(function(){ this.clearSelection(); }, this));
this.$ul.prepend($resetButton);
}
},
buildSelectAll: function() {
if (typeof this.options.selectAllValue === 'number') { this.options.selectAllValue = this.options.selectAllValue.toString(); }
var alreadyHasSelectAll = this.hasSelectAll();
if (!alreadyHasSelectAll && this.options.includeSelectAllOption && this.options.multiple && $('option', this.$select).length > this.options.includeSelectAllIfMoreThan) {
if (this.options.includeSelectAllDivider) { this.$ul.prepend($(this.options.templates.divider)); }
var $li = $(this.options.templates.li);
$('label', $li).addClass("checkbox");
if (this.options.enableHTML) { $('label', $li).html(" " + this.options.selectAllText); }
else { $('label', $li).text(" " + this.options.selectAllText); }
if (this.options.selectAllName) { $('label', $li).prepend('<input type="checkbox" name="' + this.options.selectAllName + '" />'); }
else { $('label', $li).prepend('<input type="checkbox" />'); }
var $checkbox = $('input', $li);
$checkbox.val(this.options.selectAllValue);
$li.addClass("multiselect-item multiselect-all");
$checkbox.parent().parent().addClass('multiselect-all');
this.$ul.prepend($li);
$checkbox.prop('checked', false);
}
},
buildFilter: function() {
if (this.options.enableFiltering || this.options.enableCaseInsensitiveFiltering) {
var enableFilterLength = Math.max(this.options.enableFiltering, this.options.enableCaseInsensitiveFiltering);
if (this.$select.find('option').length >= enableFilterLength) {
this.$filter = $(this.options.templates.filter);
$('input', this.$filter).attr('placeholder', this.options.filterPlaceholder);
if(this.options.includeFilterClearBtn) {
var clearBtn = $(this.options.templates.filterClearBtn);
clearBtn.on('click', $.proxy(function(event){
clearTimeout(this.searchTimeout);
this.query = '';
this.$filter.find('.multiselect-search').val('');
$('li', this.$ul).show().removeClass('multiselect-filter-hidden');
this.updateSelectAll();
if (this.options.enableClickableOptGroups && this.options.multiple) { this.updateOptGroups(); }
}, this));
this.$filter.find('.input-group').append(clearBtn);
}
this.$ul.prepend(this.$filter);
this.$filter.val(this.query).on('click', function(event) { event.stopPropagation(); }).on('input keydown', $.proxy(function(event) {
if (event.which === 13) { event.preventDefault(); }
clearTimeout(this.searchTimeout);
this.searchTimeout = this.asyncFunction($.proxy(function() {
if (this.query !== event.target.value) {
this.query = event.target.value;
var currentGroup, currentGroupVisible;
$.each($('li', this.$ul), $.proxy(function(index, element) {
var value = $('input', element).length > 0 ? $('input', element).val() : "";
var text = $('label', element).text();
var filterCandidate = '';
if ((this.options.filterBehavior === 'text')) { filterCandidate = text; }
else if ((this.options.filterBehavior === 'value')) { filterCandidate = value; }
else if (this.options.filterBehavior === 'both') { filterCandidate = text + '\n' + value; }
if (value !== this.options.selectAllValue && text) {
var showElement = false;
if (this.options.enableCaseInsensitiveFiltering) { filterCandidate = filterCandidate.toLowerCase(); this.query = this.query.toLowerCase(); }
if (this.options.enableFullValueFiltering && this.options.filterBehavior !== 'both') {
var valueToMatch = filterCandidate.trim().substring(0, this.query.length);
if (this.query.indexOf(valueToMatch) > -1) { showElement = true; }
} else if (filterCandidate.indexOf(this.query) > -1) { showElement = true; }
if(!showElement){ $(element).css('display', 'none'); $(element).addClass('multiselect-filter-hidden'); }
if(showElement){ $(element).css('display', 'block'); $(element).removeClass('multiselect-filter-hidden'); }
if ($(element).hasClass('multiselect-group')) { currentGroup = element; currentGroupVisible = showElement; }
else {
if (showElement) { $(currentGroup).show().removeClass('multiselect-filter-hidden'); }
if (!showElement && currentGroupVisible) { $(element).show().removeClass('multiselect-filter-hidden'); }
}
}
}, this));
}
this.updateSelectAll();
if (this.options.enableClickableOptGroups && this.options.multiple) { this.updateOptGroups(); }
this.options.onFiltering(event.target);
}, this), 300, this);
}, this));
}
}
},
destroy: function() {
this.$container.remove();
this.$select.show();
this.$select.prop('disabled', this.options.wasDisabled);
this.$select.data('multiselect', null);
},
refresh: function () {
var inputs = {};
$('li input', this.$ul).each(function() { inputs[$(this).val()] = $(this); });
$('option', this.$select).each($.proxy(function (index, element) {
var $elem = $(element);
var $input = inputs[$(element).val()];
if ($elem.is(':selected')) {
$input.prop('checked', true);
if (this.options.selectedClass) { $input.closest('li').addClass(this.options.selectedClass); }
} else {
$input.prop('checked', false);
if (this.options.selectedClass) { $input.closest('li').removeClass(this.options.selectedClass); }
}
if ($elem.is(":disabled")) { $input.attr('disabled', 'disabled').prop('disabled', true).closest('li').addClass('disabled'); }
else { $input.prop('disabled', false).closest('li').removeClass('disabled'); }
}, this));
this.updateButtonText();
this.updateSelectAll();
if (this.options.enableClickableOptGroups && this.options.multiple) { this.updateOptGroups(); }
},
select: function(selectValues, triggerOnChange) {
if(!Array.isArray(selectValues)) { selectValues = [selectValues]; } // Fixed deprecated $.isArray
for (var i = 0; i < selectValues.length; i++) {
var value = selectValues[i];
if (value === null || value === undefined) continue;
var $option = this.getOptionByValue(value);
var $checkbox = this.getInputByValue(value);
if($option === undefined || $checkbox === undefined) continue;
if (!this.options.multiple) { this.deselectAll(false); }
if (this.options.selectedClass) { $checkbox.closest('li').addClass(this.options.selectedClass); }
$checkbox.prop('checked', true);
$option.prop('selected', true);
if (triggerOnChange) { this.options.onChange($option, true); }
}
this.updateButtonText();
this.updateSelectAll();
if (this.options.enableClickableOptGroups && this.options.multiple) { this.updateOptGroups(); }
},
clearSelection: function () {
this.deselectAll(false);
this.updateButtonText();
this.updateSelectAll();
if (this.options.enableClickableOptGroups && this.options.multiple) { this.updateOptGroups(); }
},
deselect: function(deselectValues, triggerOnChange) {
if(!Array.isArray(deselectValues)) { deselectValues = [deselectValues]; } // Fixed deprecated $.isArray
for (var i = 0; i < deselectValues.length; i++) {
var value = deselectValues[i];
if (value === null || value === undefined) continue;
var $option = this.getOptionByValue(value);
var $checkbox = this.getInputByValue(value);
if($option === undefined || $checkbox === undefined) continue;
if (this.options.selectedClass) { $checkbox.closest('li').removeClass(this.options.selectedClass); }
$checkbox.prop('checked', false);
$option.prop('selected', false);
if (triggerOnChange) { this.options.onChange($option, false); }
}
this.updateButtonText();
this.updateSelectAll();
if (this.options.enableClickableOptGroups && this.options.multiple) { this.updateOptGroups(); }
},
selectAll: function (justVisible, triggerOnSelectAll) {
var justVisible = typeof justVisible === 'undefined' ? true : justVisible;
var allLis = $("li:not(.divider):not(.disabled):not(.multiselect-group)", this.$ul);
var visibleLis = $("li:not(.divider):not(.disabled):not(.multiselect-group):not(.multiselect-filter-hidden):not(.multiselect-collapisble-hidden)", this.$ul).filter(':visible');
if(justVisible) {
$('input:enabled' , visibleLis).prop('checked', true);
visibleLis.addClass(this.options.selectedClass);
$('input:enabled' , visibleLis).each($.proxy(function(index, element) {
var value = $(element).val();
var option = this.getOptionByValue(value);
$(option).prop('selected', true);
}, this));
} else {
$('input:enabled' , allLis).prop('checked', true);
allLis.addClass(this.options.selectedClass);
$('input:enabled' , allLis).each($.proxy(function(index, element) {
var value = $(element).val();
var option = this.getOptionByValue(value);
$(option).prop('selected', true);
}, this));
}
$('li input[value="' + this.options.selectAllValue + '"]', this.$ul).prop('checked', true);
if (this.options.enableClickableOptGroups && this.options.multiple) { this.updateOptGroups(); }
if (triggerOnSelectAll) { this.options.onSelectAll(); }
},
deselectAll: function (justVisible, triggerOnDeselectAll) {
var justVisible = typeof justVisible === 'undefined' ? true : justVisible;
var allLis = $("li:not(.divider):not(.disabled):not(.multiselect-group)", this.$ul);
var visibleLis = $("li:not(.divider):not(.disabled):not(.multiselect-group):not(.multiselect-filter-hidden):not(.multiselect-collapisble-hidden)", this.$ul).filter(':visible');
if(justVisible) {
$('input[type="checkbox"]:enabled' , visibleLis).prop('checked', false);
visibleLis.removeClass(this.options.selectedClass);
$('input[type="checkbox"]:enabled' , visibleLis).each($.proxy(function(index, element) {
var value = $(element).val();
var option = this.getOptionByValue(value);
$(option).prop('selected', false);
}, this));
} else {
$('input[type="checkbox"]:enabled' , allLis).prop('checked', false);
allLis.removeClass(this.options.selectedClass);
$('input[type="checkbox"]:enabled' , allLis).each($.proxy(function(index, element) {
var value = $(element).val();
var option = this.getOptionByValue(value);
$(option).prop('selected', false);
}, this));
}
$('li input[value="' + this.options.selectAllValue + '"]', this.$ul).prop('checked', false);
if (this.options.enableClickableOptGroups && this.options.multiple) { this.updateOptGroups(); }
if (triggerOnDeselectAll) { this.options.onDeselectAll(); }
},
rebuild: function() {
this.$ul.html('');
this.options.multiple = this.$select.attr('multiple') === "multiple";
this.buildSelectAll();
this.buildDropdownOptions();
this.buildFilter();
this.updateButtonText();
this.updateSelectAll(true);
if (this.options.enableClickableOptGroups && this.options.multiple) { this.updateOptGroups(); }
if (this.options.disableIfEmpty && $('option', this.$select).length <= 0) { this.disable(); } else { this.enable(); }
if (this.options.dropRight) { this.$ul.addClass('dropdown-menu-end'); } // Updated for BS5
},
dataprovider: function(dataprovider) {
var groupCounter = 0;
var $select = this.$select.empty();
$.each(dataprovider, function (index, option) {
var $tag;
if (Array.isArray(option.children)) { // Fixed deprecated $.isArray
groupCounter++;
$tag = $('<optgroup/>').attr({ label: option.label || 'Group ' + groupCounter, disabled: !!option.disabled, value: option.value });
forEach(option.children, function(subOption) {
var attributes = { value: subOption.value, label: subOption.label || subOption.value, title: subOption.title, selected: !!subOption.selected, disabled: !!subOption.disabled };
for (var key in subOption.attributes) { attributes['data-' + key] = subOption.attributes[key]; }
$tag.append($('<option/>').attr(attributes));
});
} else {
var attributes = { 'value': option.value, 'label': option.label || option.value, 'title': option.title, 'class': option['class'], 'selected': !!option['selected'], 'disabled': !!option['disabled'] };
for (var key in option.attributes) { attributes['data-' + key] = option.attributes[key]; }
$tag = $('<option/>').attr(attributes);
$tag.text(option.label || option.value);
}
$select.append($tag);
});
this.rebuild();
},
enable: function() {
this.$select.prop('disabled', false);
this.$button.prop('disabled', false).removeClass('disabled');
},
disable: function() {
this.$select.prop('disabled', true);
this.$button.prop('disabled', true).addClass('disabled');
},
setOptions: function(options) { this.options = this.mergeOptions(options); },
mergeOptions: function(options) { return $.extend(true, {}, this.defaults, this.options, options); },
hasSelectAll: function() { return $('li.multiselect-all', this.$ul).length > 0; },
updateOptGroups: function() {
var $groups = $('li.multiselect-group', this.$ul);
var selectedClass = this.options.selectedClass;
$groups.each(function() {
var $options = $(this).nextUntil('li.multiselect-group').not('.multiselect-filter-hidden').not('.disabled');
var checked = true;
$options.each(function() { var $input = $('input', this); if (!$input.prop('checked')) { checked = false; } });
if (selectedClass) {
if (checked) { $(this).addClass(selectedClass); } else { $(this).removeClass(selectedClass); }
}
$('input', this).prop('checked', checked);
});
},
updateSelectAll: function(notTriggerOnSelectAll) {
if (this.hasSelectAll()) {
var allBoxes = $("li:not(.multiselect-item):not(.multiselect-filter-hidden):not(.multiselect-group):not(.disabled) input:enabled", this.$ul);
var allBoxesLength = allBoxes.length;
var checkedBoxesLength = allBoxes.filter(":checked").length;
var selectAllLi = $("li.multiselect-all", this.$ul);
var selectAllInput = selectAllLi.find("input");
if (checkedBoxesLength > 0 && checkedBoxesLength === allBoxesLength) {
selectAllInput.prop("checked", true);
selectAllLi.addClass(this.options.selectedClass);
} else {
selectAllInput.prop("checked", false);
selectAllLi.removeClass(this.options.selectedClass);
}
}
},
updateButtonText: function() {
var options = this.getSelected();
if (this.options.enableHTML) { $('.multiselect .multiselect-selected-text', this.$container).html(this.options.buttonText(options, this.$select)); }
else { $('.multiselect .multiselect-selected-text', this.$container).text(this.options.buttonText(options, this.$select)); }
$('.multiselect', this.$container).attr('title', this.options.buttonTitle(options, this.$select));
},
getSelected: function() { return $('option', this.$select).filter(":selected"); },
getOptionByValue: function (value) {
var options = $('option', this.$select);
var valueToCompare = value.toString();
for (var i = 0; i < options.length; i = i + 1) { var option = options[i]; if (option.value === valueToCompare) { return $(option); } }
},
getInputByValue: function (value) {
var checkboxes = $('li input:not(.multiselect-search)', this.$ul);
var valueToCompare = value.toString();
for (var i = 0; i < checkboxes.length; i = i + 1) { var checkbox = checkboxes[i]; if (checkbox.value === valueToCompare) { return $(checkbox); } }
},
updateOriginalOptions: function() { this.originalOptions = this.$select.clone()[0].options; },
asyncFunction: function(callback, timeout, self) {
var args = Array.prototype.slice.call(arguments, 3);
return setTimeout(function() { callback.apply(self || window, args); }, timeout);
},
setAllSelectedText: function(allSelectedText) { this.options.allSelectedText = allSelectedText; this.updateButtonText(); }
};
$.fn.multiselect = function(option, parameter, extraOptions) {
return this.each(function() {
var data = $(this).data('multiselect');
var options = typeof option === 'object' && option;
if (!data) { data = new Multiselect(this, options); $(this).data('multiselect', data); }
if (typeof option === 'string') {
data[option](parameter, extraOptions);
if (option === 'destroy') { $(this).data('multiselect', false); }
}
});
};
$.fn.multiselect.Constructor = Multiselect;
$(function() { $("select[data-role=multiselect]").multiselect(); });
});
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,42 @@
.js_cls_sh_many2many_select_div .placeholder{background:transparent;}
.filter-multi-select > .viewbar > .selected-items > .item{color:#000 !important;border: 1px solid grey;}
.filter-multi-select > .viewbar > .selected-items > .item > button{color:#000 !important;}
.filter-multi-select .dropdown-item .custom-checkbox:checked ~ .custom-control-label::before{background: #212121 !important;}
.js_question-wrapper .o_survey_question_email_box,
.js_cls_sh_many2one_wrapper input,
.js_cls_sh_many2many_wrapper input,
.js_cls_sh_address_wrapper .js_cls_street,
.js_cls_sh_address_wrapper .js_cls_street2,
.js_cls_sh_address_wrapper .js_cls_city,
.js_cls_sh_address_wrapper .js_cls_zip,
.js_cls_sh_address_wrapper .js_cls_country_id,
.js_cls_sh_address_wrapper .js_cls_state_id,
.js_question-wrapper .o_survey_question_color_box,
.js_question-wrapper .js_cls_tmpl_sh_location,
.js_cls_sh_many2many_wrapper .js_cls_sh_many2many_select_div .viewbar.form-control.dropdown-toggle{
border: 0px;
border-radius: 0 !important;
border-bottom: 1px solid o-color(o-color-1);
height: 33px;
padding: 5px !important;
}
.js_question-wrapper input[type="file"]{
border: 1px solid #35979c;
}
.js_question-wrapper .o_survey_question_email_box:focus,
.js_cls_sh_many2one_wrapper input:focus,
.js_cls_sh_many2many_wrapper input:focus,
.js_cls_sh_address_wrapper .js_cls_street:focus,
.js_cls_sh_address_wrapper .js_cls_street2:focus,
.js_cls_sh_address_wrapper .js_cls_city:focus,
.js_cls_sh_address_wrapper .js_cls_zip:focus,
.js_cls_sh_address_wrapper .js_cls_country_id:focus,
.js_cls_sh_address_wrapper .js_cls_state_id:focus,
.js_cls_sh_address_wrapper .js_cls_sh_many2many_select_div .viewbar.form-control.dropdown-toggle:focus{
box-shadow: none;
}
.js_cls_sh_signature_wrapper .js_cls_sh_signature_pad > div:first-child,
.js_cls_sh_signature_wrapper .js_cls_sh_signature_pad > div:first-child + canvas.jSignature{display: none !important;}
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="Many2one_tree_view_data" model="ir.ui.view">
<field name="name">Tree view</field>
<field name="model">address.survey</field>
<field name="arch" type="xml">
<list editable="bottom">
<field name="name"/>
</list>
</field>
</record>
</data>
</odoo>
@@ -0,0 +1,210 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="Many2one_tree_view_school" model="ir.ui.view">
<field name="name">Tree view</field>
<field name="model">school.student</field>
<field name="arch" type="xml">
<list editable="bottom">
<field name="name"/>
</list>
</field>
</record>
<record id="Many2one_tree_last_skill" model="ir.ui.view">
<field name="name">Tree view</field>
<field name="model">last.skill</field>
<field name="arch" type="xml">
<list editable="bottom">
<field name="name"/>
</list>
</field>
</record>
<record id="Many2one_tree_view_year" model="ir.ui.view">
<field name="name">Tree view</field>
<field name="model">year.year</field>
<field name="arch" type="xml">
<list editable="bottom">
<field name="name"/>
</list>
</field>
</record>
<record id="Many2one_tree_view_skill" model="ir.ui.view">
<field name="name">Tree view</field>
<field name="model">skill.student</field>
<field name="arch" type="xml">
<list editable="bottom">
<field name="name"/>
</list>
</field>
</record>
<record id="Many2one_tree_view_grade" model="ir.ui.view">
<field name="name">Tree view</field>
<field name="model">grade.student</field>
<field name="arch" type="xml">
<list editable="bottom">
<field name="name"/>
</list>
</field>
</record>
<record id="Many2one_level_score" model="ir.ui.view">
<field name="name">Tree view</field>
<field name="model">level.score</field>
<field name="arch" type="xml">
<list editable="bottom">
<field name="name"/>
</list>
</field>
</record>
<record id="Many2one_other_level" model="ir.ui.view">
<field name="name">Tree view</field>
<field name="model">other.level</field>
<field name="arch" type="xml">
<list editable="bottom">
<field name="name"/>
</list>
</field>
</record>
<record id="Many2one_team_work" model="ir.ui.view">
<field name="name">Tree view</field>
<field name="model">team.work</field>
<field name="arch" type="xml">
<list editable="bottom">
<field name="name"/>
</list>
</field>
</record>
<record id="Many2many_multi_choose" model="ir.ui.view">
<field name="name">Tree view</field>
<field name="model">multi.choose</field>
<field name="arch" type="xml">
<list editable="bottom">
<field name="name"/>
</list>
</field>
</record>
<record id="Many2many_other_plan" model="ir.ui.view">
<field name="name">Tree view</field>
<field name="model">other.plan</field>
<field name="arch" type="xml">
<list editable="bottom">
<field name="name"/>
</list>
</field>
</record>
<record id="Many2many_con_con" model="ir.ui.view">
<field name="name">Tree view</field>
<field name="model">con.con</field>
<field name="arch" type="xml">
<list editable="bottom">
<field name="name"/>
</list>
</field>
</record>
<record id="Many2one_position_family" model="ir.ui.view">
<field name="name">Tree view</field>
<field name="model">position.family</field>
<field name="arch" type="xml">
<list editable="bottom">
<field name="name"/>
</list>
</field>
</record>
<record id="action_Many2one_tree_view_school" model="ir.actions.act_window">
<field name="name">គ្រឹះស្ថានសិក្សា</field>
<field name="res_model">school.student</field>
<field name="view_mode">list</field>
<field name="context">{}</field>
<field name="domain">[]</field>
</record>
<record id="action_tree_view_skill" model="ir.actions.act_window">
<field name="name">ជំនាញ</field>
<field name="res_model">skill.student</field>
<field name="view_mode">list</field>
<field name="context">{}</field>
<field name="domain">[]</field>
</record>
<record id="action_tree_view_grade" model="ir.actions.act_window">
<field name="name">កម្រិតសិក្សា</field>
<field name="res_model">grade.student</field>
<field name="view_mode">list</field>
<field name="context">{}</field>
<field name="domain">[]</field>
</record>
<record id="action_tree_view_year" model="ir.actions.act_window">
<field name="name">ឆ្នាំទទួលបានអាហារូបករណ៍</field>
<field name="res_model">year.year</field>
<field name="view_mode">list</field>
<field name="context">{}</field>
<field name="domain">[]</field>
</record>
<record id="action_tree_last_skill" model="ir.actions.act_window">
<field name="name">ជំនាញចុងក្រោយ</field>
<field name="res_model">last.skill</field>
<field name="view_mode">list</field>
<field name="context">{}</field>
<field name="domain">[]</field>
</record>
<record id="action_levelscore_skill" model="ir.actions.act_window">
<field name="name">កម្រិត</field>
<field name="res_model">level.score</field>
<field name="view_mode">list</field>
<field name="context">{}</field>
<field name="domain">[]</field>
</record>
<record id="action_tree_other_level" model="ir.actions.act_window">
<field name="name">កម្រិតផ្សេង</field>
<field name="res_model">other.level</field>
<field name="view_mode">list</field>
<field name="context">{}</field>
<field name="domain">[]</field>
</record>
<record id="action_tree_team_work" model="ir.actions.act_window">
<field name="name">ក្រុមការងារ</field>
<field name="res_model">team.work</field>
<field name="view_mode">list</field>
<field name="context">{}</field>
<field name="domain">[]</field>
</record>
<record id="action_tree_multi_chose" model="ir.actions.act_window">
<field name="name">ជម្រើសច្រើន</field>
<field name="res_model">multi.choose</field>
<field name="view_mode">list</field>
<field name="context">{}</field>
<field name="domain">[]</field>
</record>
<record id="action_tree_other_plan" model="ir.actions.act_window">
<field name="name">គម្រោងផ្សេងទៀត</field>
<field name="res_model">other.plan</field>
<field name="view_mode">list</field>
<field name="context">{}</field>
<field name="domain">[]</field>
</record>
<record id="action_tree_con_con" model="ir.actions.act_window">
<field name="name">កម្មពិធីភ្ជាប់</field>
<field name="res_model">con.con</field>
<field name="view_mode">list</field>
<field name="context">{}</field>
<field name="domain">[]</field>
</record>
<record id="action_tree_position_family" model="ir.actions.act_window">
<field name="name">ត្រូវជាអ្វីជាមួយនិស្សិត</field>
<field name="res_model">position.family</field>
<field name="view_mode">list</field>
<field name="context">{}</field>
<field name="domain">[]</field>
</record>
<menuitem name="កំណត់" id="setting_surveys" parent="survey.menu_surveys" groups="survey.group_survey_manager" />
<menuitem name="ព័ត៌មានឆ្នាំសិក្សា" action="action_tree_view_year" id="menu_view_year" parent="setting_surveys" groups="survey.group_survey_manager"/>
<menuitem name="ព័ត៌មានគ្រឹះស្ថានសិក្សា" action="action_Many2one_tree_view_school" id="view_school" parent="setting_surveys" groups="survey.group_survey_manager"/>
<menuitem name="ព័ត៌មានជំនាញ" action="action_tree_view_skill" id="menu_view_skill" parent="setting_surveys" groups="survey.group_survey_manager"/>
<menuitem name="ព័ត៌មានកម្រិតសិក្សាចុងក្រោយ" action="action_tree_view_grade" id="menu_view_grade" parent="setting_surveys" groups="survey.group_survey_manager"/>
<menuitem name="ព័ត៌មានជំនាញចុងក្រោយ" action="action_tree_last_skill" id="menu_last_skill" parent="setting_surveys" groups="survey.group_survey_manager"/>
<menuitem name="កម្រិត" action="action_levelscore_skill" id="menu_level_score" parent="setting_surveys" groups="survey.group_survey_manager"/>
<menuitem name="កម្រិតផ្សេងទៀត" action="action_tree_other_level" id="menu_other_level" parent="setting_surveys" groups="survey.group_survey_manager"/>
<menuitem name="២៣ក្រុម" action="action_tree_team_work" id="menu_team_work23" parent="setting_surveys" groups="survey.group_survey_manager"/>
<menuitem name="ជម្រើសច្រើន" action="action_tree_multi_chose" id="menu_multi" parent="setting_surveys" groups="survey.group_survey_manager"/>
<menuitem name="គម្រោងផ្សេងទៀត" action="action_tree_other_plan" id="menu_other_plan" parent="setting_surveys" groups="survey.group_survey_manager"/>
<menuitem name="កម្រោងកម្មពិធី" action="action_tree_con_con" id="menu_con_con" parent="setting_surveys" groups="survey.group_survey_manager"/>
<menuitem name="ត្រូវជាអ្វីជាមួយនិស្សិត" action="action_tree_position_family" id="menu_position_family" parent="setting_surveys" groups="survey.group_survey_manager"/>
</data>
</odoo>