Ember Data automatic refresh after save - ember.js

The Ember page I'm working on is to display a of grid of operators and their jobs and have that list automatically update when the user creates a new job. My current best attempt will draw pre-existing jobs on page load, but the page doesn't refresh with any new jobs created using 'saveNewJob' even though I can see the new job in the Data view of Ember Inspector.
Here's the code with some '..snip..' inserted to focus on the important parts:
routes/scheduler.js
export default ember.Route.extend({
model: function() {
return {
jobs: this.store.query('job', {
location: session.location,
date: session.selectedDate
},
operators: this.store.query('operator', {
location: session.location
}
}
},
action: {
saveNewJob: function(params) {
var newJob = this.store.createRecord('job',{
//job properties from params
};
var thisRoute = this;
newJob.save().then(function(){ thisRoute.refresh() });
}
}
}
templates/scheduler.hbs
..snip..
{{#each model.operators as |op|}}
{{operator-row operator=op jobs=model.jobs}}
{{/each}}
{{outlet}}
templates/components/operator-row.hbs
<!-- Draw the grid for the operator -->
..snip..
<!--Draw jobs over grid -->
{{#if jobs.isFulfilled}}
{{#each jobsForOperator as |job|}}
{{operator-job job=job}}
{{/each}}
{{/if}}
component/operator-row.js
jobsForOperator: Ember.computed('jobs', function() {
var opId = this.operator.get('id');
var retVal this.jobs.filter(function(item) {
return item.get('operator').get('id') === opId;
});
<!-- Append some drawing properties to each job in retVal -->
..snip...
},
..snip..
I haven't seen a need to add anything to controller/scheduler. The operator-job component is a simple div that uses the drawing properties to correctly place/draw the div in the operator row.
There are various ways for a new job to be created, but I purposefully left them out because they all end up calling 'saveNewJob' and I am able to get a console.log statement to fire from there.
One solution I've tried is adding a 'this.store.findAll('job');' at the start of the model function and then using 'jobs: this.store.filter('job', function() { })' to create the model.jobs property. That sees neither the existing jobs nor the newly created job returned despite seeing my date and location matches return true.
So what am I doing wrong here? Is there a better way to get this refresh to happen automatically? Appreciate any help you all can give.

There is function for just this case DS.Store.filter. You use it instead of query. Something like this:
let filteredJobs = this.store.filter(
'job',
{location: session.location, date: session.selectedDate},
job => job.get('location') === session.location && job.get('date') === session.selectedDate
);

Related

Is it possible to pass multiple arguments to onChange action in Ember Power Select?

I'm currently using the excellent ember-power-select add on as part of an ember-bootstrap form.
I have multiple drop down items on the form and I am trying to unify how they are handled into a single function that can be used as the onChange action in the power-select invocations:
{{#form.element
controlType="power-select"
label="Destination"
value=destinationSelection
options=destinationOptions
as |el|}}
{{#el.control
onChange=(action "setDropDown")
searchField="name"
as |item|}}
{{item.name}}
{{/el.control}}
{{/form.element}}
My handler function will simply set some values based on the selection of the drop down:
actions: {
setDropDown(selected, string) {
handleDropDown(selected, dropdown, this)
}
}
function handleDropDown(selected, dropdown, controller) {
let selection = `${dropdown}Selection`
let modelid = `model.${dropdown}_id`
set(controller, selection, selected)
set(controller, modelid, selected.id)
}
In order for this to work I really need to be able to pass a string to the setDropDown action from the onChange part of the component call, otherwise I have no way of telling the handler function which particular fields it should be setting without creating an action per dropdown.
However when I try passing in multiple arguments like
onChange=(action "setDropDown" "destination")
or
onChange=(action "setDropDown" selected "destination")
I lose the basic functionality of the onChange action taking the selected item as it's first argument.
I looked through the documentation and couldn't find any examples where the library author is passing multiple arguments into the onChange action and wondered if it was possible without breaking the functionality of the library.
You can use a specialized higher order helper function to create an action for ember-power-select that will ultimately invoke your action with extra arguments. Consider this helper handle-dropdown
import { helper } from '#ember/component/helper';
export function invokeFunction([prop, action]) {
return function(){
action(prop, ...arguments);
}
}
export default helper(invokeFunction);
So what we are doing here is creating the function that will be invoked by ember-power-select. In this function, we are invoking the original action with prop first, followed by every argument that ember-power-select invoked our onchange function with.
In your template, invoke this helper when passing your action to power-select
{{#power-select
onchange=(handle-dropdown 'foo' (action 'dropdownChanged'))
as |dropdown|}}
And then your action would be
actions: {
dropdownChanged(keyToSet, selectedValue){
this.set(keyToSet, selectedValue);
}
}
This would ultimately call dropdownChanged('foo', /* the selected value */)
Ember Bootstrap's Power Select integration gives you a nice API for use cases like this one. Let me give you an example.
Lets take a country selector as an example. We have a list of countries represented by a list of objects holding their two-letters country code as defined by ISO 3166-1 as id property and their name as name. The selected country should be represented on the model which is a POJO by there country code.
export default Component.extend({
// country code of country selected or null
selectedCountry: null,
// Using a computed property here to ensure that the array
// isn't shared among different instances of the compontent.
// This isn't needed anymore if using native classes and
// class fields.
countries: computed(() => {
return [
{ id: 'us', name: 'United States of America' },
{ id: 'ca', name: 'Canada' },
];
}),
// Using a computed property with getter and setter to map
// country code to an object in countries array.
selectedCountryObject: computed('selectedCountry', {
get() {
return this.countries.find((_) => _.id === this.selectedCountry);
},
set(key, value) {
this.set('selectedCountry', value.id);
return value;
}
}),
});
Now we could use Ember Bootstrap Power Select as expected:
{{#bs-form model=this as |form|}}
{{form.element controlType="power-select" property="selectedCountryObject" label="Country" options=this.countries}}
{{/bs-form}}
Disclaimer: Haven't tested that code myself, so there might be typos but I hope you get the idea.

Ionic - How to detect and pass value back to original view with $ionicHistory.goBack();

Using ionic, I am trying to have a use case to select from a list and return back to the original view with some value. I'ved already done most of the part except detecting it has returned to the original view and passing a value back to the original view.
Here's so far what i'ved accomplished:
button that goes to a list
<button class="button button-block button-outline button-positive" ng-click="performselectUnit()"> Select Unit
</button>
this is the trigger to go to the new view with the list
$scope.performselectUnit = function(){
console.log('performselectUnit');
$state.go('app.units');
}
the view with list when press performs an action on the selected row
<ion-item collection-repeat="unit in units" class="item item-icon-right item-icon-left" ng-click="selectUnit(unit.id)">
on selection of the row it goes back to the original view with $ionicHistory.goBack()
$scope.selectUnit = function(unit_id){
console.log('performselectUnit:' + unit_id);
$ionicHistory.goBack();
}
From the last function, how do detect its gone back to the original view and pass some value.
Thanks.
UPDATE:
I tried this.
Broadcast the result
$scope.selectUnit = function(unit_id){
console.log('performselectUnit:' + unit_id);
$ionicHistory.goBack();
$rootScope.$broadcast('selected-unit', { data: unit_id });
}
in the original view controller i capture the event and result.
$rootScope.$on('selected-unit', function(event, args) {
console.log("received selected-unit" + args.data);
$scope.showSelectedUnit = args.data;
});
but it NEVER got updated in the view
<label class="item item-text-wrap">
<button class="button button-block button-outline button-positive" ng-click="performselectUnit()"> Select Unit
</button>
{{showSelectedUnit}}
</label>
How can I get it to update in the view ? or is there a better way
Faced to the exact same issue, I could make it work by switching the order of calls to goBack and broadcast:
$rootScope.$broadcast('selected-unit', { data: unit_id });
$ionicHistory.goBack();
You can use pub-sub service for sharing info between two ctrl
fiddle demo
function MyCtrl($scope, datasharer) {
$scope.sharedData = datasharer.getSharedData();
$scope.send = function() {
datasharer.setSharedData($scope.name);
}
}
function My2Ctrl($scope, datasharer) {
function getSendData(data) {
console.log(data);
$scope.sharedData = data;
}
datasharer.registerForSharedData(getSendData);
}
Using $rootScope.$broadcast and $rootScope.$on should resolve your problem indeed, just use $scope.$apply in $rootScope.$on:
$rootScope.$on('selected-unit', function(event, args) {
console.log("received selected-unit" + args.data);
$scope.$apply(function() {
$scope.showSelectedUnit = args.data;
});
});
What's more, the $rootScope.$broadcast is always expensive, so you could try $rootScope.$emit instead. More about angular event, please refer to https://toddmotto.com/all-about-angulars-emit-broadcast-on-publish-subscribing/.
But the more graceful solution is use Service to share data between controllers, you could refer to Share data between AngularJS controllers.

Reload model/update template on createRecord save

I see this question is being ask all over again still don't find solution that works for such a trivial task.
This url displays a list of navigations tabs for workspaces.
http://localhost:4200/users/1/workspaces
Each of tab resolves to
http://localhost:4200/users/1/workspaces/:wid
Also on the I have a button that suppose to create a new workspace as well as new tab.
Here how controller for looks:
export default Ember.Controller.extend({
actions: {
newWorkspace: function () {
this.get('currentModel').reload();
var self = this;
var onFail = function() {
// deal with the failure here
};
var onSuccess = function(workspace) {
self.transitionToRoute('dashboard.workspaces.workspace', workspace.id);
};
this.store.createRecord('workspace', {
title: 'Rails is Omakase'
}).save().then(onSuccess, onFail);
}
}
});
When I click on button I see in ember inspector new record indeed created as well as url redirected to id that represents newly created workspace.
My question is how to force model/template to reload. I have already killed 5h trying model.reload() etc. Everything seem not supported no longer. Please please help.
UPDATE
When adding onSuccess
model.pushObject(post);
throws Uncaught TypeError: internalModel.getRecord is not a function
I believe you should call this.store.find('workspace', workspace.id) for Ember Data 1.12.x or earlier. For 1.13 and 2.0 there are more complicated hooks that determine whether or not the browser should query the server again or use a cached value; in that case, call this.store.findRecord('workspace', workspace.id, { reload: true }).
I do not know if this help. I had a similar problem. My action was performed in the route. Refresh function took care of everything.

ionicView.enter not fired on child view and its controller

I have two nested states, consisted of a parent abstract state and a child state:
.state('app.heatingControllerDetails', {
url: "/clients/:clientId/heatingControllers/:heatingControllerId",
abstract: true,
views: {
'menuContent': {
templateUrl: "templates/heatingController.html",
controller: 'HCDetailsCtrl'
}
}
})
.state('app.heatingControllerDetails.wdc', {
url: "/wdc",
views: {
'hc-details': {
templateUrl: "templates/heatingControllers/wdc.html",
controller: 'WdcDetailsCtrl'
}
},
resolve:{
hcFamily: [function(){
return 'wdc';
}]
}
})
and two controllers are:
.controller('HCDetailsCtrl',function($scope){
$scope.$on("$ionicView.enter", function (scopes, states) {
...
});
})
.controller('WdcDetailsCtrl',function($scope){
$scope.$on("$ionicView.enter", function (scopes, states) {
...
});
})
When I invoke state app.heatingControllerDetails.wdc, both controllers are created, but $ionicView.enter is only invoked on the parent controller. Any idea?
In heatingController.html, hc-details view is defined as follows:
<ion-content class="has-header" ng-show="hc">
<div ui-view name="hc-details"></div>
<div class="disableContentDiv" ng-hide="hc.state=='Online'"></div>
</ion-content>
When working with nested views, you have to use $ionicNavView events instead of $ionicView
That being said, at the last release these events are bugged, and they're currently working in a fix: Github issue
I found a work around for this. Place this in a parent of the view, or place it in the first controller that is loaded when running your application.
You can use this to observe any 'to and from' view changes. Just doing a string comparison on the url of the toState emulated ionicView.enter well enough to achieve what I needed it for. Keep in mind you need to be using UI-router to do this. Hope this helps!
$rootScope.$on('$stateChangeStart',
function(event, toState, toParams, fromState, fromParams, options){
if(toState.url == "/video/:Id"){
console.log("Leaving the view.");
}
});

How to remove events from nicEditor + emberjs

I am working on an Ember.js - based platform, where I use nicEdit. Here is my code
RichHTMLView = Ember.TextArea.extend({
id: null,
editor: null,
didInsertElement: function(){
var view = this;
view.id = this.get("elementId");
view.editor = new nicEditor({
buttonList : ['bold','italic','underline','right','center','justify', 'link', 'ul', 'ol']
}).panelInstance(view.id);
//When the editor looses focus the content of the editor is passed to descr
view.editor.addEvent('blur',function(){
view.get('controller').set('descr',view.getViewContent());
});
//So the editor looks nice
$('.nicEdit-panelContain').parent().width('100%');
$('.nicEdit-panelContain').parent().next().width('100%');
},
getViewContent: function(){
var view = this,
inlineEditor = view.editor.instanceById(view.id);
return inlineEditor.getContent();
},
willClearRender: function(){
var view = this;
}
});
So this works nicely as long as I am on the page which hosts the view, but if I transition to another route, the view has some leftovers, namely the editor is destroyed, but I assume that nicEdit keeps track of event bindings, so I end up with the blur event being bound to editor, which is undefined in the new context, as the view does not exist.
My best guess is that I need to somehow unbind the editor in the willClearRender, but I don't know how.
as I got no reply and nicEdit is abandoned I made some changes to the source-code in order to deal with this issue by adding removeEvent to bkEvent:
removeEvent: function(A, B){
if (B){
this.eventList = this.eventList || {};
this.eventList[A] = this.eventList[A] || [];
this.eventList[A].splice(this.eventList[A].indexOf(B),1);
}
Then I can remove the event in willClearRender:
view.editor.removeEvent('blur',view.onBlur);
Be aware that I've not tested it with multiple editors, as my needs do not require, but if you have multiple editors with the same callback the behavior is not defined.