I always have this issue in ember apps that are built on a rails backend. I have a groups.hbs template which lists out a bunch of groups, when you click on a group it loads the group.hbs template next to the groups template and changes the url to /groups/:group_id.
However, when I click the back and forward buttons, or try to manually load a url with a specific :group_id the group template fails to render and the console throws a giant
Uncaught TypeError: Object function () {
...
error.
group.js.coffee
App.Group = Ember.Object.extend()
App.Group.reopenClass
all: ->
App.ajax(
url: App.apiUrl('/groups')
).then (data) ->
console.log data
groups = []
for group in data.response
groups.addObject(App.Group.create(group))
console.log(groups)
groups
router.js.coffee
Mdm.Router.map ->
#resource 'groups', ->
#resource 'group', {path: "/:group_id"}
Mdm.Router.reopen
location: 'history'
I've never experienced this issue when building standalone ember apps. Any idea what would cause this?
EDIT
I should add that I am pulling my data from an api via XHR requests.
EDIT 2
I just explicitly created the GroupRoute and had it load all of the groups, this code is identical to the GroupsRoute. The template is still not rendering, but I no longer get that error.
GroupRoute
App.GroupRoute = Ember.Route.extend(model: ->
App.Group.all()
)
And GroupsRoute:
App.GroupsRoute = Ember.Route.extend(model: ->
App.Group.all()
)
EDIT 3
Here's the whole error if it helps anyone.
Uncaught TypeError: Object function () {
if (!wasApplied) {
Class.proto(); // prepare prototype...
}
o_defineProperty(this, GUID_KEY, undefinedDescriptor);
o_defineProperty(this, '_super', undefinedDescriptor);
var m = meta(this);
m.proto = this;
if (initMixins) {
// capture locally so we can clear the closed over variable
var mixins = initMixins;
initMixins = null;
this.reopen.apply(this, mixins);
}
if (initProperties) {
// capture locally so we can clear the closed over variable
var props = initProperties;
initProperties = null;
var concatenatedProperties = this.concatenatedProperties;
for (var i = 0, l = props.length; i < l; i++) {
var properties = props[i];
Ember.assert("Ember.Object.create no longer supports mixing in other definitions, use createWithMixins instead.", !(properties instanceof Ember.Mixin));
for (var keyName in properties) {
if (!properties.hasOwnProperty(keyName)) { continue; }
var value = properties[keyName],
IS_BINDING = Ember.IS_BINDING;
if (IS_BINDING.test(keyName)) {
var bindings = m.bindings;
if (!bindings) {
bindings = m.bindings = {};
} else if (!m.hasOwnProperty('bindings')) {
bindings = m.bindings = o_create(m.bindings);
}
bindings[keyName] = value;
}
var desc = m.descs[keyName];
Ember.assert("Ember.Object.create no longer supports defining computed properties.", !(value instanceof Ember.ComputedProperty));
Ember.assert("Ember.Object.create no longer supports defining methods that call _super.", !(typeof value === 'function' && value.toString().indexOf('._super') !== -1));
if (concatenatedProperties && indexOf(concatenatedProperties, keyName) >= 0) {
var baseValue = this[keyName];
if (baseValue) {
if ('function' === typeof baseValue.concat) {
value = baseValue.concat(value);
} else {
value = Ember.makeArray(baseValue).concat(value);
}
} else {
value = Ember.makeArray(value);
}
}
if (desc) {
desc.set(this, keyName, value);
} else {
if (typeof this.setUnknownProperty === 'function' && !(keyName in this)) {
this.setUnknownProperty(keyName, value);
} else if (MANDATORY_SETTER) {
Ember.defineProperty(this, keyName, null, value); // setup mandatory setter
} else {
this[keyName] = value;
}
}
}
}
}
finishPartial(this, m);
delete m.proto;
finishChains(this);
this.init.apply(this, arguments);
} has no method 'find'
EDIT
So I think I figured out the problem, when you click the back button or enter a manual url it wasn't finding the obkect based on id. So I added a find() method to the Group model. Not it looks like this:
Mdm.Group = Ember.Object.extend()
Mdm.Group.reopenClass
all: ->
Mdm.ajax(
url: Mdm.apiUrl('/groups')
).then (data) ->
console.log data
groups = []
for group in data.response
groups.addObject(Mdm.Group.create(group))
console.log(groups)
groups
find: (group_id) ->
Mdm.ajax(
url: Mdm.apiUrl("/groups/#{group_id}")
).then (data) ->
renderTemplate: (data)
And my GroupRoute looks like this:
Mdm.GroupRoute = Ember.Route.extend
model: (params) ->
console.log 'oh hai'
Mdm.Group.find(params.group_id)
Now in the console when I click the back button it is loading the data but its not associating the group template with the group_id. What is the best practice way to tell ember to do this?
I'm not a rails developer but try doing something like this for the simple model/route setup you show above
App.Group = Ember.Object.extend().reopenClass
groups: []
find: ->
$.ajax
url: "/api/groups/"
type: "GET"
cache: false
dataType: "json"
beforeSend: =>
#groups.clear()
success: (results) =>
[#groups.addObject(App.Group.create(result)) for result in results]
error: =>
alert "error: failed to load the available groups"
#groups
App.Router.map ->
#resource "groups", path: "/", ->
#route "group", path: "/:group_id"
Related
We are seeing an issue with users unable to access our production and PPE apps via LinkedIn sign in. The redirection is not happening to specified redirect URL once users provides user name and password. The network trace shows login is successful but not going to redirect URL. This has been working last 4 years or so and suddenly started failing in both environments from yesterday.
Bummer. Something went wrong
We tried verifying the network trace and a support case is raised to LinkedIn with recording. Finally we are redirected to raise the issue here.
I had the same issue and found that it was caused by using JSON.stringify to "overload" the state parameter with other parameters. In my case, I add other parameters in the following way:
providerCfg.auth_params.state = JSON.stringify({
state: providerCfg.auth_params.state,
redirectPageUrl,
redirectParams,
userTypeBit,
isLogin
})
const authUrl = new URL(providerCfg.auth_url)
Object.entries(providerCfg.auth_params).forEach(([key, val]) => {
authUrl.searchParams.append(key, encodeURIComponent(val))
})
return buildURL(providerCfg.auth_url, providerCfg.auth_params)
When I removed the call to JSON.stringify and just passed in a state parameter, the oauth flow worked correctly. Obviously, the other parameters that I passed in were important so I created my own functions to serialize and deserialize the values. The code below works well for anything other than deeply nested objects. You will need to update the metaDataCfg based on your own requirements.
const META_STRING_DELIMITER = '|'
const serializeBasicObject = (targetObj) => {
if (!targetObj) {
return ''
}
return Object.entries(targetObj).reduce((objString, [key, val]) => {
const param = `${key}=${val || ''}`
if (!objString.length) {
return param
}
return `${objString}${META_STRING_DELIMITER}${param}`
}, '')
}
const deserializeBasicObject = (targetStr) => {
if (!targetStr) {
return ''
}
const keyValPairs = targetStr.split(META_STRING_DELIMITER)
return keyValPairs.reduce((targetObj, keyValPair) => {
const splitIdx = keyValPair.indexOf('=')
const key = keyValPair.slice(0, splitIdx)
targetObj[key] = keyValPair.slice(splitIdx + 1, keyValPair.length)
return targetObj
}, {})
}
const metaDataCfg = {
state: {},
redirectPageUrl: {},
redirectParams: {
serialize: serializeBasicObject,
deserialize: deserializeBasicObject
},
userTypeBit: { deserialize: Number },
isLogin: { deserialize: dataUtil.getBoolean }
}
const getMetaString = (metaData) => {
return Object.entries(metaDataCfg).reduce((metaString, [metaDataKey, cfg]) => {
const val = (cfg.serialize) ? cfg.serialize(metaData[metaDataKey]) : metaData[metaDataKey]
const param = `${metaDataKey}=${dataUtil.isNil(val) ? '' : val}`
if (!metaString.length) {
return param
}
return `${metaString}${META_STRING_DELIMITER}${param}`
}, '')
}
export const getDataFromMetaString = (metaString) => {
const params = metaString.split(META_STRING_DELIMITER)
const data = params.reduce((metaData, param) => {
const splitIdx = param.indexOf('=')
const key = param.slice(0, splitIdx)
let val = param.slice(splitIdx + 1, param.length)
if (dataUtil.isNil(val) || !val.length) {
return metaData
}
const deserializer = metaDataCfg[key].deserialize
if (deserializer && val) {
val = deserializer(val)
}
metaData[key] = val
return metaData
}, {})
return data
}
I currently have this in a simple MVC Api controller:
var rootFolder = Umbraco.TypedMedia(200);
return rootFolder.Children().Select(s => new MediaItem
{
Name = s.Name,
Children = s.Children.Select(e => new MediaItem
{
Name = e.Name
})
});
It works, but only return level 1 and 2.
I tried using
return rootFolder.Descendants(), which returns all results from all levels - but "flattened out", so I cannot see the structure in the output.
The output is used in a simple app, navigating a tree structure.
Any ideas, as to how I can make it recursive?
Using Descendants, the output is returned like this
[
{
"Name":"dok1"
},
{
"Name":"dok2"
},
{
"Name":"dok21"
}
]
But it should be
[
{
"Name":"dok1"
},
{
"Name":"dok2"
"Children": [
{
"Name":"dok21"
}
]
}
Not sure you really need recursion here -- the solution below (or something similar) should suffice
// Dictionary between level/depth(int) and the files on that level/depth
var fileDictionary = new Dictionary<int, List<MediaItem>>();
var children = rootFolder.Children();
var depth = 1;
while (children.Any())
{
var tempList = new List<MediaItem>();
children.ForEach(child => {
tempList.Add(child);
});
fileDictionary.Add(depth, tempList);
children = children.Children();
depth++;
}
Then, you can do something like:
foreach (var key in fileDictionary.Keys)
{
// Access the key by key.Key (key would be "depth")
// Access the values by fileDictionary[key] (values would be list of MediaItem)
}
Why not just create a recursive function like so?
IEnumerable<MediaItem> ConvertToMediaItems(IEnumerable<IPublishedContent> items)
{
return items?.Select(i => new MediaItem
{
Name = i.Name,
Children = ConvertToMediaItems(i.Children)
}) ?? Enumerable.Empty<MediaItem>();
}
Then the usage would be
var rootFolder = Umbraco.TypedMedia(200);
return ConvertToMediaItems(rootFolder.Children());
You can also make the function a local function if it's only needed in one place.
I have a viewmodel which consists of a list(foreach loop) of DoctorPrices and when clicking on an item in the list it open up a CRUD form on the side. However when i update the values on the CRUD the observableArray that is bound to the foreach is not refreshing? (although the values are updates in the DB correctly)
From my data access module i call the following query.
function getDoctorServices(doctorId) {
var query = breeze.EntityQuery
.from('DoctorPrices')
.where('DoctorID', 'eq', doctorId).orderBy('ListOrder');
return manager.executeQueryLocally(query);
}
In my viewmodel i have the following code:
this.services = ko.computed(function() {
return doctorServices.getDoctorServices(doctorList.viewModel.instance.currentDoctorID());
});
services is bound using a foreach loop (not posting here as the code is simple and works)
When i click on a one of the DoctorPrices it gets the data as follows and places it in an observable:
this.selectedPrice = function (data, event) {
self.currentService(data);
self.showEdit(true);
};
I then bind selectPrice to a simple form that has the properties on it to be modified by the user. I then call manager.SaveChanges().
This results in the following problem: the value is being updated correctly but the GUI / Original List that is bound in the foreach is not being updated? Are the properties in breeze not observables? What is the best way to work with something like this.
I thought of a workaround and changing the code with something like this:
doctorList.viewModel.instance.currentDoctorID.subscribe(function() {
self.services([]);
self.services(doctorServices.getDoctorServices(doctorList.viewModel.instance.currentDoctorID()));
});
But i feel that clearing the array in that way is sloppy and not the right way of doing things specially with long lists.
Can someone please point me in the right direction on how to bind observableArray properties properly so they are updated?
Additional code my VM Component:
function services() {
var self = this;
this.showForm = ko.observable(false);
this.currentService = ko.observable();
this.services = ko.observableArray(doctorServices.getDoctorServices(doctorList.viewModel.instance.currentDoctorID()));
this.title = ko.observable();
doctorList.viewModel.instance.currentDoctorID.subscribe(function() {
self.services([]);
self.services(doctorServices.getDoctorServices(doctorList.viewModel.instance.currentDoctorID()));
self.showDetails(false);
});
this.show = function (value) {
self.showForm(value);
};
this.showDetails = ko.observable(false);
this.addNewService = function() {
self.currentService(doctorServices.createService(doctorList.viewModel.instance.currentDoctorID()));
console.log(self.currentService().entityAspect.entityState);
self.showDetails(true);
};
this.showDelete = ko.computed(function() {
if (self.currentService() == null)
return false;
else if (self.currentService().entityAspect.entityState.isDetached()) {
self.title('Add new service');
return false;
} else {
self.title('Edit service');
return true;
}
});
this.deleteService = function() {
self.currentService().entityAspect.setDeleted();
doctorServices.saveChanges();
doctorList.viewModel.instance.currentDoctorID.notifySubscribers();
};
this.closeDetails = function () {
doctorServices.manager.rejectChanges();
doctorList.viewModel.instance.currentDoctorID.notifySubscribers();
self.showDetails(false);
};
this.selectService = function (data, event) {
self.currentService(data);
self.showDetails(true);
};
this.saveChanges = function () {
console.log(self.currentService().entityAspect.entityState);
if (self.currentService().entityAspect.entityState.isDetached()) {
doctorServices.attachEntity(self.currentService());
}
console.log(self.currentService().entityAspect.entityState);
doctorServices.saveChanges();
doctorList.viewModel.instance.currentDoctorID.notifySubscribers();
self.currentService.notifySubscribers();
self.showDetails(true);
};
}
return {
viewModel: {
instance: new services()
},
template: servicesTemplate,
};
Below is my Breeze Data Class:
define('data/doctorServices', ['jquery', 'data/dataManager', 'knockout','mod/medappBase', 'breeze', 'breeze.savequeuing'], function ($, manager, ko,base, breeze, savequeuing) {
var services = ko.observableArray([]);
return {
attachEntity:attachEntity,
getServices: getServices,
services: services,
manager:manager,
getDoctorServices: getDoctorServices,
getServiceById: getServiceById,
createService:createService,
hasChanges: hasChanges,
saveChanges: saveChanges
};
function getServices() {
var query = breeze.EntityQuery.from("DoctorPrices");
return manager.executeQuery(query).then(function (data) {
services(data.results);
}).fail(function (data) {
console.log('fetch failed...');
console.log(data);
});;
}
function getDoctorServices(doctorId) {
var query = breeze.EntityQuery
.from('DoctorPrices')
.where('DoctorID', 'eq', doctorId).orderBy('ListOrder');
var set = manager.executeQueryLocally(query);
return set;
}
function getServiceById(serviceId) {
return manager.createEntity('DoctorPrice', serviceId);
//return manager.getEntityByKey('DoctorPrice', serviceId);
}
function handleSaveValidationError(error) {
var message = "Not saved due to validation error";
try { // fish out the first error
var firstErr = error.innerError.entityErrors[0];
message += ": " + firstErr.errorMessage;
base.addNotify('error', 'Could not save.', message);
} catch (e) { /* eat it for now */ }
return message;
}
function hasChanges() {
return manager.hasChanges();
}
function attachEntity(entity) {
manager.addEntity(entity);
}
function createService(doctorId) {
return manager.createEntity('DoctorPrice', { DoctorPricingID: breeze.core.getUuid(), DoctorID:doctorId }, breeze.EntityState.Detached);
};
function saveChanges() {
return manager.saveChanges()
.then(saveSucceeded)
.fail(saveFailed);
function saveSucceeded(saveResult) {
base.addNotify('success', 'Saved.', 'Your updates have been saved.');
}
function saveFailed(error) {
var reason = error.message;
var detail = error.detail;
if (error.innerError.entityErrors) {
reason = handleSaveValidationError(error);
} else if (detail && detail.ExceptionType &&
detail.ExceptionType.indexOf('OptimisticConcurrencyException') !== -1) {
// Concurrency error
reason =
"Another user, perhaps the server, " +
"may have deleted one or all of the settings." +
" You may have to restart the app.";
} else {
reason = "Failed to save changes: " + reason +
" You may have to restart the app.";
}
console.log(error);
console.log(reason);
}
}
});
Please note this is my frist attempt at both a data class and VM. At the moment i am relying heavily on clearing the array ([]) and using notifySubscribers to make the array refresh :(
I bet you're missing an observable somewhere. I can't tell because you keep hopping from property to property whose definition is not shown.
For example, I don't know how you defined this.currentService.
I'm confused by this:
this.services = ko.computed(function() {
return doctorServices.getDoctorServices(doctorList.viewModel.instance.currentDoctorID());
});
Why is it a ko.computed? Why not just make it an observable array.
self.service = ko.observableArray();
// ... later replace the inner array in one step ...
self.service(doctorServices.getDoctorServices(
doctorList.viewModel.instance.currentDoctorID()));
I urge you to follow the observability trail, confident that your Breeze entity properties are indeed observable.
vm.selectedPrice = ko.dependentObservable(function () {
return doctorServices.getDoctorServices(doctorList.viewModel.instance.currentDoctorID());
}, vm);
vm is ur model on which u applied bindings , try this it will work.
I am trying to do a simple CRUD app using Ember + Rails and I'm getting the following error when trying to go to the /workouts route.
Error while loading route: TypeError {} ember.js?body=1:415
Uncaught TypeError: Object function () {
if (!wasApplied) {
Class.proto(); // prepare prototype...
}
o_defineProperty(this, GUID_KEY, undefinedDescriptor);
o_defineProperty(this, '_super', undefinedDescriptor);
var m = meta(this), proto = m.proto;
m.proto = this;
if (initMixins) {
// capture locally so we can clear the closed over variable
var mixins = initMixins;
initMixins = null;
this.reopen.apply(this, mixins);
}
if (initProperties) {
// capture locally so we can clear the closed over variable
var props = initProperties;
initProperties = null;
var concatenatedProperties = this.concatenatedProperties;
for (var i = 0, l = props.length; i < l; i++) {
var properties = props[i];
Ember.assert("Ember.Object.create no longer supports mixing in other definitions, use createWithMixins instead.", !(properties instanceof Ember.Mixin));
for (var keyName in properties) {
if (!properties.hasOwnProperty(keyName)) { continue; }
var value = properties[keyName],
IS_BINDING = Ember.IS_BINDING;
if (IS_BINDING.test(keyName)) {
var bindings = m.bindings;
if (!bindings) {
bindings = m.bindings = {};
} else if (!m.hasOwnProperty('bindings')) {
bindings = m.bindings = o_create(m.bindings);
}
bindings[keyName] = value;
}
var desc = m.descs[keyName];
Ember.assert("Ember.Object.create no longer supports defining computed properties.", !(value instanceof Ember.ComputedProperty));
Ember.assert("Ember.Object.create no longer supports defining methods that call _super.", !(typeof value === 'function' && value.toString().indexOf('._super') !== -1));
Ember.assert("`actions` must be provided at extend time, not at create time, when Ember.ActionHandler is used (i.e. views, controllers & routes).", !((keyName === 'actions') && Ember.ActionHandler.detect(this)));
if (concatenatedProperties && indexOf(concatenatedProperties, keyName) >= 0) {
var baseValue = this[keyName];
if (baseValue) {
if ('function' === typeof baseValue.concat) {
value = baseValue.concat(value);
} else {
value = Ember.makeArray(baseValue).concat(value);
}
} else {
value = Ember.makeArray(value);
}
}
if (desc) {
desc.set(this, keyName, value);
} else {
if (typeof this.setUnknownProperty === 'function' && !(keyName in this)) {
this.setUnknownProperty(keyName, value);
} else if (MANDATORY_SETTER) {
Ember.defineProperty(this, keyName, null, value); // setup mandatory setter
} else {
this[keyName] = value;
}
}
}
}
}
finishPartial(this, m);
this.init.apply(this, arguments);
m.proto = proto;
finishChains(this);
sendEvent(this, "init");
} has no method 'find'
My code is located here: https://github.com/ecl1pse/ember-workouts
What am I doing wrong?
Edit: Upon further investigation I believe the culprit is
EmberWorkouts.WorkoutsRoute = Ember.Route.extend(
model: -> EmberWorkouts.Workout.find()
This doesn't actually return anything. How do I debug from there?
If I replace that with this
EmberWorkouts.WorkoutsRoute = Ember.Route.extend
model: -> [{title: 'hi'}, {title: 'damn'}]
The view actually renders content.
How do I get the model to collect from Rails properly?
Ember Data's interface has changed a little with the current release:
You can clear out the store.js file entirely. Ember Data will automatically set up a data store for you using the REST Adapter (unless you tell it otherwise).
Use model: -> #store.find('workout') instead.
I tested this with your app and it works.
If you haven't read through the Ember Data Guide in the last week or two (it's changed a lot), I would spend a few minutes on it.
The fix for this error (as of ember-data 1.0.0.beta.6) for me was to make sure that the JSON returned from the server included an "id" field for each model, BUT not to explicitly declare the id when setting up the Ember DS.Model.
jbuilder template:
json.scans do
json.array! #scans do |scan|
json.id scan.id # This prop has to be there
json.name scan.name
end
end
Ember model:
EmberApp.Scan = DS.Model.extend(
// Don't include the id prop here
name: DS.attr("string")
)
I have a page with dynamic list boxes(selecting value from the first list populates the values in the second list box).
The validation errors for the list boxes are working fine, but while displaying the error messages the page is getting refreshed and the selected values are been set to initial status(need to select the values again in the list boxes)
The page is designed to add any number of list boxes using ajax calls, so adding and selecting the values again is going to be a rework.
Could you help me in displaying the validation errors and keeping the selected values as they are(previously I faced a similar situation which was resolved by replacing local variables of preprocess and postprocess with a global variable, this time no luck with that approach)
Any hints/help would be great
static constraints = {
deviceMapping(
validator: {val, obj ->
Properties dm = (Properties) val;
def deviceCheck = [:];
if (obj.customErrorMessage == null) {
for (def device : dm) {
if (device.key == null || "null".equalsIgnoreCase(device.key)) {
return ["notSelected"];
}
deviceCheck.put(device.key, "");
}
if (deviceCheck.size() != obj.properties["numberOfDevices"]) {
return ["multipleDevicesError"];
}
}
}
)
customErrorMessage (
validator: {
if ("sameDeviceMultipleTimes".equals(it)) {
return ['sameDeviceMultipleTimes']
}
}
)
}
public LinkedHashMap<String, Object> preProcess(sessionObject, params, request) {
Submission submission = (Submission) sessionObject;
def selectedFileName = sessionObject.fileName;
logger.debug("submission.deviceMapping :"+submission.deviceMapping)
try {
Customer customer = Customer.get(submission.customerId);
OperatingSystem operatingSystem = OperatingSystem.get(submission.operatingSystemId)
def ftpClientService = new FtpClientService();
def files = ftpClientService.listFilesInZip(customer.ftpUser, customer.ftpPassword, customer.ftpHost, customer.ftpToPackageDirectory, selectedFileName, operatingSystem, customer.ftpCustomerTempDirectory);
def terminalService = new TerminalService();
OperatingSystem os = OperatingSystem.get(submission.getOperatingSystemId());
def manufacturers = terminalService.getAllDeviceManufacturersForType(os.getType());
logger.debug("manufacturers after os type :"+manufacturers)
logger.debug("files in preprocess :"+files)
def devicesForFiles = [:]
files.each { file ->
def devicesForThisFile = [];
submission.deviceMapping.each { device ->
if (device.value == file.fileName) {
String manufacturer = terminalService.getManufacturerFromDevice("${device.key}");
def devicesForManufacturer = terminalService.getDevicesForManufacturerAndType(manufacturer, os.getType());
devicesForThisFile.push([device:device.key, manufacturer: manufacturer, devicesForManufacturer: devicesForManufacturer]);
}
}
devicesForFiles.put(file.fileName,devicesForThisFile);
}
logger.debug("devicesForFiles :"+devicesForFiles)
return [command: this, devicesForFiles: devicesForFiles, files: files, manufacturers: manufacturers];
} catch (Exception e) {
logger.warn("FTP threw exception");
logger.error("Exception", e);
this.errors.reject("mapGameToDeviceCommand.ftp.connectionTimeOut","A temporary FTP error occurred");
return [command: this];
}
}
public LinkedHashMap<String, Object> postProcess(sessionObject, params, request) {
Submission submission = (Submission) sessionObject;
Properties devices = params.devices;
Properties files = params.files;
mapping = devices.inject( [:] ) { map, dev ->
// Get the first part of the version (up to the first dot)
def v = dev.key.split( /\./ )[ 0 ]
map << [ (dev.value): files[ v ] ]
}
deviceMapping = new Properties();
params.files.eachWithIndex { file, i ->
def device = devices["${file.key}"];
if (deviceMapping.containsKey("${device}")) {
this.errors.reject("You cannot use the same device more than once");
return [];
//customErrorMessage = "sameDeviceMultipleTimes";
}
deviceMapping.put("${device}", "${file.value}");
}
if (params.devices != null) {
this.numberOfDevices = params.devices.size(); //Used for the custom validator later on
} else {
this.numberOfDevices = 0;
}
//logger.debug("device mapping :"+deviceMapping);
submission.deviceMapping = mapping;
return [command: this, deviceMapping: mapping, devicesForFiles: devicesForFiles ];
}
}
The problem is in your gsp page. Be sure that all field are initialised with a value
<g:text value="${objectInstance.fieldname}" ... />
Also the way it is selecting values is through id, so be sure to set it as well:
<g:text value="${objectInstance.fieldname}" id=${device.manufacturer.id} ... />