<?php

/**
 *
 * @package    mahara
 * @subpackage core
 * @author     Catalyst IT Ltd
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL version 3 or later
 * @copyright  For copyright information on Mahara, please see the README file distributed with this software.
 *
 */
define('INTERNAL', 1);
define('JSON', 1);
require dirname(dirname(__FILE__)) . '/init.php';
require_once 'view.php';
require_once 'form/elements/artefactchooser.php';
$extradata = json_decode(param_variable('extradata'));
safe_require('blocktype', $extradata->blocktype);
$data = pieform_element_artefactchooser_set_attributes(call_static_method(generate_class_name('blocktype', $extradata->blocktype), 'artefactchooser_element', $extradata->value));
$data['offset'] = param_integer('offset', 0);
list($html, $pagination, $count, $offset, $artefactdata) = View::build_artefactchooser_data($data, $extradata->group, $extradata->institution);
json_reply(false, array('message' => null, 'data' => array('tablerows' => $html, 'pagination' => $pagination['html'], 'pagination_js' => $pagination['javascript'], 'count' => $count, 'results' => $count . ' ' . ($count == 1 ? get_string('result') : get_string('results')), 'offset' => $offset, 'artefactdata' => $artefactdata)));
Esempio n. 2
0
/**
 * Extension by Mahara. This api function returns the javascript required to
 * set up the element, assuming the element has been placed in the page using
 * javascript. This feature is used in the views interface.
 *
 * In theory, this could go upstream to pieforms itself
 *
 * @param Pieform $form     The form
 * @param array   $element  The element
 */
