Ember 2.5 observe session property changes - session

I've monkey-patched my router to store the current route components in a session variable:
var Router = Ember.Router.extend({
customSession: Ember.inject.service('session-custom'),
location: config.locationType,
didTransition: function() {
this._super(...arguments);
this.get('customSession').set('currentEntity', this.get('currentRouteName').split('.')[0]);
this.get('customSession').set('currentDetailView', this.get('currentRouteName').split('.')[1]);
}
});
I know that this is not the cleanest of solutions, but writing the session to the console proves that at least those parameters are set.
In my controller, I'd like to listen for changes in these parameters, but somehow this does not work:
import Ember from 'ember';
import ApplicationController from './application';
export default ApplicationController.extend({
customSession: Ember.inject.service('session-custom'),
currentRouteNameChanged: Ember.observer('customSession.currentEntity', function () {
console.log("route changed");
})
});
i.e. "route changed" is never printed to the console.
This seems quite an easy fix, but I haven't been able to find a solution on SO.
Thanks!

Perhaps consider using an initializer to inject your session-custom service into your application’s controllers. To get there, some suggestions…
First, in the Router, and elsewhere, use the conventional, camelized short-hand to inject your service, like this:
sessionCustom: Ember.inject.service(),
...and be sure to reference sessionCustom in your code, instead of customSession.
Next, create a session-custom initializer, and inject the service into your application’s controllers:
export function initialize(application) {
application.inject('controller', 'sessionCustom', 'service:session-custom');
}
export default {
name: 'session-custom',
initialize,
};
Observing route changes from the controller should now be successful:
export default Ember.Controller.extend({
currentRouteNameChanged: Ember.observer(
'sessionCustom.currentEntity', function() {
console.log("CONTROLLER: ", this.get('sessionCustom.currentEntity'));
}
),
});
These changes, of course, can also be observed from the service itself:
export default Ember.Service.extend({
currentEntity: '', // <-- it's helpful to explicitly declare
currentRouteNameChanged: Ember.observer(
'currentEntity', function() {
console.log("SERVICE: ", this.get('currentEntity'));
}
),
});
I’ve created an Ember Twiddle to demonstrate this solution.

Related

How to build a Model Layer in Vue3 just like other MVC language?

my name is DP, I have 2 years Vue2 experience, but I am new to Vue3. I am learning Vue3 recently, as I found the "setup(Composition API)" just like the "Controller(in MVC)" that I did in other language, so I am trying to build my test Vue3 project in MVC way, but I go some problem can anyone help? thx!
MVC Plan
M - use class
V - use <template> ... </template>
C - use setup
My Problem
working: using loadTopic_inSetup().then() in setup is working, because topicList_inSetup is defined in setup() too.
not working: using loadTopic_inModel() in setup is not working, I guess some kind data keep problem, because in console I can see the data already got from API
as u can see, I am not expert for js/ts, I am a backend developer, so if you know how to do it, plz help thx very much.
BTW, VUE is greet, I love it.
My Code
//APIBased.ts
import { ajax } from "#/lib/eeAxios"
export class APIBased {
//load data with given url and params
loadData(apiPath: string, params?: object): Promise<any> {
apiPath = '/v1/'+apiPath
return ajax.get(apiPath, params)
}
}
//Topic.ts
import { APIBased } from "./APIBased";
import { ref } from 'vue'
export class Topic extends APIBased {
//try keep data in model
topicList: any = ref([]);
constructor() {
super()
}
//direct return ajax.get, let setup do the then+catch
loadTopic_inSetup() {
return super.loadData('topics', { t_type_id: 1 })
}
//run ajax get set return data to this.topicList, keep data in model
loadTopic_inModel() {
super.loadData('topics', { t_type_id: 1 }).then((re) => {
console.log(re.data)
this.topicList = re.data
})
}
}
//EETest.vue
<template>
<EELayoutMainLayout>
<template v-slot:mainContent>
<h1>{{ "Hello Vue3 !!" }}</h1>
<hr/>
{{to.topicList}} //not working... just empty array
<hr/>
{{topicList_inSetup}} //working... topic list return from API show here.
</template>
</EELayoutMainLayout>
</template>
<script lang="ts">
import { defineComponent, getCurrentInstance, ref } from 'vue'
import EELayoutMainLayout from '#/components/eeLayout/EELayoutMainLayout.vue'
import { Topic } from "#/models/Topic";
export default defineComponent({
name: 'EETest',
props: {
},
setup() {
let topicList_inSetup = ref([])
const to = new Topic()
//try keep data in setup, it's working
to.loadTopic_inSetup().then((re) => {
topicList_inSetup.value = re.data
console.log(re.data)
})
//try keep data in model, the function is run, api return get, but data not show, even add ref in model
to.loadTopic_inModel()
return {
topicList,
to,
}
},
components: {
EELayoutMainLayout,
},
})
</script>
A few digressions before solving the problem. Maybe you are a java developer. I personally think it is inappropriate to write the front end with Java ideas. The design of vue3's setup is more inclined to combined functional programming
To fully understand why you need some pre knowledge, Proxy and the get and set method of Object
They correspond to the two core apis in vue, reactive and ref,
The former can only be applied to objects( because proxy can only proxy objects),The latter can be applied to any type(primary for basic javascript types, get and set can apply for any type)
You can modify the code to meet your expectations
loadTopic_inModel() {
super.loadData('topics', { t_type_id: 1 }).then((re) => {
console.log(re.data)
this.topicList.value = re.data
})
}
You cannot modify a ref object directly, a test case to explain what is reactive
when ref function is called, a will be like be wrapped in a class has value properties, and has get and set method
the effect function will call the arrow function, and in this time, the get method of a will be called and it will track as a dependence of the effect function, when a changed, the set method of a will be called, and it will trigger the arrow function,
so when you direct modify the a, the setter method will never trigger, the view will not update
const a = ref(1)
let dummy
let calls = 0
effect(() => {
calls++
dummy = a.value
})
expect(calls).toBe(1)
expect(dummy).toBe(1)
a.value = 2
expect(calls).toBe(2)
expect(dummy).toBe(2)
// same value should not trigger
a.value = 2
expect(calls).toBe(2)

