Let handsontable treat numeric as numeric type - handsontable

I have a piece of code which stringifies a handsontable: JSBin. Changing a cell value in the handsontable will change its corresponding string.
<html ng-app="app">
<head>
<script src="https://handsontable.github.io/ngHandsontable/node_modules/angular/angular.js"></script>
<script src="https://docs.handsontable.com/pro/1.8.2/bower_components/handsontable-pro/dist/handsontable.full.js"></script>
<link type="text/css" rel="stylesheet" href="https://docs.handsontable.com/pro/1.8.2/bower_components/handsontable-pro/dist/handsontable.full.min.css">
<script src="https://handsontable.github.io/ngHandsontable/dist/ngHandsontable.js"></script>
</head>
<body>
<div ng-controller="MainCtrl">
<hot-table hot-id="mytable" datarows="db"></hot-table>
<br><br>
<span>{{ data }}</span>
</div>
</body>
</html>
JavaScript:
function MainCtrl($scope, hotRegisterer) {
$scope.db = [[5, 6], [7, 8]];
$scope.$watch("db", function (newValue, oldValue) {
console.log("$watch");
var hot_instance = hotRegisterer.getInstance("mytable");
$scope.data = hot_instance.getData();
}, true)
}
MainCtrl.$inject = ['$scope', 'hotRegisterer'];
angular.module('app', ['ngHandsontable'])
.controller('MainCtrl', MainCtrl)
However, if we put a new numeric value in the table, it will be considered as string in the array, which does not align with other value format of the array.
I don't know why handsontbale treats all cell values as string type by default. Is there a way of setting up such that new numeric cell value is treated as numeric type?
Edit 1: Following the answer of #acesmndr, I have done this. And I wanted to add a watcher to db to update setting.columns according to the number of the columns. However, I realise that if we set settings.columns, we could not add/remove columns any more by the contexte menu:
Does anyone have a solution?

You must set the settings for the columns to be numeric. Here is a working snapshot of your jsbin
Apparently setting the type and invalidCellClassName setting in the main setting object applies for the entire table allowing you to add or remove columns unlike the column level setting that I suggested prior to this edit which disabled adding or removing columns. Updated snapshot of your jsbin with setting for entire table is here
function MainCtrl($scope, hotRegisterer) {
$scope.db = [[5, 6], [7, 8]];
$scope.settings = {
type: 'numeric',
invalidCellClassName: 'someCssClass'
};
$scope.$watch("db", function (newValue, oldValue) {
console.log("$watch");
var hot_instance = hotRegisterer.getInstance("mytable");
$scope.data = hot_instance.getData();
}, true)
}
MainCtrl.$inject = ['$scope', 'hotRegisterer'];
angular.module('app', ['ngHandsontable'])
.controller('MainCtrl', MainCtrl)
And add the settings to the hot-table
<hot-table hot-id="mytable" datarows="db" settings="settings"></hot-table>

Related

How to pass attribute value to a standard JS function in thymeleaf

In my template file using thymeleaf I have below code
<script type="text/javascript" >
var ue=UE.geteditor();
ue.ready(function()
{
ue.setcontent("${user.email}");
});
</script>
I want to set the content of ue to attribute user's email ,but got "${user.email}" as a string instead.
What's the correct way to use thymeleaf attribute value in plain JS function?Any help?Thx.
You need to use script inlining.
<script th:inline="javascript">
let user = {
email: [[${user.email}]]
};
let ue = UE.geteditor();
ue.ready(function() {
ue.setcontent(user.email);
});
</script>

Thymeleaf th:inline="javascript" issue