function pieform_element_artefactchooser_views_js(Pieform $form, $element)
{
    global $pagination_js;
    // NOTE: $element['name'] is not set properly at this point
    $element = pieform_element_artefactchooser_set_attributes($element);
    $element['name'] = !empty($element['selectone']) ? 'artefactid' : 'artefactids';
    $pagination_js = 'var p = ' . $pagination_js;
    // TODO: This is quite a lot of javascript to be sending inline, especially the ArtefactChooserData
    // class.
    if (!empty($element['selectone'])) {
        $artefactchooserdata = '';
        $artefactchooserselect = empty($element['selectjscallback']) ? '' : 'new ArtefactChooserSelect(data.data.artefactdata);';
    } else {
        $artefactchooserdata = 'new ArtefactChooserData();';
        $artefactchooserselect = '';
    }
    $pagination_js .= <<<EOF
var ul = getFirstElementByTagAndClassName('ul', 'artefactchooser-tabs', '{$form->get_name()}_{$element['name']}_container');
var doneBrowse = false;
var browseA = null;
var searchA = null;
var browseTabCurrent = true;
if (ul) {
    forEach(getElementsByTagAndClassName('a', null, ul), function(a) {
        p.rewritePaginatorLink(a);
        // Need to make sure the accessible hidden <span> is present
        // If loaded via ajax it may not be present
        if (a.childNodes.length < 2) {
            jQuery(a).append('<span class="sr-only">(' + get_string_ajax('tab', 'mahara') + ')</span>');
        }

        if (!doneBrowse) {
            browseA = a;
            jQuery(browseA).find('.sr-only').html('(' + get_string_ajax('tab', 'mahara') + ' ' + get_string_ajax('selected', 'mahara') + ')');
            doneBrowse = true;

            // Hide the search form
            connect(a, 'onclick', function(e) {
                addElementClass('artefactchooser-searchform', 'hidden');
                removeElementClass(searchA.parentNode, 'active');
                jQuery(browseA).find('.sr-only').html('(' + get_string_ajax('tab', 'mahara') + ' ' + get_string_ajax('selected', 'mahara') + ')');
                jQuery(searchA).find('.sr-only').html('(' + get_string_ajax('tab', 'mahara') + ')');
                addElementClass(browseA.parentNode, 'active');
                browseA.blur();
                \$('artefactchooser-searchfield').value = ''; // forget the search for now, easier than making the tabs remember it
                if (!browseTabCurrent) {
                    {$artefactchooserdata}
                    browseTabCurrent = true;
                }
                e.stop();
            });
        }
        else {
            searchA = a;

            // Display the search form
            connect(a, 'onclick', function(e) {
                showElement('artefactchooser-searchform');
                removeElementClass('artefactchooser-searchform', 'hidden');
                removeElementClass(browseA.parentNode, 'active');
                jQuery(searchA).find('.sr-only').html('(' + get_string_ajax('tab', 'mahara') + ' ' + get_string_ajax('selected', 'mahara') + ')');
                jQuery(browseA).find('.sr-only').html('(' + get_string_ajax('tab', 'mahara') + ')');
                addElementClass(searchA.parentNode, 'active');

                connect('artefactchooser-searchfield', 'onkeypress', function(e) {
                    if (e.key().code == 13) { // enter pressed - submitting form
                        e.stop();
                        signal('artefactchooser-searchsubmit', 'onclick', true);
                    }
                });

                // Wire up the search button
                connect('artefactchooser-searchsubmit', 'onclick', function(e) {
                    if (e._event != true) {
                        e.stop();
                    }

                    var loc = searchA.href.indexOf('?');
                    var queryData = [];
                    if (loc != -1) {
                        queryData = parseQueryString(searchA.href.substring(loc + 1, searchA.href.length));
                        queryData.extradata = serializeJSON(p.extraData);
                        // need to do this old school as the mochikit js is being called on a jquery page
                        queryData.search = document.getElementById('artefactchooser-searchfield').value;
                    }

                    sendjsonrequest(p.jsonScript, queryData, 'GET', function(data) {
                        // Use pagination.js to update search results
                        p.updateResults(data);
                        {$artefactchooserdata}
                        {$artefactchooserselect}
                    });
                });
                \$('artefactchooser-searchfield').focus();
                if (browseTabCurrent) {
                    {$artefactchooserdata}
                    browseTabCurrent = false;
                }
                e.stop();
            });
        }
    });
}
EOF;
    if (!empty($element['selectone']) && !empty($element['selectjscallback'])) {
        $datatable = $element['name'] . '_data';
        $pagination_js .= <<<EOF
/**
 * Call the selectjscallback function whenever a radio button is clicked
 */
function ArtefactChooserSelect(artefacts) {
    var self = this;

    this.artefacts = artefacts;

    this.init = function() {
        self.connectPagination();
        self.connectRadios();
    }

    /**
     * Connects pagination so that when a page is changed, we are told about it
     */
    this.connectPagination = function() {
        paginatorProxy.addObserver(self);
        connect(self, 'pagechanged', self.pageChanged);
    }

    /**
     * Update artefact data & connect radios to the selectjscallback
     */
    this.pageChanged = function(data) {
        self.artefacts = data.artefactdata;
        self.connectRadios(data);
    }

    this.connectRadios = function(data) {
        forEach(getElementsByTagAndClassName('input', null, '{$datatable}'), function(radio) {
            connect(radio, 'onclick', function() {
                if (self.artefacts[radio.value]) {
                    {$element['selectjscallback']}(self.artefacts[radio.value]);
                }
            });
        });
    }

    self.init();
}

// reattach listeners when page has finished updating
jQuery(window).on('pageupdated', {}, function(e, data) {
    new ArtefactChooserSelect(data.data.artefactdata);
});

new ArtefactChooserSelect(acSelectArtefacts);

EOF;
    }
    if (empty($element['selectone'])) {
        $pagination_js .= <<<EOF
/**
 * Manages the problem of changing pages in the artefact chooser losing what
 * things were selected/not selected
 */
function ArtefactChooserData() {
    var self = this;

    this.init = function() {
        self.insertElementContainers();
        self.connectPagination();
        self.connectCheckboxes();
        self.scrapeForOnpage();
        self.scrapeForSelected();
    }

    /**
     * Puts two containers into the DOM, that will each contain hidden form elements
     * - one for all the elements on the current page of results, and one for
     * the currently selected options.
     *
     * Clears out existing containers instead of making new ones, if containers
     * already exist. This happens when changing tabs on the artefact chooser
     */
    this.insertElementContainers = function() {
        self.seenElementsContainer     = \$('seen-elements-container');
        self.selectedElementsContainer = \$('selected-elements-container');

        if (self.seenElementsContainer) {
            // Clear out the list of seen elements
            replaceChildNodes(self.seenElementsContainer);
        }
        else {
            self.seenElementsContainer = DIV({'id': 'seen-elements-container', 'style': 'display: none;'});
            insertSiblingNodesAfter('artefactchooser-body', self.seenElementsContainer);
        }

        if (self.selectedElementsContainer) {
            // Clear out the list of selected elements
            replaceChildNodes(self.selectedElementsContainer);
        }
        else {
            self.selectedElementsContainer = DIV({'id': 'selected-elements-container', 'style': 'display: none;'});
            insertSiblingNodesAfter('artefactchooser-body', self.selectedElementsContainer);
        }
    }

    /**
     * Connects pagination so that when a page is changed, we are told about it
     */
    this.connectPagination = function() {
        paginatorProxy.addObserver(self);
        connect(self, 'pagechanged', self.pageChanged);
    }

    /**
     * Connects checkboxes so when they're clicked we can deal with it
     */
    this.connectCheckboxes = function() {
        forEach(getElementsByTagAndClassName('input', 'artefactid-checkbox', 'artefactchooser-body'), function(checkBox) {
            connect(checkBox, 'onclick', partial(self.checkboxClicked, checkBox));
        });
    }

    /**
     * Find all hidden onpage inputs, and move them to the container, otherwise
     * destroy them if they're already in there (which happens if we go to a
     * page we've already seen)
     */
    this.scrapeForOnpage = function() {
        forEach(getElementsByTagAndClassName('input', 'artefactid-onpage', 'artefactchooser-body'), function(i) {
            var append = true;
            forEach(self.seenElementsContainer.childNodes, function(seen) {
                if (seen.value == i.value) {
                    append = false;
                    throw MochiKit.Iter.StopIteration;
                }
            });
            if (append) {
                appendChildNodes(self.seenElementsContainer, i);
            }
            else {
                // Element is surplus to requirements
                removeElement(i);
            }
        });
    }

    /**
     * Find all hidden currently selected inputs, and move them to the selected container
     */
    this.scrapeForSelected = function() {
        forEach(getElementsByTagAndClassName('input', 'artefactid-checkbox', 'artefactchooser-body'), function(i) {
            if (i.checked) {
                self.ensureSelectedElement(i);
            }
        });
    }

    /**
     * When a checkbox is clicked, update the list of selected inputs
     */
    this.checkboxClicked = function(checkbox) {
        if (checkbox.checked) {
            // Add to the list if it's not there
            self.ensureSelectedElement(checkbox);
        }
        else {
            // Remove from the list if it's there
            self.removeSelectedElement(checkbox);
        }
    }

    /**
     * When a pagination link is clicked, update the list of seen inputs
     */
    this.pageChanged = function(data) {
        self.scrapeForOnpage();
        if (findValue(self.seenOffsets, data.offset) == -1) {
            self.scrapeForSelected();
            self.seenOffsets.push(data.offset);
        }
        else {
            self.syncroniseCheckboxStateFromContainer();
        }
        self.connectCheckboxes();
    }

    /**
     * Ensures that the element we have been given is in the list of selected
     * elements
     */
    this.ensureSelectedElement = function(element) {
        var append = true;
        forEach(self.selectedElementsContainer.childNodes, function(selected) {
            if (selected.name == element.name) {
                append = false;
                throw MochiKit.Iter.StopIteration;
            }
        });

        if (append) {
            appendChildNodes(self.selectedElementsContainer,
                INPUT({'type': 'hidden', 'name': element.name, 'value': 1})
            );
        }
    }

    /**
     * Ensures that the element we have been given is NOT in the list of
     * selected elements
     */
    this.removeSelectedElement = function(element) {
        forEach(self.selectedElementsContainer.childNodes, function(selected) {
            if (selected.name == element.name) {
                removeElement(selected);
                throw MochiKit.Iter.StopIteration;
            }
        });
    }

    /**
     * Called when the user browses back to a page they have already seen. They
     * may have added/removed what they have checked on that page, so we need
     * to syncronise the display with their choices
     */
    this.syncroniseCheckboxStateFromContainer = function() {
        forEach(getElementsByTagAndClassName('input', 'artefactid-checkbox', 'artefactchooser-body'), function(checkbox) {
            checkbox.checked = false;
            forEach(self.selectedElementsContainer.childNodes, function(selected) {
                if (selected.name == checkbox.name) {
                    // Checkbox should be checked
                    checkbox.checked = true;
                    throw MochiKit.Iter.StopIteration;
                }
            });
        });
    }

    // Contains hidden elements representing every artefact we have seen,
    // regardless of whether it has been selected
    this.seenElementsContainer = null;

    // Contains hidden elements representing every artefact that has been
    // selected on any page we have seen
    this.selectedElementsContainer = null;

    // Pagination offsets we have already seen. We have always seen offset 0
    // when we begin.
    this.seenOffsets = [0];

    self.init();
}

new ArtefactChooserData();

EOF;
    }
    return $pagination_js;
}
/**
 * Extension by Mahara. This api function returns the javascript required to 
 * set up the element, assuming the element has been placed in the page using 
 * javascript. This feature is used in the views interface.
 *
 * In theory, this could go upstream to pieforms itself
 *
 * @param Pieform $form     The form
 * @param array   $element  The element
 */