How can i override placeOrder() action in Magento 2

I'm newbie in Magento. My shop should work with a web service. I have to check availability of products from web service before magento creates a new order. And after creating order successful i have to send the orderId back to web service. All this actions should be execute when a customer confirm a button "place order".
In a picture you see an "Place Order". I not sure how Magento does create a new order. I assume that an action placeOrder() will be call. My aim is to put a method checkAvailability() before this action and and method sendOrderId() after this action. checkAvailability() and SendOrderId() are the methods from webservice.
Has somebody an idea, how and where can i do that?
Sorry about bad english. Thank you
If you need to overwrite a function instead a class method (I used to overwrite Magento_Checkout/js/action/place-order).
requirejs-config.js
var config = {
config: {
mixins: {
'Magento_Checkout/js/action/place-order': {
'My_Module/js/action/place-order': true
}
}
}
};
place-order.js
define(['mage/utils/wrapper'], function (wrapper) {
'use strict';
return function (placeOrderAction) {
return wrapper.wrap(placeOrderAction, function (originalAction, paymentData, redirectOnSuccess) {
// my own code here
return originalAction(paymentData, redirectOnSuccess);
});
};
});
For your requirement, you need to used this event.
Used this event observer to check checkAvailability()
checkout_onepage_controller_success_action
Used this event observer to used SendOrderId()
sales_order_place_after
I had a similar case. I needed to override placeOrder action that was announced in third part module (Amasty_Checkout).
So, my solution was to create mixin in my theme.
1) Announce the mixin in theme with myTheme/Amasty_Checkout/requirejs-config.js:
var config = {
config: {
mixins: {
'Amasty_Checkout/js/view/onepage': {
'Amasty_Checkout/js/view/onepage-extend': true
}
}
}
};
2) Add mixin myTheme/Amasty_Checkout/web/js/view/onepage-extend.js with code:
define(
[
'jquery',
'uiComponent',
'ko',
'uiRegistry',
'Magento_Checkout/js/model/quote',
'Amasty_Checkout/js/action/set-shipping-information',
'Amasty_Checkout/js/model/agreement-validator',
'Amasty_Checkout/js/model/agreement-validator-old',
'Magento_Checkout/js/model/payment/additional-validators',
'Amasty_Checkout/js/model/amalert',
'mage/translate'
],
function (
$,
Component,
ko,
registry,
quote,
setShippingInformationAction,
checkoutValidator,
checkoutValidatorOld,
additionalValidators,
alert,
$t
) {
'use strict';
var mixin = {
placeOrder: function () {
// Here you put your extended code
}
};
return function (target) { // target == Result that Magento_Ui/.../default returns.
return target.extend(mixin); // new result that all other modules receive
};
});
Note that in my case I copied all content in define[...] section from original module script ('Amasty_Checkout/js/view/onepage') that I needed to override.
Here is the resource that helped me with my solution https://github.com/magento/magento2/issues/1864#issuecomment-141112927
I hope this will help someone save time.

Behaviors.behaviorsLookup typical value

Quoted from Marionette.Behavior documentation
Finally, the user must define a location for where their behaviors are stored.
A simple example of this would look like this:
Marionette.Behaviors.behaviorsLookup = function() {
return window.Behaviors;
}
But window.Behaviors is undefined. When I use window everything is good. Do I miss something?
It's undefined because you probably haven't defined it yet. You would create an object window.Behaviors = {} that would be attached to the window when the app starts. From there you could register behaviors off of that and reference window.Behaviors like so,
window.Behaviors.ExampleBehavior = Marionette.Behavior.extend({
defaults: {},
events: {},
//etc..
});
Then inside of your behaviorsLookup, returning window.Behaviors won't be undefined. Here's the documentation explaining it further

Is there a way to add a Jasmine matcher to the whole environment

