A solution for two-binding between angular and a style sheet - javascript

I know this sounds silly but I'm writing a wysiwyg editor that allows Designers to create style guides. I've become very fascinated with 2 way binding in angular and was curious if it was possible to two-way bind between a css sheet and a ng-model input field. Currently, I'm making a dynamic style guide that allows a designer to colorpick a primary, secondary colors of a page. These color will change the entire theme of the site uniformly and it would be awesome to do this from the stylesheet itself.
HTML
<input type="text" ng-model="color.primary" />
<button class="btn primary-color" />
CSS
.primary-color {
background: {{color.primary}};
}
js
$scope.color {primary: '00f', secondary: '#e58'}
I know there are plenty of directives like ng-style and ng-class But I fear that every tag will have to be a directive because everything could have an ng-style/ng-class tag. Thus making my code not very dry and hard to maintain.
What if I wanted a dynamic style guide of css. A sheet that I could store as key value pairs of CSS into server like firebase, perhaps even 3way bind the changing of colors in real time? I'm pretty sure this cannot be accomplished using solely angular... would anyone have any ideas on pre compilers or hacks to accomplish this task so that it would result in one clean style guy?

This was pretty fun to work on.
You have access to all of the styles on a page through document.styleSheets, so you just need scope the rules on a style. So lets say that I have this class:
.class {
font-size: 20px;
color: blue;
}
How jsfiddle implements sheets, this is the third style sheet added to the document, so I can just assign to the scope like this:
myApp.controller('TestController', ['$scope', function ($scope) {
$scope.styles = document.styleSheets[3].rules;
}]);
This would let you do things like $scope.styles[0].style['color'] = 'red' to change the color of anything with class to red. Since it's the first thing in the style array.
But that's not cool enough, so we want to create a directive where we can change these from a ui. So we'd like to know all of the things that class controls, to create controls for them, So we can manipulate the css string to get all of those.
Next we have to create a temporary scoped object on the directive that starts off with all of the styles. The reason is that style sheets have checking, so as you type into an input if you do something like $scope.styles[0].style['color'] = 'g' and it was red, it will just reset to red.
So we create an input for each style type, with ng-model of our temp, then just listen for changes and attempt to assign to the style sheet.
I created a fiddle where I implement it, but the directive looks like this.
myApp.directive('styler', function() {
return {
scope: {
styles: '='
},
restrict: 'EA',
template: '<div ng-repeat="type in types">{{type}} <input ng-change="Change(type)" ng-model="temp_styles[type]"/></div>',
link: function(scope, elt, attrs) {
// get the actual styles
var types = scope.styles.cssText.replace(/ /g, '').split('{')[1].split('}')[0].split(';');
scope.types = [];
scope.temp_styles = {};
// get rid of "" element at the end
types.pop();
for (var i in types) {
var split = types[i].split(':');
scope.types.push(split[0]);
scope.temp_styles[split[0]] = split[1];
}
scope.Change = function(type) {
scope.styles.style[type] = scope.temp_styles[type];
};
}
};
});
Cool, dynamic two way binding of styles!
Hope this helped!

Related

Polymer, evaluate element based off object

I am using the tile example from polymers neon elements - and I am trying to make each expanded tile unique. My first try on how to do this was to pass a string in with the grid items like
{
value: 1,
color: 'blue',
template: 'slide-1'
}
And have that element be evaluated when rendered in a new element something like this. (this is the card template itself)
<template>
<div id="fixed" class$="[[_computeFixedBackgroundClass(color)]]"></div>
<div id="card" class$="[[_computeCardClass(color)]]">
<[[item.template]]></[[item.template]]>
</div>
This does not work - however I am wondering if there is some way to do this so I can load custom elements for the content of each card. For reference -https://elements.polymer-project.org/elements/neon-animation?view=demo:demo/index.html&active=neon-animated-pages , it is the grid example and I am trying to replace the content of each card once it is clicked on ( the fullsize-page-with-card.html, here is all the html for it - https://github.com/PolymerElements/neon-animation/tree/master/demo/grid ). Is this the wrong way of approaching this? Or maybe I have some syntax wrong here. Thanks!
Edit : OK, So I can send it through if i add it to the click to open the card like so
scope._onTileClick = function(event) {
this.$['fullsize-card'].color = event.detail.data.color;
this.$['fullsize-card'].template = event.detail.data.template;
this.$.pages.selected = 1;
};
and in the card's properties like so
template: {
type: String
},
So I can then evaluate it as [[template]] , however - the question still remains how to call a custom element (dynamically) using this string. I could pass a couple of properties and fill in a card or form so they are unique, but i think I would have much more creative freedom if I could call custom elements inside each card.
I have an element that allows referenced templates. There are a couple of others other there, but this one also allows data bindings to work: https://github.com/Trakkasure/dom-bindref

