Excel like behavior with SlickGrid - javascript

I'd like to mimic a particular behavior of spreadsheets with SlickGrid. The user:
clicks on a cell to activate it
enters =sum(, or whatever formula,
the original cell address is saved
the user selects the cell range (I assume that the original cell closes the editor)
focus is returned to the original cell with the new cell range appended. i.e. =sum(r1c1,r2c2).
What's throwing me off is the need to change focus.
var cell_with_formula = null;
grid = new Grid($("#myGrid"), data, columns, options);
// save original cell address, but there is no onBlur event
grid.onBlur = function(args){
cell_with_formula = args; // save address
};
grid.onCellRangeSelected = function(){
if(cell_with_formula){
// check if cell_with_formula has `=` at begining
// if so, append selected range
cell_with_formula = null;
}
};
Thanks in advance!

This is not possible in SlickGrid 1.4.x, but is going to be supported in the version 2.0 that is currently still under active development. The alpha is hosted in a separate branch on GitHub - https://github.com/mleibman/SlickGrid/tree/v2.0a, and I just checked in preliminary code that supports this with an example. Please see https://github.com/mleibman/SlickGrid/commit/17b1bb8f3c43022ee6aec89dcab185cd368b8785.
Here's a basic formula editor implementation:
function FormulaEditor(args) {
var _self = this;
var _editor = new TextCellEditor(args);
var _selector;
$.extend(this, _editor);
function init() {
// register a plugin to select a range and append it to the textbox
// since events are fired in reverse order (most recently added are executed first),
// this will override other plugins like moverows or selection model and will
// not require the grid to not be in the edit mode
_selector = new Slick.CellRangeSelector();
_selector.onCellRangeSelected.subscribe(_self.handleCellRangeSelected);
args.grid.registerPlugin(_selector);
}
this.destroy = function() {
_selector.onCellRangeSelected.unsubscribe(_self.handleCellRangeSelected);
grid.unregisterPlugin(_selector);
_editor.destroy();
};
this.handleCellRangeSelected = function(e, args) {
_editor.setValue(_editor.getValue() + args.range);
};
init();
}

Related

Script affects all divs, how can I get it to affect just one?

I am using this function in NW.JS to get a file locations of images. I use that file location in the callback to modify a div background using .css() in jquery. My problem is that the script seems to remember the last div that it modified. When I try to use this to change the background of another div after previously having used it to change the background on a different div BOTH divs change their backgrounds. I guess I need to be able to get this script to know what button clicked it and to forget anything that another button asked it to do. As you can tell I am new to javascript. How can I do that?
function chooseFile(name, handleFile) {
var chooser = document.querySelector(name);
chooser.addEventListener("change", function(evt) {
for(var f of this.files){
console.log(f.name);
console.log(f.path);
handleFile(f.name, f.path);
}
}, false);
chooser.click();
}
chooseFile('#fileDialog', function(name, path){ ... /* do something with the file(s) */ });
In many cases, it’ll make sense to write your script so that it can react to new files:
const chooser = document.getElementById('fileDialog');
// Treat chooser as a stream of new files that can be added at any time
chooser.addEventListener("change", function (evt) {
for (const f of this.files) {
console.log(f.name);
console.log(f.path);
handleFile(f.name, f.path);
}
}, false);
// Then, when you want to prompt for a new file at any point in the future…
function promptForFiles() {
chooser.click();
}
When that’s not possible, you can instead have it hold a maximum of one handler at a time by assigning to the old but reliable onchange property:
function chooseFile(name, handleFile) {
const chooser = document.querySelector(name);
chooser.onchange = function () {
for (const f of this.files) {
console.log(f.name);
console.log(f.path);
handleFile(f.name, f.path);
}
};
chooser.click();
}

Cannot update Editable grid in Azure portal extension

I'm developing an Azure portal extension that includes an editable grid blade.
The grid's blade is opened from a selector and therefore contains a parameter provider.
The grid is initialized with the edit scope of the parameter provider, which is an observable array, and enabled for row editing and inserting new rows.
When I try to update the grid, changes to existing rows are not shown and creating new line yields an empty line, like below:
I don't see any errors in the console while debugging.
Here's how I initialized the grid:
private _initialize(container: MsPortalFx.ViewModels.PartContainerContract): void {
var extensions: number = MsPortalFx.ViewModels.Controls.Lists.Grid.Extensions.EditableRow | MsPortalFx.ViewModels.Controls.Lists.Grid.Extensions.ContextMenuShortcut,
extensionsOptions: MsPortalFx.ViewModels.Controls.Lists.Grid.ExtensionsOptions<DataModels.IItem, DataModels.ISelectionItem>,
viewModel: MsPortalFx.ViewModels.Controls.Lists.Grid.ViewModel<DataModels.IItem, DataModels.ISelectionItem>;
// Set up the editable extension options.
extensionsOptions = this._createExtensionsOptions();
// Create the grid view model.
viewModel = new MsPortalFx.ViewModels.Controls.Lists.Grid.ViewModel<DataModels.IItem, DataModels.ISelectionItem>(
container,
null,
extensions,
extensionsOptions);
viewModel.showHeader = true;
viewModel.columns(this._columns);
viewModel.rowAdd = () => {
// code that extension authors need to execute when a row is added should go here.
};
this.editableGrid = viewModel;
}
private _createExtensionsOptions(): MsPortalFx.ViewModels.Controls.Lists.Grid.ExtensionsOptions<DataModels.IItem, DataModels.ISelectionItem> {
return <MsPortalFx.ViewModels.Controls.Lists.Grid.ExtensionsOptions<DataModels.IItem, DataModels.ISelectionItem>>{
editableRow: {
// Supplies editable items to the grid.
editScope: this.parameterProvider.editScope,
// put the new row at the top.
placement: MsPortalFx.ViewModels.Controls.Lists.Grid.EditableRowPlacement.Bottom,
// Create no more than 5 new rows.
maxBufferedRows: 5,
// Allow the modification of existing items.
allowEditExistingItems: true,
// Allow the creation of new items.
allowEditCreatedItems: true,
// Track the valid status
valid: ko.observable<boolean>()
}
};
}
I've reviewed several sample blades of editable grid but couldn't identify what am I doing wrong.
The problem was that I didn't set the editScopeMetadataType property of the parameter provider, which required for determining the edit scope's entity type:
this.parameterProvider = new MsPortalFx.ViewModels.ParameterProvider<DataModels.IItem[], KnockoutObservableArray<DataModels.SchemaItem>>(container, {
// This was missing.
editScopeMetadataType: wrapperTypeMetadataName,
mapIncomingDataForEditScope: (incoming) => {
return ko.observableArray(incoming);
},
mapOutgoingDataForCollector: (outgoing) => {
return outgoing();
}
});

Filtering Sigma.Js graph from external events

I have an instance of Sigma.Js 1.0.0 rendering a graph in my Canvas element. (The code is below, but you can simply scroll to Step 2 of Tutorial on the main sigmajs.org page.
As you can see from that code, when the node is clicked, clickNode event occurs, which then applies filtering to the graph, showing only the clicked node and its neighborhood and dimming the others. That's quite clear.
However, how would I make exactly the same thing happen from the outside? Suppose I have the graph rendered already and I have a Tag Cloud next to it. And I want that when I click on a #hashtag, only that node is shown in the graph and the rest are dimmed. How would I do that?
Thanks!
<div id="sigma-container"></div>
<script src="path/to/sigma.js"></script>
<script src="path/to/sigma.parsers.min.gexf.js"></script>
<script>
// Add a method to the graph model that returns an
// object with every neighbors of a node inside:
sigma.classes.graph.addMethod('neighbors', function(nodeId) {
var k,
neighbors = {},
index = this.allNeighborsIndex[nodeId] || {};
for (k in index)
neighbors[k] = this.nodesIndex[k];
return neighbors;
});
sigma.parsers.gexf(
'path/to/les-miserables.gexf',
{
container: 'sigma-container'
},
function(s) {
// We first need to save the original colors of our
// nodes and edges, like this:
s.graph.nodes().forEach(function(n) {
n.originalColor = n.color;
});
s.graph.edges().forEach(function(e) {
e.originalColor = e.color;
});
// When a node is clicked, we check for each node
// if it is a neighbor of the clicked one. If not,
// we set its color as grey, and else, it takes its
// original color.
// We do the same for the edges, and we only keep
// edges that have both extremities colored.
s.bind('clickNode', function(e) {
var nodeId = e.data.node.id,
toKeep = s.graph.neighbors(nodeId);
toKeep[nodeId] = e.data.node;
s.graph.nodes().forEach(function(n) {
if (toKeep[n.id])
n.color = n.originalColor;
else
n.color = '#eee';
});
s.graph.edges().forEach(function(e) {
if (toKeep[e.source] && toKeep[e.target])
e.color = e.originalColor;
else
e.color = '#eee';
});
// Since the data has been modified, we need to
// call the refresh method to make the colors
// update effective.
s.refresh();
});
// When the stage is clicked, we just color each
// node and edge with its original color.
s.bind('clickStage', function(e) {
s.graph.nodes().forEach(function(n) {
n.color = n.originalColor;
});
s.graph.edges().forEach(function(e) {
e.color = e.originalColor;
});
// Same as in the previous event:
s.refresh();
});
}
);
</script>
<!-- [...] -->
I hope this goes some way to answering your question.
You have a tagcloud full of words, and when a word is clicked, you want to trigger the neighbors method on your sigma instance, for which you need the node id.
Simply put, you need the function which is called when the #hashtag is clicked, to be in the same scope as the sigma instantiation.
s= new sigma({
settings: {...}
})
//more code instantiating methods etc
//let's assume your tags are in elements with class='tagword' and have the hashtag stored in a 'name' attribute
$('.tagword').on('click', function(){
var name = this.attr('name');
s.graph.nodes().forEach(function(n){
if (n.label == name){
//use the node to trigger an event in sigma
//i.e. s.graph.neighbors(n.id);
};
};
};

YUI Library - Best way to keep global reference to object?

I'm trying to use the yahoo ui history library. I don't see a great way to avoid wrapping all my function contents with the Y.use so that I can get access to the history object. I tried declaring it globally outside of the use() command, but this didn't seem to work. If you look at my showDashboard() and showReport1() methods, you can see I'm wrapping the contents, which seems redundant to have to do this for every function that uses the history. Is there a better way to do this?
All of the yahoo examples I've seen don't se functions at all and keep the entire sample inside a single use method.
<div>
Dashboard |
Report 1
</div>
<script type="text/javascript">
// Global reference to Yahoo UI object
var Y = YUI();
function showDashboard() {
Y.use('*', function (Y) {
var history = new Y.HistoryHash();
history.addValue("report", "dashboard");
});
}
function showReport1() {
Y.use('*', function (Y) {
var history = new Y.HistoryHash();
history.addValue('report', "report1");
//var x = { 'report': 'report1', 'date': '11/12/2012' };
//history.addValue("report", x);
});
}
Y.use('history', 'tabview', function (Y) {
var history = new Y.HistoryHash();
var tabview = new Y.TabView({ srcNode: '#demo' });
// Render the TabView widget to turn the static markup into an
// interactive TabView.
tabview.render();
// Set the selected report to the bookmarked history state, or to
// the first report if there's no bookmarked state.
tabview.selectChild(history.get('report') || 0);
// Store a new history state when the user selects a report.
tabview.after('selectionChange', function (e) {
// If the new tab index is greater than 0, set the "tab"
// state value to the index. Otherwise, remove the "tab"
// state value by setting it to null (this reverts to the
// default state of selecting the first tab).
history.addValue('report', e.newVal.get('index') || 0);
});
// Listen for history changes from back/forward navigation or
// URL changes, and update the report selection when necessary.
Y.on('history:change', function (e) {
// Ignore changes we make ourselves, since we don't need
// to update the selection state for those. We're only
// interested in outside changes, such as the ones generated
// when the user clicks the browser's back or forward buttons.
if (e.src === Y.HistoryHash.SRC_HASH) {
if (e.changed.report) {
// The new state contains a different report selection, so
// change the selected report.
tabview.selectChild(e.changed.report.newVal);
} else if (e.removed.report) {
// The report selection was removed in the new state, so
// select the first report by default.
tabview.selectChild(0);
}
}
if (e.changed.report) {
alert("New value: " + e.changed.report.newVal);
alert("Old value: " + e.changed.report.prevVal);
}
});
});
</script>
</form>
</body>
</html>
Instead of using plain function on click, attach handlers with YUI.
If you can change the HTML code - add id or class to the links, for example
<a id="btnShowDashboard" href="#">Dashboard</a>
Then in your use() add click handler to the buttons
Y.use('history', 'tabview', 'node', 'event', function (Y) {
var bntShowDashboard = Y.one('#btnShowDashboard');
if (bntShowDashboard) {
bntShowDashboard.on('click', function(e) {
e.preventDefault();
var history = new Y.HistoryHash();
history.addValue("report", "dashboard");
});
}
...
})
That way you will be sure than on the moment of execution "history" is loaded.
BUT there is one drawback - until YUI modules are loaded, if you click the links nothing will happen.

How do I use the Yahoo YUI to do inline cell editing that writes to a database?

So I have datatable setup using the YUI 2.0. And for one of my column definitions, I've set it up so when you click on a cell, a set of radio button options pops so you can modify that cell content.
I want whatever changes are made to be reflected in the database. So I subscribe to a radioClickEvent. Here is my code for that:
Ex.myDataTable.subscribe("radioClickEvent", function(oArgs){
// hold the change for now
YAHOO.util.Event.preventDefault(oArgs.event);
// block the user from doing anything
this.disable();
// Read all we need
var elCheckbox = oArgs.target,
newValue = elCheckbox.checked,
record = this.getRecord(elCheckbox),
column = this.getColumn(elCheckbox),
oldValue = record.getData(column.key),
recordIndex = this.getRecordIndex(record),
session_code = record.getData(\'session_code\');
alert(newValue);
// check against server
YAHOO.util.Connect.asyncRequest(
"GET",
"inlineeddit.php?sesscode=session_code&",
{
success:function(o) {
alert("good");
var r = YAHOO.lang.JSON.parse(o.responseText);
if (r.replyCode == 200) {
// If Ok, do the change
var data = record.getData();
data[column.key] = newValue;
this.updateRow(recordIndex,data);
} else {
alert(r.replyText);
}
// unblock the interface
this.undisable();
},
failure:function(o) {
alert("bad");
//alert(o.statusText);
this.undisable();
},
scope:this
}
);
});
Ex.myDataTable.subscribe("cellClickEvent", Ex.myDataTable.onEventShowCellEditor);
But when I run my code and I click on a cell and I click a radio button, nothing happens ever. I've been looking at this for some time and I have no idea what I'm doing wrong. I know you can also use asyncsubmitter within my column definition I believe and I tried that, but that also wasn't working for me.
Any ideas would be greatly appreciated.

Categories

Resources