UNPKG

get-translation

Version:
671 lines (581 loc) 19.2 kB
if(typeof define !== 'function') { var define = require('amdefine')(module); } define(function(require) { var View = inServer ? require('../../libraries/View') : require('View') , ConditionView = require('./ConditionView') , InputView = require('./InputView') , ElseView = require('./ElseView') , template = inServer ? content_appTemplates : require('contentTemplates') , _ = require('underscore'); if(inClient) { var minTimer = require('minTimer'); } return View.extend({ /** * Initializer * * @return {void} * @api public */ initialize: function(model) { this.model = model; this._conditionViews = []; this._inputViews = []; if(inClient) { this._bindMethods(); } }, /** * Bind model * * @return {void} * @api public */ bindModel: function() { this._bindConditionsAddition(); this._bindConditionRemoval(); this._bindInputAddition(); this._bindElseAddition(); }, /** * Bind else addition * * @return {void} * @api private */ _bindElseAddition: function() { var _this = this; this.model.on('change:else', function(_else) { if(_this.model.get('else')) { _this._elseView = new ElseView(_else.get('else')); _this.$('[data-row="1"]').after(_this._elseView.render()); _this._elseView.bindModel(); _this._elseView.setElement('[data-row="2"]'); _this._elseView.bindDOM(); } }); }, /** * Bind condition addition * * @return {void} * @api private */ _bindConditionsAddition: function() { var _this = this; this.model.on('add:conditions', function(condition) { var view = new ConditionView(condition) , insertingRow = condition.get('row') , $condition; // We need a defer block because firstOperand and lastOperans relation // are not set yet. _.defer(function() { // If we insert a condition by choosing `and` or `or` in the `then` dropdown // We need to increase the row property on other conditions // and inputs. Otherwise some DOM bindings/event handling // will be wrong. We check that this is condition insertion from the `then` // dropdown by checking if inserting row is equals the `else` row minus 2 if(_this.model.get('else') && insertingRow !== _this.model.get('else').get('row')) { _this.model.get('conditions').forEach(function(_condition) { var currentRow = _condition.get('row'); if(currentRow >= insertingRow && condition.cid !== _condition.cid) { _condition.set('row', currentRow + 1); } }); _this.model.get('inputs').forEach(function(input) { var currentRow = input.get('row'); if(currentRow >= insertingRow) { input.set('row', currentRow + 1); } }); var _else = _this.model.get('else'); _else.set('row', _else.get('row') + 1); $condition = $(view.render()); } else { if(insertingRow === 0) { _this.model.get('inputs').forEach(function(input) { var currentRow = input.get('row'); if(currentRow >= insertingRow) { input.set('row', currentRow + 1); } }); } $condition = $(view.render()).addClass('invisible'); } // bind conditions' DOM if(insertingRow > 0) { this.$('[data-row="' + (insertingRow - 1) + '"]').after($condition); } else { this.$('[data-row="1"]').before($condition); } var conditionSelector = '.condition[data-row="' + insertingRow + '"]:not(.condition-else)'; view.setElement(conditionSelector); view.bindDOM(); view.firstOperandView.setElement(conditionSelector + ' .condition-first-operand'); view.firstOperandView.bindDOM(); view.lastOperandView.setElement(conditionSelector + ' .condition-last-operand'); view.lastOperandView.bindDOM(); // We must insert the view in the right index in `_conditionViews` var appended = false; _this._conditionViews.some(function(conditionView, index) { if(conditionView.model.get('row') === insertingRow - 1) { _this._conditionViews.splice(index + 1, 0, view); appended = true; return true; } return false; }); if(!appended) { _this._conditionViews.push(view); } }); }); }, /** * Bind condition removal * * @return {void} * @api private */ _bindConditionRemoval: function() { var _this = this; this.model.on('remove:conditions', function(condition) { var row = condition.get('row') , inputs = _this.model.get('inputs').sort() , conditions = _this.model.get('conditions').sort() , hasSubsequentCondition = false; if(row === 0) { _this._removeFirstCondition(); return; } conditions.some(function(_condition) { if(_condition.get('row') === row - 1) { _this._removeSingleCondition(condition); return true; } if(_condition.get('row') === row + 1) { inputs.some(function(input) { if(input.get('row') === row - 1) { _this._removeConditionWithSubsequentCondition(condition); hasSubsequentCondition = true; return true; } return false; }); return true; } return false; }); if(!hasSubsequentCondition) { inputs.some(function(input, index) { if(input.get('row') === row - 1) { if(inputs.where({ row : row + 1}).length) { _this._removeConditionAndInput(condition); return true; } } return false; }); } }); }, /** * Remove condition when an input is before and one condition after. * * @return {void} * @api private */ _removeConditionWithSubsequentCondition: function(condition) { var _this = this, removalIndex, removalRow; this._conditionViews.forEach(function(conditionView, index) { if(conditionView.model === condition) { conditionView.remove(); removalRow = conditionView.model.get('row'); removalIndex = index; } // We must decrease the row index on all subsequent conditions if(removalIndex) { if(index === removalIndex + 1) { conditionView.model.set('statement', 'else if'); } if(index > removalIndex) { conditionView.model.set('row', conditionView.model.get('row') - 1); } } }); if(removalRow) { this._inputViews.forEach(function(inputView) { var row = inputView.model.get('row'); if(row > removalRow) { inputView.model.set('row', row - 1); } }); if(this._elseView) { this._elseView.model.set('row', this._elseView.model.get('row') - 1); } this._conditionViews.splice(removalIndex, 1); } }, /** * Remove condition only. This method only removes the `condition` from * the DOM by calling `Backbone.View.prototype.remove`. * * @return {void} * @api private */ _removeSingleCondition: function(condition) { var _this = this, removalIndex, removalRow; this._conditionViews.forEach(function(conditionView, index) { if(conditionView.model === condition) { conditionView.remove(); removalRow = conditionView.model.get('row'); removalIndex = index; } // We must decrease the row index on all subsequent conditions if(removalIndex && index > removalIndex) { conditionView.model.set('row', conditionView.model.get('row') - 1); } }); if(removalRow) { this._inputViews.forEach(function(inputView) { var row = inputView.model.get('row'); if(row > removalRow) { inputView.model.set('row', row - 1); } }); if(this._elseView) { this._elseView.model.set('row', this._elseView.model.get('row') - 1); } this._conditionViews.splice(removalIndex, 1); } }, /** * Remove the first condition. When removing the first condition some other logic * need to be applied. First condition and the input after it should be removed * from DOM. Then depending on if the `else` row is two or not else will be removed * or one `else if` statement will be converted to just an if statement. * * @return {void} * @api private */ _removeFirstCondition: function() { var elseRow = this.model.get('else').get('row'); this._conditionViews[0].remove(); this._conditionViews.splice(0, 1); var inputs = this.model.get('inputs').sort() , conditions = this.model.get('conditions').sort() , removedElements = 1; if(!conditions.where({ row : 1 }).length) { this._inputViews[0].model.destroy(); this._inputViews[0].remove(); this._inputViews.splice(0, 1); removedElements++; } if(elseRow === 2) { this._elseView.model.destroy(); this._elseView.remove(); this._elseView = null; this._inputViews[0].model.set('row', 0); } else { var first = true; conditions.forEach(function(condition) { condition.set('row', condition.get('row') - removedElements); if(first) { condition.set('statement', 'if'); first = false; } }); inputs.sort().forEach(function(input) { input.set('row', input.get('row') - removedElements); }); var _else = this.model.get('else'); _else.set('row', _else.get('row') - removedElements); } }, /** * Remove condition and input. This method should be called when a * condition have one input after and one input before it. This method * will remove the condition and also remove the input after it. * * @return {void} * @api private */ _removeConditionAndInput: function(condition) { var _this = this, conditionRemovalIndex, removalRow; this._conditionViews.forEach(function(conditionView, index) { if(conditionView.model === condition) { conditionView.remove(); // We remove the view pointer from TranslatioView` removalRow = conditionView.model.get('row'); conditionRemovalIndex = index; } // We must decrease the row index on all subsequent conditions if(conditionRemovalIndex && index > conditionRemovalIndex) { conditionView.model.set('row', conditionView.model.get('row') - 2); } }); var inputRemovalIndex; this._inputViews.forEach(function(inputView, index) { var row = inputView.model.get('row'); if(row == removalRow + 1) { inputView.model.destroy(); inputView.remove(); inputRemovalIndex = index; } else if(row > inputRemovalIndex) { inputView.model.set('row', row - 2); } }); if(this._elseView) { this._elseView.model.set('row', this._elseView.model.get('row') - 2); } this._inputViews.splice(inputRemovalIndex, 1); this._conditionViews.splice(conditionRemovalIndex, 1); }, /** * Bind condition removal * * @return {void} * @api private */ _bindInputAddition: function() { var _this = this; this.model.on('add:inputs', function(input) { var row = input.get('row') , view = new InputView(input); _this.$('[data-row="' + (row - 1) + '"]').after(view.render()); view.setElement('[data-row="' + row + '"]'); view.bindDOM(); _this._inputViews.push(view); }); }, /** * Bind condition removal * * @return {void} * @api private */ _bindInputRemoval: function() { var _this = this; this.model.on('remove:inputs', function(input) { }); }, /** * Bind view * * @return {void} * @api private */ bindDOM: function() { if(!has.touch) { this._addMouseInteractions(); } // We loop through each relation view and try to bind // them with our object this._conditionViews.forEach(function(conditionView) { var conditionSelector = '.condition[data-row="' + conditionView.model.get('row') + '"]'; conditionView.setElement(conditionSelector); conditionView.bindDOM(); conditionView.firstOperandView.setElement(conditionSelector + ' .condition-first-operand'); conditionView.firstOperandView.bindDOM(); conditionView.lastOperandView.setElement(conditionSelector + ' .condition-last-operand'); conditionView.lastOperandView.bindDOM(); }); this._inputViews.forEach(function(inputView) { var conditionSelector = '.input[data-row="' + inputView.model.get('row') + '"]'; inputView.setElement(conditionSelector); inputView.bindDOM(); }); if(typeof this._elseView !== 'undefined') { this._elseView.setElement('.condition-else'); this._elseView.bindModel(); } this._setElements(); this.boundDOM = true; }, /** * Bind methods * * @return {void} * @api private */ _bindMethods: function() { _.bindAll( this, '_addCondition', '_save' ); }, /** * Add desktop listeners * * @return {void} * @api private */ _addMouseInteractions: function() { this.$('[disabled]').removeAttr('disabled'); this.$el.on('click', '.add-condition', this._addCondition); this.$el.on('click', '.save', this._save); }, /** * Set elements * * @return {void} * @api private */ _setElements: function() { this.$region = $('[data-region=translation]'); this.$saveButton = $('.save'); this.$saveSpinner = $('.save-spinner'); this.$saveButtonContainer = $('.save-button-container'); }, /** * Add condition * * @delegate */ _addCondition: function(event) { var _this = this; event.preventDefault(); var _else = this.model.get('else'), row = 0, elseRow, inputRow; if(_else) { row = _else.get('row'); } // We increase the row by two. So it becomes behind the input, // which has a row that is one bigger than inserting row. elseRow = row + 2; inputRow = elseRow + 1; var $inputs = $('[data-row="' + row + '"]') .add('[data-row="' + (row + 1) + '"]') .add('[data-row="' + (row + 2) + '"]') .addClass('invisible'); var data = { statement : row === 0 ? 'if' : 'else if', firstOperand : 'value1', operator : '==', lastOperand : 'value2', vars : this.model.get('vars'), operators : cf.OPERATORS, additionalCompairOperators : cf.ADDITIONAL_COMPAIR_OPERATORS, row : row, translation : this.model }; new this.model.Condition(data); if(_else) { _.defer(function() { _else.set('row', elseRow); new _this.model.Input({ value : '', row : inputRow, translation : _this.model}); }); } else { _.defer(function() { new _this.model.Else({ row : 2, parent : _this.model }); var input = new _this.model.Input({ value : '', row : 3, translation : _this.model}); }); } _.defer(function() { $('[data-row="' + row + '"]') .add('[data-row="' + (row + 1) + '"]') .add('[data-row="' + (row + 2) + '"]') .removeClass('invisible'); }); }, /** * Save * * @delegate */ _save: function(event) { var _this = this; event.preventDefault(); this.$saveSpinner.show(); this.$saveButtonContainer.removeClass('is-waiting').addClass('is-loading'); minTimer.start(1000); this.model.save(null, { success: function(model, response, options) { minTimer.end(function() { _this.$saveButtonContainer.removeClass('is-loading').addClass('is-waiting'); setTimeout(function() { _this.$saveSpinner.hide(); }, 300); }); }, error: function() { minTimer.end(function() { _this.$saveButtonContainer.removeClass('is-loading').addClass('is-waiting'); setTimeout(function() { _this.$saveSpinner.hide(); alert('Couldn\'t save your translation, please try again later.'); }, 300); }); } }); }, /** * To HTML * * @return {void} * @api public */ toHTML: function() { var _this = this , html = '', values = [] , json = this.model.toJSON() , conditions = this.model.get('conditions') , inputs = this.model.get('inputs'); // We loop through each relation object to get the HTML. We use `row` // to determine the order of the HTML conditions.forEach(function(condition) { var view = new ConditionView(condition); _this._conditionViews.push(view); values[condition.get('row')] = view.render(); }); inputs.forEach(function(input, index) { var view = new InputView(input); _this._inputViews.push(view); values[input.get('row')] = view.render(); }); // `else` might be null if there is no conditions on translations var _else = this.model.get('else'); if(_else) { this._elseView = new ElseView(_else); values[_else.get('row')] = this._elseView.render(); } json.values = values.join(''); html += template['Translation'](json); return html; }, /** * Remove * * @delegate */ hide: function() { this.$region.addClass('hidden'); this.$region.empty(); }, /** * Remove * * @delegate */ show: function() { this.$region.removeClass('hidden'); }, /** * Determine whether to render or not * * @return {String} * @api public * @autocalled */ should: function(path) { return 'update'; } }); });