I don't know how to solve the following: I'd like to let my Model generate real javascript dynamically based on some model logic.
This final piece of javascript code then should be added inside the $(document).ready { } part of my html page.
The thing is: If I use inline="javascript", the code gets quoted as my getter is a String (that is how it is mentioned in the Thymeleaf doc but it's not what I need ;-)
If I use inline="text" in is not quoted but all quotes are escaped instead ;-) - also nice but unusable 8)
If I try inline="none" nothing happens.
Here are the examples
My model getter created the following Javascript code.
PageHelper class
public String documentReady() {
// do some database operations to get the numbers 8,5,3,2
return "PhotoGallery.load(8,5,3,2).loadTheme(name='basic')";
}
So if I now try inline="javascript"
<script th:inline="javascript">
/*<![CDATA[*/
jQuery().ready(function(){
/*[[${pageHelper.documentReady}]]*/
});
/*]]>*/
</script>
it will be rendered to
<script>
/*<![CDATA[*/
jQuery().ready(function(){
'PhotoGallery.load(8,5,3,2).loadTheme(name=\'basic\')'
});
/*]]>*/
</script>
Which doesn't help as it is a String literal, nothing more (this is how Thymeleaf deals with it).
So if I try inline="text" instead
<script>
/*<![CDATA[*/
jQuery().ready(function(){
PhotoGallery.load(8,5,3,2).loadTheme(name='basic')
});
/*]]>*/
</script>
Which escapes the quotes.
inline="none" I do not really understand, as it does nothing
<script>
/*<![CDATA[*/
jQuery().ready(function(){
[[${pageHelper.documentReady}]]
});
/*]]>*/
</script>
To be honest I have no idea how to solve this issue and hopefully anybody out there knows how to deal with this.
Many thanks in advance
Cheers
John
I would change the approach.
Thymeleaf easily allows you to add model variables in your templates to be used in Javascript. In my implementations, I usually put those variables somewhere before the closing header tag; to ensure they're on the page once the JS loads.
I let the template decide what exactly to load, of course. If you're displaying a gallery, then render it as you would and use data attributes to define the gallery that relates to some JS code. Then write yourself a nice jQuery plugin to handle your gallery.
A relatively basic example:
Default Layout Decorator: layout/default.html
<!doctype html>
<html xmlns:layout="http://www.thymeleaf.org" xmlns:th="http://www.thymeleaf.org">
<head>
<title>My Example App</title>
<object th:remove="tag" th:include="fragments/scripts :: header" />
</head>
<body>
<div layout:fragment="content"></div>
<div th:remove="tag" th:replace="fragments/scripts :: footer"></div>
<div th:remove="tag" layout:fragment="footer-scripts"></div>
</body>
</html>
The thing to notice here is the inclusion of the generic footer scripts and then a layout:fragment div defined. This layout div is what we're going to use to include our jQuery plugin needed for the gallery.
File with general scripts: fragments/scripts.html
<div th:fragment="header" xmlns:th="http://www.thymeleaf.org">
<script type="text/javascript" th:inline="javascript">
/*<![CDATA[*/
var MY_APP = {
contextPath: /*[[#{/}]]*/,
defaultTheme: /*[[${theme == null} ? null : ${theme}]]*/,
gallery: {
theme: /*[[${gallery == null} ? null : ${gallery.theme}]]*/,
images: /*[[${gallery == null} ? null : ${gallery.images}]]*/,
names: /*[[${gallery == null} ? null : ${gallery.names}]]*/
}
};
/*]]>*/
</script>
</div>
<div th:fragment="footer" xmlns:th="http://www.thymeleaf.org">
<script type="text/javascript" src="/js/jquery.js"></script>
<script type="text/javascript" src="/js/my_app.js"></script>
</div>
In the scripts file, there are 2 fragments, which are included from the decorator. In the header fragment, a helpful context path is included for the JS layer, as well as a defaultTheme just for the hell of it. A gallery object is then defined and assigned from our model. The footer fragment loads the jQuery library and a main site JS file, again for purposes of this example.
A page with a lazy-loaded gallery: products.html
<html layout:decorator="layout/default" xmlns:layout="http://www.thymeleaf.org/" xmlns:th="http://www.thymeleaf.org">
<head>
<title>Products Landing Page</title>
</head>
<body>
<div layout:fragment="content">
<h1>Products</h1>
<div data-gallery="lazyload"></div>
</div>
<div th:remove="tag" layout:fragment="footer-scripts">
<script type="text/javascript" src="/js/my_gallery.js"></script>
</div>
</body>
</html>
Our products page doesn't have much on it. Using the default decorator, this page overrides the page title in the head. Our content fragment includes a title in an h1 tag and an empty div with a data-gallery attribute. This attribute is what we'll use in our jQuery plugin to initialize the gallery.
The value is set to lazyload, so our plugin knows that we need to find the image IDs in some variable set somewhere. This could have easily been empty if the only thing our plugin supports is a lazyloaded gallery.
So the layout loads some default scripts and with cleverly placed layout:fragments, you allow certain sections of the site to load libraries independent of the rest.
Here's a basic Spring controller example, to work with our app: MyController.java
#Controller
public class MyController {
#RequestMapping("/products")
public String products(Model model) {
class Gallery {
public String theme;
public int[] images;
public String[] names;
public Gallery() {
this.theme = "basic";
this.images = new int[] {8,5,3,2};
this.names = new String[] {"Hey", "\"there's\"", "foo", "bar"};
}
}
model.addAttribute("gallery", new Gallery());
return "products";
}
}
The Gallery class was tossed inline in the products method, to simplify our example here. This could easily be a service or repository of some type that returns an array of identifiers, or whatever you need.
The jQuery plugin that we created, could look something like so: my_gallery.js
(function($) {
var MyGallery = function(element) {
this.$el = $(element);
this.type = this.$el.data('gallery');
if (this.type == 'lazyload') {
this.initLazyLoadedGallery();
}
};
MyGallery.prototype.initLazyLoadedGallery = function() {
// do some gallery loading magic here
// check the variables we loaded in our header
if (MY_APP.gallery.images.length) {
// we have images... sweet! let's fetch them and then do something cool.
PhotoGallery.load(MY_APP.gallery.images).loadTheme({
name: MY_APP.gallery.theme
});
// or if load() requires separate params
var imgs = MY_APP.gallery.images;
PhotoGallery.load(imgs[0],imgs[1],imgs[2],imgs[3]).loadTheme({
name: MY_APP.gallery.theme
});
}
};
// the plugin definition
$.fn.myGallery = function() {
return this.each(function() {
if (!$.data(this, 'myGallery')) {
$.data(this, 'myGallery', new MyGallery(this));
}
});
};
// initialize our gallery on all elements that have that data-gallery attribute
$('[data-gallery]').myGallery();
}(jQuery));
The final rendering of the products page would look like so:
<!doctype html>
<html>
<head>
<title>Products Landing Page</title>
<script type="text/javascript">
/*<![CDATA[*/
var MY_APP = {
contextPath: '/',
defaultTheme: null,
gallery: {
theme: 'basic',
images: [8,5,3,2],
names: ['Hey','\"there\'s\"','foo','bar']
}
};
/*]]>*/
</script>
</head>
<body>
<div>
<h1>Products</h1>
<div data-gallery="lazyload"></div>
</div>
<script type="text/javascript" src="/js/jquery.js"></script>
<script type="text/javascript" src="/js/my_app.js"></script>
<script type="text/javascript" src="/js/my_gallery.js"></script>
</body>
</html>
As you can see, Thymeleaf does a pretty good job of translating your model to valid JS and actually adds the quotes where needed and escapes them as well. Once the page finishes rendering, with the jQuery plugin at the end of the file, everything needed to initialize the gallery should be loaded and ready to go.
This is not a perfect example, but I think it's a pretty straight-forward design pattern for a web app.
instead of ${pageHelper.documentReady} use ${pageHelper.documentReady}

