ionicView.enter not fired on child view and its controller - state

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.");
}
});

Related

How do I expose state properties from my add on component to the developer?

I'm currently experimenting with creating add-ons in Ember 3.8 and I'm a bit stuck on how to expose state properties to the developer. I have a simple button in my addon:
//ui-button/component.js
import Component from '#ember/component';
import layout from './template';
import { oneWay } from '#ember/object/computed'
export default Component.extend({
layout,
tagName: '',
type: 'button',
task: null,
isRunning: oneWay('task.isRunning'),
disabled: oneWay('task.isRunning'),
onClick(){},
actions: {
click(){
event if 'type' = submit)
event.preventDefault();
let task = this.task;
let onClick = this.onClick;
task ? task.perform() : onClick();
}
}
});
//ui-button/template.hbs
<button onclick={{action "click"}} disabled={{disabled}} type={{type}}>
{{yield}} {{if disabled "..."}}
</button>
The tasks and actions simply live on application controller:
buttonTask: task(function*() {
yield timeout(2000)
yield alert("Clicked after 2 seconds")
}),
actions: {
clicker() {
alert('clicked')
}
}
I can call it from an ember project like this:
<UiButton #onClick={{action "clicker"}}>
Angle Bracket
</UiButton>
<UiButton #task={{task buttonTask}}>
Angle Bracket with task
</UiButton>
and it works fine, but I'd like to be able to give the developer access to disabled or isRunning so that they can add their own behaviour.
I tried to create an intermediate component that yielded out the properties:
//ui-button-yield/template.hbs
{{yield (hash
button=(component "ui-button"
task=#task)
isRunning=task.isRunning
disabled=task.disabled
)
}}
and call it like this:
{{#ui-button-yield as |button|}}
{{#button.button task=buttonTask}}
Handlebars Yield {{if button.isRunning "!!!"}}
{{/button.button}}
{{/ui-button-yield}}
But although the button works I can't access either of the disabled or isRunningproperties from the codeblock. Furthermore, according to Ember Inspector these properties aren't available on the ui-button-yield component, only the ui-button.
your problem is that you yield from ui-button-yield but you've defined isRunning and disabled in ui-button.
If you really want to use this wrapping contextual compoent then you need to move your logic to it.
You got very close but your consuming template needs to be setup a little differently
{{#ui-button-yield as |button isRunning disabled|}}
{{#button.button task=buttonTask}}
Handlebars Yield {{if isRunning "!!!"}}
{{/button.button}}
{{/ui-button-yield}}
You may also want to consider:
{{#ui-button-yield task=buttonTask as |button isRunning disabled|}}
{{#button.button}}
Handlebars Yield {{if isRunning "!!!"}}
{{/button.button}}
{{/ui-button-yield}}

Ember Data automatic refresh after save

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
);

Page loading before transition effects happen

I finally got PJAX all setup and working perfect on my Foundation 5 site and its time to add my page transitions. For some reason no matter what I try the page loads and then the transition happens.
Here is my website with with one of the transitions I tried
I've also tried simple things like:
$(document)
.on('pjax:start', function() { $('#main').fadeOut(200); })
.on('pjax:end', function() { $('#main').fadeIn(200); })
I also ran into aenism.com/teleportation-is-scary/ in my searches for a solution and its what I currently have running on my pages.
Here is an example of it working: Demo Site
I'm not sure what the problem could be at this point.
I found a solution that works perfect for fading out and back in again. I have not tested it with other animations but it looks like it should do the trick. I hope this helps someone else!
// USER CLICKS LINK WITH PJAX CLASS
$('body').delegate('a.pjax', 'click', function() {
// CONTENT FADE OUT TRANSITION BEGINS
$('#main-content').fadeOut(300, function() {
// CALLBACK TO RUN PJAX AFTER FADEOUT
$.pjax({
url: target,
container: '#main-content',
fragment: '#main-content'
})
})
// STOP THE LINK FROM WORKING NORMALLY
return false;
})
// PJAX DOIN THANGS TO THE CONTENT FRAGMENT IDENTIFIED ABOVE
$('#main-content')
.on('pjax:start', function() {
// KEEPING THE MAIN CONTENT HIDDEN
$(this).fadeOut(0)
})
.on('pjax:end', function() {
// FADE IN THE MAIN CONTENT
$(this).fadeIn(300)
// FUNCTIONS LOADED AGAIN AFTER PJAX ENDS GO HERE
})
WOOO That suggestion worked, had to tweak it a bunch to get it to fit with my page transitions, but this is what I ended up with (works off of css3 animations):
$("body").delegate('a[data-pjax]', 'click', function(event) {
var target = $(this).attr("href");
if (contentpage == "true" || errorpage == "true") { $(".contentimage").append('<div class="pjax-loading"></div>'); }
$("body").removeClass("pjax-fadeIn").addClass("pjax-fadeOut").one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function(){
$.pjax({url: target, container: '#content', fragment: '#content'});
});
return false;
})
$("#content").on('pjax:start', function() {
$("body").removeClass("pjax-fadeOut").addClass("pjax-hide");
}).on('pjax:complete', function() {
$("body").removeClass("pjax-hide").addClass("pjax-fadeIn");
});

Passing parameters between states in Ember.StateManager

When using Ember.StateManager, the most common transition between Em.States involve some parameter or another. Currently, I am using instance variables within the StateManager to pass parameters between States, when I do go from one state to another using goToState. This seems incredibly ugly to me. Is there a reason there is not a more standard way of passing parameters? Or should I use a different pattern.
For example,
App.stateManager = Em.StateManager.create({
initialState: 'listContacts',
listContacts: Em.ViewState.create({
...
actionSelectContact: function(manager, context) {
manager.set('selectedContact', context);
manager.goToState('showContact');
}
}),
showContact: Em.ViewState.create({
enter: function(manager, transition) {
var contactToShow = manager.get('selectedContact');
...
}
...
})
})
Is there a better way to do this parameter passing between states?
Tom Dale just added a transitionTo method to deal with this. transitionTo takes a context object along with the name of the target state. Now within your action you could do something like,
viewStates = Ember.StateManager.create({
showingPeople: Ember.ViewState.create({
view: ContactListView
}),
showDetailAction: function(mgr, selectedPerson) {
mgr.transitionTo('showingPersonDetail', selectedPerson);
},
showingPersonDetail: Ember.ViewState.create({
setupContext: function(manager, context) {
this.set('person', context);
},
view: PersonDetailView
})
})
You could also get more fancier and pass parameters for multiple states along the way like,
stateManager.transitionTo(['planters', { company: true }], ['nuts', { product: true }]);
I'm not an Ember expert, but I think you could achieve this using stateManager.send() method where the second argument will be the object you want to pass between states.
Most of your answer is in the Ember.StateManager documentation.
There was a pull request in Ember talking about extra params in goToState() method here, but it has been closed because goToState() should only be used internally as joewest says here with tomdale:
goToState should only be called from within a state. To accomplish this, just implement an action that takes additional parameters, and have it call goToState for you.

How do I observe *all* property changes on a model object?

I have a model built from a JSON object.
// extend the json model to get all props
App.Model = Ember.Object.extend(window.jsonModel);
I want to automatically save the model when anything is updated. Is there any way I can add an observer to the whole model?
EDIT: // adding the solution I currently go
For now I do:
// XXX Can't be right
for (var prop in window.jsonModel) {
if (window.jsonModel.hasOwnProperty(prop)) {
App.model.addObserver(prop, scheduleSave);
}
}
This is a large form, which means I'm adding tons of observers – it seems so inefficient.
A firebug breakpoint at Ember.sendEvent() reveals that there are events called App.model.lastName:change being sent. I could hack in an intercept there, but was hoping for an official way.
You can bind to isDirty property of subclass of DS.Model. The isDirty changes from false to true when one of model properties changes. It will not serve well for all cases because it changes only once until reset or committed, but for your case -
I want to automatically save the model when anything is updated. Is there any way I can add an observer to the whole model?
it may work fine.
From the article:
autosave: function(){
this.save();
}.observes('attributes'),
save: function(){
var self = this,
url = this.get('isNew') ? '/todos.json' : '/todos/'+this.get('id')+'.json',
method = this.get('isNew') ? 'POST' : 'PUT';
$.ajax(url, {
type: 'POST',
// _method is used by Rails to spoof HTTP methods not supported by all browsers
data: { todo: this.get('attributes'), _method: method },
// Sometimes Rails returns an empty string that blows up as JSON
dataType: 'text',
success: function(data, response) {
data = $.trim(data);
if (data) { data = JSON.parse(data); }
if (self.get('isNew')) { self.set('id', data['todo']['id']); }
}
});
},
isNew: function(){
return !this.get('id');
}.property('id').cacheable(),
I had the same requirement, and not finding a suitable answer, I implemented one.
Try this: https://gist.github.com/4279559
Essentially, the object you want to observe all the properties of MUST be a mixed of Ember.Stalkable. You can observe the properties of that object as 'item.#properties' (or, if you bake observers directly on the Stalkable, '#properties' alone works. "#ownProperties", "#initProperties" and "#prototypeProperties" also work, and refer to (properties that are unique to an instance and not defined on any prototype), (properties that are defined as part of the create() invocation), and (properties that are defined as part of the class definition).
In your observers, if you want to know what properties changed and invoked the handler, the property "modifiedProperties", an array, will be available with the names of the changed properties.
I created a virtual property _anyProperty that can be used as a dependent key:
import Ember from 'ember';
Ember.Object.reopen({
// Virtual property for dependencies on any property changing
_anyPropertyName: '_anyProperty',
_anyProperty: null,
propertyWillChange(keyName) {
if (keyName !== this._anyPropertyName) {
this._super(this._anyPropertyName);
}
return this._super(keyName);
},
propertyDidChange(keyName) {
if (keyName !== this._anyPropertyName) {
this._super(this._anyPropertyName);
}
return this._super(keyName);
}
});