I am dropping an external object into angular-ui-calendar using angular-dragdrop.
The external object is coming from this list:
<div class="fc-event" data-drag="true"
jqyoui-draggable="{animate:true}"
ng-model="test_object"
ng-repeat="test_object in test_objects">
Draggable - {{ test_object.name }}
</div>
The fullcalendar is set up with:
<div id="ApptsCalendar" calendar="ApptsCalendar"
ui-calendar="calendarOptions.calendar"
ng-model="eventSources" data-drop="true"
jqyoui-droppable="{multiple:true, onDrop: 'drop_function'}"
data-jqyoui-options>
</div>
When dropped, I can process that event using fullcalendar 'drop' method with:
$scope.calendarOptions = {
calendar: {
editable: true,
droppable: true,
drop: function(date,jsEvent,ui,resourceId){
console.log("Dropped from calendarOptions")
console.log(resourceId);
$scope.eventSources[0].push({
id:5,
title: 'dropped event (fake)',
start: date
});
}
}
};
or from the angular-dragdrop 'onDrop' callback to call a 'drop' function:
jqyoui-droppable="{multiple:true, onDrop: 'drop'}"
Both can trigger when I want, but neither seem to have the two pieces I need. I need to have the object value being dropped (defined in ng-model) and the date being dropped into.
Basically, I want to push the event to the the eventSources with:
$scope.eventSources[0].push({
id:5,
title: '...name of object...',
start: '...date of target dropped on...'
});
http://plnkr.co/edit/fj858Htb2FRUg5h1pucP?p=preview
Well, one of the things you wanted is already there. It's date on which the event is dropped. You get it from the first argument of the drop function. It's a moment object (according to the docs) so you might want to use .toDate() in order to get the JS Date object.
The other thing is the value of the event which got dropped. According to the same docs page, the DOM object of the event is accessible using this inside drop function.
Now, this is a bit unconventional way (I don't see many choices here), what you can do is, with the ng-repeat iterating over event objects, you can keep an attribute with value from each object which can later be accessed inside the drop function. For example, see how I added customEventName="{{test_object.name}}" in here:
<div class="fc-event tech_draggable" data-drag="true" id="{{test_object.name}}"
customEventName="{{test_object.name}}" jqyoui-draggable="{animate:true}"
ng-model="test_object" ng-repeat="test_object in test_objects" ...>
Draggable - {{ test_object.name }}
</div>
Then, in the drop function, that can be accessed using this.getAttribute('customEventName') like this:
$scope.calendarOptions = {
calendar: {
editable: true,
droppable: true,
drop: function(momentDate, jsEvent, ui, resourceId) {
console.log(momentDate.toDate()) // gives JS Date object
console.log(this.getAttribute('customEventName')); // gives event2/event3 etc.
//... more
}
}
};
An alternative is to make an attribute with a string representing the scope variable name:
<div ng-repeat="test_object in test_objects">
<div class="fc-event tech_draggable"
data-drag="true"
jqyoui-draggable="{animate:true}"
ng-repeat="test_object in test_objects"
style="margin-bottom:1px;"
data-jqyoui-options="{helper: 'clone'}"
scope-data-name="test_objects[{{$index}}]"
>
Draggable - {{ test_object.name }}
</div>
</div>
And using $scope.$eval to get the actual object:
$scope.calendarOptions = {
calendar: {
editable: true,
droppable: true,
drop: function(date,jsEvent,ui,resourceId){
var scopeDataName = this.getAttribute('scope-data-name');
var data = $scope.$eval(scopeDataName);
$scope.eventSources[1].push({
id: $scope.eventSources[0].length,
title: `${data.name} ${data.description}`,
start: date
});
}
}
};
The DEMO on PLNKR
After some more research, I think fullcalendar has the solution already.
I can use data-event attribute in the element:
data-event='{"title":"{{ test_object.name }}"}'
With that, there is no need to even have a 'drop' function... fullcalendar natively supports drag and drop.
I can then optionally use eventReceive to handle a drop from an external resource and use eventDrop to handle an internal event move.
http://plnkr.co/edit/fj858Htb2FRUg5h1pucP?p=preview
Related
I have a JSFiddle below to explain my problem but basically I have an array called tiles which has a title. When the instance is created() I add a field to this array called active
I then output this array in an <li> element and loop through it outputting the title and active objects. My problem is as you can see in the fiddle when I run v-on:click="tile.active = true" nothing happens to the active state written in the <li> element
but if I run v-on:click="tile.title = 'test'" it seems to update the active object and the title object.
Its strange behaviour I can't seem to work out why. Does anyone have any ideas?
https://jsfiddle.net/jgb34dqo/
Thanks
It's to do with Vue not knowing about your properties, change your array to this:
tiles: [
{
title: 'tile one',
active: false
},
{
title: 'tile two',
active: false
},
{
title: 'tile three',
active: false
}
]
This allows Vue to know about the active property and in turn it knows to monitor that variable.
It's worth looking at this link about Vue Reactivity as it helps with understanding how and when data will change automagically.
If you must add the properties after
take a look at $set. It allows you to add props to an object that then get watched by vue. See this fiddle, notice the change:
this.tiles.forEach(function(tile) {
// Tell vue to add and monitor an `active` prop against the tile object
this.$set(tile, 'active', false);
}.bind(this))
Inside a Kendo Tree List for Angular Js I have added a Graph as a kendo template.
<script id="progressStatus" type="text/x-kendo-template">
<div ng-if="'#:Status#' == 'Loading'">
<div kendo-progress-bar="progressBar1" k-min="0" k-max="100" k-value="#:Percentage#" style="width: 100%;"></div>
</div>
</script>
And I bind it to the tree list as part of column declaration
{ field: "Status", template: $("#progressStatus").html(), width: "170px" }
So far good. And I am able to display the value in UI.
However I am not sure how to show following
How to make it of type percent, i tried with k-type='percent' but no luck
If Percentage > 50 show the graph in yellow and text (50%) in red
Unfortunately, some options seems not to work with angular directives. I could not get to work k-type (like you). In my dojo that attribute seems to break the widget. After checking this page, I could use type the following way:
<div kendo-progress-bar="progressBar1" k-options="progressBarOptions" style="width: 100%;"></div>
.controller("MyController", function($scope) {
$scope.dataSource = [
'foo', 'bar', 'baz'
];
$scope.progressBarOptions = {
min: 0,
max: 100,
value: 50,
type: "percent"
};
});
Demo. That will make percent type work.
Now, changing the color of the widget based on the value is another problem. The ProgressBar don't have any kind templates and it is poor in events(only complete and change). It seems that your bar doesn't changes it's value, it's is static, right? So I tried to realize a way to call change event with animation which should call change after being complete. It would be like an initialization event. But, animation doesn't seems to work either. I tried with k-animation and in the init options, but no luck. Double checked for typos but I'm sure that wasn't the case. It's a shame.
Anyway, you can use the ugly and non-straightforward way using a function which you should call in your grid's dataBound event:
var changeBarColor = function()
{
$('[data-role="progressbar"]').each(function() {
$(this).find(".k-state-selected").addClass(
$(this).data("kendoProgressBar").value() < 50
? "yellow-bar"
: "red-bar"
);
});
};
Demo. Again: It's a shame the widget lacks of such a simple and useful feature like that.
I hope I'm wrong but that is the far I could get on this. Good luck.
I am new to Angular.js so I am not sure if this is the right approach. I have two scopes that are used to display 2 sets of buttons. The second set should be dependent on the button I click in the first set.
<!-- Insulation placement -->
$rootScope.roofs = [
{
type: 'roof',
label: 'Pitched Roof'
},
{
type: 'attic',
label: 'Floor of Attic'
}
];
<!-- Roof insulation types -->
$rootScope.roofInsulation = [
{
target: 'roof',
type: 'between_rafters',
label: 'Between Rafters'
},
{
target: 'roof',
type: 'between_rafters_counter_batten',
label: 'Between Rafters With A Counter Batten'
},
{
target: 'roof',
type: 'between_rafters_calibel',
label: 'Between Rafters With Calibel'
},
{
target: 'roof',
type: 'between_rafters_thermal_laminate',
label: 'Between Rafters With Thermal Laminate'
},
{
target: 'attic',
type: 'test',
label: 'Test'
}
];
and my HTML
<div ng-repeat="types in roofs">
<button ng-click="myFilter = {target: '{{types.type}}'}" class="btn btn-primary" type="button">{{types.label}}</button>
</div>
<div>
<button ng-repeat="variants in roofInsulation | filter: myFilter" class="btn btn-secondary" type="button">{{variants.label}}</button>
</div>
I realize that myFilter in the ng-click is a bit of a hack, but aside from that I can't get it to filter the results of ng-repeat. The myFilter variable does return the proper result {target: 'roof'} (for the first button). Do I assume correctly that it's because the first set of buttons is in a different scope than the second one?
You are not really using 2 different scopes here. If you had been using 2 different controllers or different directives then you would have got 2 different scopes. You are using $rootScope which is common across the entire angular app.
The reason myFilter is not working is because angular is unable to parse the expression in ng-click correctly, it's better you write a method (exposed to the scope) and change the value of myFilter in the method. It's a good practice as well as a better way to achieve what you are trying to do.
HTML
<button ng-click="setFilter(types)" class="btn btn-primary" type="button">{{types.label}}</button>
JS
$rootScope.setFilter = function(types) {
$rootScope.myFilter = {target: types.type};
}
Check this fiddle here, I have created a working example based on your code.
EDIT
Even if your target variable is an array there shouldn't be any issue because Anguar's pipe filter will take care of it.
I have updated and created a new fiddle to show it, check it here.
So if target is an array having 2 values - ['roof', 'attic'], that particular element will be shown for both the buttons.
Examples here http://www.material-ui.com/#/components/popover are valid for one Popover element on the page only. If one follows their examples it is not possible to have more than one Popover on one page. Because Popovers begin interfering each other.
For example. Here is a markup for a single popover. And imagine that you have ten of these in one page:
<div onClick={this.openPopover}>Open Popover</div>
<Popover
open={this.state.open}
anchorEl={this.state.anchorEl}
anchorOrigin={{horizontal: 'left', vertical: 'bottom'}}
targetOrigin={{horizontal: 'left', vertical: 'top'}}
onRequestClose={this.closePopover}
animated={true}
>
<div style={{padding: '20px'}}>
<p>Hello World</p>
</div>
</Popover>
Here is their React handlers according to material-ui.com examples. And with this, if I trigger one popover, all of them are opened. Because this.state.open is common for every popover:
getInitialState() {
return {
open: false,
};
},
openPopover(event) {
this.setState({
open: true,
anchorEl: event.currentTarget,
});
},
closePopover(reason: string) {
this.setState({
open: false,
});
},
I want to have only those two handlers - openPopover and closePopover. I do not want to create 20 separate handlers for my 10 popovers.
Because of strange signature of closePopover function (for some reason they made it to expects a string instead of an event or an anchorElement) it is not possible to pass it anything to determine property on-the-fly as ES6 allows, for example:
getInitialState() {
return {
popover1: false,
};
},
// e.g. getAttribute('data-name') == 'popover1'
// <div onClick={this.openPopover} data-name="popover1">Open Popover</div>
openPopover(event) {
this.setState({
[event.currentTarget.getAttribute('data-name')]: true,
anchorEl: event.currentTarget,
});
},
// and here instead of useless "reason: string", an event or anchorElement would do the trick
closePopover(reason: string) {
this.setState({
[event.currentTarget.getAttribute('data-name')]: false,
});
},
What is material-ui.com best practice to have multiple popovers in one page? Hope the answer isn't to create a separate React Popover class for each popover on the page. Also hope it is not to create a separate handler functions for each and every popover.
The right way of dealing with multiple similar material-ui components is binding name with the callback function.
For example you have to deal with two popover :
Define state identifier for each popover like following at your constructor :
this.state = {
first : false,
second: false,
};
Then you can call individual popover actions with:
onTouchTap={this.handleTouchTap.bind(this, 'first')}
onTouchTap={this.handleTouchTap.bind(this, 'second')}
and finally at your handleTouchTap function, you can define :
handleTouchTap(name, event, index, value) {
event.preventDefault();
var change = {};
change[name] = true;
this.setState(change)
};
This don't need to create multiple handlers. You just need bind name to identify them.
Alternatives:
Create 20 handlers (which I know you don't want to do)
Depending upon what they are attached to, you may be able to create your own component that encapsulates the field/title/etc and popover. If all your fields are of the same type, that should be easy. If you have 5, then you'll need to create 5 different components.
Embed an identifier in your anchor element that you then key off of in your handlers with a big switch or if-elseif chain.
Write a handlerCreator function that returns a closure that encapsulates the identifier when the handlerCreate function returns the function closure.
I have an order line grid where I need to be able to open the popup editor form programmatically with the edit form fields pre-populated (using AngularJs).
In the HTML, I have a lineGrid and an addButton, which calls addRow() on the ticketEntryController:
<div id="wrapper" class="container-fluid" ng-controller="ticketEntryController">
<div ng-controller="ticketLineController">
<div kendo-grid="ticketLineGrid" k-options="getTicketLineGridOptions()"></div>
</div>
<button id="addButton" ng-click="addRow()" class="btn btn-primary btn-sm">Add Row</button>
</div>
Here is the ticketEntryController:
(function () {
'use strict';
angular.module('app').controller('ticketEntryController', ticketEntryController);
function ticketEntryController($scope) {
$scope.lineGrid = {};
$scope.addRow = function () {
var item = { itemNo: 'TEST123', itemDescr: 'Some description' };
$scope.$broadcast('AddRow', item);
}
}
})();
Here is part of the ticketLineController:
function ticketLineController($scope) {
$scope.$on('AddRow', function(event, item) {
console.log("ticketLineController, AddRow: " + item.itemNo);
$scope.itemNo = item.itemNo;
$scope.itemDescr = item.itemDescr;
$scope.ticketLineGrid.addRow();
});
Plunker: http://plnkr.co/edit/VG39UlTpyjeTThpTi4Gf?p=preview
When the Add Row button is clicked, the editor popup form opens up, but all fields are empty. How can I populate the fields (like they are when you click the Edit button for an existing row)?
I figured out how to get a row to be pre-populated for you, although I'm not sure if this is necessarily the best way to do it, but it does accomplish the job - I'm more familiar with AngularJs, not so much with Kendo UI.
The only place that the Kendo API allows you to change/set the new item that you are adding is in the edit event but I couldn't see a way to send your own object along to the event when you call addRow so you need to have a reference to a shared object in your controller with I called itemForAdd. Before calling addRow() in your controller, you need to set the itemForAdd object with the actual object that you want to pre-populate the form with.
var itemForAdd = {};
$scope.$on('AddRow', function(event, item) {
// save reference to item to use for pre-population
itemForAdd = item;
$scope.ticketLineGrid.addRow();
});
Now in the edit event that the Kendo API sends out, you can populate the items from your selected item in the model item. It's not really required, but I also like to clear out objects that I use like this so in the save and cancel events, I clear out the shared itemForAdd object.
edit: function (e) {
if (e.model.isNew()) {
e.model.set("itemNo", itemForAdd.itemNo);
e.model.set("itemDescr", itemForAdd.itemDescr);
}
var popupWindow = e.container.getKendoWindow();
e.container.find(".k-edit-form-container").width("auto");
popupWindow.setOptions({
width: 640
});
},
save: function(e) {
if (e.model.isNew()) {
// clear out the shared object
itemForAdd = {};
}
},
cancel: function(e) {
if (e.model.isNew()) {
// clear out the shared object
itemForAdd = {};
}
}
With the previous changes, the functionality that you want is mostly working but the data in the table in the edit popup doesn't show the updated values. This is because the Kendo data bindings apparently didn't know they had to update. I couldn't figure out how to make that work, so I just used the AngularJs style bindings for that table (where you had +=itemNo=+), so that the values in the table would update based on the changes in the model object:
<tbody>
<tr>
<td>{{dataItem.itemNo}}</td>
<td>{{dataItem.itemDescr}}</td>
<td>{{dataItem.cat}}</td>
<td>{{dataItem.mfg}}</td>
<td>{{dataItem.mfgPartNo}}</td>
</tr>
</tbody>
But there was one more issue at this point, only the itemNo was being updated, not the itemDescr and that was because itemDescr was set as editable: false in your grid configuration, so I had to changed it to editable: true
fields: {
id: { type: "string", editable: false },
itemDescr: { type: "string", editable: true },
...
},
And finally, here is an updated plunker with my changes: http://plnkr.co/edit/rWavvMh4dRFAsJjuygQX?p=preview