Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
fun-apps / templates / course_pages / index.html
Size: Mime:
## mako
<%!
from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse
%>

<%inherit file="/funsite/parts/base-fluid.html" />

<%block name="title">${_("Courses")}</%block>
<%namespace name="breadcrumbs" file="/funsite/parts/breadcrumbs.html"/>
<%namespace name='static' file='/static_content.html'/>

<%block name="js_extra">
    <script src="${static.url('funsite/jquery/dist/jquery.min.js')}"></script>
    <script src="${static.url('js/vendor/underscore-min.js')}"></script>
    <script src="${static.url('js/vendor/backbone-min.js')}"></script>

<%def name="static_include(path)">
    <%
    from django.template.engine import Engine
    from django.template.loaders.app_directories import Loader
    engine = Engine(dirs=settings.DEFAULT_TEMPLATE_ENGINE['DIRS'])
    source, template_path = Loader(engine).load_template_source(path)
    %>
    ${source}
</%def>

% for template_name in ['small-course-block', 'criterion-value', 'filter']:
<script type="text/template" id="${template_name}-tpl">
    ${static_include("course_pages/js/" + template_name + ".underscore")}
</script>
% endfor

<script>
    (function ($, _, Backbone) {

        var loadTemplate = function(name) {
            var templateSelector = '#' + name + '-tpl',
                templateText = $(templateSelector).text();
            if (!templateText) {
                console.error('Failed to load ' + name + ' template');
            }
            return _.template(templateText);
        };

        var FILTERS = [
            {
                title: "${_('New courses')}", name: 'status', type: 'select', select: '#status-select',
                values: [
                    {value: 'new', title: "${_('First session')}", count: ${courses_count_new}},
                ]
            },
            {
                title: "${_('Availability')}", name: 'availability', type: 'select', select: '#availability-select',
                values: [
                    {value: 'starting_soon', title: "${_('Starting soon')}", count: ${courses_count_starting_soon}},
                    {value: 'enrollment_ending_soon', title: "${_('Imminent end of enrollment')}", count: ${courses_count_enrollment_ending_soon}},
                    {value: 'opened', title: "${_('Open for enrollment')}", count: ${courses_count_opened}},
                    {value: 'started', title: "${_('Already started')}", count: ${courses_count_started}},
                    {value: 'browsable', title: "${_('Browsable')}", count: ${courses_count_browsable}},
                    {value: 'archived', title: "${_('Archived')}", count: ${courses_count_archived}},
                ]
            },
            {
                title: '${_("Themes")}', name: 'subject', type: 'select', select: '#theme-select',
                max: 8, all_button: "${_('View <%= count %> themes')}", overlay: '#all-themes-overlay',
                values: [
                    %for course_subject in course_subjects:
                    {value: "${course_subject.slug}", title: "${course_subject.get_short_name()}", count: ${course_subject.public_courses_count}},
                    %endfor
                ]
            },
            {
                title: "${_('Universities')}", name: 'university', type: 'select', select: '#university-select',
                max: 10, all_button: "${_('View <%= count %> universities')}", overlay: '#all-universities-overlay',
                values: [
                    %for university in universities:
                    {value: "${university.code}", title: "${university.get_short_name()}", count: ${university.public_courses_count}},
                    %endfor
                ]
            },
            /*
            {title: '${_("Certified training")}', name: 'certificate', type: 'checkbox', values: [
                {value: "certificat", title: "Certificate", count: 50 },
            ]},
            */
            {
                title: '${_("Language")}', name: 'language', type: 'select',
                values: [
                    %for language in languages:
                    {value: "${language['language']}", title: "${language['title']}", image: "${language['language'] + '-flag'}", count: "${language['count']}"},
                    %endfor
                ]
            },
        ];

        var AppRouter = Backbone.Router.extend({
            routes: {
                "filter/:name/:slug?page=:page&rpp=:rpp&sort=:sortBy": "filterByAvailability",
                "filter/:name/:slug?page=:page&rpp=:rpp": "filterByAvailability",
                "filter/:name/:slug": "filterByAvailability",
                "filter": "filterByAvailability",
                "": "filterByAvailability",
                "filter?page=:page&rpp=:rpp&sort=:sortBy": "sort",
                "search?query=:query&page=:page&rpp=:rpp&sort=:sortBy": "search",
                "search?query=:query&page=:page&rpp=:rpp": "search",
                "search?query=:query": "search",
            },
            initialize: function(options) {
                this.criterioncollection = options.criterioncollection;
                this.filter = options.filter;
            	this.timeout_handle=null;
                if (!window.location.hash) {
                     window.history.pushState('', '', '#filter/availability/starting_soon');
                }
            },
            search: function(query, page, rpp, sortBy) {
                /* the smaller the query the more we wait */
                var delay = query.length > 4 ? 750 :( query.length > 1 ? 1500 : 3000 );
                if (this.timeout_handle!==null) {
                    window.clearTimeout(this.timeout_handle);
                }
                var msearch = this._search;
                /** JS trick this is overloaded in Timeout callbacks **/
                var that= this;
                this.timeout_handle = window.setTimeout(
                    function () { msearch(query, page, rpp, sortBy, that)
                },delay);
            },
            _search: function(query, page, rpp, sortBy, that) {
                var attributes = {
                    query: query,
                    selected: null,
                };
    		var that;
                if (rpp) {
                    attributes.resultsPerPage = parseInt(rpp);
                }
                if (page) {
                    attributes.currentPage = parseInt(page);
                }
                if (sortBy) {
                    attributes.sortBy = sortBy;
                }
                that.filter.set(attributes);
            },
            sort: function(page, rpp, sortBy) {
                return this.filterByAvailability(null, null, page, rpp, sortBy);
            },
            filterByAvailability: function(name, slug, page, rpp, sortBy) {
                var attributes = {
                    query: null,
                }
                if (name) {
                    var criterion = this.criterioncollection.findWhere({'name': name});
                    var value = criterion.getValue(slug);
                    var $select = $('select[id^="' + name + '"]');
                    $select.val(slug);

                    attributes.selected = value;
                } else {
                    attributes.selected = null;
                }
                if (rpp) {
                    attributes.resultsPerPage = parseInt(rpp);
                }
                if (page) {
                    attributes.currentPage = parseInt(page);
                }
                if (sortBy) {
                    attributes.sortBy = sortBy;
                }
                this.filter.set(attributes);
            },
        });

        // Value of a possible filter. E.g: Language=English, Availability=Ends-soon, etc.
        var Value = Backbone.Model.extend({
            criterionName: function() {
                return this.collection.criterion.get("name");
            }
        });
        var ValueCollection = Backbone.Collection.extend({
            model: Value,
            initialize: function(models, options) {
                this.criterion = options.criterion;
            },
        });
        var ValueView = Backbone.View.extend({
            tagName: 'li',
            className: "criterion",
            template: loadTemplate("criterion-value"),

            initialize: function() {},

            events: {"click": "onClick"},

            isCheckbox: function() {
                return this.model.collection.criterion.get('type') === 'checkbox';
            },
            render: function() {
                // if an image is provided we add some CSS classes
                var image = this.model.get('image');
                if (image) {
                    this.$el.addClass('icon ' + image);
                } else if (this.isCheckbox()) {
                    this.$el.addClass('filter-checkbox');
                }

                this.$el.html(this.template(this.model.toJSON()));
                return this;
            },
            onClick: function(event) {
                if (this.isCheckbox()) {
                    this.$el.toggleClass('checked');
                    this.model.set('value', this.$el.hasClass('checked') ? 'certificate' : '');
                }
                this.model.collection.trigger('valueSelected', this.model);
            }
        });
        var SelectValueView = ValueView.extend({
            tagName: 'option',
            className: '',
            render: function() {
                this.$el.attr('value', this.model.get('value'));
                this.$el.text(this.model.get('title'))
                return this;
            }
        })


        // only display `max` items in filter columns (others will be displayed when `all` button clicked)
        var ShortValueCollectionView = Backbone.View.extend({
            tagName: 'ul',
            className: "criteria",

            initialize: function(options) {
                this.listenTo(this.collection, 'add', this.addOne);
                this.listenTo(this.collection, 'valueSelected', this.valueSelected);
                this.max = options.max;
                this.count = 0;
            },
            valueSelected: function(value) {
                // ShortValueCollectionView and LongValueCollectionView use the same collection,
                // so this event will be triggered for both display
                this.collection.criterion.collection.trigger('criterionSelected', value)
                $('.hide-on-escape-key').hide();
            },
            addOne: function(item) {
                if (this.count < this.max) {
                    var view = new ValueView({model: item});
                    this.$el.append(view.render().el);
                    this.count += 1;
                }
            },

        });

        // Collection view that stores the value views listed whenever we click
        // on a "all" button.
        var LongValueCollectionView = Backbone.View.extend({
            initialize: function() {
                this.listenTo(this.collection, 'add', this.addOne);
                this.sortedViews = [];
            },
            viewTitle: function(index) {
                return this.sortedViews[index].model.get("title");
            },
            createView: function(model) {
                var view = new ValueView({model: model});
                return view;
            },
            addOne: function(item) {
                // Insert view at the proper position, sorted by title
                var view = this.createView(item);
                var insertBeforeIndex;
                var itemTitle = item.get('title');
                for (insertBeforeIndex=0; insertBeforeIndex < this.sortedViews.length; insertBeforeIndex++) {
                  // We use locale-sensitive string comparison to make sure accented titles are correctly sorted
                  if (itemTitle.localeCompare(this.viewTitle(insertBeforeIndex)) < 0) {
                      break;
                  }
                }
                if (insertBeforeIndex === this.sortedViews.length) {
                    this.$el.append(view.render().$el);
                } else {
                    this.sortedViews[insertBeforeIndex].$el.before(view.render().$el);
                }
                this.sortedViews.splice(insertBeforeIndex, 0, view);
            },
        });

        var SelectValueCollectionView = LongValueCollectionView.extend({
            events: {"change": "onChange"},
            initialize: function(options) {
                SelectValueCollectionView.__super__.initialize.apply(this);
                this.$el.append($('<option>').text(options.title));
            },
            createView: function(model) {
                var view = new SelectValueView({model: model});
                return view;
            },
            onChange: function(event) {
                var model = this.collection.findWhere({value: event.target.value});
                this.collection.criterion.collection.trigger('criterionSelected', model);
                // reset other selects
                $('.small-device-filter select').not('#' + this.$el.attr('id')).prop('selectedIndex', 0);
            },
        });

        // A criterion is a collection of possible filters from one kind. E.g: Language, Availability, etc.
        var Criterion = Backbone.Model.extend({
            defaults: {
                // if no max value, we use a big one to display all items (and save JS LOC)
                "max":  100
            },
            initialize: function() {
                this.valuecollection = new ValueCollection([], {criterion: this});
            },
            getValue: function(value) {
                return this.valuecollection.findWhere({value: value});
            }
        });

        var CriterionView = Backbone.View.extend({
            tagName: 'div',

            initialize: function() {
                // Top elements from value collection
                this.valuecollectionview = new ShortValueCollectionView({
                        collection: this.model.valuecollection,
                        max: this.model.get('max')});

                // 'select' DOM element for mobile view
                this.selectvaluecollectionview = new SelectValueCollectionView({
                        el: this.model.get('select'),
                        collection: this.model.valuecollection,
                        title: this.model.get('title')});

                if (this.model.get('all_button')) {
                    // Complete list of possible values
                    this.longvaluecollectionview = new LongValueCollectionView({
                            el: this.model.get('overlay') + ' ul',
                            collection: this.model.valuecollection});

                    // Hide overlay on click outside of overlay
                    var overlay = $(this.model.get("overlay"));
                    var that = this;
                    var onBodyClick = function(e) {
                      if (!overlay.is(e.target) && !that.$(".all-items").is(e.target)) {
                          overlay.hide();
                      }
                    }
                    $('body').on("click", onBodyClick);
                };
                this.model.valuecollection.add(this.model.get("values"))
            },

            render: function() {
                // We don't use a template here
                this.$el.html($('<h2>').text(this.model.get('title')));
                this.$el.append(this.valuecollectionview.render().el);
                if (this.model.get('all_button')) {
                    var allButtonContent = "<i class='glyphicon glyphicon-plus'></i> ";
                    allButtonContent += _.template(this.model.get("all_button"))({
                        count: this.model.valuecollection.length
                    });
                    var allButton = $('<span>').attr({'class': 'btn btn-transparent btn-dark-bg btn-block btn-padded all-items'});
                    allButton.html(allButtonContent);

                    this.$el.append(allButton);
                }

                return this;
            },

            events: {
                'click span.all-items': 'toggleAllItemsOverlay'
            },

            toggleAllItemsOverlay: function(event) {
                var overlay = $(this.model.get('overlay'));
                overlay.toggle();

                if (overlay.is(':visible')) {
                    /* Calculate vertical overlay's position regarding clicked button */
                    var top =  $(event.currentTarget).prev().prev().offset().top - 68;
                    overlay.css('top', top + 'px');

                    /* Calculate vertical UL's height regarding the number of items and columns as Chrome seems to fail to do it right */
                    var height = Math.ceil(overlay.find('li').length / overlay.find('ul').css('column-count'));
                    var li_height = $('li.criterion').outerHeight(true);
                    li_height +=3;  // quick fix to compensate university names which use 2 lines... We have to use shorter name
                    overlay.find('ul').css('height', (height * li_height) + 'px');
                }
            },
        })

        var CriterionCollection = Backbone.Collection.extend({
            model: Criterion,
        });

        var CriterionCollectionView = Backbone.View.extend({
            initialize: function() {
                this.listenTo(this.collection, 'add', this.addOne);
            },
            addOne: function(criterion) {
                var view = new CriterionView({model: criterion});
                this.$el.append(view.render().el);
            },
        });

        var Filter = Backbone.Model.extend({
            defaults: {
                selected: null,
                resultsPerPage: 50,
                resultsPerPageChoices: [20, 50, 100, -1],
                currentPage: 1,
                query: null,
                sortBy: "",
            },
            startNewSearch: function(value) {
                this.set({
                    selected: value,
                    currentPage: 1,
                    query: null,
                    sortBy: "",
                });
            }
        });
        var ResultStats = Backbone.Model.extend({
            defaults: {
                totalCount: null,
            }
        });
        var FilterView = Backbone.View.extend({
            initialize: function(options) {
                this.resultstats = options.resultstats;
                this.listenTo(this.model, 'change', this.render);
                this.listenTo(this.model, 'change', this.updateUrl);
                this.listenTo(this.resultstats, 'change', this.render);
            },
            updateUrl: function() {
                var url = "";
                var selected = this.model.get("selected");
                var query = this.model.get("query");
                var page = this.model.get("currentPage");
                var rpp = this.model.get("resultsPerPage");
                var sortBy = this.model.get("sortBy");
                var parameters = [];

                // Action
                if (selected) {
                    url = 'filter/' + selected.criterionName() + '/' + selected.get('value');
                } else if (query) {
                    url = 'search';
                    parameters.push({name: "query", value: query});
                } else {
                    url = 'filter';
                }
                if (page || rpp) {
                    parameters.push({name: "page", value: page || 1});
                    parameters.push({name: "rpp", value: rpp || 50});
                }
                if (sortBy) {
                    parameters.push({name: "sort", value: sortBy});
                }
                url += '?' + $.param(parameters);

                Backbone.history.navigate(url, {trigger: false});

                // Trigger Google analytics event
                var _gaq = (_gaq || []);
                _gaq.push('_trackPageview', "${reverse('fun-courses:index')}" + "/" + url);
            },
            events: {
                "click .glyphicon-remove": "clearSelectedValue",

                "click .filter-dropdown": "toggleDropdown",
                "click .filter-dropdown.results-per-page li": "setResultsPerPage",
                "click .filter-dropdown.sort-by li": "setOrderBy",

                "click .result-page": "changeCurrentPage",
            },
            template: loadTemplate("filter"),
            clearSelectedValue: function() {
                this.model.startNewSearch(null);
                $('.small-device-filter select').prop('selectedIndex', 0);  // reset selects
            },
            toggleDropdown: function(e) {
                var dropdown = $(e.target);
                if(!dropdown.hasClass("filter-dropdown")) {
                    // Clicks on inside elements should trigger events, too
                    dropdown = dropdown.parent(".filter-dropdown");
                }
                var dropdownChoices = dropdown.find("ul");
                dropdownChoices.css("width", dropdown.outerWidth());
                dropdownChoices.toggle();
                e.stopPropagation();
            },
            setResultsPerPage: function(e) {
                var value = parseInt($(e.target).attr("value"));
                this.model.set("resultsPerPage", value);
                e.stopPropagation();
            },
            setOrderBy: function(e) {
                var value = $(e.target).attr("value");
                this.model.set("sortBy", value);
                e.stopPropagation();
            },
            changeCurrentPage: function(e) {
                this.model.set("currentPage", parseInt($(e.target).attr("data-page")));
            },
            render: function() {
                var context = this.model.toJSON();
                context.totalCount = this.resultstats.get("totalCount");
                context.totalPageCount = Math.ceil(context.totalCount / context.resultsPerPage);
                context.sortByValues = {
                    "": "${_('Default')}",
                    //"title": "${_('Title')}",
                    "start_date": "${_('Start date')}",
                    //"-start_date": "${_('Start date (decreasing)')}",
                    //"start_enrollment_date": "${_('Enrollment date')}",
                }
                this.$el.html(this.template(context));
                return this;
            }
        });

        var Course = Backbone.Model.extend({
            defaults: {
                "thumbnails": {}
            }
        });
        var CourseView = Backbone.View.extend({
            tagName: 'li',
            template: loadTemplate("small-course-block"),
            initialize: function() {
                this.listenTo(this.model, "destroy", this.remove);
                this.listenTo(this.model, "remove", this.remove);
            },
            render: function() {
                var context = this.model.toJSON();
                context.course_about = '/courses/'+ context.key + '/about';
                this.$el.html(this.template(context));
                return this;
            },
        });
        var CourseCollection = Backbone.Collection.extend({
            model: Course,
            initialize: function(models, options) {
                this.filter = options.filter;
                this.resultstats = options.resultstats;
            },
            url: function() {
                // build query parameters
                var parameters = [];
                var selectedValue = this.filter.get("selected");
                if (selectedValue) {
                    parameters.push({
                        name: selectedValue.criterionName(),
                        value: selectedValue.get("value")
                    });
                }
                if (this.filter.get("resultsPerPage") >= 0) {
                    parameters.push({
                        name: "rpp",
                        value: this.filter.get("resultsPerPage"),
                    })
                }
                parameters.push({
                    name: "page",
                    value: this.filter.get("currentPage"),
                })
                if (this.filter.get("query")) {
                    parameters.push({
                        name: "query",
                        value: this.filter.get("query"),
                    })
                }
                if (this.filter.get("sortBy")) {
                    parameters.push({
                        name: "sort",
                        value: this.filter.get("sortBy"),
                    })
                }
                return "${reverse('fun-courses-api:courses-list')}" + '?' + $.param(parameters);
            },
            parse: function (response) {
               this.resultstats.set("totalCount", response.count);
               return response.results;
            },
        });
        var CourseCollectionView = Backbone.View.extend({
            initialize: function(data) {
                this.listenTo(this.collection, 'add', this.addOne);
                this.listenTo(this.collection, 'request', this.syncing);
            },
            addOne: function(item) {
                var view = new CourseView({model: item});
                this.$el.append(view.render().el);
            },
            syncing: function() {
                this.$el.html("<div class='spinner'></div>");
            },
            render: function() {
                // Remove spinner
                this.$el.find('.spinner').remove();
                // clear previously added fake blocks
                this.$el.find('.fake').remove();
                // add some fake blocks to get correct alignment on last line
                for (var i = 5; i >= 0; i--) {
                    this.$el.append($('<li>').attr('class', 'small-course fake'));
                };
                return this;
            }
        });

        var AppView = Backbone.View.extend({
            initialize: function(data) {
                // filtering criteria
                var criterioncollection = new CriterionCollection;
                var criterioncollectionview = new CriterionCollectionView({
                    el: this.$(".criterions"),
                    collection: criterioncollection
                });

                this.listenTo(criterioncollection, 'criterionSelected', this.criterionSelected)
                criterioncollection.add(data.filters)

                // result stats
                var resultstats = new ResultStats;

                // selected filter
                this.filter = new Filter;
                this.filterview = new FilterView({
                    model: this.filter,
                    el: this.$('.result-navigation'),
                    resultstats: resultstats,
                }).render();
                this.listenTo(this.filter, 'change', this.refreshCourses);
                this.listenTo(this.filter, 'initialized', this.refreshCourses);

                var approuter = new AppRouter({
                    criterioncollection: criterioncollection,
                    filter: this.filter
                });

                // course list
                this.coursecollection = new CourseCollection([], {filter: this.filter, resultstats: resultstats});
                this.coursecollectionview = new CourseCollectionView({
                    el: this.$('#course-filtering-results'),
                    collection: this.coursecollection
                });
                this.listenTo(this.coursecollection, 'sync', this.courseCollectionSynced)

                this.refreshCourseRequest = null;

            },
            criterionSelected: function(value) {
                this.filter.startNewSearch(value);
            },

            refreshCourses: function(){
                // Empty collection
                while (this.coursecollection.length > 0) {
                    this.coursecollection.pop();
                }
                // Fill collection
                if (this.refreshCourseRequest) {
                    // Abort previous request
                    this.refreshCourseRequest.abort();
                }
                this.refreshCourseRequest = this.coursecollection.fetch();
                window.scrollTo(0, 0);
            },
            courseCollectionSynced: function(){
                this.coursecollectionview.render()
            }
        });

        var App = new AppView({
            el: $('#course-search'),
            filters: FILTERS
        });
        Backbone.history.start();
        App.refreshCourses();
    })($, _, Backbone);