DYNAMIC HTML (FONT SIZES)

I've written a code that displays 2 buttons and one text. When we click on the button, "GROW" the size of the text should grow and when we click, "SHRINK" the size should reduce. But this is working only for one click. When I click second time, the functions are not invoked. Why is this happening?
Here is the code for it..
<html>
<head>
<script type="text/javascript">
function Grow()
{
var a=document.getElementById(1);
a.style.fontSize=a.style.fontSize+50;
}
function Shrink()
{
var a=document.getElementById(1);
a.style.fontSize=20;
}
</script>
</head>
<body>
<input type="button" value="grow" onclick="Grow()">
<input type="button" value="shrink" onclick="Shrink()">
<p id="1"> Hello </p>
</body>
When you perform the grow action for the first time it automatically uses the units px as you started off with a null value. You will need to parse the value of .fontSize before you can perform arithmetic on it. Try this...
parseInt(a.style.fontSize.replace('px', ''));
to get a numerical value you can perform arithmetic on.
In full...
function Grow()
{
var a=document.getElementById(1);
// Get a number we can perform arithmetic on
size = parseInt(a.style.fontSize.replace('px',''));
// Additional check needed because original dimensions not specified
if (!isNaN(size)) { // If we now have a number we can use
a.style.fontSize=size+50;
} else { // Otherwise, set to 50 (assuming we are starting from 0px)
a.style.fontSize=50;
}
}

Google Places API types functionality..

