I've written a custom editor for Slickgrid which shows drop down box with items from sql db. These items are in the formal [id] [text]
I wish to store the [id] into the slickgrid data, but show the [text] to the user. There doesn't appear to be a callback for "display" rather than "store", so not sure how to do this? Hopefully I don't have to write a custom renderer as well?
eg.
this.init = function () {
$select = $("<SELECT tabIndex='0' class='editor-result'><OPTION value='1'>Passed</OPTION><OPTION value='0'>Failed</OPTION></SELECT>");
$select.appendTo(args.container);
$select.focus();
};
this.serializeValue = function () {
return $select.val();
};
this.applyValue = function (item, state) {
item[args.column.field] = state;
};
It appears that yes, I do have to add a custom renderer (or rather, a formatter), but it's quite simple, so not so bad:
function ResultFormatter(row, cell, value, columnDef, dataContext) {
return value ? "Passed" : "Failed";
}
Related
I have legacy html data that I'm trying to edit with CKEditor5. The data format is:
<my-data-element url="https://something.com/more/stuff"></my-data-element>
The desired model format is
<my-model-element>
https://something.com/more/stuff
</my-model-element>
where the url attribute in the data is now the text of the model element. my-model-element is an editable widget so the user can easily modify the existing URL, copy/paste/etc. When the model is convert to data, the text in my_model-element should be converted to the url value for my-data-element. Reading the value of the url attribute is relatively easy, but I can't figure out how to set the text of the my-model-element. While this looks similar to a link, it's not a link. I considered borrowing from the link editing code, but that's a lot of code and this should be a root level object.
For data down casting, extracting the value of the element to set as the url is easy. The code below leaves the text of my-model-element in my-data-element but I can deal with that for now. It also results in my-data-element having the attribute undefined="undefined", for some reason, but I can also live with that.
schema.register( 'my-model-element', {
isObject: true,
allowWhere: '$block',
allowAttributes: ['url'],
allowContentOf: '$block'
} );
conversion.for( 'dataDowncast' ).elementToElement( {
model: 'myElement',
view: ( modelItem, {writer: viewWriter } ) => {
const data = modelItem.getChild(0).data;
const elem = viewWriter.createContainerElement (
'my-data-element', { url: data }
);
return elem;
}
} );
conversion.for( 'dataDowncast' ).attributeToAttribute( {
model: 'url',
// view has to be a function or the url doesn't get updated
view: () => 'url',
});
For up casting I can get the url from my-data-element, but have not been successful setting the text of my-model-element. Instead, the text value of my-model-element remains empty.
conversion.for( 'upcast' ).elementToElement( {
model: ( viewElement, {writer: modelWriter }) => {
// Pulling the URL works
const url = viewElement.getAttribute('url');
// But creating the child of the new element doesn't
const text = modelWriter.createText(`${url} DOESNT WORK`);
const elem = modelWriter.createElement('my-model-element', {}, text);
return elem;
},
view: {
name: 'my-data-element',
}
} );
I've read the majority of the CKEditor5 documentation on up and down casting, and the tutorials on block, inline, and data driven widgets.
I want to create a table that represents the data from a model that i have loaded in the forge viewer.
i want the table to be in a docking panel, and show the propertys for elements in the model ( level, name, comment,Area, type name--> for each element [if it has the property])
i have tried to use the API reference, and create a DataTable, but i did not find examples of how to actually impliment it.
where and when do i need to set the datatable? ( after or before creating the docking pannel?)
what is the content of the arrays that i should pass in the constructor? ( according to the documentation : array of arrays for the rows, and array for the columns. is the row array, is simply an array that contains the columns arrays?)
this is my current code for the extension that shows the amount to instances in the model for each property that i want to adjust:
'''
class testTest extends Autodesk.Viewing.Extension {
constructor(viewer, options) {
super(viewer, options);
this._group = null;
this._button = null;
}
load() {
console.log('testTest has been loaded');
return true;
}
unload() {
// Clean our UI elements if we added any
if (this._group) {
this._group.removeControl(this._button);
if (this._group.getNumberOfControls() === 0) {
this.viewer.toolbar.removeControl(this._group);
}
}
console.log('testTest has been unloaded');
return true;
}
// The Viewer contains all elements on the model, including categories (e.g. families or part definition),
//so we need to enumerate the leaf nodes, meaning actual instances on the model.
getAllLeafComponents(callback) {
this.viewer.getObjectTree(function (tree) {
let leaves = [];// an empty 'leaf' list that we want to fill wiith the objects that has no mo children
//dbId== object id
// for each child that we enumerate from a root, call a code , and finally a true /false flag parameter that run the function recursivly for all the children of a child.
tree.enumNodeChildren(tree.getRootId(), function (dbId) {
if (tree.getChildCount(dbId) === 0) {
leaves.push(dbId);// if the object has no children--> add it to the list.
}
}, true);// the last argument we past ("true") will make sure that the function in the seccond argument ("function (dbId)(...)" ")will run recursively not only for the children of the roots,
//but for all the children and childrtn's children.
callback(leaves);//return the leaves
});
}
onToolbarCreated() {
// Create a new toolbar group if it doesn't exist
this._group = this.viewer.toolbar.getControl('allMyAwesomeExtensionsToolbar');//if there is no controller named "allMyAwesomeExtensionsToolbar" create one
if (!this._group) {
this._group = new Autodesk.Viewing.UI.ControlGroup('allMyAwesomeExtensionsToolbar');
this.viewer.toolbar.addControl(this._group);// add the control to tool bar
}
// Add a new button to the toolbar group
this._button = new Autodesk.Viewing.UI.Button('testTest');
this._button.onClick = (ev) => {
// Check if the panel is created or not
if (this._panel == null) {//check if there is an instance of our pannel. if not- create one
this._panel = new ModelSummaryPanel(this.viewer, this.viewer.container, 'modelSummaryPanel', 'Model Summary');
}
// Show/hide docking panel
this._panel.setVisible(!this._panel.isVisible());//cal a method from the parent to show/ hide the panel -->use this to toggle from visible to invisible
this._panel.set
// If panel is NOT visible, exit the function
if (!this._panel.isVisible())
return;
// First, the viewer contains all elements on the model, including
// categories (e.g. families or part definition), so we need to enumerate
// the leaf nodes, meaning actual instances of the model. The following
// getAllLeafComponents function is defined at the bottom
this.getAllLeafComponents((dbIds) => {// now we have the list of the Id's of all the leaves
// Now for leaf components, let's get some properties and count occurrences of each value
debugger;
const filteredProps = ['Level','Name','Comments','Area','Type Name'];
// Get only the properties we need for the leaf dbIds
this.viewer.model.getBulkProperties(dbIds,filteredProps , (items) => {
// Iterate through the elements we found
items.forEach((item) => {
// and iterate through each property
item.properties.forEach(function (prop) {
// Use the filteredProps to store the count as a subarray
if (filteredProps[prop.displayName] === undefined)
filteredProps[prop.displayName] = {};
// Start counting: if first time finding it, set as 1, else +1
if (filteredProps[prop.displayName][prop.displayValue] === undefined)
filteredProps[prop.displayName][prop.displayValue] = 1;
else
filteredProps[prop.displayName][prop.displayValue] += 1;
});
});
// Now ready to show!
// The PropertyPanel has the .addProperty that receives the name, value
// and category, that simple! So just iterate through the list and add them
filteredProps.forEach((prop) => {
if (filteredProps[prop] === undefined) return;
Object.keys(filteredProps[prop]).forEach((val) => {
this._panel.addProperty(val, filteredProps[prop][val], prop);
this.dt = new DataTabe(this._panel);
this.dt.setData()
});
});
});
});
};
this._button.setToolTip('Or Levis extenssion');
this._button.addClass('testTest');
this._group.addControl(this._button);
}
}
Autodesk.Viewing.theExtensionManager.registerExtension('testTest', testTest);'''
'''
We have a tutorial with step-by-step on how to do it.
Please, refer to Dashboard tutorial, specifically in Data Grid section.
In this case, the tutorial uses an external library (Tabulator) to show the data.
See fiddle here: https://fiddle.sencha.com/#fiddle/2iig&view/editor
The docs (https://docs.sencha.com/extjs/6.6.0/classic/Ext.ux.TreePicker.html#event-change) list 'change' in the events section but when I set the value or reset the field this event never fires. The 'select' event fires as expected but that only fires when the user selects a field.
EDIT:
Based on Snehal's suggestion below, I was able to accomplish this using the following override. Not sure if there is a simpler way to do it but this was the best I could manage:
Ext.define('MyApp.overrides.TreePicker', {
override: 'Ext.ux.TreePicker',
setValue: function (value) {
var me = this,
record;
me.value = value;
if (me.store.loading) {
// Called while the Store is loading. Ensure it is processed by the onLoad method.
return me;
}
// try to find a record in the store that matches the value
record = value ? me.store.getNodeById(value) : me.store.getRoot();
if (value === undefined) {
record = me.store.getRoot();
me.value = record.getId();
} else {
record = me.store.getNodeById(value);
}
// zeke - this is the only line I added to the original source
// without this the 'change' event is not fired
me.callSuper([value]);
// set the raw value to the record's display field if a record was found
me.setRawValue(record ? record.get(me.displayField) : '');
return me;
}
});
Because setValue function does not call this.callParent(). You can do something like this in setValue function.
setValue: function(value) {
var me = this,
record;
if (me.store.loading) {
// Called while the Store is loading. Ensure it is processed by the onLoad method.
return me;
}
// try to find a record in the store that matches the value
record = value ? me.store.getById(value) : me.store.getRoot();
me.callParent([record.get('valueField')]);
return me;
},
OK, I'm obviously not understanding how functions are used in javascript. Given the below code snippet, mozilla firefox is telling me that calcUpper is not defined. Basically I want to define a function and use that function later on in the view on different fields. I tried moving the function definition outside of the kendo model, but with no better results. Can someone show me how I can achieve this?
var viewModel = kendo.observable({
calcUpper: function (fieldName) {
var value = this.get(fieldName);
if (value == "")
return "";
else
return parseInt(value) - 1;
},
jobNum: '',
SRCPerif: '',
SRCOnTargetUpper: calcUpper('SRCPerif'),
SRCOnTargetLower: '',
SRCConcernUpper: calcUpper('SRCOnTargetLower'),
//...other fields...
});
I'm using a marionette compositeview to display a collection, the code is below.
However, inside my collection I have 5 filters that are bound to events that then update the collection from the API.
The way I see it, theres two options, I would like opinions on which is better:
1) Use a layout view, somehow figure out how a compositeview can catch the filter views options and update the collection.
2) Use onRender to display the filter views and again catch the events in the compositeview
define(["marionette", "text!app/templates/posts/collection.html", "app/collections/posts", "app/views/posts/item"],
function(Marionette, Template, Collection, Item) {
"use strict"
return Backbone.Marionette.CompositeView.extend({
template: Template,
itemView: Item,
itemViewContainer: "tbody",
filter: {
from: 0,
to: 15,
publish_target: null,
status: null,
type: null,
publish_from_date: null, //publish_from_date=2014-01-07
publish_to_date: null, //publish_to_date=2014-01-07
publish_from_time: null, //publish_from_time=01%3A00%20AM
publish_to_time: null, //publish_to_time=12%3A30%20AM
location_id: null,
client_id: null
},
events: {
'change .filterBy': 'onClickFilter',
'change .filterByDate': 'onClickFilterDate'
},
collectionEvents: {
'sync': 'hideLoading'
},
initialize: function(options) {
//set loading, important we do this because we re-trigger the collection
this.setLoading();
// don't call a new collection unless its the init load, we lose collection automatically triggered events otherwise
if (_.isEmpty(options) || !_.has(options, 'newCollection')) {
this.collection = new Collection()
}
//strip any null key values from this.filter so the api doesnt filter crap
this.filter = _.cleanNullFieldsFromObject(this.filter);
//fetch the collection
return this.collection.fetch({data: this.filter})
},
// date was triggered, so get the details
onClickFilterDate: function() {
var publishFrom = new Date($('#publish_from_date').val());
var publishTo = new Date($('#publish_to_date').val());
this.filter.publish_from_date = _.dateToYMD(publishFrom);
this.filter.publish_to_date = _.dateToYMD(publishTo);
this.filter.publish_from_time = _.dateToHM(publishFrom);
this.filter.publish_to_time = _.dateToHM(publishTo);
// from time is greater than two time, then fetch the collection
if ( (publishFrom.getTime() / 1000) < (publishTo.getTime() / 1000) ) {
this.initialize({newCollection: true});
}
},
// a typical filter is clicked, so figure out whats happening
onClickFilter: function (ev) {
var type = $('#'+ev.currentTarget.id).data('type')
switch (type) {
case 'status':
this.filter.status = $('#filterStatus').val();
break;
case 'publish_target':
this.filter.publish_target = $('#filterPublishTarget').val();
break;
case 'type':
this.filter.type = $('#filterType').val();
break;
case 'client_id':
this.filter.client_id = $('#filterClientId').val();
break;
case 'location_id':
this.filter.location_id = $('#filterLocationId').val();
break;
}
this.initialize({newCollection: true});
},
hideLoading: function() {
this.$el.find('.loading-latch').removeClass('loading-active');
},
//set loading by appending to the latch
setLoading: function() {
this.$el.find('.loading-latch').addClass('loading-active');
}
})
})
define(["marionette", "text!app/templates/posts/item.html"],
function(Marionette, Template) {
"use strict"
return Backbone.Marionette.ItemView.extend({
template: Template,
tagName: "tr",
initialize: function () {
this.model.set('statusReadable', this.model.getStatus());
}
})
})
I actually went ahead and built both. I prefer the layout view.
Both have the concept of having a filter object that fetches and passes query params to the api.
Here are both solutions.
Using a layout that catches filter:changes vents to then update a filter object that gets passed into the collection view (I favour this because only the collection gets redrawn when a filter is changed)
http://pastebin.com/XNmQjs1i
Using a collection view that catches class events and redraws the view (I don't favour this because everything gets redrawn everytime the user changes a filter)
http://pastebin.com/WML2iiM4
I'm sure this might come in handy for marionette newbies, theres a lot of my learning poured into this. Enjoy.