I'm trying to send form details to a google sheets script working. Currently the following code submits a form field to a google sheet perfectly.
var sheetName = 'Sheet1'
var scriptProp = PropertiesService.getScriptProperties()
function intialSetup () {
var activeSpreadsheet = SpreadsheetApp.getActiveSpreadsheet()
scriptProp.setProperty('key', activeSpreadsheet.getId())
}
function doPost (e) {
var lock = LockService.getScriptLock()
lock.tryLock(10000)
try {
var doc = SpreadsheetApp.openById(scriptProp.getProperty('key'))
var sheet = doc.getSheetByName(sheetName)
var headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0]
var nextRow = sheet.getLastRow() + 1
var newRow = headers.map(function(header) {
return header === 'timestamp' ? new Date() : e.parameter[header]
})
sheet.getRange(nextRow, 1, 1, newRow.length).setValues([newRow])
return ContentService
.createTextOutput(JSON.stringify({ 'result': 'success', 'row': nextRow }))
.setMimeType(ContentService.MimeType.JSON)
}
catch (e) {
return ContentService
.createTextOutput(JSON.stringify({ 'result': 'error', 'error': e }))
.setMimeType(ContentService.MimeType.JSON)
}
finally {
lock.releaseLock()
}
}
But I'm trying to only submit a specific field value, and if submitted, only once. So it would error if it existed in the google sheet. I can't figure it out.
Here is the JS
const scriptURL = 'HIDDEN'
const form = document.forms['submit-to-google-sheet']
const loading = document.querySelector('.js-loading')
const successMessage = document.querySelector('.js-success-message')
const errorMessage = document.querySelector('.js-error-message')
form.addEventListener('submit', e => {
e.preventDefault()
showLoadingIndicator()
fetch(scriptURL, { method: 'POST', body: new FormData(form)})
.then(response => showSuccessMessage(response))
.catch(error => showErrorMessage(error))
})
function showLoadingIndicator () {
form.classList.add('is-hidden')
loading.classList.remove('is-hidden')
}
function showSuccessMessage (response) {
console.log('Submitted', response)
setTimeout(() => {
successMessage.classList.remove('is-hidden')
loading.classList.add('is-hidden')
}, 500)
}
function showErrorMessage (error) {
console.error('Error!', error.message)
setTimeout(() => {
errorMessage.classList.remove('is-hidden')
loading.classList.add('is-hidden')
}, 500)
}
Here is the HTML
<div class="form-container">
<form name="submit-to-google-sheet">
<input name="code" type="text" placeholder="Enter Code" required>
<button type="submit">Submit</button>
</form>
<div class="loading js-loading is-hidden">
<div class="loading-spinner">
<svg><circle cx="25" cy="25" r="20" fill="none" stroke-width="2" stroke-miterlimit="10"/></svg>
</div>
</div>
<p class="js-success-message is-hidden">You're not a winner, better luck next time.</p>
<p class="js-error-message is-hidden">Error: Something wen't wrong, please try again later.</p>
</div>
Assuming you want to check for duplicates for the content of newRow[1] in column 2:
Retrieve the values from column B and convert them into a 1D array with flat()
Verify with indexOf() either the content of newRow is already contained in column B (indexOf() will return -1 if a value is not contained in an array)
A sample where newRow[1] was manually set to 123 for testing purposes:
function doPost (e) {
var lock = LockService.getScriptLock()
lock.tryLock(10000)
try {
var doc = SpreadsheetApp.openById(scriptProp.getProperty('key'))
var sheet = doc.getSheetByName(sheetName)
var headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0]
var nextRow = sheet.getLastRow() + 1
var newRow = headers.map(function(header) {
return header === 'timestamp' ? new Date() : e.parameter[header]
})
newRow[1] = 123;
var columnTwo = sheet.getRange(1,2, sheet.getLastRow(), 1).getValues().flat();
if (columnTwo.indexOf(newRow[1]) == - 1){
sheet.getRange(nextRow, 1, 1, newRow.length).setValues([newRow])
return ContentService
.createTextOutput(JSON.stringify({ 'result': 'success', 'row': nextRow }))
.setMimeType(ContentService.MimeType.JSON)
}
}
catch (e) {
return ContentService
.createTextOutput(JSON.stringify({ 'result': 'error', 'error': e }))
.setMimeType(ContentService.MimeType.JSON)
}
finally {
lock.releaseLock()
}
}
UPDATE
indexOf() only works correctly for values of the same content AND data type.
If your spreadsheet values are numbers, but the incoming posted value is recognized string, you need to convert it to a number for correct comparison. This can be done by comparing with
if (columnTwo.indexOf(Number(newRow[1])) == - 1){...
Related
I really think I'm going crazy here. So I made a shopping-type app, add to cart and etc.
I have extra comments on each Item which the user can enter any text they want to. The state is correct on "onChangeText" of the input, I checked it with the console. However, when I want to save it (after an edit of the cart), despite the console.log's making sense and that showing the correct state etc, the options changes successfully, but the comments does not. This is very weird as all the other cart array object do change, except the comment. Here is the code:
saveItemCart() {
var index = this.state.cartItems.indexOf(x => x.date === this.state.dateAdded);
let cartItemsArray = this.state.cartItems;
temp = { 'catNumber': this.state.catNumber, 'itemNumber': this.state.itemNumber, 'quantity': this.state.itemQuantity, 'options': this.state.selectedOptions, 'date': this.state.dateAdded, 'comments': this.state.commentsText};
cartItemsArray[index] = temp;
this.setState({ cartItems: cartItemsArray }, () => {
this.updateTotal();
this.setState({ selectedOptions: [], checked: [] })
})
}
State:
state = {
ModalItemShown: false,
ModalCartShown: false,
ModalEditShown: false,
isShowToast: false,
isShowToastError: false,
rest_id: this.props.route.params['restaurantid'],
menuData: this.getMenu(),
catNumber: 1,
itemNumber: 1,
dateAdded: null,
commentsText: "",
cartItems: [],
checked: [],
selectedOptions: [],
total: 0,
itemQuantity: 1,
}
Input (inside the render):
<View style={styles.modalOptions}>
<Input placeholder="..." placeholderTextColor="gray" defaultValue={this.state.commentsText} onChangeText={text => this.setState({ commentsText: text }, () => {console.log(this.state.commentsText)})} />
</View>
<Button shadowless style={styles.button1} color="warning" onPress={() => {
let my = this;
this.showToast(true);
setTimeout(function () { my.showToast(false) }, 1000);
this.saveItemCart();
this.hideModalEdit(false);
}}
>Save</Button>
The problem is in the input of the EDIT cart modal(i.e. page). If the user puts the comment initially before adding it to his cart, then when he opens the edit modal it has the correct comment. If I change it in the edit and click save, and check again, it does not work.
updateTotal:
updateTotal() {
var cartArrayLen = this.state.cartItems.length;
var i;
var j;
var total = 0;
var cart = this.state.cartItems;
for (i = 0; i < cartArrayLen; i++) {
var catNum = cart[i]["catNumber"];
var itemNum = cart[i]["itemNumber"];
var q = cart[i]["quantity"];
var itemPrice = this.state.menuData[catNum - 1][catNum.toString()]["itemsarray"][itemNum]["price"];
itemPrice = Number(itemPrice.replace(/[^0-9.-]+/g, ""));
total = total + (itemPrice * q);
var allItemOptions = this.state.menuData[catNum - 1][catNum.toString()]["itemsarray"][itemNum].options;
var k;
for (k = 0; k < cart[i].options.length; k++) {
var title = cart[i].options[k]["title"];
var index_onmenu = allItemOptions.findIndex(x => x.title === title);
var option_price;
if (Array.isArray(cart[i].options[k].indexes)) {
for (j = 0; j < cart[i].options[k].indexes.length; j++) {
var price_index = cart[i].options[k].indexes[j];
option_price = allItemOptions[index_onmenu].price[price_index]
total = total + Number(option_price) * q
}
} else {
j = cart[i].options[k].indexes
option_price = allItemOptions[index_onmenu].price[j]
total = total + Number(option_price) * q
}
}
};
console.log("New Total: " + total);
this.setState({ total: total }, () => {
this.setState({ itemQuantity: 1 })
})
}
I don't see where you defined the temp variable, but assuming that it is defined somewhere, I think the problem is in how you are updating the cartItems array. Let me explain it by adding some comments in your code:
saveItemCart() {
var index = this.state.cartItems.indexOf(x => x.date === this.state.dateAdded);
let cartItemsArray = this.state.cartItems; // --> Here you are just copying the array reference from the state
temp = { 'catNumber': this.state.catNumber, 'itemNumber': this.state.itemNumber, 'quantity': this.state.itemQuantity, 'options': this.state.selectedOptions, 'date': this.state.dateAdded, 'comments': this.state.commentsText};
cartItemsArray[index] = temp;
this.setState({ cartItems: cartItemsArray }, () => { // --> then you just updated the content of the array keeping the same reference
this.updateTotal();
this.setState({ selectedOptions: [], checked: [] })
})
}
Try doing creating a new array, so the react shallow comparison algorithm can track the state change, like this:
this.setState({ cartItems: Array.from(cartItemsArray) }, /* your callback */);
or
this.setState({ cartItems: [...cartItemsArray]}, /* your callback */);
Hope it helps!
I have a huge json data source (over 50,000 + lines) loaded in memory from a static file.
Note: It's not important why I have it in a static file.
I use select2 (v 4.0.5) that initializes as:
function initSelect2(selectName, dataSelect) {
var pageSize = 20;
$.fn.select2.amd.require(["select2/data/array", "select2/utils"],
function (ArrayData, Utils) {
function CustomData($element, options) {
CustomData.__super__.constructor.call(this, $element, options);
}
Utils.Extend(CustomData, ArrayData);
CustomData.prototype.query = function (params, callback) {
if (!("page" in params)) {
params.page = 1;
}
var data = {};
data.results = dataSelect.slice((params.page - 1) * pageSize, params.page * pageSize);
data.pagination = {};
data.pagination.more = params.page * pageSize < dataSelect.length;
callback(data);
};
$('#mySelect3').select2({
ajax: {},
dataAdapter: CustomData,
width: '100%'
});
});
}
I have one big problem. I can not set the value to select from jQuery. If I try like this:
$ ("#mySelect3").val(17003).trigger("change");
nothing will happen. But I think I'm doing it badly. If I understand the documentation I think I should implement function:
CustomData.prototype.current = function (callback) {}
that should create the data, and then function:
CustomData.prototype.query = function (params, callback) {}
should only filter them.
Can you please help me, how do I implement select2 initialization, that can work with many options and can by set from jQuery?
With custom data adapter you don't need pagination :
// create huge array
function mockData() {
var hugeArray = [];
for (let i = 0; i < 50000; i++) {
el = {
id: i,
text: 'My mock data ' + i,
};
hugeArray.push(el);
}
return hugeArray;
};
// define custom dataAdapter
$.fn.select2.amd.define("myCustomDataAdapter",
['select2/data/array','select2/utils'],
function (ArrayData, Utils) {
function CustomData ($element, options) {
CustomData.__super__.constructor.call(this, $element, options);
};
Utils.Extend(CustomData, ArrayData);
CustomData.prototype.query = function (params, callback) {
var data = {
// here replace mockData() by your array
results: mockData()
};
callback(data);
};
return CustomData;
}
);
//
$('#mySelect3').select2({
allowClear: true,
// use dataAdapter here
dataAdapter:$.fn.select2.amd.require("myCustomDataAdapter"),
});
And with search you can do like this :
// create huge array
function mockData() {
var hugeArray = [];
for (let i = 0; i < 50000; i++) {
el = {
id: i,
text: 'My mock data ' + i,
};
hugeArray.push(el);
}
return hugeArray;
};
// define custom dataAdapter
$.fn.select2.amd.define("myCustomDataAdapter",
['select2/data/array','select2/utils'],
function (ArrayData, Utils) {
function CustomData ($element, options) {
CustomData.__super__.constructor.call(this, $element, options);
};
Utils.Extend(CustomData, ArrayData);
CustomData.prototype.query = function (params, callback) {
var data = {
// here replace mockData() by your array
results: mockData()
};
if ($.trim(params.term) === '') {
callback(data);
} else {
if (typeof data.results === 'undefined') {
return null;
}
var filteredResults = [];
data.results.forEach(function (el) {
if (el.text.toUpperCase().indexOf(params.term.toUpperCase()) == 0) {
filteredResults.push(el);
}
});
if (filteredResults.length) {
var modifiedData = $.extend({}, data, true);
modifiedData.results = filteredResults;
callback(modifiedData);
}
return null;
}
};
return CustomData;
}
);
//
$('#mySelect3').select2({
minimumInputLength: 2,
tags: false,
allowClear: true,
// use dataAdapter here
dataAdapter:$.fn.select2.amd.require("myCustomDataAdapter"),
});
I had the same issue and this is how I solved it.
Note: We are using dataAdapter because we dealing with large that, so I am assuming your drop-down will contain large amount of data.
After implementing your DataAdapter with a query use this example to initialize your select2.
if(selectedValue !== null )
{
$("#item_value").select2({
placeholder: 'Select an option',
allowClear: true,
disabled: false,
formatLoadMore: 'Loading more...',
ajax: {},
data: [{ id: selectedValue, text: selectedValue }],
dataAdapter: customData
});
$("#item_value").val(selectedValue).trigger('change');
}else{
$("#item_value").select2({
placeholder: 'Select an option',
allowClear: true,
disabled: false,
formatLoadMore: 'Loading more...',
ajax: {},
dataAdapter: customData
});
}
To set selected value in select2 you need to pass some data into data option, but as we are dealing with large amount of data. You can't pass the complete array of large data you have as it's going to cause your browser window to freeze and lead to a bad user performance.
But instead set the data option only with the selected value you got from db or variable.
I hope this helps.
I'm looking for a way to show document type alias or name of selected content in Multinode tree picker inside content tab.
This will tremendously help me to quickly identify type of content attached. Also hovering a content added in MNTP inside content shows numeric path which is not quite useful, is there a way to show path names instead of id's?
Attaching image for reference.
Kindly suggest.
The easiest way to do this is to simply modify the MNTP property editor and have it show the extra data. It is however not really recommended to modify these files since it will be reverted every time you upgrade your Umbraco site.
There is a bit of a hacky workaround to achieve what you want however;
Angular has something called interceptors, allowing you to intercept and modify requests being made. By registrering an interceptor you could intercept the requests to contentpicker.html and redirect it to a contentpicker.html file located somewhere else - this means you will be able to just copy this file somewhere else and not modify the one in the umbraco folder being overwritten by upgrades.
Unfortunately in this case it isn't enough to just override the view being sent to the browser, since the document type alias is not actually available to that view - so the view would never be able to show the alias even if we modified it.
To work around that, you could create a copy of the contentpicker.controller.js file and create a modified version of that to use in your custom contentpicker.html view. This modified ContentPickerController would make sure to include the contentTypeAlias in the model being used in rendering the view.
All of this can be wrapped up as a "package" in the App_Plugins folder and it will be nicely separated away from being overwritten when you upgrade Umbraco, while being automatically loaded via the package.manifest file. The only caveat is that in case something is updated in the content picker code, you would have to manually merge those updates over into your custom content picker files - fortunately the content picker is rarely updated.
Place the following files in App_Plugins/CustomContentPicker/ folder and you should have what you want without actually modifying the core code:
package.manifest
{
"javascript": [
"~/App_Plugins/CustomContentPicker/customcontentpicker.controller.js"
]
}
customcontentpicker.html
<div ng-controller="Umbraco.PropertyEditors.CustomContentPickerController" class="umb-editor umb-contentpicker">
<ng-form name="contentPickerForm">
<ul class="unstyled list-icons"
ui-sortable
ng-model="renderModel">
<li ng-repeat="node in renderModel" ng-attr-title="{{model.config.showPathOnHover && 'Path: ' + node.path || undefined}}">
<i class="icon icon-navigation handle"></i>
<a href="#" prevent-default ng-click="remove($index)">
<i class="icon icon-delete red hover-show"></i>
<i class="{{node.icon}} hover-hide"></i>
{{node.name}}<br />
({{node.contentTypeAlias}})
</a>
<div ng-if="!dialogEditor && ((model.config.showOpenButton && allowOpenButton) || (model.config.showEditButton && allowEditButton))">
<small ng-if="model.config.showOpenButton && allowOpenButton"><a href ng-click="showNode($index)"><localize key="open">Open</localize></a></small>
<small ng-if="model.config.showEditButton && allowEditButton"><a href umb-launch-mini-editor="node"><localize key="edit">Edit</localize></a></small>
</div>
</li>
</ul>
<ul class="unstyled list-icons" ng-show="model.config.multiPicker === true || renderModel.length === 0">
<li>
<i class="icon icon-add blue"></i>
<a href="#" ng-click="openContentPicker()" prevent-default>
<localize key="general_add">Add</localize>
</a>
</li>
</ul>
<!--These are here because we need ng-form fields to validate against-->
<input type="hidden" name="minCount" ng-model="renderModel" />
<input type="hidden" name="maxCount" ng-model="renderModel" />
<div class="help-inline" val-msg-for="minCount" val-toggle-msg="minCount">
You need to add at least {{model.config.minNumber}} items
</div>
<div class="help-inline" val-msg-for="maxCount" val-toggle-msg="maxCount">
You can only have {{model.config.maxNumber}} items selected
</div>
</ng-form>
<umb-overlay
ng-if="contentPickerOverlay.show"
model="contentPickerOverlay"
view="contentPickerOverlay.view"
position="right">
</umb-overlay>
</div>
customcontentpicker.controller.js
angular.module('umbraco.services').config([
'$httpProvider',
function ($httpProvider) {
$httpProvider.interceptors.push(function ($q) {
return {
'request': function (request) {
var url = 'views/propertyeditors/contentpicker/contentpicker.html';
if (request.url.indexOf(url) !== -1) {
request.url = request.url.replace(url, '/App_Plugins/CustomContentPicker/customcontentpicker.html');
}
return request || $q.when(request);
}
};
});
}]);
// Below is contentpicker.controller.js modified
//this controller simply tells the dialogs service to open a mediaPicker window
//with a specified callback, this callback will receive an object with a selection on it
function customContentPickerController($scope, dialogService, entityResource, editorState, $log, iconHelper, $routeParams, fileManager, contentEditingHelper, angularHelper, navigationService, $location) {
function trim(str, chr) {
var rgxtrim = (!chr) ? new RegExp('^\\s+|\\s+$', 'g') : new RegExp('^' + chr + '+|' + chr + '+$', 'g');
return str.replace(rgxtrim, '');
}
function startWatch() {
//We need to watch our renderModel so that we can update the underlying $scope.model.value properly, this is required
// because the ui-sortable doesn't dispatch an event after the digest of the sort operation. Any of the events for UI sortable
// occur after the DOM has updated but BEFORE the digest has occured so the model has NOT changed yet - it even states so in the docs.
// In their source code there is no event so we need to just subscribe to our model changes here.
//This also makes it easier to manage models, we update one and the rest will just work.
$scope.$watch(function () {
//return the joined Ids as a string to watch
return _.map($scope.renderModel, function (i) {
return i.id;
}).join();
}, function (newVal) {
var currIds = _.map($scope.renderModel, function (i) {
return i.id;
});
$scope.model.value = trim(currIds.join(), ",");
//Validate!
if ($scope.model.config && $scope.model.config.minNumber && parseInt($scope.model.config.minNumber) > $scope.renderModel.length) {
$scope.contentPickerForm.minCount.$setValidity("minCount", false);
}
else {
$scope.contentPickerForm.minCount.$setValidity("minCount", true);
}
if ($scope.model.config && $scope.model.config.maxNumber && parseInt($scope.model.config.maxNumber) < $scope.renderModel.length) {
$scope.contentPickerForm.maxCount.$setValidity("maxCount", false);
}
else {
$scope.contentPickerForm.maxCount.$setValidity("maxCount", true);
}
});
}
$scope.renderModel = [];
$scope.dialogEditor = editorState && editorState.current && editorState.current.isDialogEditor === true;
//the default pre-values
var defaultConfig = {
multiPicker: false,
showOpenButton: false,
showEditButton: false,
showPathOnHover: false,
startNode: {
query: "",
type: "content",
id: $scope.model.config.startNodeId ? $scope.model.config.startNodeId : -1 // get start node for simple Content Picker
}
};
if ($scope.model.config) {
//merge the server config on top of the default config, then set the server config to use the result
$scope.model.config = angular.extend(defaultConfig, $scope.model.config);
}
//Umbraco persists boolean for prevalues as "0" or "1" so we need to convert that!
$scope.model.config.multiPicker = ($scope.model.config.multiPicker === "1" ? true : false);
$scope.model.config.showOpenButton = ($scope.model.config.showOpenButton === "1" ? true : false);
$scope.model.config.showEditButton = ($scope.model.config.showEditButton === "1" ? true : false);
$scope.model.config.showPathOnHover = ($scope.model.config.showPathOnHover === "1" ? true : false);
var entityType = $scope.model.config.startNode.type === "member"
? "Member"
: $scope.model.config.startNode.type === "media"
? "Media"
: "Document";
$scope.allowOpenButton = entityType === "Document" || entityType === "Media";
$scope.allowEditButton = entityType === "Document";
//the dialog options for the picker
var dialogOptions = {
multiPicker: $scope.model.config.multiPicker,
entityType: entityType,
filterCssClass: "not-allowed not-published",
startNodeId: null,
callback: function (data) {
if (angular.isArray(data)) {
_.each(data, function (item, i) {
$scope.add(item);
});
} else {
$scope.clear();
$scope.add(data);
}
angularHelper.getCurrentForm($scope).$setDirty();
},
treeAlias: $scope.model.config.startNode.type,
section: $scope.model.config.startNode.type
};
//since most of the pre-value config's are used in the dialog options (i.e. maxNumber, minNumber, etc...) we'll merge the
// pre-value config on to the dialog options
angular.extend(dialogOptions, $scope.model.config);
//We need to manually handle the filter for members here since the tree displayed is different and only contains
// searchable list views
if (entityType === "Member") {
//first change the not allowed filter css class
dialogOptions.filterCssClass = "not-allowed";
var currFilter = dialogOptions.filter;
//now change the filter to be a method
dialogOptions.filter = function (i) {
//filter out the list view nodes
if (i.metaData.isContainer) {
return true;
}
if (!currFilter) {
return false;
}
//now we need to filter based on what is stored in the pre-vals, this logic duplicates what is in the treepicker.controller,
// but not much we can do about that since members require special filtering.
var filterItem = currFilter.toLowerCase().split(',');
var found = filterItem.indexOf(i.metaData.contentType.toLowerCase()) >= 0;
if (!currFilter.startsWith("!") && !found || currFilter.startsWith("!") && found) {
return true;
}
return false;
}
}
//if we have a query for the startnode, we will use that.
if ($scope.model.config.startNode.query) {
var rootId = $routeParams.id;
entityResource.getByQuery($scope.model.config.startNode.query, rootId, "Document").then(function (ent) {
dialogOptions.startNodeId = ent.id;
});
} else {
dialogOptions.startNodeId = $scope.model.config.startNode.id;
}
//dialog
$scope.openContentPicker = function () {
$scope.contentPickerOverlay = dialogOptions;
$scope.contentPickerOverlay.view = "treepicker";
$scope.contentPickerOverlay.show = true;
$scope.contentPickerOverlay.submit = function (model) {
if (angular.isArray(model.selection)) {
_.each(model.selection, function (item, i) {
$scope.add(item);
});
}
$scope.contentPickerOverlay.show = false;
$scope.contentPickerOverlay = null;
}
$scope.contentPickerOverlay.close = function (oldModel) {
$scope.contentPickerOverlay.show = false;
$scope.contentPickerOverlay = null;
}
};
$scope.remove = function (index) {
$scope.renderModel.splice(index, 1);
angularHelper.getCurrentForm($scope).$setDirty();
};
$scope.showNode = function (index) {
var item = $scope.renderModel[index];
var id = item.id;
var section = $scope.model.config.startNode.type.toLowerCase();
entityResource.getPath(id, entityType).then(function (path) {
navigationService.changeSection(section);
navigationService.showTree(section, {
tree: section, path: path, forceReload: false, activate: true
});
var routePath = section + "/" + section + "/edit/" + id.toString();
$location.path(routePath).search("");
});
}
$scope.add = function (item) {
var currIds = _.map($scope.renderModel, function (i) {
return i.id;
});
if (currIds.indexOf(item.id) < 0) {
item.icon = iconHelper.convertFromLegacyIcon(item.icon);
$scope.renderModel.push({ name: item.name, id: item.id, icon: item.icon, path: item.path, contentTypeAlias: item.metaData.ContentTypeAlias });
}
};
$scope.clear = function () {
$scope.renderModel = [];
};
var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
var currIds = _.map($scope.renderModel, function (i) {
return i.id;
});
$scope.model.value = trim(currIds.join(), ",");
});
//when the scope is destroyed we need to unsubscribe
$scope.$on('$destroy', function () {
unsubscribe();
});
//load current data
var modelIds = $scope.model.value ? $scope.model.value.split(',') : [];
entityResource.getByIds(modelIds, entityType).then(function (data) {
//Ensure we populate the render model in the same order that the ids were stored!
_.each(modelIds, function (id, i) {
var entity = _.find(data, function (d) {
return d.id == id;
});
if (entity) {
entity.icon = iconHelper.convertFromLegacyIcon(entity.icon);
$scope.renderModel.push({ name: entity.name, id: entity.id, icon: entity.icon, path: entity.path, contentTypeAlias: entity.metaData.ContentTypeAlias });
}
});
//everything is loaded, start the watch on the model
startWatch();
});
}
angular.module('umbraco').controller("Umbraco.PropertyEditors.CustomContentPickerController", customContentPickerController);
Can't seem to figure out what the problem is here. This worked perfectly fine when using 3.5, but does not work with 4.0.
I am using select2.full.js as well which supports using input in this manner.
html:
<input id="vcreate-filter" type="text" name="settings[filter]" class="form-control" style="width:100%;"/>
js:
$("#vcreate-filter").select2({
placeholder: "Select or enter application...",
allowClear: true,
multiple: false,
ajax: {
dataType: 'json',
delay: 1000,
type: 'post',
url: '/process/get_application_list.php',
data: function (term, page) {
return {
term: term, // search term
page_limit: 25, // page size
page: page // page number
};
},
results: function (data, page) {
var more = (page * 25) < data.total; // whether or not there are more results available
return {
results: data.results,
more: more
};
}
},
createSearchChoice:function(term, data) {
if ($(data).filter(function() {
return this.text.localeCompare(term)===0; }).length===0) {
return {id:term, text:term};
}
}
}).on('change', function() {
$(this).valid();
});
get_application_list.php:
.......
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);
// make sure there are some results else a null query will be returned
if( count($results) > 0 )
{
foreach( $results as $row )
{
$ajax_result['results'][] = array(
'id' => htmlspecialchars($row['event_target'], ENT_QUOTES, 'UTF-8'),
'text' => htmlspecialchars($row['event_target'], ENT_QUOTES, 'UTF-8')
);
}
}
else
{
// 0 results send a message back to say so.
$ajax_result['results'][] = array(
'id' => 0,
'text' => 'No results found...'
);
}
// return result array to ajax
echo json_encode($ajax_result);
html: use select element instead of input.
<select id="vcreate-filter" type="text" name="settings[filter]" class="form-control" style="width:100%;"> </select>`
js: use processResults instead of 'results' as callback property.
processResults: function (data, page) {
var more = (page * 25) < data.total; // whether or not there are more results available
return {
results: data.results,
more: more
};
}
assuming the json is in the correct format [{"id": "1", "text": "One"}, {"id": "2", "text": "Two"}]
Breaking changes seems to be documented here.
I have a slickgrid with a checkbox column.
I want to capture the row id when the checkbox in the selected row is checked or unchecked.
Attached is the script for slickgrid in my view
I want when user checks the checkbox, active column in database should be set to true and when it is unchecked, Active column should be set to false.
<script type="text/javascript">
//$('input[type="button"]').enabled(false);
var grid;
var columnFilters = {};
var jsonmodel = #Html.Raw(Json.Encode(Model));
//function LoadNewMessagesData(messages) {
var dataView;
var data = [];
////console.log('lena:' + subdata.length);
$.each(jsonmodel, function (idx, spotlight) {
var pd = moment(spotlight.PostedDate);
data.push({ id: spotlight.ID, Department: spotlight.SubSection,Title: spotlight.Title, PostedDate: pd.format('YYYY-MM-DD'), Active: spotlight.Active })
});
var columns = [
{ id: '0', name: "Department", field: "Department", sortable: true},
{ id: '1', name: "Title", field: "Title", sortable: true},
{ id: '2', name: "PostedDate", field: "PostedDate", sortable: true }
];
var checkboxSelector = new Slick.CheckboxSelectColumn({
cssClass: "slick-cell-checkboxsel"
})
columns.push(checkboxSelector.getColumnDefinition());
var options = {
enableCellNavigation: true,
explicitInitialization: true,
enableColumnReorder: false,
multiColumnSort: true,
autoHeight: true,
forceFitColumns: true
};
dataView = new Slick.Data.DataView();
grid = new Slick.Grid("#myGrid", dataView, columns, options);
grid.setSelectionModel(new Slick.RowSelectionModel());
var pager = new Slick.Controls.Pager(dataView, grid, $("#pager"));
grid.registerPlugin(checkboxSelector);
dataView.onRowCountChanged.subscribe(function (e, args) {
grid.updateRowCount();
grid.render();
});
dataView.onRowsChanged.subscribe(function (e, args) {
grid.invalidateRows(args.rows);
console.log('testing');
grid.render();
});
dataView.onPagingInfoChanged.subscribe(function (e, pagingInfo) {
var isLastPage = pagingInfo.pageNum == pagingInfo.totalPages - 1;
var enableAddRow = isLastPage || pagingInfo.pageSize == 0;
var options = grid.getOptions();
if (options.enableAddRow != enableAddRow) {
grid.setOptions({ enableAddRow: enableAddRow });
}
});
var rowIndex;
grid.onCellChange.subscribe(function (e, args) {
console.log('onCellChange');
rowIndex = args.row;
});
if (grid.getActiveCell()) {
}
//onSelectedRowsChanged event, if row number was changed call some function.
grid.onSelectedRowsChanged.subscribe(function (e, args) {
if (grid.getSelectedRows([0])[0] !== rowIndex) {
console.log('onSelectedRowsChanged');
}
});
grid.onSort.subscribe(function (e, args) {
var cols = args.sortCols;
var comparer = function (dataRow1, dataRow2) {
for (var i = 0, l = cols.length; i < l; i++) {
var field = cols[i].sortCol.field;
var sign = cols[i].sortAsc ? 1 : -1;
var value1 = dataRow1[field], value2 = dataRow2[field];
var result = (value1 == value2 ? 0 : (value1 > value2 ? 1 : -1)) * sign;
if (result != 0) {
return result;
}
}
return 0;
}
dataView.sort(comparer, args.sortAsc);
});
grid.init();
dataView.beginUpdate();
dataView.setItems(data);
// dataView.setFilter(filter);
dataView.endUpdate();
//}
function RefreshMessagesGrid() {
//console.log('RefreshMessagesGrid');
//grid.invalidate();
grid.invalidateRows();
// Call render to render them again
grid.render();
grid.resizeCanvas();
}
</script>`enter code here`
Thanks in Advance !!!
You have to bind click event on rows....and get the id of the row whose checkbox is checked/unchecked
function bindClickOnRow() {
if($(this).find('.checkboxClass').attr('checked') == 'checked'){
checked = true;
} else {
checked = false;
}
rowId = $(this).attr('id');
}