I recently took over the development of an AngularJS application created by my company, and am currently trying to add a button to a cell in a table, which the user will be able to use as a navigation button, and set the location it directs you to as any one of the user's custom pages.
In the table directive, I have added the following code:
.directive('appTable', function(fxTag) {
return {
...
template: '...',
controller: function($scope, $element, $compile, Global, fxEvent, fxSearch, NotifyMgr) {
...
var goToPageBtnTmpl = $compile(
'<a href="javascript:;" ng-click="goToPage(target)"' +
'<class="btn btn-xs btn-brand">Go to Page</a>'
);
console.log("goToPageBtnTmpl defined: ", goToPageBtnTmpl);
...
var goToPage = function(target) {
// Code to navigate to the page set by the user
console.log("goToPage button added: ");
};
...
}
}
})
and in ctrl.js, there is a toWidgetObj() function, which creates a widget based on the details that the user selects/ enters on a form:
}).controller('WidgetPickerCtrl', function($scope, $timeout, fxTag, gPresets, appWidget, appUI, pagesPresets) {
...
function toWidgetObj() {
...
var widgetObj = {
name: $scope.widget.name,
label: $scope.widget.label,
attr: angular.copy($scope.widget)
};
switch(widgetObj.name) {
case 'app-table':
...
angular.forEach(widgetObj.table.rows, function(row) {
if(row.length > 0) {
reducedRows.push(row.map(
function(tag) {
if(tag.isTag) {
return {tag: tag.tag, nounit: tag.nounit};
}
if(tag.isBtn) {
var goToPageBtnTmpl = $compile(
'Go to page'
)
}
}
));
}
});
...
break;
...
}
...
return widgetObj;
}
...
});
When I currently click the 'Edit widget' button on a table widget, the 'Edit Widget dialog is opened, and I add a button to a cell in the table. When I then click the 'Preview' button, to update the widget with the changes I have entered in the dialog, I see the print statement from the directive displayed:
goToPageBtnTmpl defined: publicLinkFn(scope, cloneConnectFn, transcludeControllers, parentBoundTranscludeFn){
assertArg(scope, 'scope');
and I am expecting the table to show a the button that has been compiled by the line:
var goToPageBtnTmpl = $compile(
'<a href="javascript:;" ng-click="goToPage(target)"' +
'class = "btn btn-xs btn-brand">Go to page</a>'
)
that will take the user to the page whose address I specified in the input on the dialog.
But what I actually see when I click 'Preview' is the table displayed, and the cell where the button should be displayed is actually showing the link that I typed (i.e. the address of the page I am expecting it to take the user to when clicked).
My debug in the console is stating that the button has been added successfully:
Tag is a button:
{tag: "pages/userpage1", isTag: false, isBtn: true, nounit: false, units: undefined}
isBtn
:
true
isTag
:
false
nounit
:
false
tag
:
"pages/userpage1"
units
:
undefined
but I don't actually see the button displayed on the page.
Anyone have any suggestions what I'm doing wrong here?
Edit
So, as I've looked into this further, I think I may have found the reason that the button is not displayed: the code where the button is compiled is written in the table directive's controller function:
.directive('appTable', function(fxTag) {
return {
...
controller: function(...) {
...
var gotToPageBtnTmpl = $compile(
...
);
So this is run when the page first loads. However, I am trying to add the button to the table manually, after the page has already loaded, and the code I'm using to do this is written later in the same controller function:
if(!$scope.noTagAlarm()) {
angular.forEach($scope.config.columns, function(col) {
...
if(col.header.startsWith(":")) {
angular.forEach($scope.config.rows, function(row) {
col.template = function(value, row, metadata) {
goToPageBtnTmpl(value);
}
})
}else{
console.log("column doesn't start with ':' ");
}
...
});
}
My thought is that since this code is written in the directive's controller function, it is probably only run when the page is first loaded, and not when I edit the widget using my 'edit widget' dialog, so the HTML is not rendered.
Would that be the case? If so, how can I reload the widget after editing it without refreshing the whole page? Or, if not, what am I actually doing wrong here?
The issue here was that the function used in the col.template needed to return the button I wanted to create:
col.template = function(value, row, metadata) {
return [
umWidget.getBtnTmpl(
$scope.$new(true),
goToPage,
value,
value)
];
}
I defined the getBtnTmpl() function in Widget/service.js with:
function getBtnTmpl(scope, fn, target, title) {
scope.fn = fn;
scope.target = target
var pageTitle = title.split('/');
scope.title = pageTitle[1];
return btnTmpl(scope);
}
and set btnTmpl as a global variable in Widget/service.js with:
var btnTmpl = $compile(
'<a href="javascript:;" ng-click="fn(target)"' +
'class="btn btn-xs btn-brand">{{title}}</a>'
);
Related
I'm using react-router-dom and what I want is to be able to close a Modal when I click browser back button.
Also, in my scenario, the modal component is not the part of Switch. So how can I close the modal.
Thanks in advance. :)
You could probably use something like this to detect the press of the Back button.
componentDidUpdate() {
window.onpopstate = e => {
}
}
And then, depending on your modal (Bootstrap or something else) you can call .hide() or .close().
I've made a simple hook called useAppendLocationState that does all the job:
function SomeComponent() {
const [showModal , appendShowModal ] = useAppendLocationState('showModal');
return (
<div>
<div>...some view...</div>
<button onClick={() => appendOpenModal(true)}>open modal</button>
{showModal && <SomeModal closeHandler={() => window.history.back()} />}
</div>
)
}
useAppendLocationState returns a array with two entries just like useState, The first entry is the state prop value coming from browser location state and the second entry is a method that pushes a new item to browser history with new state prop appended to current location state.
here is our useAppendLocationState definition:
import { useHistory } from 'react-router';
export function useAppendLocationState(key) {
if (!key) throw new Error("key cannot be null or empty value")
const history = useHistory()
const currentLocationState = history.location.state;
const appendStateItemValue = (value) => {
const newLocationState = { ...currentLocationState }
newLocationState[key] = value;
history.push(history.location.pathname, newLocationState)
}
const stateItemValue = history.location.state && history.location.state[key]
return [stateItemValue, appendStateItemValue]
}
export default useAppendLocationState
Have you tried: ComponentWillUnmount?
I have a requirement like saving two previous login details.
I am done with it. But my view will update only on refresh.But scope values are updated.
Tried with scope.apply,digest,timeout. But nothing seems to work here.
$scope.loginUserName=localStorage.getItem("loginUserName");
$scope.userName=localStorage.getItem("userName");
$scope.mobileNumber=localStorage.getItem("mobileNumber");
$scope.loginData = {};
$scope.userLogin = function(loginData) {
userService.userLogin(loginData).then(function(success) {
var res=success.message;
if(res==='success'){
if(localStorage.getItem("userName1")==null || localStorage.getItem("userName1") == success.firstName){
localStorage.setItem("userName1",success.firstName);
localStorage.setItem("loginUserName",success.firstName);
}else if(localStorage.getItem("userName2")==null || localStorage.getItem("userName2") == success.firstName ){
localStorage.setItem("userName2",success.firstName);
localStorage.setItem("loginUserName",success.firstName);
}
localStorage.setItem("userName",success.firstName);
$scope.userName=success.firstName;
$scope.mobileNumber = success.mobileNumber;
$scope.loginData = {};
$state.go('app.home');
}else{
$scope.message ='Wrong pin.Try again or click Forgot password to reset it.';
}
},function(error){
});
};
$scope.loginPerson = function(mobileNumber,userName){
localStorage.setItem("loginUserName",userName);
// here userName is updating,but not reflecting in view
$scope.loginUserName=localStorage.getItem("loginUserName");
//setTimeout(function(){ $scope.$apply(); });
console.log("In loginPerson:"+userName);
$state.go('app.start');
}
start.html
<span ng-if="loginUserName !=null">
<p class="startP">Enter pin for {{loginUserName}}
<i class="icon ion-chevron-down" ui-sref="app.loginOptions">
</i></p>
</span>
State
//Here is the state details,I have same controller for two state.
.state('app.loginOptions', {
url: '/loginOptions',
views: {
'menuContent': {
templateUrl: 'templates/loginOptions.html',
controller:'LoginCtrl'
}
}
})
.state('app.start',{
url:'/start',
views:{
'menuContent':{
templateUrl:'templates/start.html',
controller:'LoginCtrl'
}
}
EDIT
I have used within object also,But nothing is changed.
step 1) please use angular copy while get data from localstorage to $scope $scope.xyz=angular.copy(localstorage.get('key'))
after implement step 1 then not work use $scope.$apply(); after set value in $scope.
try to use loginUserName as a property of an object instead a property of scope directly. Sometimes angularjs fail to update view for these values.
Like
$scope.data={
loginUserName:""
};
Then inside your function
$scope.userLogin = function(loginData) {
...
$scope.data.loginUserName=localStorage.getItem("loginUserName");
// To check it
console.log($scope.data);
}
html
<span ng-if="data.loginUserName !=null">
...
</span>
Update
Change the loginPerson function like below.
$scope.loginPerson = function(mobileNumber,userName){
localStorage.setItem("loginUserName",userName);
$scope.data.loginUserName=localStorage.getItem("loginUserName");
console.log("In loginPerson:"+userName);
console.log($scope.data);
}
The problem I'm having is that if a user clicks to edit a cell but then does something on another view that re-renders the BootstrapTable, the cell is still focused and retaining the old value until the user clicks somewhere else on the table.
I tried the following:
onBootstrapTableRef(instance) {
this.bootstrapTableRef = instance;
}
componentDidUpdate() {
this.bootstrapTableRef.reset(); //feel like either of these lines should do the job
this.bootstrapTableRef.cleanSelected();
}
public render() {
const cellEdit = {
mode: "click",
blurToSave: true
}
return (
<BootstrapTable data={this.props.settings} keyField="settingStage"
bordered={false} striped cellEdit={cellEdit} ref={this.onBootstrapTableRef}>
...
</BootstrapTable>
</div>
</div>
);
}
I added a custom field named "deldate" in "ps_orders" table, and I added a text box on "OPC" checkout page. Now when I click on order confirm button the value in the textbox should be saved in "deldate" field of "ps_orders" table.
The textbox is showing perfectly but in which files do I need to make changes to save the textbox value in the table?
(Theme is default one.)
class/order/order.php
class OrderCore extends ObjectModel
{
public $deldate;
}
And
public static $definition = array(
'fields' => array(
'deldate'=> array('type' => self::TYPE_STRING),
),
)
Shopping-cart.tpl
<div class="box">
<div class="required form-group">
<form method="post">
<label for="Fecha de entrega deseada">{l s='Desired delivery date' mod='deldate'}</label>
<input type="text" id="deldate" name="deldate" class="form-control" value="hello" />
</form>
</div>
</div>
Ok, I figured out the solution...
If you want to add some information to the order in the checkout process you have to save this informations elsewhere, if you look the cart table are very similar to order table.
Why you have to do this? Because you don't have an order before the confirmation by customer, so until the checkout is not complete that informations can't be saved in the order table.
So, first, create the field in database, in this case you have to add in ps_orders and in the ps_cart as well.
(In your case I suggest to use a DATETIME field)
Second, override the Order class:
class Order extends OrderCore
{
public function __construct($id = null, $id_lang = null)
{
self::$definition['fields']['deldate'] = array('type' => self::TYPE_DATE);
parent::__construct($id, $id_lang);
}
}
and the Cart class:
class Cart extends CartCore
{
public function __construct($id = null, $id_lang = null)
{
self::$definition['fields']['deldate'] = array('type' => self::TYPE_DATE);
parent::__construct($id, $id_lang);
}
}
Now we have to save the field during the checkout process, so we override the OrderController:
class OrderController extends OrderControllerCore
{
public function processAddress()
{
parent::processAddress();
// Here we begin our story
if(Tools::getIsset('deldate')) // Check if the field isn't empty
{
$deldate = Tools::getValue('deldate');
// Here you must parse and check data validity (I leave to you the code)
/* ... */
// Assign the data to context cart
$this->context->cart->deldate = $deldate;
// Save information
$this->context->cart->update();
}
}
}
Now you have to 'transport' this informations from the cart to the order, this will be done through the PaymentModule class, specifically with the validateOrder method.
So, another override:
class PaymentModule extends PaymentModuleCore
{
public function validateOrder($id_cart, $id_order_state, $amount_paid, $payment_method = 'Unknown', $message = null, $extra_vars = array(), $currency_special = null, $dont_touch_amount = false, $secure_key = false, Shop $shop = null)
{
$result = parent::validateOrder($id_cart, $id_order_state, $amount_paid, $payment_method, $message, $extra_vars, $currency_special, $dont_touch_amount, $secure_key, $shop);
if($result)
{
$oldcart = new Cart($id_cart);
$neworder = new Order($this->currentOrder);
$neworder->deldate = $oldcart->deldate;
$neworder->update();
return true; // important
}
else
{
return $result;
}
}
}
After all of this you have the deldate field saved. However, I absolutely don't suggest this method, it's more safe and simple with a module and hooks... But this is another story :)
This will works only with the five steps checkout.
For next code lines, God save me...
If you want to works with OPC you have to dirty your hands with JS and override the OrderOpcController.
Start with the JS, edit the order-opc.js in js folder of enabled theme, find bindInputs function and append this lines of code:
function bindInputs()
{
/* ... */
$('#deldate').on('change', function(e){
updateDelDateInput(); // custom function to update deldate
});
}
then append to the file your custom function:
function updateDelDateInput()
{
$.ajax({
type: 'POST',
headers: { "cache-control": "no-cache" },
url: orderOpcUrl + '?rand=' + new Date().getTime(),
async: false,
cache: false,
dataType : "json",
data: 'ajax=true&method=updateDelDate&deldate=' + encodeURIComponent($('#deldate').val()) + '&token=' + static_token ,
success: function(jsonData)
{
if (jsonData.hasError)
{
var errors = '';
for(var error in jsonData.errors)
//IE6 bug fix
if(error !== 'indexOf')
errors += $('<div />').html(jsonData.errors[error]).text() + "\n";
alert(errors);
}
// Here you can add code to display the correct updating of field
},
error: function(XMLHttpRequest, textStatus, errorThrown) {
if (textStatus !== 'abort')
alert("TECHNICAL ERROR: unable to save message \n\nDetails:\nError thrown: " + XMLHttpRequest + "\n" + 'Text status: ' + textStatus);
}
});
}
Then override the OrderOpcController, copy all the init method and change the line of code as below:
class OrderOpcController extends OrderOpcControllerCore
{
public function init()
{
// parent::init(); // comment or delete this line
FrontController::init(); // Very important!
// Then in this switch `switch (Tools::getValue('method'))` add your case
/* ... */
case 'updateDelDate':
if(Tools::isSubmit('deldate'))
{
$deldate = urldecode(Tools::getValue('deldate'));
// Here you must parse and check data validity (I leave to you the code)
/* ... */
// Assign the data to context cart
$this->context->cart->deldate = $deldate;
// Save information
$this->context->cart->update();
$this->ajaxDie(true);
}
break;
/* ... */
}
}
Obviously, is necessary the override of Order, Cart and PaymentModule as well.
PS: I hope that I didn't forget anything.
You can try also with this module
https://www.prestashop.com/forums/topic/589259-m%C3%B3dulo-selector-fecha-en-pedido/?p=2489523
Try this in the override of the class Order
class Order extends OrderCore
{
public function __construct($id = null, $id_lang = null)
{
parent::__construct($id, $id_lang);
self::$definition['fields']['deldate'] = array('type' => self::TYPE_STRING);
Cache::clean('objectmodel_def_Order');
}
}
The Cache::clean is need because getDefinition tries to retrieve from cache, and cache is set without the override on parent::__construct
I then tried to create a new empty Order and get the definition fields and it showed there, so it should save to mysql
$order = new Order();
var_dump(ObjectModel::getDefinition($order));exit;
I'm currently trying to learn Angular and i have a list of products, and every product can be favorited which puts them in a menu called "Favorites" (What it really does is inserts a row in db, when clicked again it removes from db).
The functionality is done, i just want the button to have two "states" (when a product is favorited it should be yellow, and when its not favorited it should be transparent).
So somehow the link must check the controller which returns true or false depending on if the product exists in db, table: Part_Favorites.
CSS:
.active { background:yellow;}
.inactive { background:transparent;)
HTML:
<a ng-click="myfavorites()">Add favorites</a>
Controller
$scope.myfavorites = function (parts) {
CategoryService.myfavorites(parts, function (callback) {
//$route.reload();
window.location.reload(false);
});
if ($scope.loading == false) {
item.Checked == true ? item.Checked = false : item.Checked = true;
$scope.loopFilters();
$rootScope.loading = true;
$location.url('/Category/' + $routeParams.id + '/' + $scope.url);
}
};
Service:
myfavorites: function (data, callback) {
$http.post(shopID + '/Product/MyFavourites?SalesPartNo=' + data).success(function (data) {
callback(data)
}).error(function (data) {
callback(data);
});
},
EDIT: Updated my post with Service. Still need help!
The code works, and it inserts/deletes into a favorite menu. But how do i make it so it changes class on the button depending if its favorited or not?
<a ng-click="myfavorites()" ng-class="{active: item.Checked, inactive: !item.Checked}">Add favorites</a>
You can simplify the code a little bit:
item.Checked = !item.Checked;
item.className = item.Checked ? "active" : "inactive";
I believe you could print the class directly inside your template:
{{className}}
So it would just update it on the front end automatically.
You can use ng-class in here.
When you retriving the items get the favorite information too.
$scope.retrieveItems = function() {
//get the items with their favorite information(isFavorite [true,false])
}
Then in your html:
<a ng-click="myfavorites()" ng-class="{'active':item.isFavorite, 'inactive':!item.isFavoirte"/>