I spent more time for understand why instance of class after remove directive does not destroy. I wrote the following code. Please help me to resolve it. Code provide below. The result in the console log!!
'use strict';
var app = angular.module('app', []);
app.controller('mainController', function($scope, Service) {
$scope.service = Service;
$scope.checkAll = function () {
$scope.service.triggerListener('update');
};
$scope.add = function () {
$scope.count.push({});
};
$scope.object = {
updateAll: function () {
console.log('Count of directive "person"');
}
};
$scope.removeElement = function () {
$scope.count.splice(0, 1);
};
$scope.count = [0, 1, 2, 3, 4];
});
app.service('Service', function() {
this.listeners = [];
this.addListeners = function (object, event, callback) {
if (!this.listeners.hasOwnProperty(event)) {
this.listeners[event] = [];
}
this.listeners[event].push(object[callback]);
};
this.triggerListener = function(event) {
if (this.listeners.hasOwnProperty(event)) {
for (var i = 0; i < this.listeners[event].length; i++) {
this.listeners[event][i]();
}
}
};
});
app.directive('person', function() {
var directive = {
restrict: 'E',
template: '<button id="{{vm.index}}">Person</button> ' +
'<button ng-click="vm.add(index)">add</button>' +
'<button ng-click="vm.removeElement(index)">Clear</button>',
scope: {
index: '=',
service: '=',
removeElement: '&',
object: '=',
add: '&'
},
controller: function() {
},
link: function (scope) {
scope.vm.service.addListeners(scope.vm.object, 'update', 'updateAll');
},
controllerAs: 'vm',
bindToController: true
};
return directive;
});
<!DOCTYPE html>
<html lang="en" ng-app="app">
<head>
<meta charset="UTF-8">
<title></title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.2/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body ng-controller="mainController">
<div ng-repeat="item in count">
<person add="add()" index="$index" service="service" object="object" remove-element="removeElement()" show="show()">{{$index}}</person>
</div>
<button ng-click="checkAll()">Count of "person" directive</button>
</body>
</html>
Your directive only adds to the listeners array ... but there is nothing in your code to remove anything from that array
You need a removeListener method that would be called in the removeElement method.
$scope.removeElement = function () {
var index = // get index of element
$scope.count.splice(index, 1);
Service.removeListener(index);
};
In service:
this.removeListener = function(index){
this.listeners.splice(index,1);
};
Alternatively you could use $destroy event in directive:
link: function (scope) {
Service.addListeners(scope.vm.object, 'update', 'updateAll');
scope.$on('$destroy', function(){
Service.removeListener(scope.index);
});
},
Note that injecting Service into directive is simpler and cleaner than passing it through html attributes to scope
app.directive('person', function(Service) {
Related
I want to control the display of elements using a variable that is defined in the controller.
But if variables change async, my code doesn't work as expected.
In my example, I output to console value of vm.fruit inside methods setFruit and hasFruit.
And after set value for vm.fruit in setFruit, in hasFruit vm.fruit value is undefined.
Have ideas about how to fix it?
And I don't want call controllers method inside directive.
UPD. I removed the definition exampleController from asyncChoice, how suggested #LeroyStav. I think he is right. But this not solved the problem.
angular.module('app', [])
.controller('exampleController', exampleController)
.directive('wrapper', wrapper)
.directive('asyncChoice', asyncChoice);
function exampleController() {
var vm = this;
vm.selectMode = false;
vm.fruit = undefined;
vm.hasFruit = hasFruit;
vm.selectFruit = selectFruit;
vm.setFruit = setFruit;
function hasFruit() {
console.log('hasFruit: ' + vm.fruit);
return (typeof vm.fruit !== 'undefined');
}
function selectFruit() {
vm.selectMode = true;
}
function setFruit(fruit) {
setTimeout(
function() {
vm.fruit = fruit;
vm.selectMode = false;
console.log(vm.fruit);
},
1000
);
}
}
function wrapper() {
return {
restrict: 'E',
trancslude: true,
controller: 'exampleController',
controllerAs: 'vm'
};
}
function asyncChoice() {
return {
template: `
<button ng-click="selectFruit({fruit: '🍊'})">🍊</button>
<button ng-click="selectFruit({fruit: '🍋'})">🍋</button>
`,
scope: {
selectFruit: '&'
},
controller: 'exampleController',
controllerAs: 'vm'
}
}
angular.bootstrap(
document.getElementById('root'), ['app']
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.5/angular.min.js"></script>
<div id="root">
<wrapper>
<button ng-if="!vm.hasFruit()" ng-click="vm.selectFruit()">Choice fruit</button>
<p ng-if="vm.hasFruit()">You choice <span ng-bind="vm.fruit"></span></p>
<async-choice ng-if="vm.selectMode" select-fruit="vm.setFruit(fruit)"></async-choice>
</wrapper>
</div>
I provide $scope to exampleController and call $scope.$digest() after execution async operation.
angular.module('app', [])
.controller('exampleController', exampleController)
.directive('wrapper', wrapper)
.directive('asyncChoice', asyncChoice);
exampleController.$inject = ['$scope'];
function exampleController($scope) {
var vm = this;
vm.selectMode = false;
vm.fruit = undefined;
vm.hasFruit = hasFruit;
vm.selectFruit = selectFruit;
vm.setFruit = setFruit;
function hasFruit() {
console.log('hasFruit: ' + vm.fruit);
return (typeof vm.fruit !== 'undefined');
}
function selectFruit() {
vm.selectMode = true;
}
function setFruit(fruit) {
setTimeout(
function() {
vm.fruit = fruit;
vm.selectMode = false;
console.log(vm.fruit);
$scope.$digest();
},
1000
);
}
}
function wrapper() {
return {
restrict: 'E',
trancslude: true,
controller: 'exampleController',
controllerAs: 'vm'
};
}
function asyncChoice() {
return {
template: `
<button ng-click="selectFruit({fruit: '🍊'})">🍊</button>
<button ng-click="selectFruit({fruit: '🍋'})">🍋</button>
`,
scope: {
selectFruit: '&'
},
controller: 'exampleController',
controllerAs: 'vm'
}
}
angular.bootstrap(
document.getElementById('root'), ['app']
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.5/angular.min.js"></script>
<div id="root">
<wrapper>
<button ng-if="!vm.hasFruit()" ng-click="vm.selectFruit()">Choice fruit</button>
<p ng-if="vm.hasFruit()">You choice <span ng-bind="vm.fruit"></span></p>
<async-choice ng-if="vm.selectMode" select-fruit="vm.setFruit(fruit)"></async-choice>
</wrapper>
</div>
I am trying to set a angular variable value in controller function which is created by a directive. Somehow it is not working for some unknown reasons. The value is displayed when set independently but doesn't work when i try to assign value in a controller function.
My code is as below,
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular.min.js"></script>
</head>
<body>
<div ng-app="mainApp">
<div ng-controller="MyController">
<div id="Details" class="Details">{{Details}}</div></br>
<div id="Test" class="Test">
<collection collection='testdata'></collection>
</div>
</div>
</div>
</body>
<script>
var mainApp = angular.module("mainApp", [])
mainApp.directive('collection', function () {
return {
restrict: "E",
replace: true,
scope: {collection: '=', showFn : '&'},
template: "<ul><member ng-repeat='member in collection' member='member'></member></ul>"
}
})
mainApp.directive('member', function ($compile) {
var NewChild = "<li><span ng-click=ShowDetailsFunc()>{{member.TagName}}</span></li>";
return {
restrict: "E",
replace: true,
scope: {member: '=', ShowHideCtrlFunc : '&', ShowDetailsCtrlFunc : '&'},
template: NewChild,
controller: 'MyController',
link: function (scope, element, attrs) {
var collectionSt = '<collection collection="member.children"></collection>';
if (angular.isArray(scope.member.children)) {
$compile(collectionSt)(scope, function(cloned, scope) {
element.attr('xml-path', scope.member.TagPath);
element.append(cloned);
});
scope.ShowDetailsFunc = function() {
scope.ShowDetailsCtrlFunc(element,event);
}
}
}
}
})
mainApp.controller('MyController', function ($scope) {
$scope.testdata = [{"TagName":"MyRootNode","TagPath":">MyRootNode","children":[{"TagName":"LandXML","TagPath":">MyRootNode>ChildItems>LandXML","children":[{"TagName":"Units","TagPath":">MyRootNode>ChildItems>ChildItems[1]>Units","children":[{"TagName":"Imperial","TagPath":">MyRootNode>ChildItems>ChildItems[1]>ChildItems[1]>Imperial","children":[]},{"TagName":"Project","TagPath":">MyRootNode>ChildItems>ChildItems[1]>Project","children":[]},{"TagName":"Application","TagPath":">MyRootNode>ChildItems>ChildItems[1]>Application","children":[{"TagName":"Author","TagPath":">MyRootNode>ChildItems>ChildItems[1]>ChildItems[2]>Author","children":[]},{"TagName":"Alignments","TagPath":">MyRootNode>ChildItems>ChildItems[1]>Alignments","children":[]},{"TagName":"Roadways","TagPath":">MyRootNode>ChildItems>ChildItems[1]>Roadways","children":[{"TagName":"Roadway","TagPath":">MyRootNode>ChildItems>ChildItems[1]>ChildItems[3]>Roadway[1]","children":[]},{"TagName":"Roadway","TagPath":">MyRootNode>ChildItems>ChildItems[1]>ChildItems[3]>Roadway[2]","children":[]},{"TagName":"Roadway","TagPath":">MyRootNode>ChildItems>ChildItems[1]>ChildItems[3]>Roadway[3]","children":[]},{"TagName":"Roadway","TagPath":">MyRootNode>ChildItems>ChildItems[1]>ChildItems[3]>Roadway[4]","children":[]},{"TagName":"Roadway","TagPath":">MyRootNode>ChildItems>ChildItems[1]>ChildItems[3]>Roadway[5]","children":[]}]}]}]}]},{"TagName":"Surfaces","TagPath":">MyRootNode>ChildItems>Surfaces","children":[{"TagName":"Surface1","TagPath":">MyRootNode>ChildItems>ChildItems[2]>Surface1","children":[]},{"TagName":"Surface2","TagPath":">MyRootNode>ChildItems>ChildItems[2]>Surface2","children":[]}]}]}]
$scope.Details = "defalut value"
$scope.ShowDetailsCtrlFunc = function(element,event) {
console.log("in function ShowDetailsCtrlFunc");
var myxmlpath = $(element).attr("xml-path")
$scope.Details = getObjects($scope.testdata, 'TagPath', myxmlpath)[0].TagName;
console.log($scope.Details)
//event.stopImmediatePropagation();
};
});
function getObjects(obj, key, val) {
var objects = [];
for (var i in obj) {
if (!obj.hasOwnProperty(i)) continue;
if (typeof obj[i] == 'object') {
objects = objects.concat(getObjects(obj[i], key, val));
} else if (i == key && obj[key] == val) {
objects.push(obj);
}
}
return objects;
}
</script>
</html>
Please help me to understand where i am making mistake and how it can be rectified.
Please help. Many thanks in advance.
The problem is that you are nesting MyController-controller when you create the directives in this circular way. The result is that the $scope.Details gets set on the specific nested scope instead of the one which is presenting the value in the view.
You can solve this by $emit the change which will send the new value upstream and finally reach the presenting scope value.
Here's an example
var mainApp = angular.module("mainApp", [])
mainApp.directive('collection', function () {
return {
restrict: "E",
replace: true,
scope: {collection: '=', showFn : '&'},
template: "<ul><member ng-repeat='member in collection' member='member'></member></ul>"
}
})
mainApp.directive('member', function ($compile) {
var NewChild = "<li><span ng-click=ShowDetailsFunc()>{{member.TagName}}</span></li>";
return {
restrict: "E",
replace: true,
scope: {member: '=', ShowHideCtrlFunc : '&', ShowDetailsCtrlFunc : '&'},
template: NewChild,
controller: 'MyController',
link: function (scope, element, attrs) {
var collectionSt = '<collection collection="member.children"></collection>';
if (angular.isArray(scope.member.children)) {
$compile(collectionSt)(scope, function(cloned, scope) {
element.attr('xml-path', scope.member.TagPath);
element.append(cloned);
});
scope.ShowDetailsFunc = function() {
scope.ShowDetailsCtrlFunc(element);
}
}
}
}
})
mainApp.controller('MyController', function ($scope) {
$scope.testdata = [{"TagName":"MyRootNode","TagPath":">MyRootNode","children":[{"TagName":"LandXML","TagPath":">MyRootNode>ChildItems>LandXML","children":[{"TagName":"Units","TagPath":">MyRootNode>ChildItems>ChildItems[1]>Units","children":[{"TagName":"Imperial","TagPath":">MyRootNode>ChildItems>ChildItems[1]>ChildItems[1]>Imperial","children":[]},{"TagName":"Project","TagPath":">MyRootNode>ChildItems>ChildItems[1]>Project","children":[]},{"TagName":"Application","TagPath":">MyRootNode>ChildItems>ChildItems[1]>Application","children":[{"TagName":"Author","TagPath":">MyRootNode>ChildItems>ChildItems[1]>ChildItems[2]>Author","children":[]},{"TagName":"Alignments","TagPath":">MyRootNode>ChildItems>ChildItems[1]>Alignments","children":[]},{"TagName":"Roadways","TagPath":">MyRootNode>ChildItems>ChildItems[1]>Roadways","children":[{"TagName":"Roadway","TagPath":">MyRootNode>ChildItems>ChildItems[1]>ChildItems[3]>Roadway[1]","children":[]},{"TagName":"Roadway","TagPath":">MyRootNode>ChildItems>ChildItems[1]>ChildItems[3]>Roadway[2]","children":[]},{"TagName":"Roadway","TagPath":">MyRootNode>ChildItems>ChildItems[1]>ChildItems[3]>Roadway[3]","children":[]},{"TagName":"Roadway","TagPath":">MyRootNode>ChildItems>ChildItems[1]>ChildItems[3]>Roadway[4]","children":[]},{"TagName":"Roadway","TagPath":">MyRootNode>ChildItems>ChildItems[1]>ChildItems[3]>Roadway[5]","children":[]}]}]}]}]},{"TagName":"Surfaces","TagPath":">MyRootNode>ChildItems>Surfaces","children":[{"TagName":"Surface1","TagPath":">MyRootNode>ChildItems>ChildItems[2]>Surface1","children":[]},{"TagName":"Surface2","TagPath":">MyRootNode>ChildItems>ChildItems[2]>Surface2","children":[]}]}]}]
$scope.Details = "defalut value";
$scope.ShowDetailsCtrlFunc = function(element) {
console.log("in function ShowDetailsCtrlFunc");
var myxmlpath = angular.element(element).attr("xml-path")
var detail = getObjects($scope.testdata, 'TagPath', myxmlpath)[0].TagName;
console.log(detail);
$scope.$emit('detailSelected',detail);
//event.stopImmediatePropagation();
};
$scope.$on('detailSelected',function($event, message){
$scope.Details = message;
});
});
function getObjects(obj, key, val) {
var objects = [];
for (var i in obj) {
if (!obj.hasOwnProperty(i)) continue;
if (typeof obj[i] == 'object') {
objects = objects.concat(getObjects(obj[i], key, val));
} else if (i == key && obj[key] == val) {
objects.push(obj);
}
}
return objects;
}
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<script data-require="angularjs#1.6.4" data-semver="1.6.4" src="https://code.angularjs.org/1.6.4/angular.min.js"></script>
</head>
<body>
<div ng-app="mainApp">
<div ng-controller="MyController">
<div id="Details" class="Details">{{Details}}</div></br>
<div id="Test" class="Test">
<collection collection='testdata'></collection>
</div>
</div>
</div>
</body>
</html>
In the code snippet I try to use a controller FooCtrl which is defined in the included template app/foo.html by using the directive common.script.
angular.module('common.script', []).directive('script', function() {
return {
restrict: 'E',
scope: false,
compile: function(element, attributes) {
if (attributes.script === 'lazy') {
var code = element.text()
new Function(code)()
}
}
}
})
angular.module('app.templates', ['app/foo.html'])
angular.module("app/foo.html", []).run(function($templateCache) {
$templateCache.put("app/foo.html",
"<script data-script=\"lazy\">\n" +
" console.log('Before FooCtrl')\n" +
" angular.module('app').controller('FooCtrl', function($scope) {\n" +
" console.log('FooCtrl')\n" +
" })\n" +
"<\/script>\n" +
"<div data-ng-controller=\"FooCtrl\">app\/foo.html\n" +
"<\/div>"
)
})
angular.module('app', ['common.script', 'app.templates']).controller('ApplicationCtrl', function($scope) {
console.log('ApplicationCtrl')
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.3/angular.min.js"></script>
<div data-ng-app="app" data-ng-controller="ApplicationCtrl">
<div data-ng-include="'app/foo.html'"></div>
</div>
But instead of the expected output FooCtrl in the console AngularJS throws:
Error: [ng:areq] Argument 'FooCtrl' is not a function [...]
I don't understand why! The code in the template is executed before the exception is thrown, thus the controller should be defined. How could I fix that?
The real problem here is lazy loading of resources! There are tons of material and related posts about this topic.
The solution here could be an extended common.script directive:
'use strict'
angular.module('common.script', [])
.config(function($animateProvider, $controllerProvider, $compileProvider, $filterProvider, $provide) {
angular.module('common.script').lazy = {
$animateProvider: $animateProvider,
$controllerProvider: $controllerProvider,
$compileProvider: $compileProvider,
$filterProvider: $filterProvider,
$provide: $provide
}
})
.directive('script', function() {
return {
restrict: 'E',
scope: {
modules: '=script'
},
link: function(scope, element) {
var offsets = {}, code = element.text()
function cache(module) {
offsets[module] = angular.module(module)._invokeQueue.length
}
function run(offset, queue) {
var i, n
for (i = offset, n = queue.length; i < n; i++) {
var args = queue[i], provider = angular.module('common.script').lazy[args[0]]
provider[args[1]].apply(provider, args[2])
}
}
if (angular.isString(scope.modules)) {
cache(scope.modules)
} else if (angular.isArray(scope.modules)) {
scope.modules.forEach(function(module) {
cache(module)
})
}
/*jshint -W054 */
new Function(code)()
Object.keys(offsets).forEach(function(module) {
if (angular.module(module)._invokeQueue.length > offsets[module]) {
run(offsets[module], angular.module(module)._invokeQueue)
}
})
}
}
})
The only downside of this solution is that you have to specify the module(s) you want to extend in a script tag:
<script data-script="'app'">
angular.module('app').controller('FooCtrl', function($scope) {
console.log('Works!')
})
</script>
I'm trying to build a directive with a controller, which updates a ViewModel-variable and calls a callback-function. In the callback-function the updated variable should be used, but it still got the old value.
HTML:
<div ng-app="app" ng-controller="AppCtrl">
Var: {{vm.var}}
<ng-element var="vm.var" func="vm.func()"></ng-element>
</div>
JavaScript:
var app = angular.module('app', []);
app.controller('AppCtrl', function($scope) {
$scope.vm = {
var: 'One',
func: function() {
alert($scope.vm.var);
}
};
});
app.directive('ngElement', function(){
return {
restrict: 'E',
scope: true,
bindToController: {
var: '=',
func: '&'
},
controllerAs: 'ctrl',
replace: true,
template: '<button ng-click="ctrl.doIt()">Do it</button>',
controller: function() {
this.doIt = function() {
this.var = 'Two';
this.func();
};
}
};
});
So when clicking the button, doIt() ist called, updates var and calls func(). But when func() is executed, var still got the old value "One". Right after the execution the ViewModel gets updated and the value is "Two".
Is there any way to update the ViewModel before executing the function?
JSFiddle
Not sure exactly what your directive is doing, as I've never used bindToController, but this seemed to work:
directive('ngElement', function () {
return {
restrict: 'E',
scope: {
var: '=',
func: '&'
},
replace: true,
template: '<button ng-click="doIt()">Do it</button>',
controller: ['$scope', '$timeout', function($scope, $timeout) {
$scope.doIt = function() {
$scope.var = 'Two';
$timeout(function () {
$scope.func();
});
};
}]
};
});
I am trying to update testVar1 the ng-model attr for the input. The value succesfully gets
$scope.testVar1 = menuElements[$scope.element.id].value;
But when i change the value of
menuElements[$scope.element.id].value;
I want testVar1 to update along with its input view
Is this possible? if so what am i doing wrong? I made a function below to try and hard set the code to val = 2 but it was not succesful it seems that the scope variables only update when you build the page(at least the way ive written it)
HTML:
<div class="well">
<label for="{{element.id}}">{{element.info}}:</label>
<input class="ui-slider" type="range" ng-model="testVar1" ng-change="changeValue(element.id)" name="{{element.id}}" min="{{element.min}}" max="{{element.max}}" id="{{element.id}}"/>
<button ng-click="setTestValue()">Test</button>
</div>
Directive and controller
cordovaAngular.directive('myCustomer', function () {
return {
restrict: 'A',
scope: {
element: '=',
elementArray: '='
},
templateUrl: elementURL,
controller: function ($scope) {
var test = JSON.stringify($scope.elementArray);
$scope.selectedOption = "Success"
$scope.testVar1 = menuElements[$scope.element.id].value;
console.log($scope.testVar1);
console.log($scope.element.id);
$scope.changeOption = function (selectedItem) {
$scope.selectedOption = selectedItem;
// alert(1);
}
$scope.changeValue = function (id) {
menuElements[id].onChange();
}
$scope.setTestValue = function () {
menuElements[$scope.element.id].value = 2;
$scope.testVar1.
console.log($scope.testVar1);
}
}
};
});
I think you can use $watch
Assign menuElements to a $scopevariable and add a $watch listener to it.
$scope.$watch('menuElements', function(newVal, oldVal){
// When menuElementes change, update testVar1 here
$scope.testVar1 = menuElements[$scope.element.id].value;
}, true);
The AngularJS docs for $watch
You need menuElements to be part of the scope in order to be able to watch changes in it. Since your directive has isolated scope, it should be in the scope of your directive. Here is an example of doing it:
HTML
<body ng-controller="ctrl" id="ctrl">
<ul>
<li ng-repeat="element in data.elementArray">{{element.id}} - {{data.menuElements[element.id].value}}</li>
</ul>
<div my-customer="" element="data.element" element-array="data.elementArray" menu-elements="data.menuElements"></div>
</body>
JavaScript
angular.module('app', []).
controller('ctrl', ['$scope', function($scope) {
$scope.data = {
elementArray: [{
id: 'el1',
info: 'Element Info 1',
min: 0,
max: 9
}, {
id: 'el2',
info: 'Element Info 2',
min: 10,
max: 19
}],
menuElements: {
'el1': {
value: 1
},
'el2': {
value: 15
}
}
};
$scope.data.element = $scope.data.elementArray[0];
}]).
directive('myCustomer', function() {
return {
template: '<div class="well">' +
'<label for="{{element.id}}">{{element.info}}:</label>' +
'<input class="ui-slider" type="range" ng-model="testVar1" ng-change="changeValue(element.id)" name="{{element.id}}" min="{{element.min}}" max="{{element.max}}" id="{{element.id}}"/>' +
'<button ng-click="setTestValue()">Test</button>' +
'</div>',
scope: {
element: '=',
elementArray: '=',
menuElements: '=' // <= add menuElements to scope
},
controller: ['$scope', function($scope) {
$scope.setTestValue = function() {
$scope.menuElements[$scope.element.id].value = 5;
}
}],
link: function(scope, element, attr) {
scope.$watch(function() { // <= Watch changes of scope.menuElements[scope.element.id].value
return scope.menuElements[scope.element.id].value;
}, function(value) {
scope.testVar1 = value;
});
}
}
});
Plunker: http://plnkr.co/edit/FLl6pLBdCWAfTbvGSBxQ?p=preview
Edit:
If you need to modify scope from outside of Angular, you can still do it, by fetching scope of DOM element related to that scope:
function setValue() {
var scope = angular.element(document.getElementById('ctrl')).scope();
scope.$apply(function() {
scope.data.menuElements[scope.data.element.id].value = 7;
});
}
Plunker: http://plnkr.co/edit/LXwJEtCIxVNgAb5AdJM6?p=preview