There are plenty of documents that show how to add a matcher to a Jasmine spec (here, for example).
Has anyone found a way to add matchers to the whole environment; I'm wanting to create a set of useful matchers to be called by any and all tests, without copypasta all over my specs.
Currently working to reverse engineer the source, but would prefer a tried and true method, if one exists.
Sure, you just call beforeEach() without any spec scoping at all, and add matchers there.
This would globally add a toBeOfType matcher.
beforeEach(function() {
var matchers = {
toBeOfType: function(typeString) {
return typeof this.actual == typeString;
}
};
this.addMatchers(matchers);
});
describe('Thing', function() {
// matchers available here.
});
I've made a file named spec_helper.js full of things like custom matchers that I just need to load onto the page before I run the rest of the spec suite.
Here's one for jasmine 2.0+:
beforeEach(function(){
jasmine.addMatchers({
toEqualData: function() {
return {
compare: function(actual, expected) {
return { pass: angular.equals(actual, expected) };
}
};
}
});
});
Note that this uses angular's angular.equals.
Edit: I didn't know it was an internal implementation that may be subjected to change. Use at your own risk.
jasmine.Expectation.addCoreMatchers(matchers)
Based on previous answers, I created the following setup for angular-cli. I also need an external module in my matcher (in this case moment.js)
Note In this example I added an equalityTester, but it should work with a customer matcher
Create a file src/spec_helper.ts with the following contents:
// Import module
import { Moment } from 'moment';
export function initSpecHelper() {
beforeEach(() => {
// Add your matcher
jasmine.addCustomEqualityTester((a: Moment, b: Moment) => {
if (typeof a.isSame === 'function') {
return a.isSame(b);
}
});
});
}
Then, in src/test.ts import the initSpecHelper() function add execute it. I placed it before Angular's TestBed init, wich seems to work just fine.
import { initSpecHelper } from './spec_helper';
//...
// Prevent Karma from running prematurely.
__karma__.loaded = function () {};
// Init our own spec helper
initSpecHelper();
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
//...

ExtJS4 modularization with stand-alone controllers?

I'm trying to split an ExtJS4 application into modules.
- app
- store
- controller
- model
- view
- module
- m1
- model
- view
- controller
- m2
- model
- ...
The problem is, when I start the application and it inits one of the m1 controllers, the controller has no this.control() function.
-- edit --
I defined a class inside of the controller folder.
Ext.define( 'App.module.ping.controller.Ping', {
extend: 'Ext.app.Controller',
requires: [
'App.module.ping.view.PingPanel'
],
init: function() {
this.control( {
'#app.module.ping': {
render: this.onPanelRendered
}
} );
},
onPanelRendered: function() {
...
}
} );
Later I call
Ext.create('App.module.ping.controller.Ping').init();
but I get this error:
Uncaught TypeError: Cannot call method 'control' of undefined
Ext.define.control./lib/extjs-4.1.0/src/app/Controller.js:414
Ext.define.init./app/module/ping/app/controller/Ping.js:11
Ext.define.init./app/module/ping/app/Module.js:11
(anonymous function)app.js:17
(anonymous function)app.js:35
Module.js is the file with the create() call
Ping.js is the file with the define() call
-- edit2 --
Pathological example:
input:
Ext.define( 'MyController', {
extend: 'Ext.app.Controller',
init: function() { console.log('initialized'); }
});
output:
function constructor() {
return this.constructor.apply(this, arguments);
}
input:
Ext.create('MyController');
output:
constructor
input:
MyController.create();
output:
constructor
-- edit3 --
The controllers require the application object in the config object when created. The application object adds itself to all the controllers, which are not manually created with create. It calls something like this:
Ext.create('App.controller.Users', { application: this, ... } );
Later it uses the application object to redirect the control call to it.
control: function ( config ) { this.application.control( config ) };
So I'm probably gonna implement some mechanism, which adds the application to those controllers automatically, when I create them.
Have you tried
var pingController = Application.getController('App.module.ping.controller.Ping');
pingController.init(); //or pingController.init(Application);
where Application is a reference to the object created during Ext.application.launch - such as
var Application = {};
Ext.application({
//all of your settings,
launch:function(){
Application = this;
//other launch code
}
}
});
To anyone else who comes here searching for a solution to:
Uncaught TypeError: Cannot call method 'control' of undefined
Day long frustration aside, the above answer did not solve my problem, but lead me to try the following, which did fix the issue:
// app.js
Ext.application({
...
launch: function(){
var me = this;
...
me.getController('ControllerName');
}
});
// inside controller/ControllerName.js
init: function(app){
var me = this;
app.control({
'#editButton': {
click: function(){
me.someFunction();
}
}
});
},
That app variable is the same as the "var Application = {}" that's in the chosen answer for the question -- meaning I didn't need to add it globally (or at all). I must've tried a million different things, but this finally worked. Hope it saves someone else.
Don't need to call init() manually. Just call Ext.create and constructor will be called automatically.

Resources