Apply style on insert into div

I'm building a search by tags input box as seen here:
http://jsfiddle.net/Newtt/7nUAf/
Forgive the terrible styling as this is just a small component of a larger application and I've just added the styles needed to show my issue.
My search box is a div that has it's text inserted using Jquery as follows:
$(document).ready(function () {
$('.search-box').click(function () {
$('.search-options').toggle();
});
$('.options').click(function () {
var d = $('.search-box').html();
console.log(d);
var c = $(this).html();
console.log(c);
if (d != '') {
$('.search-box').html(d + ', ' + c);
} else {
$('.search-box').html(c);
}
$('.search-options').hide();
});
$('#reset').click(function () {
$('.search-box').html('');
});
});
where .search-box is the input div, .options are the clickable options from the drop down box search-options.
Currently, the text of each option is inserted into the search-box div. I need this to be styled dynamically while it enters the search box.
I tried something on the lines of:
$('<span>').addClass('tag').append(
$('<span>').text(value).append(' '),
$('<a>', {
href : '#',
title : 'Removing tag',
text : 'x'
});
where the tag class is defined in the style sheet to style the element to look like a tag,
but this doesn't work at all. Can someone help me out with how to achieve styling the input text to look like a tag from, say, Evernote notebooks?
Thanks!
I adapted your fiddle. Just wrap c in a span with a class (like you were trying to do in the second part of your post) and apply styles in css. I have just made the background red, but it should be easy enough to make it look like a tag like the ones in the drop down do.
http://jsfiddle.net/7nUAf/1/
JS:
$('.options').click(function () {
var d = $('.search-box').html();
var c = $(this).html();
$('.search-box').append('<span class="tag">'+c +'</span>');
$('.search-options').hide();
});
CSS:
.tag {
background: red;
}
For what you are looking to do - there are lots of excellent plug ins already available that provide much "prettier" functionality and with much less work on your part. Some have already been suggested in the comments - I might suggest consider using "chosen". The syntax is amazingly simple. Just create a select box as follows:
<select id="test" multiple>
<option>pdf</option>
<option>document</option>
</select>
Then in your document ready function you simply need to call chosen plugin:
$(document).ready(function () {
$('#test').chosen({width: "80%"});
});
I put together an example that does this on JSFiddle here: http://jsfiddle.net/7nUAf/3/. Once you get to the point that you have it working you can easily style the elements by inspecting what elements chosen is creating. For example the "li.search-choice" selector will allow you to style the selected items.
In General - even if you don't like this particular plug in, always consider running a search for existing items that do what you are looking for. In the case that these aren't perfect you can always improve them and provide that insight back to the community as a whole. In that way, everyone learns together.
Best of luck!

create HTML element dynamically

I am very new to angular js. I want to create an input box on click of particular div. Here I need to create element on div which repeating.
<div><div ng-repeat ng-click="create();"></div><div>
What will be the best way to do so?
DOM manipulation in Angular is done via directives (There is paragraph on 'Creating a Directive that Manipulates the DOM' here)
First, read through this excellent article: How do i think in Angular if i have a jQuery background
The Angular Team also provides a pretty neat tutorial, which definetly is worth a look: http://docs.angularjs.org/tutorial
While Angular is pretty easy and fun to use once you have wrapped your head around the concepts, it can be quite overwhelming to dive into the cold. Start slow and do not try to use each and every feature from the beginning. Read a lot.
I strongly recommend egghead.io as a learning resource. The video-tutorials there are bite-sized and easy to watch and understand. A great place for both beginners and intermediates. Start from the bottom here.
Some folks have done great things with Angular. Take a look at http://builtwith.angularjs.org/ and check out some source code.
Use an array and ng-repeat to do that. Have a look at the following code.
I crated scope variable as an empty array. Then created a function to add values to that array.
app.controller('MainCtrl', function($scope) {
$scope.inputFields = [];
$scope.count = 0;
$scope.addField = function(){
$scope.inputFields.push({name:"inputText"+$scope.count++});
}
});
I used ng-repeat with this array. and called the function on the click event of a div.
<div ng-click="addField()">Click here to add</div>
<div ng-repeat="inputField in inputFields">
<input type="text" name="inputField.name">
</div>
Check this working link
Update - Show only one text box on click
I created addField() as follows.
$scope.addField = function(){
$scope.newTextField = "<input type='text' name='myTxt'>";
}
To render this html in my view file I created a new directive called compile as follows.
app.directive('compile', function($compile) {
// directive factory creates a link function
return function(scope, element, attrs) {
scope.$watch(
function(scope) {
// watch the 'compile' expression for changes
return scope.$eval(attrs.compile);
},
function(value) {
// when the 'compile' expression changes
// assign it into the current DOM
element.html(value);
// compile the new DOM and link it to the current
// scope.
// NOTE: we only compile .childNodes so that
// we don't get into infinite loop compiling ourselves
$compile(element.contents())(scope);
}
);
};
});
Then used this directive in my view.html file
<body ng-controller="MainCtrl">
<div ng-click="addField()">Click to Add</div>
<div compile="newTextField"></div>
</body>
click here to view the working link

Angular directives with ng-if seem to loose model

I am fairly new to Angular and trying to make a directive that will construct a form input, usually a text-input, but sometimes a select box based on whether or not the input is associated with an array of options. Simplifying down, my code looks roughly like this:
html
<init ng-init = "ops = [
{value:'hello',label:'Hello All'},
{value:'bye',label:'Good-bye everyone'}]"></init>
<init ng-init = "fType =
{id:'greeting',label:'Greeting',type:'enum', 'options':ops}">
</init>
<simpleselect field="fType" ng-Model="foomodel"></simpleselect>
{{foomodel}}
Directive
.directive('simpleselect',function(){
return {
restrict: 'E',
replace:true,
template:[
'<div><select ',
'ng-if ="type=\'select\'"',
'name="{{field.id}}"',
'ng-model="ngModel" ',
'ng-options="option.value as option.label for option in field.options">',
'</select>{{ngModel}}</div>',
].join(),
scope:{
field:'=',
ngModel:'='
},
link:function(scope, elem, attrs, ctrl){
scope.type = 'select';
}
}
});
This almost works. If I remove the ng-if on the select box, my select box and my model stay in sync just fine. But what I want is to be able to choose which control within the directive. Is this a misuse of ng-if and is there another path?
Can use template:function(element,attrs) if using angular version >=1.1.4
template:function(element,attrs){
var template='<div>';
var type= attrs.fieldType;
if( type=='select'){
template+='<select ng-options=......>';
}
if( type=='text' ){
template +='<input ......./>';
}
template +='</div>';
return template;
}
Modify your template as follows:
template: [
'<div ng-if="field.type==\'select\'">', // <-- move ng-if here
'<select name="{{field.id}}"',
'ng-model="ngModel" ',
'ng-options="option.value as option.label for option in field.options">',
'</select>',
'{{ngModel}}',
'</div>'
].join(''),
Also note there are couple of errors:
1). ng-if should have == instead of = and field.type instead of just type
2). .join('') instead of .join()
Demo http://jsfiddle.net/2YE3b/
As a couple of folks suggested, I could have used ng-show, but I didn't want to pollute my DOM with all the input types I was not using. I also could have set my directive with a list of individual properties instead of passing in a 'field' object and then watching them in my template function to determine the particulars of my input, like charlietfl's solution.
Instead, since I want to determine which input type control to use based on a number of attributes in the model itself, I have chosen to resolve a good portion of the rendering of my control in the link method of my directive, using the $compile service. Then I can both make macro layout decisions based on the model I pass into scope and still resolve the particulars of each input using angular style template syntax.
For a simple selectbox, this would have been overkill and either of the two other answers here would have been better, but because I want my directive to determine if a control should be a text input, textarea, selectbox, radio buttons, or checkboxes depending only on the requirements of the model I need to be able to read the model first and then compile with it.
Doing rendering in the link method feels a bit wrong, so I don't mean to be saying I have a great solution, but if it helps anyone, that's great. If others with more experience with Angular than me find that offensive, I would also love to be straightened out. :^)
Here is an example of my more complicated checkbox option within the directive:
link:function(scope, elem, attrs, ctrl){
...some logic to examine the model to determine which input type to use...
if(scope.type === 'checkbox'){
if(typeof scope.ngModel === 'string') scope.ngModel = scope.ngModel.split(/[ ,]+/);
tmp = [
'<div class="option chk tall" ng-repeat="option in field.options">',
'<label><input ng-model="ngModel" ng-value="option.value" ng-checked="ngModel.indexOf(option.value) > -1" name="{{field.id}}" type="checkbox" />{{option.label}}</label>',
'<div class="description">{{option.description}}</div>',
'</div>{{ngModel}}'].join('');
elem.on('change',function(e){
if(e.target.checked && scope.ngModel.indexOf(e.target.value) < 0) scope.ngModel.push(e.target.value);
if(!e.target.checked)
scope.ngModel.splice(scope.ngModel.indexOf(e.target.value),1);
});
}
elem.find('div').html(tmp);
$compile(elem.contents())(scope);
}
I am not at all in love with the on-click stuff to keep my model and UI in sync, but for now, I am going to live with it.
I had a similar problem and you can actually access the parent model via $parent.boundattribute.
As described somewhere in the comments, ng-if adds a subscope and thus the model does not update backwards.
In my case ng-show would not work, I had to really remove the part of the DOM and this solved the problem.
<select ng-if="type='select'"
name="{{field.id}}"
ng-model="$parent.ngModel"
ng-options="option.value as option.label for option in field.options">
</select>

