Skip to content

Commit

Permalink
Merge branch 'release/v0.4.1' into issue/1790
Browse files Browse the repository at this point in the history
* release/v0.4.1: (22 commits)
  Editor performance improvements (#1798)
  use debounce instead of throttle
  throttle callbacks use on to properly remove scroll listener in remove function
  Fix bug with restoring dashboard preferences
  Fix issues with window resizing and dashboard paging
  Make dashboard layouts a bit less hard-coded
  Fix issues highlighted by review
  article view now generated after article model saved
  Amend check to allow undefined data
  Switch to local log func
  Stop throwing error if repo update check failed
  Fix version check
  Rewrite projectsView.js to fix pagination
  Add override for removal of list items
  Handle response errors better
  Fix bug with recursive function call
  Remove testing header
  Update CHANGELOG for release 0.4.0 (#1739)
  Enchancements to install/update (#1726)
  Amend contentModel._children to allow for arrays
  ...

# Conflicts:
#	frontend/src/modules/scaffold/views/scaffoldAssetView.js
  • Loading branch information
taylortom committed Dec 15, 2017
2 parents 4be4b3a + b7d8f78 commit ed85b41
Show file tree
Hide file tree
Showing 43 changed files with 1,537 additions and 1,765 deletions.
35 changes: 35 additions & 0 deletions frontend/src/core/collections/contentCollection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// LICENCE https://github.com/adaptlearning/adapt_authoring/blob/master/LICENSE
define(function(require) {
var Backbone = require('backbone');
var Origin = require('core/origin');
var Helpers = require('core/helpers');

var ContentCollection = Backbone.Collection.extend({
initialize : function(models, options) {
this._type = options._type;
this.model = Helpers.contentModelMap(this._type);
this._courseId = options._courseId;
this._parentId = options._parentId;
this.url = options.url || 'api/content/' + options._type + this.buildQuery();

this.on('reset', this.loadedData, this);
},

buildQuery: function() {
var query = '';
if(this._courseId) {
query += '_courseId=' + this._courseId
}
if(this._parentId) {
query += '_parentId=' + this._parentId
}
return query ? '?' + query : '';
},

loadedData: function() {
Origin.trigger('contentCollection:dataLoaded', this._type);
}
});

return ContentCollection;
});
204 changes: 119 additions & 85 deletions frontend/src/core/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,21 +205,6 @@ define(function(require){
}
},

getAssetFromValue: function(url) {
var urlSplit = url.split('/')
var fileName = urlSplit[urlSplit.length - 1];
// Get courseAsset model
var courseAsset = Origin.editor.data.courseassets.findWhere({_fieldName: fileName});

if (courseAsset) {
var courseAssetId = courseAsset.get('_assetId');

return '/api/asset/serve/' + courseAssetId;
} else {
return '';
}
},

ifImageIsCourseAsset: function(url, block) {
if (url.length !== 0 && url.indexOf('course/assets') == 0) {
return block.fn(this);
Expand All @@ -228,21 +213,6 @@ define(function(require){
}
},

getThumbnailFromValue: function(url) {

var urlSplit = url.split('/')
var fileName = urlSplit[urlSplit.length - 1];
// Get courseAsset model
var courseAsset = Origin.editor.data.courseassets.findWhere({_fieldName: fileName});
if (courseAsset) {
var courseAssetId = courseAsset.get('_assetId');
return '/api/asset/thumb/' + courseAssetId;
} else {
return '/api/asset/thumb/' + url;
}

},

ifAssetIsExternal: function(url, block) {
if(Handlebars.helpers.isAssetExternal(url)) {
return block.fn(this);
Expand Down Expand Up @@ -297,75 +267,139 @@ define(function(require){

return success;
},

validateCourseContent: function(currentCourse) {
// Let's do a standard check for at least one child object
var containsAtLeastOneChild = true;

// checks for at least one child object
validateCourseContent: function(currentCourse, callback) {
var containsAtLeastOneChild = true;
var alerts = [];

function iterateOverChildren(model) {
// Return the function if no children - on components
if(!model._children) return;

var currentChildren = model.getChildren();

// Do validate across each item
if (currentChildren.length == 0) {
containsAtLeastOneChild = false;

var children = _.isArray(model._children) ? model._children.join('/') : model._children;
alerts.push(
"There seems to be a "
+ model.get('_type')
+ " with the title - '"
+ model.get('title')
+ "' with no "
+ children
);

return;
} else {
// Go over each child and call validation again
currentChildren.each(function(childModel) {
iterateOverChildren(childModel);
});
var iterateOverChildren = function(model, index, doneIterator) {
if(!model._childTypes) {
return doneIterator();
}

}

iterateOverChildren(currentCourse);

if(alerts.length > 0) {
model.fetchChildren(function(currentChildren) {
if (currentChildren.length > 0) {
return helpers.forParallelAsync(currentChildren, iterateOverChildren, doneIterator);
}
containsAtLeastOneChild = false;
var children = _.isArray(model._childTypes) ? model._childTypes.join('/') : model._childTypes;
alerts.push(model.get('_type') + " '" + model.get('title') + "' missing " + children);
doneIterator();
});
};
// start recursion
iterateOverChildren(currentCourse, null, function() {
var errorMessage = "";
for(var i = 0, len = alerts.length; i < len; i++) {
errorMessage += "<li>" + alerts[i] + "</li>";
if(alerts.length > 0) {
for(var i = 0, len = alerts.length; i < len; i++) {
errorMessage += "<li>" + alerts[i] + "</li>";
}
return callback(new Error(errorMessage));
}

Origin.Notify.alert({
type: 'error',
title: Origin.l10n.t('app.validationfailed'),
text: errorMessage,
callback: _.bind(this.validateCourseConfirm, this)
});
}

return containsAtLeastOneChild;
callback(null, true);
});
},

validateCourseConfirm: function(isConfirmed) {
if (isConfirmed) {
Origin.trigger('editor:courseValidation');
}
},

isValidEmail: function(value) {
var regEx = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
if (value.length === 0 || !regEx.test(value)) {
return false;
} else {
return true;
}
},

contentModelMap: function(type) {
var contentModels = {
contentobject: 'core/models/contentObjectModel',
article: 'core/models/articleModel',
block: 'core/models/blockModel',
component: 'core/models/componentModel',
courseasset: 'core/models/courseAssetModel'
};
if(contentModels.hasOwnProperty(type)) {
return require(contentModels[type]);
}
},

/**
* Ensures list is iterated (doesn't guarantee order), even if using async iterator
* @param list Array or Backbone.Collection
* @param func Function to use as iterator. Will be passed item, index and callback function
* @param callback Function to be called on completion
*/
forParallelAsync: function(list, func, callback) {
if(!list.hasOwnProperty('length') || list.length === 0) {
if(typeof callback === 'function') callback();
return;
}
// make a copy in case func modifies the original
var listCopy = list.models ? list.models.slice() : list.slice();
var doneCount = 0;
var _checkCompletion = function() {
if((++doneCount === listCopy.length) && typeof callback === 'function') {
callback();
}
};
for(var i = 0, count = listCopy.length; i < count; i++) {
func(listCopy[i], i, _checkCompletion);
}
},

/**
* Ensures list is iterated in order, even if using async iterator
* @param list Array or Backbone.Collection
* @param func Function to use as iterator. Will be passed item, index and callback function
* @param callback Function to be called on completion
*/
forSeriesAsync: function(list, func, callback) {
if(!list.hasOwnProperty('length') || list.length === 0) {
if(typeof callback === 'function') callback();
return;
}
// make a copy in case func modifies the original
var listCopy = list.models ? list.models.slice() : list.slice();
var doneCount = -1;
var _doAsync = function() {
if(++doneCount === listCopy.length) {
if(typeof callback === 'function') callback();
return;
}
var nextItem = listCopy[doneCount];
if(!nextItem) {
console.error('Invalid item at', doneCount + ':', nextItem);
}
func(nextItem, doneCount, _doAsync);
};
_doAsync();
},

/**
* Does a fetch for model in models, and returns the latest data in the
* passed callback
* @param models {Array of Backbone.Models}
* @param callback {Function to call when complete}
*/
multiModelFetch: function(models, callback) {
var collatedData = {};
helpers.forParallelAsync(models, function(model, index, done) {
model.fetch({
success: function(data) {
collatedData[index] = data;
done();
},
error: function(data) {
console.error('Failed to fetch data for', model.get('_id'), + data.responseText);
done();
}
});
}, function doneAll() {
var orderedKeys = Object.keys(collatedData).sort();
var returnArr = [];
for(var i = 0, count = orderedKeys.length; i < count; i++) {
returnArr.push(collatedData[orderedKeys[i]]);
}
callback(returnArr);
});
}
};

Expand Down
6 changes: 3 additions & 3 deletions frontend/src/core/models/articleModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ define(function(require) {

var ArticleModel = ContentModel.extend({
urlRoot: '/api/content/article',
_parent: 'contentObjects',
_siblings: 'articles',
_children: 'blocks'
_parentType: 'contentobject',
_siblingTypes: 'article',
_childTypes: 'block'
});

return ArticleModel;
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/core/models/blockModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ define(function(require) {

var BlockModel = ContentModel.extend({
urlRoot: '/api/content/block',
_parent: 'articles',
_siblings: 'blocks',
_children: 'components',
_parentType: 'article',
_siblingTypes: 'block',
_childTypes: 'component',
// Block specific properties
layoutOptions: null,
dragLayoutOptions: null,
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/core/models/componentModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ define(function(require) {

var ComponentModel = ContentModel.extend({
urlRoot: '/api/content/component',
_parent: 'blocks',
_siblings: 'components',
_parentType: 'block',
_siblingTypes: 'component',
// These are the only attributes which should be permitted on a save
// TODO look into this...
whitelistAttributes: [
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/core/models/componentTypeModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ define(function(require) {
var ComponentTypeModel = ContentModel.extend({
idAttribute: '_id',
urlRoot: '/api/componenttype',
_parent: 'blocks',
_parent: 'block',

comparator: function(model) {
return model.get('displayName');
Expand Down
Loading

0 comments on commit ed85b41

Please sign in to comment.