Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Editor performance improvements #1798

Merged
merged 67 commits into from
Dec 13, 2017
Merged
Show file tree
Hide file tree
Changes from 66 commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
ce03207
Rename model attributes
taylortom Nov 7, 2017
abfdea9
Remove unnecessary collections from editor data
taylortom Nov 7, 2017
36d34de
Update attribute references
taylortom Nov 7, 2017
073025c
Remove unused functions
taylortom Nov 7, 2017
c976a69
Update models to use actual collection item names
taylortom Nov 8, 2017
4937847
Refactor editor menu code to allow for async loading
taylortom Nov 13, 2017
0db016b
Stop re-rendering if item already selected
taylortom Nov 13, 2017
c9991f0
Remove references to unused model attributes
taylortom Nov 13, 2017
5bbbffc
Improve comments
taylortom Nov 13, 2017
028db36
Stop code executing for each item
taylortom Nov 13, 2017
05605aa
Refactor get functions
taylortom Nov 13, 2017
97b0fe3
Remove unused bits
taylortom Nov 14, 2017
14d56a3
Fix various issues
taylortom Nov 14, 2017
712de98
Remove unused imports
taylortom Nov 24, 2017
3a4dcb9
Refactor to only call listenTo once
taylortom Nov 24, 2017
32407b2
Update ContentModel to return children as array
taylortom Nov 25, 2017
9380948
Remove unused cut functionality
taylortom Nov 25, 2017
5a053cd
Remove unused code
taylortom Nov 25, 2017
1cd4537
Update component render to work with async code
taylortom Nov 25, 2017
a316fd1
Handle re-rendering in parent
taylortom Nov 25, 2017
ab04e06
Make re-render async
taylortom Nov 25, 2017
4bdce27
Fix model accessor
taylortom Nov 25, 2017
5a49fb3
Remove unused imports
taylortom Nov 25, 2017
3ca2321
Simplify component delete code
taylortom Nov 25, 2017
8d77012
Stop fetchSiblings from returning self
taylortom Nov 25, 2017
baf3e8b
Add data-id to all editorOriginViews for convenience
taylortom Nov 25, 2017
87426c2
Removed unused ‘ancestors’ code
taylortom Nov 25, 2017
eb4ba2e
Fix page getter
taylortom Nov 25, 2017
51fd064
Refactor new block code
taylortom Nov 25, 2017
0a9b0c0
Refactor layout code
taylortom Nov 25, 2017
d036c02
Make destroy async to ensure we catch any errors
taylortom Nov 25, 2017
66fde4e
Refactor for brevity
taylortom Nov 25, 2017
589fe4f
Refactor block rendering for readability
taylortom Nov 25, 2017
2e2c711
Refactor new article code
taylortom Nov 25, 2017
480341a
Remove sibling fetch
taylortom Nov 25, 2017
327dab4
Fix server copy/paste
taylortom Nov 26, 2017
e64ba51
Remove unused var
taylortom Nov 26, 2017
a71acef
Remove unused route
taylortom Nov 26, 2017
d61a5aa
Remove unused sync classes
taylortom Nov 26, 2017
784268d
Allow for proper async rendering in page views
taylortom Nov 26, 2017
45a2a4c
Fix whitespace
taylortom Nov 27, 2017
e421b1c
Refactor for readability
taylortom Nov 27, 2017
7ea7933
Remove logs
taylortom Nov 27, 2017
ae01ca9
Remove asset URL helpers
taylortom Nov 27, 2017
a0ab2f6
Fix courseassets
taylortom Nov 27, 2017
a6e4cb3
Fix async course validation
taylortom Nov 27, 2017
ed3e92c
Merge branch 'develop' into issue/1758
taylortom Nov 27, 2017
31aaff7
Remove noisy warning
taylortom Nov 27, 2017
305f187
Remove log
taylortom Nov 27, 2017
6414e66
Fix scope issue
taylortom Nov 27, 2017
4126783
Fix issue with external assets
taylortom Nov 28, 2017
5048b84
Move stuff around
taylortom Nov 28, 2017
5a895b1
Remove spaghetti logic from scaffoldAsset template
taylortom Nov 28, 2017
835585e
Remove log
taylortom Nov 28, 2017
0eeca2c
Remove log
taylortom Nov 28, 2017
fefceb8
Stop unnecessary 404 errors
taylortom Nov 28, 2017
a8fa940
Fix courseassets clean-up
taylortom Nov 28, 2017
d453931
Improve helper to allow for iterators which modify the original list
taylortom Nov 28, 2017
8988d04
Improve performance
taylortom Nov 28, 2017
1c696e3
Fix issues
taylortom Dec 6, 2017
dcea6bb
Fix menu item state restoration
taylortom Dec 6, 2017
a779556
Fix merge issues
taylortom Dec 6, 2017
1a5533b
Don’t break for components with no supportedLayout
taylortom Dec 6, 2017
a27cc10
Move async fetch code to index so scaffold can render correctly
taylortom Dec 7, 2017
d673bf7
Refactor for readability
taylortom Dec 7, 2017
7830da0
Fix var reference
taylortom Dec 7, 2017
6afe7ca
Update error messages
taylortom Dec 11, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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