changing css class model using JavaScript

Is it in any way possible, to change a css class model using JavaScript?
Pseudo code:
function updateClass(className, newData) {
cssInterface.alterClass(className, newData);
}
className being the name of the class, which is supposed to be changed (like ".footer") and newData being the new class content (like border: "1px solid pink;").
The target is, actually, just to save space: I am working with CSS3-animations, so changing one attribute of an element, which is affected by it's class, will terminate the animation of of it - The (in my case) font size won't change anymore. Using different classes will require an entire new set of classes for all affected elements, I'd like to avoid this.
I am not searching for a change via
element.className = "foo";
or
element.style.fontSize = "15pt";
Thanks for your help, guys :)
Here's my function to do this...
function changeCSS(typeAndClass, newRule, newValue) // modify the site CSS (if requred during scaling for smaller screen sizes)
{
var thisCSS=document.styleSheets[0] // get the CSS for the site as an array
var ruleSearch=thisCSS.cssRules? thisCSS.cssRules: thisCSS.rules // work out if the browser uses cssRules or not
for (i=0; i<ruleSearch.length; i++) // for every element in the CSS array
{
if(ruleSearch[i].selectorText==typeAndClass) // find the element that matches the type and class we are looking for
{
var target=ruleSearch[i] // create a variable to hold the specific CSS element
var typeExists = 1;
break; // and stop the loop
}
}
if (typeExists)
{
target.style[newRule] = newValue; // modify the desired class (typeAndClass) element (newRule) with its new value (newValue).
}
else
{
alert(typeAndClass + " does not exist.");
}
}
Called with (example)
changeCSS("div.headerfixed","-moz-transform-origin", "100% 0%");
hope this helps.
See my answer here. To answer your question: Yes, it's possible.
#CSS3: I tried exactly the same in one of my html5 experiments. I created an extra <style> element, and changed its contentText to the CSS class definitions I needed. Of course changing the cssRule object would be much cleaner :-)
As far as I can tell the CSS object model cannot easily tell you whether you already have an existing style rule for a particular class, but you can easily append a new rule for that class and it will override any previous declaration of that style.
I found an example of creating dynamic stylesheets.
You should take a look at dojo, it has some nice features where you can do just that..
require(["dojo/dom-class"], function(domClass){
// Add a class to some node:
domClass.add("someNode", "anewClass");
});
http://dojotoolkit.org/reference-guide/1.7/dojo/addClass.html

Categories

Resources