function pieform_element_artefactchooser_views_js(Pieform $form, $element)
{
    global $pagination_js;
    // NOTE: $element['name'] is not set properly at this point
    $element = pieform_element_artefactchooser_set_attributes($element);
    $element['name'] = !empty($element['selectone']) ? 'artefactid' : 'artefactids';
    $pagination_js = 'var p = ' . $pagination_js;
    // TODO: This is quite a lot of javascript to be sending inline, especially the ArtefactChooserData
    // class.
    if (!empty($element['selectone'])) {
        $artefactchooserdata = '';
    } else {
        $artefactchooserdata = 'new ArtefactChooserData();';
    }
    $pagination_js .= <<<EOF
var ul = getFirstElementByTagAndClassName('ul', 'artefactchooser-tabs', '{$form->get_name()}_{$element['name']}_container');
var doneBrowse = false;
var browseA = null;
var searchA = null;
var browseTabCurrent = true;
if (ul) {
    forEach(getElementsByTagAndClassName('a', null, ul), function(a) {
        p.rewritePaginatorLink(a);
        if (!doneBrowse) {
            doneBrowse = true;

            browseA = a;
            // Hide the search form
            connect(a, 'onclick', function(e) {
                hideElement('artefactchooser-searchform');
                removeElementClass(searchA.parentNode, 'current');
                addElementClass(browseA.parentNode, 'current');
                browseA.blur();
                \$('artefactchooser-searchfield').value = ''; // forget the search for now, easier than making the tabs remember it
                if (!browseTabCurrent) {
                    {$artefactchooserdata}
                    browseTabCurrent = true;
                }
                e.stop();
            });
        }
        else {
            searchA = a;

            // Display the search form
            connect(a, 'onclick', function(e) {
                showElement('artefactchooser-searchform');
                removeElementClass(browseA.parentNode, 'current');
                addElementClass(searchA.parentNode, 'current');
                \$('artefactchooser-searchfield').focus();
                if (browseTabCurrent) {
                    {$artefactchooserdata}
                    browseTabCurrent = false;
                }
                e.stop();
            });

            connect('artefactchooser-searchfield', 'onkeypress', function(e) {
                if (e.key().code == 13) { // enter pressed - submitting form
                    e.stop();
                    signal('artefactchooser-searchsubmit', 'onclick', true);
                }
            });

            // Wire up the search button
            connect('artefactchooser-searchsubmit', 'onclick', function(e) {
                if (e._event != true) {
                    e.stop();
                }

                var loc = searchA.href.indexOf('?');
                var queryData = [];
                if (loc != -1) {
                    queryData = parseQueryString(searchA.href.substring(loc + 1, searchA.href.length));
                    queryData.extradata = serializeJSON(p.extraData);
                    queryData.search = \$('artefactchooser-searchfield').value;
                }

                sendjsonrequest(p.jsonScript, queryData, 'GET', function(data) {
                    var tbody = getFirstElementByTagAndClassName('tbody', null, p.datatable);
                    if (tbody) {
                        if (
                            (document.all && document.documentElement && typeof(document.documentElement.style.maxHeight) != "undefined" && !window.opera)
                            ||
                            (/Konqueror|AppleWebKit|KHTML/.test(navigator.userAgent))) {
                            var temp = DIV({'id':'ie-workaround'});
                            temp.innerHTML = '<table><tbody>' + data.data.tablerows + '</tbody></table>';
                            swapDOM(tbody, temp.childNodes[0].childNodes[0]);
                            removeElement(temp);
                        }
                        else {
                            // This does not work in IE and Konqueror, the tbody 
                            // innerHTML property is readonly.
                            // http://www.ericvasilik.com/2006/07/code-karma.html
                            tbody.innerHTML = data['data']['tablerows'];
                        }
                    }

                    {$artefactchooserdata}

                    // Update the pagination
                    if (\$(p.id)) {
                        var tmp = DIV();
                        tmp.innerHTML = data['data']['pagination'];
                        swapDOM(p.id, tmp.firstChild);

                        // Run the pagination js to make it live
                        eval(data['data']['pagination_js']);

                        // Update the result count
                        var results = getFirstElementByTagAndClassName('div', 'results', p.id);
                        if (results) {
                            results.innerHTML = data['data']['results'];
                        }
                    }
                });
            });
        }
    });
}
EOF;
    if (empty($element['selectone'])) {
        $pagination_js .= <<<EOF
/**
 * Manages the problem of changing pages in the artefact chooser losing what 
 * things were selected/not selected
 */
function ArtefactChooserData() {
    var self = this;

    this.init = function() {
        self.insertElementContainers();
        self.connectPagination();
        self.connectCheckboxes();
        self.scrapeForOnpage();
        self.scrapeForSelected();
    }

    /**
     * Puts two containers into the DOM, that will each contain hidden form elements 
     * - one for all the elements on the current page of results, and one for 
     * the currently selected options.
     *
     * Clears out existing containers instead of making new ones, if containers 
     * already exist. This happens when changing tabs on the artefact chooser
     */
    this.insertElementContainers = function() {
        self.seenElementsContainer     = \$('seen-elements-container');
        self.selectedElementsContainer = \$('selected-elements-container');

        if (self.seenElementsContainer) {
            // Clear out the list of seen elements
            replaceChildNodes(self.seenElementsContainer);
        }
        else {
            self.seenElementsContainer = DIV({'id': 'seen-elements-container', 'style': 'display: none;'});
            insertSiblingNodesAfter('artefactchooser-body', self.seenElementsContainer);
        }

        if (self.selectedElementsContainer) {
            // Clear out the list of selected elements
            replaceChildNodes(self.selectedElementsContainer);
        }
        else {
            self.selectedElementsContainer = DIV({'id': 'selected-elements-container', 'style': 'display: none;'});
            insertSiblingNodesAfter('artefactchooser-body', self.selectedElementsContainer);
        }
    }

    /**
     * Connects pagination so that when a page is changed, we are told about it
     */
    this.connectPagination = function() {
        paginatorProxy.addObserver(self);
        connect(self, 'pagechanged', self.pageChanged);
    }

    /**
     * Connects checkboxes so when they're clicked we can deal with it
     */
    this.connectCheckboxes = function() {
        forEach(getElementsByTagAndClassName('input', 'artefactid-checkbox', 'artefactchooser-body'), function(checkBox) {
            connect(checkBox, 'onclick', partial(self.checkboxClicked, checkBox));
        });
    }

    /**
     * Find all hidden onpage inputs, and move them to the container, otherwise 
     * destroy them if they're already in there (which happens if we go to a 
     * page we've already seen)
     */
    this.scrapeForOnpage = function() {
        forEach(getElementsByTagAndClassName('input', 'artefactid-onpage', 'artefactchooser-body'), function(i) {
            var append = true;
            forEach(self.seenElementsContainer.childNodes, function(seen) {
                if (seen.value == i.value) {
                    append = false;
                    throw MochiKit.Iter.StopIteration;
                }
            });
            if (append) {
                appendChildNodes(self.seenElementsContainer, i);
            }
            else {
                // Element is surplus to requirements
                removeElement(i);
            }
        });
    }

    /**
     * Find all hidden currently selected inputs, and move them to the selected container
     */
    this.scrapeForSelected = function() {
        forEach(getElementsByTagAndClassName('input', 'artefactid-checkbox', 'artefactchooser-body'), function(i) {
            if (i.checked) {
                self.ensureSelectedElement(i);
            }
        });
    }

    /**
     * When a checkbox is clicked, update the list of selected inputs
     */
    this.checkboxClicked = function(checkbox) {
        if (checkbox.checked) {
            // Add to the list if it's not there
            self.ensureSelectedElement(checkbox);
        }
        else {
            // Remove from the list if it's there
            self.removeSelectedElement(checkbox);
        }
    }

    /**
     * When a pagination link is clicked, update the list of seen inputs
     */
    this.pageChanged = function(data) {
        self.scrapeForOnpage();
        if (findValue(self.seenOffsets, data.offset) == -1) {
            self.scrapeForSelected();
            self.seenOffsets.push(data.offset);
        }
        else {
            self.syncroniseCheckboxStateFromContainer();
        }
        self.connectCheckboxes();
    }

    /**
     * Ensures that the element we have been given is in the list of selected 
     * elements
     */
    this.ensureSelectedElement = function(element) {
        var append = true;
        forEach(self.selectedElementsContainer.childNodes, function(selected) {
            if (selected.name == element.name) {
                append = false;
                throw MochiKit.Iter.StopIteration;
            }
        });

        if (append) {
            appendChildNodes(self.selectedElementsContainer,
                INPUT({'type': 'hidden', 'name': element.name, 'value': 1})
            );
        }
    }

    /**
     * Ensures that the element we have been given is NOT in the list of 
     * selected elements
     */
    this.removeSelectedElement = function(element) {
        forEach(self.selectedElementsContainer.childNodes, function(selected) {
            if (selected.name == element.name) {
                removeElement(selected);
                throw MochiKit.Iter.StopIteration;
            }
        });
    }

    /**
     * Called when the user browses back to a page they have already seen. They 
     * may have added/removed what they have checked on that page, so we need 
     * to syncronise the display with their choices
     */
    this.syncroniseCheckboxStateFromContainer = function() {
        forEach(getElementsByTagAndClassName('input', 'artefactid-checkbox', 'artefactchooser-body'), function(checkbox) {
            checkbox.checked = false;
            forEach(self.selectedElementsContainer.childNodes, function(selected) {
                if (selected.name == checkbox.name) {
                    // Checkbox should be checked
                    checkbox.checked = true;
                    throw MochiKit.Iter.StopIteration;
                }
            });
        });
    }

    // Contains hidden elements representing every artefact we have seen, 
    // regardless of whether it has been selected
    this.seenElementsContainer = null;

    // Contains hidden elements representing every artefact that has been 
    // selected on any page we have seen
    this.selectedElementsContainer = null;

    // Pagination offsets we have already seen. We have always seen offset 0 
    // when we begin.
    this.seenOffsets = [0];

    self.init();
}

new ArtefactChooserData();

EOF;
    }
    return $pagination_js;
}