I have to develop a tool for automatic conversion from one E-learning standard Content Package to another, as of now I've considered IMS and SCORM but I didn't completely understand what could be the best reason for developing an automatic conversion tool from IMS to SCORM or vice-versa. I mean what benefits can one avail from such tool? This is the reason which I've got in one technical paper. Can anyone please explain and clarify this or tell me some good reason behind developing such a tool:
"IMS [6] and SCORM [7] specications share many of their
characteristics but also have some key differences in the
representation of the structure of the learning content. Other
important issue to be considered is the extension of these systems and
the huge number of organizations that use them. This situation leads
to the fact that the same contents are created, maintained, modied
and so on [8]. These are usually not identical copies, but the
contents are the same and aim to the same learning objective. So it
would be useful to share the work among the organization to avoid
repeating the work over and over again. This would save time and
resources and would lead to high quality contents. The main obstacle
to get this is the, previously presented, existence of different types
of models for contents. From all of this, we can infer the utility of
automatic conversion between formats. This conversion will allow the
interaction between learning platforms at a high level of automa-
tion." - E-LEARNING CONTENTS AUTOMATIC CONVERSION By Guiterrez, Jose Maria et al.
JavaScript(SCORM Package)
<script type="text/javascript">
<![CDATA[
var numQuestions = 2;
var rawScore = 0;
var actualScore = 0;
var question0;
var question1;
var key0 = 0;
var key1 = 1;
function getAnswer()
{
doLMSSetValue("cmi.interactions.0.id","key0b8");
doLMSSetValue("cmi.interactions.0.type","choice");
doLMSSetValue("cmi.interactions.0.correct_responses.0.pattern",
"0");
for (var i=0; i < 2; i++)
{
if (document.getElementById("quizForm8").key0b8[i].checked)
{
question0 = document.getElementById("quizForm8").key0b8[i].value;
doLMSSetValue("cmi.interactions.0.student_response",question0);
break;
}
}
doLMSSetValue("cmi.interactions.1.id","key1b8");
doLMSSetValue("cmi.interactions.1.type","choice");
doLMSSetValue("cmi.interactions.1.correct_responses.0.pattern",
"1");
for (var i=0; i < 2; i++)
{
if (document.getElementById("quizForm8").key1b8[i].checked)
{
question1 = document.getElementById("quizForm8").key1b8[i].value;
doLMSSetValue("cmi.interactions.1.student_response",question1);
break;
}
}
}
function calcRawScore(){
if (question0 == key0)
{
doLMSSetValue("cmi.interactions.0.result","correct");
rawScore++;
}
else
{
doLMSSetValue("cmi.interactions.0.result","wrong");
}
if (question1 == key1)
{
doLMSSetValue("cmi.interactions.1.result","correct");
rawScore++;
}
else
{
doLMSSetValue("cmi.interactions.1.result","wrong");
}
}
function calcScore2()
{
computeTime(); // the student has stopped here.
document.getElementById("quizForm8").submitB.disabled = true;
getAnswer();
calcRawScore();
actualScore = Math.round(rawScore / numQuestions * 100);
alert("Your score is " + actualScore + "%")
doLMSSetValue( "cmi.core.score.raw", actualScore+"" );
var mode = doLMSGetValue( "cmi.core.lesson_mode" );
if ( mode != "review" && mode != "browse" ){
if ( actualScore < 50 )
{
doLMSSetValue( "cmi.core.lesson_status", "failed" );
}
else
{
doLMSSetValue( "cmi.core.lesson_status", "passed" );
}
doLMSSetValue( "cmi.core.exit", "" );
}
exitPageStatus = true;
doLMSCommit();
doLMSFinish();
}
]]>
</script>
HTML
<?xml version="1.0" encoding="utf-8"?>
<html xmlns="http://www.w3.org/1999/xhtml">
<!-- Other Code -->
<body>
<div id="outer">
<div class="QuizTestIdevice" id="id8">
<script src="calculate.js" type="text/javascript"></script>
<form name="quizForm8" id="quizForm8" action="javascript:calcScore2();">
<div class="iDevice_inner">
<div class="passrate" value="50"></div>
<div class="question">
<div id="taquestion0b8">
1> TEXT FOR QUESTION 1.
</div><br />
True<input type="radio" name="key0b8" value="0" id="taoptionAnswer0q0b8" />
False<input type="radio" name="key0b8" value="1" id="taoptionAnswer1q0b8" />
</div><br />
<div class="question">
<div id="taquestion1b8">
2> TEXT FOR QUESTION 2.
</div><br />
True<input type="radio" name="key1b8" value="0" id="taoptionAnswer0q1b8" />
False<input type="radio" name="key1b8" value="1" id="taoptionAnswer1q1b8" />
</div><br />
<input type="submit" name="submitB" value="SUBMIT ANSWERS" />
</div>
</form>
</div>
</div>
</body>
</html>
Javascript and HTML for IMS Package
<html>
<body>
<div class="QuizTestIdevice" id="id8">
<script type="text/javascript">
<!-- //<![CDATA[
var numQuestions = 4;
var rawScore = 0;
var actualScore = 0;
var question0;
var question1;
var key0 = 0;
var key1 = 1;
var key2 = 0;
var key3 = 0;
function getAnswer()
{
for (var i=0; i < 2; i++)
{
if (document.getElementById("quizForm8").key0b8[i].checked)
{
question0 = document.getElementById("quizForm8").key0b8[i].value;
break;
}
}
for (var i=0; i < 2; i++)
{
if (document.getElementById("quizForm8").key1b8[i].checked)
{
question1 = document.getElementById("quizForm8").key1b8[i].value;
break;
}
}
}
function calcRawScore(){
if (question0 == key0)
{
rawScore++;
}
if (question1 == key1)
{
rawScore++;
}
}
function calcScore2()
{
getAnswer();
calcRawScore();
actualScore = Math.round(rawScore / numQuestions * 100);
document.getElementById("quizForm8").submitB.disabled = true;
alert("Your score is " + actualScore + "%")
}
//]]> -->
</script>
<form name="quizForm8" id="quizForm8" action="javascript:calcScore2();">
<div class="iDevice emphasis1">
<img alt="" class="iDevice_icon" src="icon_question.gif" />
<span class="iDeviceTitle">SCORM Quiz</span>
<div class="iDevice_inner">
<div class="passrate" value="50"></div>
<div class="question">
<div id="taquestion0b8" class="block" style="display:block">1> QUESTION 1
</div><br/>
<table><tr><td><input type="radio" name="key0b8" value="0" />
</td><td>
<div id="taoptionAnswer0q0b8" class="block" style="display:block">True
</div></td></tr>
<tr><td><input type="radio" name="key0b8" value="1" />
</td><td>
<div id="taoptionAnswer1q0b8" class="block" style="display:block">False
</div>
<br/><input type="submit" name="submitB" value="SUBMIT ANSWERS"/>
</form>
</body>
</html>
Thanking you!
When I create hand-coded courses I try to keep everything as neutral as possible. eLearning standards, such as SCORM, are usually using what we call a "wrapper", it basically handles all basic errors and the connection process.
When you go from one version to another, like SCORM 1.2 to SCORM 2004 4th Edition, the logic doesn't really change, it is improved but all the previous elements are usually here.
So you basically make a new wrapper that has the same functions' names and all the basics are already working. And your wrapper is not fundamentally different, it just replaces the "cmi.xxx".
To go from one standard to another, if the fundamentals are the same, you could use another wrapper and it would work. It is usually the case for most simple courses, as soon as you start having interactions, exercises and so on, it can become tricky.
To avoid those bad moments, keep all your interactions logic seperate from your course.
Now, your course is probably not hand-coded and generated by a software?
In such case you will need to analyze what's "under the hood", trying to figure what is done when and how to replace it with the other standard's way of handling each part.
I think a converter is something very unsafe to do, it would be very course-dependent and cannot be applied to all courses the same, unless you find a way to simply replace it's interactions with a wrapper. But if you can provide a tool that can generate a course compatible with all standards, you'll already make a lot of users happy!
Now to get back to your question, I think you need to understand what are eLearning standards made of :
Files to be displayed in the browser.
A manifest file to describe the content you're giving to the LMS.
A way of communication between the course and its LMS.
The files are usually HTML pages and everything that goes around. The manifest is an file usually written with XML. The communication is in most cases setup with Javascript.
IMS is a standard at the manifest level, it describes what should be in your .xml file.
SCORM is a standard that takes into account the two last items of the list. (based on AICC, it defines what both the course and the LMS need to comply to.)
Now if you need to create a tool to convert the XML file, I think any technology would be able to do it, as it is technically just plain text.
Related
I have a form with an upload field that allows users to select multiple files. However, I need to be able to allow the user to select file 1 from folder 1, then go and select file 2 from folder 2, and so on.
Currently, when the user selects file 1 from folder 1 then hits "Open", the selection window closes (leaving the user on my form). Then if the user goes and select file 2 from folder 2 and hits the "Open" button, file 1 is removed, leaving only file 2.
Basically, the user is unable to select multiple files unless they're all in the same location. Is there a way to make file 1 stay selected after file 2 is chosen?
How about this?
The solution uses HTML, jQuery/Javascript, and PHP (for server-side handling of this data). The idea is: 1.) HTML Form: Includes one "browse" button that allows the user to select multiple files (within one directory). 2.) jQuery: The option to create a new button in the form that allows users to select multiple files (within a different directory -- or even the same one actually!), with the ability to create new buttons "infinitely". 3.) PHP: As a bonus, I put some thought into packaging the data nicely for server-side handling.
Here is what the HTML form could look like (I used a found-icon for the clickable object, but you can easily replace it with a graphic of your choosing).
<form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>" enctype='multipart/form-data'>
Select files: <br/>
<input type='file' name='files0[]' id="files0" multiple><br/><br/><br/>
<span style="font-size: 10pt;">Click "+" for more files
<i id="more_files" class="general foundicon-plus" style="color: blue;cursor: pointer;"></i></span>
<br/><br/><br/>
<input type="submit" name="submit" value="Submit">
</form>
Here is the jQuery/Javascript to create a new "browse" button once the event is triggered (this even places it after the LAST "browse" button!):
<script type="text/javascript">
//jQuery
$(document).ready(function() {
$(document).on('click','#more_files', function() {
var numOfInputs = 1;
while($('#files'+numOfInputs).length) { numOfInputs++; }//once this loop breaks, numOfInputs is greater than the # of browse buttons
$("<input type='file' multiple/>")
.attr("id", "files"+numOfInputs)
.attr("name", "files"+numOfInputs+"[]")
.insertAfter("#files"+(numOfInputs-1));
$("<br/>").insertBefore("#files"+numOfInputs);
});
});
</script>
<script>
//vanilla javascript version
var location = document.getElementById("fileBrowsers");
var br = document.createElement("BR");
location.appendChild(br);
var input = document.createElement("input");
input.type = "file";
input.name = "files"+numOfInputs+"[]";
input.id = "files"+numOfInputs;
input.multiple = true;
location.appendChild(input);
</script>
Finally, and possibly most important, how to wrap up the data on the server in a familiar format:
<?php
if(isset($_POST['submit']) && !empty($_FILES)) {
$files = array();
$files = $_FILES['files0'];
//var_dump($files);//this array will match the structure of $_FILES['browser']
//Iterate through each browser button
$browserIterator = 1;
while(isset($_FILES['files'.$browserIterator])) {
//Files have same attribute structure, so grab each attribute and append data for each attribute from each file
foreach($_FILES['files'.$browserIterator] as $attr => $values) {//get each attribute
foreach($_FILES['files'.$browserIterator][$attr] as $fileValue) {//get each value from attribute
$files[$attr][] = $fileValue;//append value
}
}
$browserIterator++;
}
//Use $files like you would use $_FILES['browser'] -- It is as though all files came from one browser button!
$fileIterator = 0;
while($fileIterator < count($files['name'])) {
echo $files['name'][$fileIterator]."<br/>";
$fileIterator++;
}
}
?>
Update Note: jQuery script and vanilla Javascript accomplish the same goal. I ran into an issue that required the vanilla version. You only need one of them.
No, you can't. This is a behaviour defined by the operating systems and may vary between them. You can't control these things precisly and you will always fear what happen.
If the amount of folders people have to choose is quite small you could offer multiple upload fields.
Another solution is using old school (non-multiple) file inputs. In this case you cannot select multiple files to upload, but you can remove any file and add another. Initially there is only one file input on page, but when you select a file, it hiding and replacing by filename with delete button, and new file input appears.
var fileInput = document.getElementById('fileInput_0');
var filesList = document.getElementById('fileList');
var idBase = "fileInput_";
var idCount = 0;
var inputFileOnChange = function() {
var existingLabel = this.parentNode.getElementsByTagName("LABEL")[0];
var isLastInput = existingLabel.childNodes.length<=1;
if(!this.files[0]) {
if(!isLastInput) {
this.parentNode.parentNode.removeChild(this.parentNode);
}
return;
}
var filename = this.files[0].name;
var deleteButton = document.createElement('span');
deleteButton.innerHTML = '×';
deleteButton.onclick = function(e) {
this.parentNode.parentNode.parentNode.removeChild(this.parentNode.parentNode);
}
var filenameCont = document.createElement('span');
filenameCont.innerHTML = filename;
existingLabel.innerHTML = "";
existingLabel.appendChild(filenameCont);
existingLabel.appendChild(deleteButton);
if(isLastInput) {
var newFileInput=document.createElement('input');
newFileInput.type="file";
newFileInput.name="file[]";
newFileInput.id=idBase + (++idCount);
newFileInput.onchange=inputFileOnChange;
var newLabel=document.createElement('label');
newLabel.htmlFor = newFileInput.id;
newLabel.innerHTML = '+';
var newDiv=document.createElement('div');
newDiv.appendChild(newFileInput);
newDiv.appendChild(newLabel);
filesList.appendChild(newDiv);
}
}
fileInput.onchange=inputFileOnChange;
#fileList > div > label > span:last-child {
color: red;
display: inline-block;
margin-left: 7px;
cursor: pointer;
}
#fileList input[type=file] {
display: none;
}
#fileList > div:last-child > label {
display: inline-block;
width: 23px;
height: 23px;
font: 16px/22px Tahoma;
color: orange;
text-align: center;
border: 2px solid orange;
border-radius: 50%;
}
<form enctype="multipart/form-data" method="post">
<div id="fileList">
<div>
<input id="fileInput_0" type="file" name="file[]" />
<label for="fileInput_0">+</label>
</div>
</div>
</form>
document.querySelector("input").addEventListener("change", list_files);
function list_files() {
var files = this.files;
for (var i = 0; i < files.length; i++) {
console.log(files[i].name);
}
}
<input type="file" multiple>
Just wanted to share: If you are in a situation where there are no classes within a certain section and the value of that particular div cannot be hardcoded and changes, using protractor e2e framework I have managed to locate the specific div using this method:
Adding an example of html that does not have a class for every element
<div class="row">
<div class="page_banner">A Dude's Profile</div>
<div class="profile_details">
<div class="profile_name">
<h3>Tony Adams</h3>
</div>
<div>ta#bogus.com</div>
<div>0883424324</div>
</div>
</div>
In the case where you need to say identify that there is a unique mobile number, so the value is not consistent.
function mobileNumberAssert() {
element.all(by.css('.profile_details'))
.get(1) // number of divs in css
.getText()
.then(function(textFoundInCss) {
if(textFoundInCss > 10) {
return true;
console.log('there is a mobile number present with 10 digits');
} else {
return false;
}
});
}
For Debugging you can console log the "textFoundInCss" working your way to locate that particular div.
Since it seems like you are looking for an element based on the text you could use by.cssContainingText() to make this a lot easier.
const el = element(by.cssContainingText('div', `Text I'm looking for`);
el.isPresent().then(isPresent => {
if (isPresent) {
// do something ...
} else {
return false;
}
}
Is it possible to bind a state (attribute) of a paper-checkbox [checked|unchecked] dynamically to an attribute like [readonly|disabled] inside a paper-input element? This is my implementation so far:
<template repeat="{{item in lphasen}}">
<div center horizontal layout>
<paper-checkbox unchecked on-change="{{checkStateChanged}}" id="{{item.index}}"></paper-checkbox>
<div style="margin-left: 24px;" flex>
<h4>{{item.name}}</h4>
</div>
<div class="container"><paper-input disabled floatingLabel id="{{item.index}}" label="LABEL2" value="{{item.percent}}" style="width: 120px;"></paper-input></div>
</div>
</template>
The behavior should be as follow:
When the user uncheck a paper-checkbox, then the paper-input element in the same row should be disabled and/or readonly and vice versa. Is it possible to directly bind multiple elements with double-mustache or do I have to iterate the DOM somehow to manually set the attribute on the paper-input element? If YES, could someone explain how?
Another way to bind the checked state of the paper-checkbox.
<polymer-element name="check-input">
<template>
<style>
#checkbox {
margin-left: 1em;
}
</style>
<div center horizontal layout>
<div><paper-input floatingLabel label="{{xlabel}}" value="{{xvalue}}" disabled="{{!xenable}}" type="number" min="15" max="200"></paper-input></div>
<div><paper-checkbox id="checkbox" label="Enable" checked="{{xenable}}"></paper-checkbox></div>
</div>
</template>
<script>
Polymer('check-input', {
publish:{xenable:true, xvalue:'',xlabel:''}
});
</script>
</polymer-element>
<div>
<check-input xenable="true" xvalue="100" xlabel="Weight.1"></check-input>
<check-input xenable="false" xvalue="185" xlabel="Weight.2"></check-input>
</div>
jsbin demo http://jsbin.com/boxow/
My preferred approach would be to refactor the code to create a Polymer element responsible for one item. That way, all of the item specific behaviour is encapsulated in one place.
Once that is done, there are a couple ways of doing this.
The easiest would be to simply create an on-tap event for the check box that toggles the value of a property and sets the disabled attribute accordingly.
<paper-checkbox unchecked on-tap="{{checkChanged}}"></paper-checkbox>
//Other markup for item name display
<paper-input disabled floatingLabel id="contextRelevantName" style="width:120 px;"></paper-input>
One of the benefits of putting this into it's own polymer element is that you don't have to worry about unique id's anymore. The control id's are obfuscated by the shadowDOM.
For the scripting, you would do something like this:
publish: {
disabled: {
value: true,
reflect: false
}
}
checkChanged: function() {
this.$.disabled= !this.$.disabled;
this.$.contextRelevantName.disabled = this.$.disabled;
}
I haven't tested this, so there might be some tweaks to syntax and what have you, but this should get you most of the way there.
Edit
Based on the example code provided in your comment below, I've modified your code to get it working. The key is to make 1 element that contains an either row, not multiple elements that contain only parts of the whole. so, the code below has been stripped down a little bit to only include the check box and the input it is supposed to disable. You can easily add more to the element for other parts of your item displayed.
<polymer-element name="aw-leistungsphase" layout vertical attributes="label checked defVal contractedVal">
<template>
<div center horizontal layout>
<div>
<paper-checkbox checked on-tap="{{checkChanged}}" id="checkbox" label="{{label}}"></paper-checkbox>
</div>
<div class="container"><paper-input floatingLabel id="contractedInput" label="Enter Value" value="" style="width: 120px;"></paper-input></div>
</div>
</template>
<script>
Polymer('aw-leistungsphase', {
publish: {
/**
* The label for this input. It normally appears as grey text inside
* the text input and disappears once the user enters text.
*
* #attribute label
* #type string
* #default ''
*/
label: '',
defVal : 0,
contractedVal : 0
},
ready: function() {
// Binding the project to the data-fields
this.prj = au.app.prj;
// i18n mappings
this.i18ndefLPHLabel = au.xlate.xlate("hb.defLPHLabel");
this.i18ncontractedLPHLabel = au.xlate.xlate("hb.contractedLPHLabel");
},
observe : {
'contractedVal' : 'changedLPH'
},
changedLPH: function(oldVal, newVal) {
if (oldVal !== newVal) {
//this.prj.hb.honlbl = newVal;
console.log("Geänderter Wert: " + newVal);
}
},
checkChanged: function(e, detail, sender) {
console.log(sender.label + " " + sender.checked);
if (!this.$.checkbox.checked) {
this.$.contractedInput.disabled = 'disabled';
}
else {
this.$.contractedInput.disabled = '';
}
console.log("Input field disabled: " + this.$.contractedInput.disabled);
}
});
</script>
</polymer-element>
Hi I have the following html:
<div id="div1">
<input id="input1" data-bind="value: inputIn, valueUpdate: 'afterKeydown'" >
// ... more stuff between
</div>
<div id="div2">
<input id="input2" data-bind="value: inputOut">
// .... more stuff between
</div>
My model is this:
function MyModel() {
"use strict";
this.inputIn = ko.observable("");
this.inputOut = ko.computed(function() {
return transformOutput(this.inputIn());
}
}
Applying the bindings:
var myModel = new MyModel();
ko.applyBindings(model, document.getElementById("div2"));
ko.applyBindings(model, document.getElementById("div1"));
I want changes in input 1 to effect input 2. What is wrong? Is their way around this without changing the html. The element are in two different divs and I want the knockout binding to apply to be apply across cross root domain.
Your problem isn't with how you are binding it (although I don't think it's worth the trouble). Your problem is that this isn't what you think it is in the computed property (it's actually window).
Try this:
function MyModel() {
var self = this;
this.inputIn = ko.observable("");
this.inputOut = ko.computed(function () {
return transformOutput(self.inputIn());
});
}
Here's a simple fiddle
Edited a bit to try and explain this better
I have a problem in which i'm not sure how to even start to go about solving. It's also very hard to explain.
Basically, I have a storage location in Firebase that looks like this:
(in this image it only shows one storage location. later there is another with a title of 'Test' and a body of 'As you can see, this messes up')
What I want to do is have it so that when I use this code:
html file:
<div class="span8" id="verticalLine">
<h2 id="main-header"><center>Latest news</center></h2>
<div id="newsDivHead"></div>
<br/>
<div id="newsDiv"></div>
<script>
var newsData = new Firebase("https://agn.firebaseio.com/web/news/mc/")
newsData.limit(10).on('child_added', function (snapshot) {
var message = snapshot.val();
$('<div/>').text(message.body).appendTo($('#newsDiv'));
$('<div/>').text(message.title).appendTo($('#newsDivHead'));
$('#newsDiv')[0].scrollTop = $('#newsDiv')[0].scrollHeight;
});
</script>
<!---
$('<div/>').text(message.body).prepend($('<em/>')
.text(message.title+': ')).appendTo($('#newsDiv'));
$('#newsDiv')[0].scrollTop = $('#newsDiv')[0].scrollHeight;
});
--->
</div>
</div>
I want it to give a result like this:
Notice the formatting of the title and the break.
But unfortunately it comes out like this:
What I want to happen is it formats the value stored in message.title so that it appears above the message.body and is formatted in the format (or something similar). I have realised that this is not possible using the two div tags, I can only use the one.
So what do I do? Is this even possible? The title and text are in the 'varibles' (firebase ref) message.title and message.body respectivley (As you can see in the code.)
Any Help gladly appreciated!
This doesn't have anything to do with Firebase, but is rather a HTML issue. You have two divs in your HTML, one to show titles and another to show content. Don't do this and use CSS classes instead of visually separate titles from text.
<div class="span8" id="verticalLine">
<h2 id="main-header"><center>Latest news</center></h2>
<div id="content">
</div>
</div>
var newsData = new Firebase("https://agn.firebaseio.com/web/news/mc/")
newsData.limit(10).on('child_added', function (snapshot) {
var message = snapshot.val();
var body = $('<div/>').class('body').text(message.body);
var title = $('<div/>').class('title').text(message.title);
$('<div/>').append(title).append(body).appendTo($('#content'));
});
In your CSS, set the styles for the classes 'body' and 'title' as you do currently for your 'newsDiv' and 'newsDivHead' IDs:
.body {
font-size: 12px;
}
.title {
font-size: 18px;
font-weight: bold;
}