Updated skills visualization

skala
Yannick Warnier 12 years ago
parent 78ec9585b4
commit ee59bb488c
  1. 5
      main/admin/index.php
  2. 37
      main/admin/skill_example.csv
  3. 6
      main/admin/skills.json.php
  4. 312
      main/admin/skills_import.php
  5. 46
      main/admin/skills_wheel.php
  6. 159
      main/inc/lib/javascript/coffeewheel/wheel.js
  7. 4
      main/inc/lib/javascript/d3.v2.min.js
  8. 48
      main/inc/lib/skill.lib.php
  9. 50
      main/template/default/skill/skill_wheel.tpl

@ -250,17 +250,16 @@ if (api_is_platform_admin()) {
*/
//Skills
if (api_get_setting('allow_skills_tool') == 'true') {
$blocks['skills']['icon'] = Display::return_icon('logo.gif', get_lang('Skills'), array(), ICON_SIZE_SMALL, false);
$blocks['skills']['label'] = get_lang('Skills');
$items = array();
$items[] = array('url'=>'skills.php', 'label' => get_lang('SkillsTree'));
$items[] = array('url'=>'skills_wheel.php', 'label' => get_lang('SkillsWheel'));
$items[] = array('url'=>'skills_import.php', 'label' => get_lang('SkillsImport'));
$items[] = array('url'=>'skills_profile.php', 'label' => get_lang('SkillsProfile'));
$items[] = array('url'=>'skills_gradebook.php', 'label' => get_lang('SkillsAndGradebooks'));
$blocks['skills']['items'] = $items;
$blocks['skills']['extra'] = null;
$blocks['skills']['search_form'] = null;

@ -0,0 +1,37 @@
id;parent_id;name;description
2;1;Reflection;
3;1;Manage learning;
4;1;Information literacy;
5;1;Organization skills;
6;1;Networking w/others;
7;1;Researching;
8;1;Communication skills;
9;1;Creative skills;
10;1;Collaboration skills;
11;4;Identify info;
12;4;Find information;
13;5;Personal librarianship;
14;5;Personal categorization;Personal categorization and taxonomies
15;6;Shared knowledge;Knowing what your network of people knows
16;6;Knowledge links;Knowing who might have additional knowledge and resources to help you
17;7;Researching;
18;7;Canvassing;
19;7;Paying attention;
20;7;Interviewing;
21;7;Observational;Observational 'cultural anthropology' skills
22;8;Perception;
23;8;Intuition;
24;8;Expression;
25;8;Visualization;
26;8;Interpretation;
27;9;Imagination;
28;9;Pattern recognition;
29;9;Appreciation;
30;9;Innovation;
31;9;Inference;
32;9;Understanding systems;Understanding of complex adaptive systems
33;10;Coordination;
34;10;Synchronization;
35;10;Experimentation;
36;10;Cooperation;
37;10;Design;
1 id parent_id name description
2 2 1 Reflection
3 3 1 Manage learning
4 4 1 Information literacy
5 5 1 Organization skills
6 6 1 Networking w/others
7 7 1 Researching
8 8 1 Communication skills
9 9 1 Creative skills
10 10 1 Collaboration skills
11 11 4 Identify info
12 12 4 Find information
13 13 5 Personal librarianship
14 14 5 Personal categorization Personal categorization and taxonomies
15 15 6 Shared knowledge Knowing what your network of people knows
16 16 6 Knowledge links Knowing who might have additional knowledge and resources to help you
17 17 7 Researching
18 18 7 Canvassing
19 19 7 Paying attention
20 20 7 Interviewing
21 21 7 Observational Observational 'cultural anthropology' skills
22 22 8 Perception
23 23 8 Intuition
24 24 8 Expression
25 25 8 Visualization
26 26 8 Interpretation
27 27 9 Imagination
28 28 9 Pattern recognition
29 29 9 Appreciation
30 30 9 Innovation
31 31 9 Inference
32 32 9 Understanding systems Understanding of complex adaptive systems
33 33 10 Coordination
34 34 10 Synchronization
35 35 10 Experimentation
36 36 10 Cooperation
37 37 10 Design

@ -0,0 +1,6 @@
<?php
require '../inc/global.inc.php';
$skills = new Skill();
//$all = $skills->get_all(false,false,null,0);
$all = $skills->get_skills_tree_json();
echo $all;

@ -0,0 +1,312 @@
<?php
/* For licensing terms, see /license.txt */
/**
* This tool allows platform admins to add skills by uploading a CSV or XML file
* @package chamilo.admin
* @documentation Some interesting basic skills can be found in the "Skills" section here: http://en.wikipedia.org/wiki/Personal_knowledge_management
*/
/**
* Validate the imported data.
*/
$language_file = array ('admin', 'registration');
$cidReset = true;
require '../inc/global.inc.php';
require_once api_get_path(LIBRARY_PATH).'mail.lib.inc.php';
require_once api_get_path(LIBRARY_PATH).'fileManage.lib.php';
require_once api_get_path(LIBRARY_PATH).'classmanager.lib.php';
require_once api_get_path(LIBRARY_PATH).'import.lib.php';
function validate_data($skills) {
$errors = array();
$skills = array();
// 1. Check if mandatory fields are set.
$mandatory_fields = array('id', 'parent_id', 'name');
foreach ($skills as $index => $skill) {
foreach ($mandatory_fields as $field) {
if (empty($skill[$field])) {
$skill['error'] = get_lang(ucfirst($field).'Mandatory');
$errors[] = $skill;
}
}
// 2. Check skill ID is not empty
if (!isset($skill['id']) || empty($skill['id'])) {
$skill['error'] = get_lang('SkillImportNoID');
$errors[] = $skill;
}
// 3. Check skill Parent
if (!isset($skill['parent_id'])) {
$skill['error'] = get_lang('SkillImportNoParent');
$errors[] = $skill;
}
// 4. Check skill Name
if (!isset($skill['name'])) {
$skill['error'] = get_lang('SkillImportNoName');
$errors[] = $skill;
}
}
return $errors;
}
/**
* Save the imported data
* @param array List of users
* @return void
* @uses global variable $inserted_in_course, which returns the list of courses the user was inserted in
*/
function save_data($skills) {
if (is_array($skills)) {
$parents = array();
foreach ($skills as $index => $skill) {
if (isset($parents[$skill['parent_id']])) {
$skill['parent_id'] = $parents[$skill['parent_id']];
} else {
$skill['parent_id'] = 1;
}
$skill['a'] = 'add';
$saved_id = $skill['id'];
$skill['id'] = null;
$oskill = new Skill();
$skill_id = $oskill->add($skill);
$parents[$saved_id] = $skill_id;
}
}
}
/**
* Read the CSV-file
* @param string $file Path to the CSV-file
* @return array All userinformation read from the file
*/
function parse_csv_data($file) {
$skills = Import :: csv_to_array($file);
foreach ($skills as $index => $skill) {
$skills[$index] = $skill;
}
return $skills;
}
/**
* XML-parser: handle start of element
*/
function element_start($parser, $data) {
$data = api_utf8_decode($data);
global $skill;
global $current_tag;
switch ($data) {
case 'Skill' :
$skill = array ();
break;
default :
$current_tag = $data;
}
}
/**
* XML-parser: handle end of element
*/
function element_end($parser, $data) {
$data = api_utf8_decode($data);
global $skill;
global $skills;
global $current_value;
switch ($data) {
case 'Skill' :
$skills[] = $skill;
break;
default :
$skill[$data] = $current_value;
break;
}
}
/**
* XML-parser: handle character data
*/
function character_data($parser, $data) {
$data = trim(api_utf8_decode($data));
global $current_value;
$current_value = $data;
}
/**
* Read the XML-file
* @param string $file Path to the XML-file
* @return array All userinformation read from the file
*/
function parse_xml_data($file) {
global $current_tag;
global $current_value;
global $skill;
global $skills;
$skills = array();
$parser = xml_parser_create('UTF-8');
xml_set_element_handler($parser, 'element_start', 'element_end');
xml_set_character_data_handler($parser, 'character_data');
xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false);
xml_parse($parser, api_utf8_encode_xml(file_get_contents($file)));
xml_parser_free($parser);
return $skills;
}
$this_section = SECTION_PLATFORM_ADMIN;
api_protect_admin_script(true);
//$tool_name = get_lang('ImportSkillsListXMLCSV');
$tool_name = get_lang('ImportSkillsListCSV');
$interbreadcrumb[] = array ("url" => 'index.php', "name" => get_lang('PlatformAdmin'));
set_time_limit(0);
$extra_fields = UserManager::get_extra_fields(0, 0, 5, 'ASC', true);
$user_id_error = array();
$error_message = '';
if ($_POST['formSent'] AND $_FILES['import_file']['size'] !== 0) {
$file_type = $_POST['file_type'];
Security::clear_token();
$tok = Security::get_token();
$allowed_file_mimetype = array('csv','xml');
$error_kind_file = false;
$ext_import_file = substr($_FILES['import_file']['name'],(strrpos($_FILES['import_file']['name'],'.')+1));
if (in_array($ext_import_file,$allowed_file_mimetype)) {
if (strcmp($file_type, 'csv') === 0 && $ext_import_file == $allowed_file_mimetype[0]) {
$skills = parse_csv_data($_FILES['import_file']['tmp_name']);
$errors = validate_data($skills);
$error_kind_file = false;
} elseif (strcmp($file_type, 'xml') === 0 && $ext_import_file == $allowed_file_mimetype[1]) {
$skills = parse_xml_data($_FILES['import_file']['tmp_name']);
$errors = validate_data($skills);
$error_kind_file = false;
} else {
$error_kind_file = true;
}
} else {
$error_kind_file = true;
}
// List skill id whith error.
$skills_to_insert = $skill_id_error = array();
if (is_array($errors)) {
foreach ($errors as $my_errors) {
$skill_id_error[] = $my_errors['SkillName'];
}
}
if (is_array($skills)) {
foreach ($skills as $my_skill) {
if (!in_array($my_skill['SkillName'], $skill_id_error)) {
$skills_to_insert[] = $my_skill;
}
}
}
if (strcmp($file_type, 'csv') === 0) {
save_data($skills_to_insert);
} elseif (strcmp($file_type, 'xml') === 0) {
save_data($skills_to_insert);
} else {
$error_message = get_lang('YouMustImportAFileAccordingToSelectedOption');
}
if (count($errors) > 0) {
$see_message_import = get_lang('FileImportedJustSkillsThatAreNotRegistered');
} else {
$see_message_import = get_lang('FileImported');
}
if (count($errors) != 0) {
$warning_message = '<ul>';
foreach ($errors as $index => $error_skill) {
$warning_message .= '<li><b>'.$error_skill['error'].'</b>: ';
$warning_message .= '<strong>'.$error_skill['SkillName'].'</strong>&nbsp;('.$error_skill['SkillName'].')';
$warning_message .= '</li>';
}
$warning_message .= '</ul>';
}
// if the warning message is too long then we display the warning message trough a session
if (api_strlen($warning_message) > 150) {
$_SESSION['session_message_import_skills'] = $warning_message;
$warning_message = 'session_message';
}
if ($error_kind_file) {
$error_message = get_lang('YouMustImportAFileAccordingToSelectedOption');
} else {
//header('Location: '.api_get_path(WEB_CODE_PATH).'admin/skills_import.php?action=show_message&warn='.urlencode($warning_message).'&message='.urlencode($see_message_import).'&sec_token='.$tok);
//exit;
}
}
Display :: display_header($tool_name);
if (!empty($error_message)) {
Display::display_error_message($error_message);
}
if (!empty($see_message_import)) {
Display::display_normal_message($see_message_import);
}
$form = new FormValidator('user_import','post','skills_import.php');
$form->addElement('header', '', $tool_name);
$form->addElement('hidden', 'formSent');
$form->addElement('file', 'import_file', get_lang('ImportFileLocation'));
$group = array();
$group[] = $form->createElement('radio', 'file_type', '', 'CSV (<a href="skill_example.csv" target="_blank">'.get_lang('ExampleCSVFile').'</a>)', 'csv');
//$group[] = $form->createElement('radio', 'file_type', null, 'XML (<a href="skill_example.xml" target="_blank">'.get_lang('ExampleXMLFile').'</a>)', 'xml');
$form->addGroup($group, '', get_lang('FileType'), '<br/>');
$form->addElement('style_submit_button', 'submit', get_lang('Import'), 'class="save"');
$defaults['formSent'] = 1;
$defaults['sendMail'] = 0;
$defaults['file_type'] = 'csv';
$form->setDefaults($defaults);
$form->display();
$list = array();
$list_reponse = array();
$result_xml = '';
$i = 0;
$count_fields = count($extra_fields);
if ($count_fields > 0) {
foreach ($extra_fields as $extra) {
$list[] = $extra[1];
$list_reponse[] = 'xxx';
$spaces = '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;';
$result_xml .= $spaces.'&lt;'.$extra[1].'&gt;xxx&lt;/'.$extra[1].'&gt;';
if ($i != $count_fields - 1) {
$result_xml .= '<br/>';
}
$i++;
}
}
?>
<p><?php echo get_lang('CSVMustLookLike').' ('.get_lang('MandatoryFields').')'; ?> :</p>
<blockquote>
<pre>
<b>id</b>;<b>parent_id</b>;<b>name</b>;<b>description</b><br />
<b>n</b>;<b>m</b>;<b>xxx</b>;xxx<br />
</pre>
</blockquote>
<!--p><?php echo get_lang('XMLMustLookLike').' ('.get_lang('MandatoryFields').')'; ?> :</p>
<blockquote>
<pre>
&lt;?xml version=&quot;1.0&quot; encoding=&quot;<?php echo api_refine_encoding_id(api_get_system_encoding()); ?>&quot;?&gt;
&lt;Skills&gt;
&lt;Skill&gt;
<b>&lt;id&gt;n&lt;/id&gt;</b>
<b>&lt;parent_id&gt;n&lt;/parent_id&gt;</b>
<b>&lt;name&gt;xxx&lt;/name&gt;</b>
&lt;description&gt;xxx&lt;/description&gt;
&lt;/Skill&gt;
&lt;/Skills&gt;
</pre>
</blockquote-->
<?php
Display :: display_footer();

@ -0,0 +1,46 @@
<?php
/* For licensing terms, see /license.txt */
/**
* @package chamilo.admin
*/
// Language files that need to be included.
$language_file = array('admin');
$cidReset = true;
require_once '../inc/global.inc.php';
require_once api_get_path(LIBRARY_PATH).'skill.lib.php';
$this_section = SECTION_PLATFORM_ADMIN;
api_protect_admin_script();
if (api_get_setting('allow_skills_tool') != 'true') {
api_not_allowed();
}
//Adds the JS needed to use the jqgrid
$htmlHeadXtra[] = api_get_js('d3.v2.min.js');
$skill = new Skill();
$type = 'edit'; //edit
//$tree = $skill->get_skills_tree_json(null, true);
//$html = $skill_visualizer->return_html();
//$html = $skill_visualizer->return_html();
//$url = api_get_path(WEB_AJAX_PATH).'skill.ajax.php?1=1';
$tpl = new Template(null, false, false);
//$tpl->assign('url', $url);
//$tpl->assign('html', $html);
$tpl->assign('html', api_get_js('coffeewheel/wheel.js'));
//$tpl->assign('skill_visualizer', $skill_visualizer);
//$tpl->assign('js', $skill_visualizer->return_js());
//$tpl->assign('js', api_get_js('coffeewheel/wheel.js'));
//
$content = $tpl->fetch('default/skill/skill_wheel.tpl');
$tpl->assign('content', $content);
$tpl->display_no_layout_template();

@ -0,0 +1,159 @@
/* Define constants and size of the wheel */
/** Total width of the wheel (also counts for the height) */
var w = 800,
h = w,
r = w / 2,
/** x/y positionning of the center of the wheel */
x = d3.scale.linear().range([0, 2 * Math.PI]),
y = d3.scale.pow().exponent(1.1).domain([0, 1]).range([0, r]),
/** Padding in pixels before th string starts */
p = 3,
/** Duration of click animations */
duration = 1000,
/** Levels to show */
l = 3;
/* Locate the #div id element */
var div = d3.select("#vis");
/* Remove the image (make way for the dynamic stuff */
div.select("img").remove();
/* Append an element "svg" to the #vis section */
var vis = div.append("svg")
.attr("width", w + p * 2)
.attr("height", h + p * 2)
.append("g")
.attr("transform", "translate(" + (r + p) + "," + (r/1.5 + p) + ")");
/* ...update translate variables to change coordinates of wheel's center */
/* Add a small label to help the user */
div.append("p")
.attr("id", "intro")
.text("Click to zoom!");
/* Generate the partition layout */
var partition = d3.layout.partition()
.sort(null)
/** Value here seems to be a calculation of the size of the elements
depending on the level of depth they're at. Changing it makes
elements pass over the limits of others... */
.value(function(d) { return 5.8 - d.depth; });
/* Generate an arc which will define the whole wheel context */
var arc = d3.svg.arc()
.startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); })
.endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); })
.innerRadius(function(d) { return Math.max(0, d.y ? y(d.y) : d.y); })
.outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)); });
/* ...adding "+1" to "y" function's params is really funny */
/* Exexute the calculation based on a JSON file provided */
/*d3.json("wheel.json", function(json) {*/
/** Get the JSON list of skills and work on it */
d3.json("skills.json.php", function(json) {
/** Define the list of nodes based on the JSON */
var nodes = partition.nodes({children: json});
var path = vis.selectAll("path").data(nodes);
path.enter().append("path")
.attr("id", function(d, i) { return "path-" + i; })
.attr("d", arc)
.attr("fill-rule", "evenodd")
.style("fill", colour)
.on("click", click);
var text = vis.selectAll("text").data(nodes);
var textEnter = text.enter().append("text")
.style("fill-opacity", 1)
.style("fill", function(d) {
return brightness(d3.rgb(colour(d))) < 125 ? "#eee" : "#000";
})
.attr("text-anchor", function(d) {
return x(d.x + d.dx / 2) > Math.PI ? "end" : "start";
})
.attr("dy", ".2em")
.attr("transform", function(d) {
/** Get the text details and define the rotation and general position */
var multiline = (d.name || "").split(" ").length > 1,
angle = x(d.x + d.dx / 2) * 180 / Math.PI - 90,
rotate = angle + (multiline ? -.5 : 0);
return "rotate(" + rotate + ")translate(" + (y(d.y) + p) + ")rotate(" + (angle > 90 ? -180 : 0) + ")";
})
.on("click", click);
/** Managing text - alway maximum two words */
textEnter.append("tspan")
.attr("x", 0)
.text(function(d) { return d.depth ? d.name.split(" ")[0] : ""; });
textEnter.append("tspan")
.attr("x", 0)
.attr("dy", "1em")
.text(function(d) { return d.depth ? d.name.split(" ")[1] || "" : ""; });
function click(d) {
path.transition()
.duration(duration)
.attrTween("d", arcTween(d));
// Somewhat of a hack as we rely on arcTween updating the scales.
text
.style("visibility", function(e) {
return isParentOf(d, e) ? null : d3.select(this).style("visibility");
})
.transition().duration(duration)
.attrTween("text-anchor", function(d) {
return function() {
return x(d.x + d.dx / 2) > Math.PI ? "end" : "start";
};
})
.attrTween("transform", function(d) {
var multiline = (d.name || "").split(" ").length > 1;
return function() {
var angle = x(d.x + d.dx / 2) * 180 / Math.PI - 90,
rotate = angle + (multiline ? -.5 : 0);
return "rotate(" + rotate + ")translate(" + (y(d.y) + p) + ")rotate(" + (angle > 90 ? -180 : 0) + ")";
};
})
.style("fill-opacity", function(e) { return isParentOf(d, e) ? 1 : 1e-6; })
.each("end", function(e) {
d3.select(this).style("visibility", isParentOf(d, e) ? null : "hidden");
});
}
});
/* Returns whether p is parent of c */
function isParentOf(p, c) {
if (p === c) return true;
if (p.children) {
return p.children.some(function(d) {
return isParentOf(d, c);
});
}
return false;
}
/* Generated random colour */
function colour(d) {
if (d.children) {
// There is a maximum of two children!
var colours = d.children.map(colour),
a = d3.hsl(colours[0]),
b = d3.hsl(colours[1]);
// L*a*b* might be better here...
return d3.hsl((a.h + b.h) / 2, a.s * 1.2, a.l / 1.2);
}
return d.colour || "#fff";
}
/* Interpolate the scales! */
function arcTween(d) {
var my = maxY(d),
xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
yd = d3.interpolate(y.domain(), [d.y, my]),
yr = d3.interpolate(y.range(), [d.y ? 20 : 0, r]);
return function(d) {
return function(t) { x.domain(xd(t)); y.domain(yd(t)).range(yr(t)); return arc(d); };
};
}
/* */
function maxY(d) {
return d.children ? Math.max.apply(Math, d.children.map(maxY)) : d.y + d.dy;
}
/* Use a formula for contrasting colour
http://www.w3.org/WAI/ER/WD-AERT/#color-contrast
*/
function brightness(rgb) {
return rgb.r * .299 + rgb.g * .587 + rgb.b * .114;
}

