I'm using a morris.js pie chart in my grails project and now i try to count an int after a button click and refresh the chart. But if i use "redirect" it refresh the whole site.
So is there a recommended way to refresh only the chart?
gsp:
<g:form controller="blankTest">
<g:actionSubmit value="Update" action="action"/>
</g:form>
<div id="myfirstchart" style="height: 250px;"></div>
<g:javascript id="test">
new Morris.Donut({
// ID of the element in which to draw the chart.
element: 'myfirstchart',
label: 'Test',
resize: true,
colors : ['#00ff00', '#ff0000', '#ffff00'],
data: [
{ label: 'Number1', value: ${number}},
{ label: 'Number2', value: ${number1}},
{ label: 'Number3', value: ${number2}}
],
});
</g:javascript>
Controller:
int number = 0
def index() {
[number: number, number1: 34, number2: 8]
}
def action() {
number++
redirect view: 'index'
}
The idea is creating a request by an ajax call, which will give you a response that you will use for updating your Morris donut.
So you could use your form for creating a ajax call. You have two options:
Using remote tags (out of the box on 1.x and 2.x grails versions, or with remote tags plugin on grails 3.x. Using a remote form, you could update a piece of your html code with the response from an action, which renders a template.
Implementing your own ajax method, recommended for a well code implementation, cause a remote ajax grails tag appends js code on your HTML. It is not good at all (a front-end dev will kill you and a kitty cat also :) ), and is deprecated.
This ajax request has to return you a new data model, could be in a JSON format and you can update your donut with the method js setData.
<g:form name="form" controller="blankTest">
<input type="submit" value="Update"/>
</g:form>
<div id="myfirstchart" style="height: 250px;"></div>
<g:javascript id="test">
//we will need this reference for updating it
var donut = new Morris.Donut({
// ID of the element in which to draw the chart.
element: 'myfirstchart',
label: 'Test',
resize: true,
colors : ['#00ff00', '#ff0000', '#ffff00'],
data: [
{ label: 'Number1', value: ${number}},
{ label: 'Number2', value: ${number1}},
{ label: 'Number3', value: ${number2}}
],
});
$(document).ready(function(){
$('#form').submit(function(event){
event.preventDefault(); //blocks native form submit
$.ajax('${createLink(controller: 'blankTest', action: 'action')}', {
success: function (data) {
var response = $.parseJSON(data);
donut.setData(response); //updating the donut with json response
}
});
});
});
</g:javascript>
And your action should be something like this
def action() {
number++
List result = [
[label: 'Number1', value: number],
[label: 'Number2', value: number1],
[label: 'Number3', value: number2]
]
render result as JSON
}
For more complex JSON's consider use JSON views if you are using Grails greater than v.3.1
Related
I keep hitting a wall when trying to get the parent data passed down to the child component.
My view:
<%= react_component 'Items', { data: #items } %>
My Items component makes an ajax call, sets state, and renders Item. Leaving key={this.props.id} out of the Item instance passed into the mapping function makes it so that the component html renders to the page. But add the key in, and I get a console error: Uncaught TypeError: Cannot read property 'id' of undefined
Here's 'Items':
var Items = React.createClass({
loadItemsFromServer: function() {
$.ajax({
url: this.props.url,
dataType: 'json',
cache: false,
success: function(data) {
this.setState({data: data});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
componentDidMount: function() {
this.loadItemsFromServer();
},
render: function() {
var itemNodes = this.props.data.map(function() {
return (
<Item key={this.props.id} />
);
});
return (
<div className="ui four column doubling stackable grid">
{itemNodes}
</div>
);
}
});
My item.js.jsx component just formats each Item:
var Item = React.createClass({
render: function() {
return (
<div className="item-card">
<div className="image">
</div>
<div className="description">
<div className="artist">{this.props.artist}</div>
</div>
</div>
);
}
});
The React dev tools extension shows the props and state data inside Items. The children, however, are empty.
I'm aware of this, but I'm setting key with this.props.id. I'm not sure what I'm missing?
I found a couple of problems with the code you posted, in the Items component
You're rendering this.props.data while in fact this.state.data is the one being updated with the ajax request. You need to render this.state.data but get the initial value from props
The map iterator function takes an argument representing the current array element, use it to access the properties instead of using this which is undefined
The updated code should look like this
var Item = React.createClass({
render: function() {
return (
<div className="item-card">
<div className="image">
</div>
<div className="description">
<div className="artist">{this.props.artist}</div>
</div>
</div>
);
}
});
var Items = React.createClass({
getInitialState: function() {
return {
// for initial state use the array passed as props,
// or empty array if not passed
data: this.props.data || []
};
},
loadItemsFromServer: function() {
var data = [{
id: 1,
artist: 'abc'
}, {
id: 2,
artist: 'def'
}]
this.setState({
data: data
});
},
componentDidMount: function() {
this.loadItemsFromServer();
},
render: function() {
// use this.state.data not this.props.data,
// since you are updating the state with the result of the ajax request,
// you're not updating the props
var itemNodes = this.state.data.map(function(item) {
// the map iterator function takes an item as a parameter,
// which is the current element of the array (this.state.data),
// use (item) to access properties, not (this)
return (
// use key as item id, and pass all the item properties
// to the Item component with ES6 object spread syntax
<Item key={item.id} {...item} />
);
});
return (
<div className="ui four column doubling stackable grid">
{itemNodes}
</div>
);
}
});
And here is a working example http://codepen.io/Gaafar/pen/EyyGPR?editors=0010
There are a couple of problems with your implementation.
First of all, you need to decide: Do you want to render the #items passed to the Items component from your view? Or do you want to load them asynchronous?
Because right now I get the impression you are trying to do both...
Render items passed from view
If you want to render the items from your view passed to the component, make sure it's proper json. You might need to call 'as_json' on it.
<%= react_component 'Items', { data: #items.as_json } %>
Then, in your Component, map the items to render the <Item /> components. Here is the second problem, regarding your key. You need to define the item variable to the callback function of your map function, and read the id from it:
var itemNodes = this.props.data.map(function(item) {
return (
<Item key={item.id} artist={item.artist} />
);
});
Note, I also added the author as prop, since you are using it in your <Item /> Component.
You can remove your componentDidMount and loadItemsFromServer functions, since you are not using them.
Load items asynchronous
If you want to load the items asynchronously, like you are trying to do in your loadItemsFromServer function, first of all, pass the url from your view and remove the {data: #items} part, since you will load the items from your component, something like:
<%= react_component 'Items', { url: items_path(format: :json) } %>
If you want to render the asynchronous fetched items, use:
var itemNodes = this.state.data.map(function(item) {
return (
<Item key={item.id} artist={item.artist} />
);loadItemsFromServer
});
Note I changed this.props.map to this.state.map
You can now use your componentDidMount and loadItemsFromServer functions to load the data and save them to state.
I have somewhat of a complex requirement here (a real head-scratcher)... and I'm not sure on the best way to proceed:
Requirement:
Build a page for managing widgets (CMS content blocks) in MVC5 using AngularJS for the frontend (as per the rest of the admin UI). The problem is that each widget has its own specific set of properties. They all share some properties like Title, IsEnabled, etc.. but an HTML Widget for example will have a BodyContent field and a Slider Widget would have a collection of images, etc..
My first thought was using [UIHint] and Html.EditorFor so that each widget type will have its own markup.. I think that's pretty straightforward, but how could we get the properties from any such arbitrary widget into the AngularJS model?
Example Controller
widgetsApp.controller('widgetController', function ($scope, $http) {
$scope.emptyGuid = '00000000-0000-0000-0000-000000000000';
$scope.id = $scope.emptyGuid;
$scope.title = '';
$scope.order = 0;
$scope.enabled = false;
$scope.widgetType = '';
$scope.zoneId = $scope.emptyGuid;
// etc
// how to get properties of ANY widget type?
Is this even possible? Is there a better solution? Note, I might consider changing the code to use Knockout or some other such framework if it can support my requirements.
Edit
Note that the issue is further complicated because of the fact of needing to then pass such a model back to the server and dealing with it there. In regular MVC controllers, I can use Request.Form to inspect what other values are there, but I'm using Web API and not sure if that's possible there.
Edit 2
Okay, so I think I'm on the right track, but still having issues. Firstly, here's my progress:
I found out about .factory and made a test page like this:
<div ng-app="myApp">
<div ng-controller="controller1">
<button class="btn btn-primary" ng-click="showAllInfo()">Show Info</button>
</div>
<div ng-controller="controller2">
</div>
</div>
<script type="text/javascript">
var myApp = angular.module('myApp', []);
myApp.factory('widgetModel', function () {
return {
id: '00000000-0000-0000-0000-000000000000',
title: '',
order: 0,
enabled: false,
widgetName: '',
widgetType: '',
zoneId: '00000000-0000-0000-0000-000000000000',
displayCondition: '',
widgetValues: '',
pageId: null,
cultureCode: '',
refId: null,
};
});
// This is representative of the main controller
myApp.controller('controller1', function ($scope, widgetModel) {
$scope.emptyGuid = '00000000-0000-0000-0000-000000000000';
$scope.model = widgetModel;
$scope.model.id = $scope.emptyGuid;
$scope.showAllInfo = function () {
alert("id: " + $scope.model.id + ", New Property: " + $scope.model.myNewProperty);
};
});
// This is representative of the details controller (to add properties specific to that particular widget type)
myApp.controller('controller2', function ($scope, widgetModel) {
$scope.model = widgetModel;
$scope.model.myNewProperty = "My Awesome Widget";
});
</script>
The above test works beautifully.. however, when I use this sort of code in my real application it fails to work and the reason I believe is because the second controller is injected into the DOM later on.. here's what's happening:
I have a div as follows
<div ng-bind-html="widgetDetails"></div>
and after loading the other details, I load the html for this as such:
$http.get("/admin/widgets/get-editor-ui/" + $scope.model.id).success(function (json) {
$scope.widgetDetails = $sce.trustAsHtml(json.Content);
});
That works.. I can see my the html controls for my new properties there.. the following snippet is the HTML which is injected into the above div:
<div ng-controller="widgetDetailsController">
<div class="col-sm-12 col-md-12">
<div class="form-group">
#Html.Label("BodyContent", "Body Content", new { #class = "control-label" })
#Html.TextArea("BodyContent", null, new { #class = "form-control", ng_model = "model.bodyContent", ui_tinymce = "tinyMCEOptions_BodyContent" })
</div>
</div>
<button class="btn" ng-click="test()">Test</button>
</div>
<script type="text/javascript">
widgetsApp.controller('widgetDetailsController', function ($scope, $http, widgetModel) {
$scope.model = widgetModel;
$scope.json = angular.fromJson($scope.model.widgetValues);
$scope.model.bodyContent = $scope.json.bodyContent || "";
$scope.test = function () {
alert($scope.model.bodyContent);
};
});
</script>
When I click, the "Test" button, nothing happens...
I tried to load a controller dynamically via the method outlined at this link: http://www.bennadel.com/blog/2553-loading-angularjs-components-after-your-application-has-been-bootstrapped.htm
It doesn't work. To be honest though, I am new to AngularJS and don't really know all the ins out outs of it.. any help would be great.
IF you are just looking to get the properties and their values, then on AngularJS or Javascript side you can just iterate over the object properties to get all the properties defined over the object.
for(var key in obj){
$scope[key]=obj[key];
}
Once on scope you can bind it to the view using ng-model.
This approach would get you the data but metadata about the data such as control to render for property need would not work.
For advance scenarios you should try to send metadata about each properties that can help render it on the view.
If ng-model is setup correctly all data would be send to server.
On the server you can use the dynamic keyword as input parameter to webapi method and there should be a similar method to iterate over the payload using key value pair.
I ended up changing to KnockoutJS, partly because AngularJS ended up being a bit overkill for my needs, but also because it couldn't handle this situation very nicely (or at least there was no obvious and clean way to do it). My KnockoutJS solution is below:
In the main page, I add an html element:
<fieldset id="widget-details"></fieldset>
An example of arbitrary HTML to be injected:
<div id="widget-content" class="col-sm-12 col-md-12">
<div class="form-group">
#Html.Label("BodyContent", "Body Content", new { #class = "control-label" })
#Html.TextArea("BodyContent", null, new { #class = "form-control", data_bind = "wysiwyg: bodyContent, wysiwygConfig: tinyMCEConfig" })
</div>
</div>
<script type="text/javascript">
function updateModel() {
var data = ko.mapping.fromJSON(viewModel.widgetValues());
viewModel.bodyContent = ko.observable("");
if (data && data.BodyContent) {
viewModel.bodyContent(data.BodyContent());
}
viewModel.tinyMCEConfig = {
theme: "modern",
plugins: [
"advlist autolink lists link image charmap print preview hr anchor pagebreak",
"searchreplace wordcount visualblocks visualchars code fullscreen",
"insertdatetime media nonbreaking save table contextmenu directionality",
"emoticons template paste textcolor"
],
toolbar1: "insertfile undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image",
toolbar2: "print preview media | forecolor backcolor emoticons",
image_advtab: true,
templates: [
{ title: 'Test template 1', content: 'Test 1' },
{ title: 'Test template 2', content: 'Test 2' }
],
content_css: tinyMCEContentCss
};
};
function onBeforeSave() {
var data = {
BodyContent: viewModel.bodyContent()
};
viewModel.widgetValues(ko.mapping.toJSON(data));
};
</script>
Then in my script for the main page, I use the following:
$.ajax({
url: "/admin/widgets/get-editor-ui/" + self.id(),
type: "GET",
dataType: "json",
async: false
})
.done(function (json) {
var result = $(json.Content);
var content = $(result.filter('#widget-content')[0]);
var details = $('<div>').append(content.clone()).html();
$("#widget-details").html(details);
var scripts = result.filter('script');
scripts.appendTo('body');
// ensure the function exists before calling it...
if (typeof updateModel == 'function') {
updateModel();
var elementToBind = $("#widget-details")[0];
ko.cleanNode(elementToBind);
ko.applyBindings(viewModel, elementToBind);
}
})
.fail(function () {
$.notify("There was an error when retrieving the record.", "error");
});
and when I save, I call this code:
// ensure the function exists before calling it...
if (typeof onBeforeSave == 'function') {
onBeforeSave();
}
Works really well.
I have a jQueryUI autocomplete that pulls from a list of customers and is attached based on the selector [input data-role="customer-search"]. Once a customer is selected, I make a AJAX call to get the full customer detail. This part I have working fine. The issue is that I am having trouble figuring out a way to incorporate knockout into this. My ideal situation is a custom binding like "onSelect: customerSelected", which would take in the selected Customer JSON and integrate it into the overall model, which would then cause updates to a bunch of fields on the page with bingings such as model.Customer.Address, model.Customer.Type.
The place I am butting my head against is that connection point after I've gotten the Customer JSON back from the AJAX call, how to send it to the "customerSelected" method on the viewmodel tied to the same input I attached the jQuery autocomplete.
Here is a simplified version of a bindinghandler my team wrote for handling autocomplete. When an item is selected, the item is inserted into an observable array in the view model. It is bound in the following manner:
<input type="text" data-bind="autoComplete:myObservableArray, source:'myUrl'" />
You can customize what happens when an item is selected in the 'select:' area.
ko.bindingHandlers.autoComplete = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var postUrl = allBindingsAccessor().source; // url to post to is read here
var selectedObservableArrayInViewModel = valueAccessor();
$(element).autocomplete({
minLength: 2,
autoFocus: true,
source: function (request, response) {
$.ajax({
url: postUrl,
data: { term: request.term },
dataType: "json",
type: "POST",
success: function (data) {
response(data);
}
});
},
select: function (event, ui) {
var selectedItem = ui.item;
if (!_.any(selectedObservableArrayInViewModel(), function (item) { return item.id == selectedItem.id; })) { //ensure items with the same id cannot be added twice.
selectedObservableArrayInViewModel.push(selectedItem);
}
}
});
}
};
Hopefully, it's something like this that you're looking for. If you need something clarified, let me know.
Note Besides jquery and knockout, this example uses underscore.js ( the _.any() function)
valueUpdate: blur
data-bind="value: textbox, valueUpdate: blur" binding fixed the problem for me:
$(function() {
$(".autocomplete").autocomplete({
source: [
"ActionScript",
"AppleScript",
"Asp",
"BASIC",
"C",
"C++",
"Clojure",
"COBOL",
"ColdFusion",
"Scheme"]
});
});
var viewModel = {
textbox: ko.observable()
};
ko.applyBindings(viewModel);
<script src="//cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//code.jquery.com/ui/1.11.3/jquery-ui.min.js"></script>
<input type="text" class="autocomplete" data-bind="value: textbox, valueUpdate: blur"/>
I've found here that to overwrite one of the autocomplete events. But can somebody please provide me with example how to do the same?
The appendTo option does indeed work as expected, and if you inspect at the DOM, the <ul> results element will be attached to the element. However, due to absolute positioning generated by jQueryUI, the list still appears directly under the <input>.
That said, you can override the internal _renderItem to directly append results to a completely different element, for example:
HTML
<input id="autocomplete"/>
<div class="test">Output goes here:<br/><ul></ul></div>
JavaScript
$('input').autocomplete({
search: function(event, ui) {
$('.test ul').empty();
},
source: ["something", "something-else"]
}).data('autocomplete')._renderItem = function(ul, item) {
return $('<li/>')
.data('item.autocomplete', item)
.append(item.value)
.appendTo($('.test ul'));
};
I have also created a demo to demonstrate this. Please note that the latest jQuery library has not had jQueryUI tested against it fully, so I am using the previous version which allows me to select to include jQueryUI directly with the jsFiddle options.
<div class="test">Output goes here:<br/></div>
<script>
$("input#autocomplete").autocomplete({
source: ["something", "something-else"],
appendTo: ".text",
position: { my: "left top", at: "left bottom", of: ".test" }
// other options here
});
</script>
I needed more control over where to put the data, so this is how I went about it:
$("#input").autocomplete({
minLength: 3,
source: [
"ActionScript",
"AppleScript",
"Asp"
],
response: function(event, ui) {
console.log(ui.content);
// put the content somewhere
},
open: function(event, ui) {
// close the widget
$(this).autocomplete('close');
}
});
hle's answer worked awesome for me and gives you more flexibility! Here is my test code that was modified by his answer:
$("#autocomplete").autocomplete({
minLength: 3,
source: ["something", "something-else"],
response: function(event, ui)
{
console.log(ui.content);
// put the content somewhere
},
open: function(event, ui)
{
// close the widget
$(this).autocomplete('close');
}
});
Although this question is pretty old but i got a pretty easy solution. No hack, nothing just in jQuery way:
Instead of autocomplete response function, just add response data in div on success
$(document).ready(function () {
$("#book-code-search").autocomplete({
minLength: 2,
delay: 500,
source: function (request, response) {
$.ajax( {
url: "server side path that returns json data",
data: { searchText: request.term, param2 : $("#type").val()},
type: "POST",
dataType: "json",
success: function( data ) {
$("#data-success").html(data.returnedData); //returnedData is json data return from server side response
/* response($.map(data, function (item) {
return {
label: item.FullDesc,
value: item.FullDesc
}
})) */
}
});
}
});
});
<link href="https://code.jquery.com/ui/1.12.1/themes/smoothness/jquery-ui.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<div id='data-success' style='color: green;'></div>
<input type='text' placeholder='Enter Book Code' id='book-code-search' />
<input type='hidden' id='type' value='book'>
I want to display jquery errors in tooltips like in this image:
there is some jquery plugins around which could do that:
http://www.position-relative.net/creation/formValidator/demos/demoValidators.html
http://validity.thatscaptaintoyou.com/
But they not designed to work with asp.net mvc 3.0 validation attributes.
Is there any similar plugin which would do the thing? or any thing else i can do to display errors like that in tooltips?
We have done something similar to this using Jquery validation plugin. For this we have to create the popup div initially.
Javascript Fn
$("#formID").validate({
submitHandler: function (form) {
CallFunction();
},
highlight: function (element, errorClass) {
HidePopup(element);
ShowPopup(element);
},
unhighlight: function (element, errorClass) {
HidePopup(element);
},
errorElement: "span",
errorPlacement: function (error, element) {
error.appendTo(element.prev("label"));
},
rules: {
txtName:"required"
}
});
function ShowPopup(paramElement)
{
//function to show popup and position div accordingly
$('#div'+paramElement.Id).show();
}
function HidePopup(paramElement)
{
//function to hide popup
$('#div'+paramElement.Id).hide();
}
**Html**
<form id="formID" action="">
<input name="txtName" type="text" id="txtName" />
<div id="divtxtName" >Please enter name</div>
</form>