Files
PetitTetonMeteor/imports/ui/TestList.js

266 lines
10 KiB
JavaScript

import './TestList.html';
import swal from 'sweetalert2';
import dragula from 'dragula';
//******************************************************************
//** Tests a list of objects held by an object, where the list is sortable and dragable.
//** The objects are not in a repository (not in Mongo).
//**
//** The specific domain is a `SalesSheet` which holds a list of `Product` objects which each have a `name` property.
//******************************************************************
Template.TestList.onCreated(function() {
let template = this;
//Here, this is the template, and this.data is an object containing the 'parentTemplate' and 'salesSheet' properties.
//Save the sales sheet as a property of this template to make the code later easier to read.
//Note: This is not reactive because we don't expect the sales sheet to change without closing the editor (re-editing would open a new template instance). Also, the sales sheet is a clone of the real one, so any changes will be lost if not saved.
this.salesSheet = {products: [{name: "A Product", productId: 1}, {name: "E Product", productId: 2}, {name: "B Product", productId: 3}, {name: "G Product", productId: 4}, {name: "H Product", productId: 5}, {name: "I Product", productId: 6}, {name: "C Product", productId: 7}, {name: "D Product", productId: 8}, {name: "F Product", productId: 9}]};
this.productsDependency = new Tracker.Dependency;
this.sort = function(sortAlphabetical) {
let firstIndex = 0;
let products = template.salesSheet.products;
let length = 0;
while(firstIndex + length < products.length && products[firstIndex + length].productId) {
length++;
}
//Sort the part of the array that contains products under the sorted heading.
products.partialSort(firstIndex, length, function(a, b) {
return sortAlphabetical ? (a.name < b.name ? -1 : 1) : (a.name > b.name ? -1 : 1);
});
//Notify anything depending on the products list that they have been modified.
template.productsDependency.changed();
}
});
Template.TestList.onRendered(function() {
let template = this;
//Setup the drag and drop for the view.
this.drake = dragula([this.$('.configurationProductsListing')[0], this.$('.configurationControls')[0]], {
moves: function(el, container, handle, sibling) {
//Don't allow drag and drop of buttons - we want them to be clickable.
return !$(handle).hasClass("button");
},
//Checks whether the element `el` can be moved from the container `target`, to the container `source`, above the `sibling` element.
accepts: function(el, target, source, sibling) {
return (!sibling || !$(sibling).hasClass('newHeading'));
},
copy: function(el, source) {
return $(el).hasClass('heading') && $(source).hasClass('configurationControls');
},
ignoreInputTextSelection: true
}).on('drop', function(el, target, source, sibling) {
if($(el).hasClass('heading')) {
if(el.parentNode) {
let array = template.salesSheet.products;
//Add the heading to the product array.
array.add({name: "New Heading"}, $(el).index());
//Remove the element that was just added by the D&D. The element will be re-added by the template in just a moment. We need the template to add the element so that events will be properly handled for it by meteor.
el.parentNode.removeChild(el);
//Notify the template engine that the products list has changed so it can be re-rendered.
template.productsDependency.changed();
}
}
else {
//Get the item from the DOM using the blaze data structure. We could make this more blaze agnostic by attaching the object as data to the DOM in the view, but we really can't escape blaze, so why bother.
//let item = el.$blaze_range.view._templateInstance.data;
let productId = $(el).data('model');
let array = template.salesSheet.products;
let item = undefined;
for(let product of array) {
if(productId === product.productId) {
item = product;
break;
}
}
if(item) {
//Rearrange the array of products on the sheet.
array.move(array.indexOf(item), $(el).index());
}
else {
console.log("ERROR: Unable to locate the moved item.");
}
}
});
});
Template.TestList.onDestroyed(function() {
//Clean up after the drag and drop.
this.drake.destroy();
});
Template.TestList.events({
'click #sortAsc': function(event, template) {
Template.instance().sort(true);
},
'click #sortDesc': function(event, template) {
Template.instance().sort(false);
}
});
Template.TestList.helpers({
products: function() {
let template = Template.instance();
let products = template.salesSheet.products;
//Mark this call as depending on the products array. When we change the array later, we will call changed() on the dependency and it will trigger this function (and the calling template setup) to be re-run.
template.productsDependency.depend();
return products;
}
});
//Note: The data to this template is a product metadata object that is part of a sheet and wrappers (by ID association) a product in the system. See the schema in SalesSheet.js, look for 'products.$' to see the type definition for this data.
Template.TestListRow.onCreated(function() {
let template = this;
this.handleHeaderEditorCancelAndClose = function() {
let $inputField = template.$("input[name='name']");
let index = template.$('.heading').index();
//Reset the text field.
$inputField.val(template.parentTemplate(1).salesSheet.products[index].name);
template.$('.heading .nameEditor, .heading .headingNameRow').removeClass('edit');
};
this.handleHeaderEditorApplyAndClose = function() {
let $inputField = template.$("input[name='name']");
let name = $inputField.val();
let index = template.$('.heading').index();
if(name) name = name.trim();
if(name && name.length > 0) {
template.parentTemplate(1).salesSheet.products[index].name = name;
template.$('.heading .name').text(name);
}
else {
template.parentTemplate(1).salesSheet.products.splice(index, 1);
template.parentTemplate(1).productsDependency.changed();
}
template.$('.heading .nameEditor, .heading .headingNameRow').removeClass('edit');
};
this.handleProductEditorCancelAndClose = function() {
let $inputField = template.$("input[name='name']");
let index = template.$('.product').index();
//Reset the text field.
$inputField.val(template.parentTemplate(1).salesSheet.products[index].name);
template.$('.product .nameEditor, .product .name').removeClass('edit');
};
this.handleProductEditorApplyAndClose = function() {
let $inputField = template.$("input[name='name']");
let name = $inputField.val();
let index = template.$('.product').index();
template.parentTemplate(1).salesSheet.products[index].name = name;
template.$('.product .name').text(name);
template.$('.product .nameEditor, .product .name').removeClass('edit');
};
});
Template.TestListRow.helpers({
measureName: function(measureId) {
return Template.instance().parentTemplate(1).measures.get(measureId).name;
},
measures: function() {
let product = Meteor.collections.Products.findOne(this.productId);
return product.measures;
},
isSelected: function(measureId) {
return this.measureIds.includes(measureId);
},
isProduct: function() {
return !!this.productId;
},
showMeasures: function() {
return Session.get(PREFIX + "showMeasures");
}
});
Template.TestListRow.events({
'dblclick .heading .name': function(event, template) {
template.$('.nameEditor, .headingNameRow').addClass('edit');
template.$('input[name="name"]').select();
},
'blur .heading input[name="name"]': function(event, template) {
template.handleHeaderEditorApplyAndClose();
},
'keyup .heading input[name="name"]': function(event, template) {
if(event.which === 13 || event.which === 9) { //Enter or Tab
template.handleHeaderEditorApplyAndClose();
event.stopPropagation();
return false;
}
else if(event.which === 27) { //Escape
template.handleHeaderEditorCancelAndClose();
event.stopPropagation();
return false;
}
},
'click .heading .accept': function(event, template) {
template.handleHeaderEditorApplyAndClose();
},
'click .heading .reject': function(event, template) {
template.handleHeaderEditorCancelAndClose();
},
'dblclick .product .name': function(event, template) {
template.$('.nameEditor, .name').addClass('edit');
template.$('input[name="name"]').select();
},
'blur .product input[name="name"]': function(event, template) {
template.handleProductEditorApplyAndClose();
},
'keyup .product input[name="name"]': function(event, template) {
if(event.which === 13 || event.which === 9) { //Enter or Tab
template.handleProductEditorApplyAndClose();
event.stopPropagation();
return false;
}
else if(event.which === 27) { //Escape
template.handleProductEditorCancelAndClose();
event.stopPropagation();
return false;
}
},
'click .product .accept': function(event, template) {
template.handleProductEditorApplyAndClose();
},
'click .product .reject': function(event, template) {
template.handleProductEditorCancelAndClose();
},
'click .measureButton': function(event, template) {
let measureId = $(event.target).data("model");
$(event.target).toggleClass("selected");
if(this.measureIds.includes(measureId))
this.measureIds.remove(measureId);
else
this.measureIds.add(measureId);
},
'click .heading .sort': function(event, template) {
let width = event.currentTarget.offsetWidth;
let x = event.pageX - event.currentTarget.offsetLeft;
let sortAlphabetical = x <= (width / 2);
let headingIndex = template.$(event.target).closest(".heading").index();
let firstIndex = headingIndex + 1;
let products = template.parentTemplate(1).salesSheet.products;
let length = 0;
while(firstIndex + length < products.length && products[firstIndex + length].productId) {
length++;
}
//Sort the part of the array that contains products under the sorted heading.
products.partialSort(firstIndex, length, function(a, b) {
return sortAlphabetical ? (a.name < b.name ? -1 : 1) : (a.name > b.name ? -1 : 1);
});
//Notify anything depending on the products list that they have been modified.
template.parentTemplate(1).productsDependency.changed();
}
});