File diff suppressed because one or more lines are too long

@ -282,6 +282,20 @@ class SkillRelUser extends Model {
class Skill extends Model {
var $columns = array('id', 'name','description', 'access_url_id');
var $required = array('name');
/** Array of colours by depth, for the coffee wheel. Each depth has 4 col */
var $colours = array(
0 => array('#f9f0ab', '#ecc099', '#e098b0', '#ebe378'),
1 => array('#d5dda1', '#4a5072', '#8dae43', '#72659d'),
2 => array('#b28647', '#2e6093', '#393e64', '#1e8323'),
3 => array('#9f6652', '#9f6652', '#9f6652', '#9f6652'),
4 => array('#af643c', '#af643c', '#af643c', '#af643c'),
5 => array('#72659d', '#72659d', '#72659d', '#72659d'),
6 => array('#8a6e9e', '#8a6e9e', '#8a6e9e', '#8a6e9e'),
7 => array('#92538c', '#92538c', '#92538c', '#92538c'),
8 => array('#2e6093', '#2e6093', '#2e6093', '#2e6093'),
9 => array('#3a5988', '#3a5988', '#3a5988', '#3a5988'),
10 => array('#393e64', '#393e64', '#393e64', '#393e64'),
);
public function __construct() {
$this->table = Database::get_main_table(TABLE_MAIN_SKILL);
@ -579,4 +593,38 @@ class Skill extends Model {
unset($skills);
return $skills_tree;
}
/**
* Get skills tree as a simplified JSON structure
*
*/
public function get_skills_tree_json($user_id = null, $return_flat_array = false) {
$tree = $this->get_skills_tree($user_id, $return_flat_array);
$simple_tree = array();
foreach ($tree['children'] as $element) {
$simple_tree[] = array('name' => $element['name'], 'children' => $this->get_skill_json($element['children']));
}
//$s = array('\/');
//return str_replace($s,'/',json_encode($simple_tree[0]['children']));
return json_encode($simple_tree[0]['children']);
}
/**
* Get JSON element
*/
public function get_skill_json($subtree, $depth = 1) {
$simple_sub_tree = array();
if (is_array($subtree)) {
foreach ($subtree as $elem) {
$tmp = array();
$tmp['name'] = $elem['name'];//.'<a href="">'.$elem['id'].'</a>';
if (is_array($elem['children'])) {
$tmp['children'] = $this->get_skill_json($elem['children'], $depth+1);
} else {
$tmp['colour'] = $this->colours[$depth][rand(0,3)];
}
$simple_sub_tree[] = $tmp;
}
return $simple_sub_tree;
}
return null;
}
}

@ -0,0 +1,50 @@
<div id="menu" class="well" style="top:20px; left:20px; width:380px; z-index: 9000; opacity: 0.9;">
<h3>{{'Skills'|get_lang}}</h3>
<div class="btn-group">
<a style="z-index: 1000" class="btn" id="add_item_link" href="#">{{'AddSkill'|get_lang}}</a>
<a style="z-index: 1000" class="btn" id="return_to_root" href="#">{{'Root'|get_lang}}</a>
<a style="z-index: 1000" class="btn" id="return_to_admin" href="{{_p.web_main}}admin">{{'BackToAdmin'|get_lang}}</a>
</div>
</div>
<div id="vis"><img src=""></div>
{{ html }}
<!--div id="dialog-form" style="display:none; z-index:9001;">
<p class="validateTips"></p>
<form class="form-horizontal" id="add_item" name="form">
<fieldset>
<input type="hidden" name="id" id="id"/>
<div class="control-group">
<label class="control-label" for="name">{{'Name'|get_lang}}</label>
<div class="controls">
<input type="text" name="name" id="name" size="40" />
</div>
</div>
<div class="control-group">
<label class="control-label" for="name">{{'Parent'|get_lang}}</label>
<div class="controls">
<select id="parent_id" name="parent_id" />
</select>
</div>
</div>
<div class="control-group">
<label class="control-label" for="name">{{'Gradebook'|get_lang}}</label>
<div class="controls">
<select id="gradebook_id" name="gradebook_id[]" multiple="multiple"/>
</select>
<span class="help-block">
{{'WithCertificate'|get_lang}}
</span>
</div>
</div>
<div class="control-group">
<label class="control-label" for="name">{{'Description'|get_lang}}</label>
<div class="controls">
<textarea name="description" id="description" class="span3" rows="7"></textarea>
</div>
</div>
</fieldset>
</form>
</div-->
Loading…
Cancel
Save