I am using FabricJS with an AngularJS application. I am able to add text to a canvas and, using the kitchensink example located here, I can perform functions such as bold, italic, underline, etc.
However, the issue I have is how to change the font family, text align, font size, etc. since when I make a selection from a dropdown for font family, no changes occur... but it works in the Kitchensink example.
I am using the Kitchensink example as I need to not only add text, but edit it once it shows on the Canvas, and this appears to have what I need.
A button (which is working) has an HTML element such as:
<button class="btn btn-object-action" type="button" ng-class="{'btn-inverse': isBold()}" ng-click="toggleBold()">
Bold</button>
Which is backed up by the following in the Controller:
$scope.toggleBold = function () {
setActiveStyle('fontWeight',
getActiveStyle('fontWeight') === 'bold' ? '' : 'bold');
};
As I stated, this works as intended. Where I am having challenges is changing something like the Font Family or Font Size in that the change is achieved without a button click. Here is sample HTML for the Font Family select from the Kitchensink example:
<label style="display: inline-block;" for="font-family">Font family:</label><select class="btn-object-action" id="font-family" bind-value-to="fontFamily">
<option value="arial">Arial</option>
<option value="helvetica" selected="">Helvetica</option>
<option value="myriad pro">Myriad Pro</option>
</select>
This is backed up by this in the controller:
function getActiveProp(name) {
var object = canvas.getActiveObject();
if (!object) return '';
return object[name] || '';
}
function setActiveProp(name, value) {
var object = canvas.getActiveObject();
if (!object) return;
object.set(name, value).setCoords();
canvas.renderAll();
}
$scope.getFontFamily = function () {
return getActiveProp('fontFamily').toLowerCase();
};
$scope.setFontFamily = function (value) {
setActiveProp('fontFamily', value.toLowerCase());
};
function watchCanvas($scope) {
function updateScope() {
$scope.$$phase || $scope.$digest();
canvas.renderAll();
}
canvas
.on('object:selected', updateScope)
.on('group:selected', updateScope)
.on('path:created', updateScope)
.on('selection:cleared', updateScope);
}
$scope.getSelected = function () {
return canvas.getActiveObject();
};
$scope.canvas = canvas;
$scope.getActiveStyle = getActiveStyle;
addAccessors($scope);
watchCanvas($scope);
I am more used to using ng-model than bind-value-to, in fact, I have never seen or used bind-value-to before in Angular apps/not sure how and if I should be using it.
My main question is how do I get the dropdowns working where if I select a value it updates per the Kitchensink example for text here? What I am missing/is there a better way given that my need is to add and edit stylized text.
You need to bind a change event to your select element so that when you change the select value, you execute the function that will change the font family.
bind-value-to is a custom directive in the kitchensink app so don't worry about it.
Related
Have little dilemma here. I'm building text editor in angular js. The problem that I have is, when user selects part of text within a paragraph or heading I need to change styling of that part of text to bold / italic etc.
So basically I need to wrap selected text in <strong></strong> or <em></em>.
Plunker
I have a directive
editorApp.directive('watchSelection', function() {
return function(scope, elem) {
elem.on('mouseup', function() {
scope.startPosition = elem[0].selectionStart;
scope.endPosition = elem[0].selectionEnd;
// scope.selected = elem[0].value.substring(start, end);
scope.$apply();
});
};
});
That gets text selection its startposition and endposition. On button click I need to wrap that selection in specific tags, which I'm hoping to accomplish with this function:
$scope.boldText = function(startPosition, endPosition) {
$scope.start = startPosition;
$scope.end = endPosition;
var htmlStart = angular.element('<strong>');
var htmlEnd = angular.element('</strong>');
$scope.start.append(htmlStart);
$scope.end.append(htmlEnd);
};
I relatively new to angular and I might have taken a bigger bite than I can handle :)
Issue is I can't get selection to wrap inside them tags.
You don't need to watch anything.
$scope.boldText = function() {
document.execCommand('bold');
};
This will bold the selected text.
ng-img-crop is an awesome directive however I am having trouble adapting it to my scenario. My issue is that when a user has an image I would like to give them the option to resize the image if they would like to.
So here is the code I am attempting to use:
js:
vm.userImageOriginal = vm.editUser.image_pkey ? 'api/file/' + vm.editUser.image_pkey : null;
html:
<img-crop image="profileVM.userImageOriginal" result-image="profileVM.userImageNew"
area-type="square" result-image-size="300" on-change="profileVM.imageCropped = true;"></img-crop>
So I two issues:
1) I only want to upload the new image if the user has indeed changed the cropping. I tried setting a flag in on-change but it looks like on-change gets executed on initialization as well. Is there any way to know if the user has actually cropped?
2) Is there any way to set the position of the square/circle. In my scenario, if there is an existing user image, I would like to set the cropping square to the dimensions of the current image (i.e. the border of the image).
Thanks in advance.
Solved like this:
Add the following attribute to ng-img-crop directive in html:
on-load-done="profileVM.addCroppingWatcher()"
Here is the function:
function addCroppingWatcher(){
if (croppingWatcher)
return;
$window.setTimeout(function(){
croppingWatcher = $scope.$watch(
function(){ return vm.userImageNew; },
function(newVal, oldVal){
if (oldVal && oldVal != newVal) {
vm.imageCropped = true;
croppingWatcher();
}
}
);
}, 0);
}
I am trying to toggle the medium editor option (disableEditing) on button click. On the click the value for the medium editor option is changed but the medium editor does not use 'updated' value.
AngularJS Controller
angular.module('myApp').controller('MyCtrl',
function MyCtrl($scope) {
$scope.isDisableEdit = false;
});
Html Template
<div ng-app='myApp' ng-controller="MyCtrl">
<span class='position-left' medium-editor ng-model='editModel' bind-options="{'disableEditing': isDisableEdit, 'placeholder': {'text': 'type here'}}"></span>
<button class='position-right' ng-click='isDisableEdit = !isDisableEdit'>
Click to Toggle Editing
</button>
<span class='position-right'>
toggle value - {{isDisableEdit}}
</span>
</div>
I have created a jsfiddle demo.
I think initialising medium editor on 'click' could solve the issue, but i am not sure how to do that either.
using thijsw angular medium editor and yabwe medium editor
For this specific use case, you could try just disabling/enabling the editor when the button is clicked:
var editor = new MediumEditor(iElement);
function onClick(event) {
if (editor.isActive) {
editor.destroy();
} else {
editor.setup();
}
}
In the above example, the onClick function is a handler for that toggle button you defined.
If you're just trying to enable/disable the user's ability to edit, I think those helpers should work for you.
MediumEditor does not currently support changing configuration options on an already existing instance. So, if you were actually trying to change a value for a MediumEditor option (ie disableEditing) you would need to .destroy() the previous instance, and create a new instance of the editor:
var editor = new MediumEditor(iElement),
editingAllowed = true;
function onClick(event) {
editor.destroy();
if (editingAllowed) {
editor = new MediumEditor(iElement, { disableEditing: true });
} else {
editor = new MediumEditor(iElement);
}
editingAllowed = !editingAllowed;
}
Once instantiated, you can use .setup() and .destroy() helper methods to tear-down and re-initialize the editor respectively. However, you cannot pass new options unless you create a new instance of the editor itself.
One last note, you were calling the init() method above. This method is not officially supported or documented and it may be going away in future releases, so I would definitely avoid calling that method if you can.
Or you could just use this dirty hack : duplicate the medium-editor element (one with disableEditing enabled, the other with disableEditing disabled), and show only one at a time with ng-show / ng-hide :)
<span ng-show='isDisableEdit' class='position-left' medium-editor ng-model='editModel' bind-options="{'disableEditing': true ,'disableReturn': isDisableEdit, 'placeholder': {'text': 'type here'}}"></span>
<span ng-hide='isDisableEdit' class='position-left' medium-editor ng-model='editModel' bind-options="{'disableEditing':false ,'disableReturn': isDisableEdit, 'placeholder': {'text': 'type here'}}"></span>
You can see jsfiddle.
I have lots of input, textarea and select on some pages (angular templates).
I want to redefine "input" directive such that It will take a value like ViewMode = true from either localStorage and convert all inputs as label. If I change the ViewMode then on page refresh input should behave properly.
But I do not want to edit any input tag on any angular template.
Means I want to override input, textarea and select as my own angular directive.
I am not able to start. Where from should I start? (I have experience of custom directive with new name, but not with any exciting HTML tag name)
Note: I do not want to use readonly (with proper style) since it requires editing all input tag. Not only that I have custom directives with isolated scope, so I need to pass the ViewMode value to all custom directives. More over if user press CTRL+A content readonly field is not being selected.
I am looking for a solution kind of as follows
ViewButtonClickEvent () {
set localStorage.viewMode = true;
callExistingEditMethod();
}
EditButtonClickEvent () {
set localStorage.viewMode = false;
callExistingEditMethod();
}
editPagesModule.directive('input', {
if(localStorage.viewMode != true)
//Keep all existing functionality with ng-model
}
else {
//replace input with span or label.
}
})
You could create directives called input, select and textarea, which would automatically be compiled without having to change your existing markup.
Working examples: JSFiddle & Plunker
It would look something like this:
angular.module('myApp', [])
.directive('input', inputDirective)
.directive('select', inputDirective)
.directive('textarea', inputDirective)
.factory('$editMode', editModeFactory)
;
inputDirective.$inject = ['$editMode'];
function inputDirective($editMode) {
return {
link: postLink
};
function postLink(scope, iElement, iAttr) {
scope.$watch($editMode.get, function(edit) {
if (iElement[0].nodeName === 'SELECT') {
if (edit === 'true') iElement.removeAttr('disabled');
else iElement.attr('disabled', true);
}
else {
if (edit === 'true') iElement.removeAttr('readonly');
else iElement.attr('readonly', true);
}
});
}
}
editModeFactory.$inject = ['$window'];
function editModeFactory($window) {
return {
get: function() {
return $window.localStorage.getItem('editMode');
},
set: function(value) {
$window.localStorage.setItem('editMode', value);
}
};
}
I did use the readonly attribute (disabled for select), because the only other option I can think of would be to replace the entire input element with something like a div. You would also have to cache the original element, so you can restore it later...and doing that sort of thing would break your bindings so you'd have to recompile every input, every time. That just seems like a horrible idea.
There's a jquery.minicolors pluggin, and there is a angularified version of this plugin that I wish use.
The usage is like so:
On page open data for color picker (hex color and opacity) is retrieved from server.
Then that values is used to "build" colorpicker (so that its color and opacity are equal to values received from server).
Then user may click on picker and change color and opacity. Results must be reflected in corresponding $scope variables.
I have binded color hex value via ng-model and it works like a charm, but I have troubles with opacity. I have tried to use ng-attr- for data-opacity attribute, but when I change manually opacity, results are not "sent" back to scope variable..
<input minicolors="options" ng-model="thecolor" ng-attr-data-opacity="{{opacity}}">
To demonstrate this I have created a plunker demo with description.
The problem is that the input field only binds to color part. If you add a ng-change to your input you will see that this is only fired if you change the color but not when opacity is changed.
You should move your code into a directive. Here you can use the "change" eventHandler from minicolors as describedin another question Minicolors AngularJS and the docs
UPDATE
If you take a look at the code
http://kaihenzler.github.io/angular-minicolors/angular-minicolors.js
It seems like the change event is there but only binds the hex value.
var settings = getSettings();
settings.change = function (hex) {
scope.$apply(function () {
ngModel.$setViewValue(hex);
});
};
I am guessing it should be.
var settings = getSettings();
settings.change = function (hex,opacity) {
scope.$apply(function () {
ngModel.$setViewValue(hex);
});
};
But the opacity is not passed into any value. Seems like a bug in the directive. If you use a console.log(opacity) you can see the value change.
Update 2.
Simple fix for you
settings.change = function (hex,opacity) {
scope.opacity = opacity
scope.$apply(function () {
ngModel.$setViewValue(hex);
});
};
After many attempts I have managed to modify source code to be able to two-way-bind opacity value to variable I desire. Here's a modified plunker.
What I've added:
First, we modify the tag by passing in additional watch-opacity="varname" attribute to define what variable should hold the initial opacity value. This variable will also be changed when we change the opacity slider and viceversa - the opacity slider will change when new data is written into this variable (ex. data received from server).
<input minicolors="options" ng-model="thecolor" data-opacity="" watch-opacity="settings.somewhat.opacity">
Then we get the opacity value from our settings and ask minicolors to use it as opacity for now, or use 1.0 if the variable have no value yet.
var opacity = scope.$eval(attrs.watchOpacity) || "1.0";
element.minicolors('opacity', opacity);
And then we modify our onchange function so that our settings variable for opacity is being updated also when opacity is changed due to opacity slider change:
var settings = getSettings();
settings.change = function (hex, opacity) { // <- fix!
scope.$apply(function () {
ngModel.$setViewValue(hex);
$parse(attrs.watchOpacity).assign(scope, opacity); // <- fix!
});
};
That's it! This way we can define variable for each colorpicker that will hold opacity value and bind any opacity changes via slider to it.
You can use an attribute watch:
(edited)
// Template
<input minicolors="options" ng-model="thecolor">
// Controller:
$("input[minicolors]").each(function() {
var $self = $(this);
$scope.$watch(
function() {
return $self.attr("data-opacity");
},
function(opacity) {
$scope.opacity[ $self.index() ] = opacity;
});
});