</script>
</%block>

<%block name="content">
    <div class="breadcrumbs-wrapper">
        ${breadcrumbs.breadcrumbs(_("Courses"))}
    </div>
</%block>


<%block name="content_fullwidth2">

<div class="courses-page">
    <div class="row-fluid">
        <div class="col-lg-36 text-center">
            <h1 class="big-title">${_("Courses")}</h1>
        </div>
    </div>


    <div class="row-fluid">

        <div id="course-search" class="course-list clearfix">

            <div class="course-list-table">

                <div class="left hidden-xs">
                    <div class="filters criteria-block">
                        <div class="title">${_("Refine search results")}</div>
                        <div class="criterions"></div>
                    </div>
                </div>

                <div class="right row">
                    <div class="small-device-filter row visible-xs">
                        <div class="title">${_("Refine search results")}</div>
                        <select id="status-select">
                        </select>
                        <select id="availability-select">
                        </select>
                        <select id="university-select">
                        </select>
                        <select id="theme-select">
                        </select>

                    </div>

                    <div class="result-navigation result-navigation-top">
                    </div>

                    <div class="list">
                        <ul id="course-filtering-results"></ul>
                    </div>

                    <div class="result-navigation result-navigation-bottom"></div>

                    <div id="all-themes-overlay" class="criteria-overlay criteria-block hide-on-escape-key overlay">
                        <div class="pull-right">
                            <span class="close-overlay glyphicon glyphicon-remove"></span>
                        </div>
                        <h2>${_("All themes")}</h2>
                        <ul class="criteria"></ul>
                    </div>

                    <div id="all-universities-overlay" class="criteria-overlay criteria-block hide-on-escape-key overlay">
                        <div class="pull-right">
                            <span class="close-overlay glyphicon glyphicon-remove"></span>
                        </div>
                        <h2>${_("All universities")}</h2>
                        <ul class="criteria"></ul>
                    </div>
                </div>


            </div>
        </div>
    </div>
</div>
</%block>