<html>
<head>
<title></title>
<script type="text/javascript" src="http://maps.googleapis.com/maps/api/js?libraries=places&sensor=true"></script>
<script src="Scripts/jquery-1.4.1.min.js" type="text/javascript"></script>
<script type="text/javascript">
$(function () {
var input = document.getElementById('location');
var options = {
types: ["locality"]
};
autocomplete = new google.maps.places.Autocomplete(input, options);
});
</script>
</head>
<body>
<div>Location: <input type="text" id="location" style="width:400px;" /></div>
</body>
</html>
There is my code to generate my autocomplete location text input. Google's list of supported types (http://code.google.com/apis/maps/documentation/places/supported_types.html) shows "locality" as being the type to use when I do not wish for everything to come back in the result(businesses, etc). I am getting no results.
Basically, what I would like to achieve is to search for a city (IE: Toronto) And only see results like: "Toronto, ON, Canada". Perhaps I am confused on how I implement this API.
Thank you very much for your replies!
I think the option you are looking for according to the docs is "geocode" ( http://code.google.com/apis/maps/documentation/javascript/reference.html#AutocompleteOptions ):
var options = {
types: ["geocode"]
};
you can also use the country restriction
example:
<script type="text/javascript" src="http://maps.googleapis.com/maps/api/js?libraries=places&sensor=false"></script>
<script type="text/javascript">
function initialize()
{
var input = document.getElementById('searchTextField');
var options = {
types: ['(cities)'],
componentRestrictions: {country: "ca"}
};
autocomplete = new google.maps.places.Autocomplete(input, options);
}
google.maps.event.addDomListener(window, 'load', initialize);
</script>
<input id="searchTextField" type="text" size="50" placeholder="Anything you want!">
now you can easily add a dropdown with a selection of cities and re-filter the cities, when onchange of the dropdown occurs :)
Try, check out the jsfiddle:
var options = {
types: ['geocode']
};
types, which can be either establishment or geocode, representing
businesses or addresses, respectively. If types is not specified, both
types are returned.
If the user types Tor in the input field and the output you want is Toronto, ON, Canada then you should use types=(regions), with the brackets.
I don't know if the option was present when the question was asked, but it is available now.
Sample Request:
https://maps.googleapis.com/maps/api/place/autocomplete/json?input=Tor&key=<YOUR_API_KEY_HERE>&types=(regions)
You can use like this
types: ['geocode' || 'establishment' || 'address']

Custom validation of dijit textboxes

I was today seeking the net for information about textbox-validation, but even on Dojo-Homepage I couldn't get any useful information.
My problem: I've got a NumberSpinner, in which only numbers in steps of ten (10, 20, 30, ...) should be allowed. But I've got no idea how to set a validator for this. In the 'constraints'-statement there seems to be no possibility to do this. And I don't know how to use the validator-function so that the box shows me warning sign immediately when typing in somethin wrong.
Another question is how to check if any part of a form is not valid before sending it. Is there an attribute in every input/select-box like 'valid' to check them all at once?
Oh, one hint, I create all widgets programmatically.
Hopefully anyone out there can help me!!!
Best regards,
Robin
You can override the NumberSpinner's isValid() method. For example:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>class</title>
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/dojo/1.3/dijit/themes/soria/soria.css" />
<script src="http://ajax.googleapis.com/ajax/libs/dojo/1.3/dojo/dojo.xd.js"></script>
<script type="text/javascript">
dojo.config.parseOnLoad = true;
dojo.addOnLoad(function() {
dojo.require('dijit.form.NumberSpinner');
dojo.require('dojo.parser');
dojo.addOnLoad(function() {
new dijit.form.NumberSpinner({
isValid: function(isFocused) {
var value = parseFloat(this.attr('value'));
if (isNaN(value) || (value % 10 != 0)) {
return false;
} else {
return true;
}
}
}, 'here');
});
});
</script>
</head>
<body class="soria">
<div id="here"></div>
</body>
</html>
dijit.form.NumberSpinner is derived from dijit.form.ValidationTextBox, and as such it would accept the same arguments (see dijit.form.ValidationTextBox docs and inline docs in its source code). Just write a regular expression (as a string) that can validate your input. Something like that should do the trick:
var box = new dijit.form.NumberSpinner({
regExpGen: function(){ return "\\d+0"; }
}, "my_node");
You can also use the delta/smallDelta attribute for this. See the example at Dojo Campus

Resources