Search code examples
jsrenderjsviews

How to have id(primary key) value in a select update corresponding bound values


In a nutshell I want a select box populated with a domain with the option values being the id/primary key, and when the select is changed, I'd like the corresponding records' bound values updated. You can see bureau_id() here does change but not name or personal_area of the same record. I feel like there is an observable here but I used map() and I can't figure out how to tap into it. In the view model I also thought the id parameter might somehow help with this.

I'm also wondering if there's a better method of using domains to control the editing of items in the data by id and updating domain fields related to that id. The thought was to display a select when a value is clicked and return to the text after a selection is made.

$(function() {
	$.views.viewModels({
		Root: {
			getters: [
				{
					getter: "bureaus", 
					type: "Bureau"
				}
			]	
		},
		Bureau: {
			id: "bureau_id",
			getters: ["bureau_id","name","personal_area"]
		}
	});	

	data = {
		bureaus: [
			{  
		        "bureau_id":40,
		        "name":"Bureau of Emergency Communications",
		        "personal_area":1200
		    },
		    {  
				"bureau_id":30,
				"name":"Office of the City Attorney",
				"personal_area":1090
			}
		]

	} 

	domains = {
		"bureau":  [  
		 {  
		    "bureau_id":41,
		    "name":"Bureau of Development Services",
		    "personal_area":1210
		 },
		 {  
		    "bureau_id":40,
		    "name":"Bureau of Emergency Communications",
		    "personal_area":1200
		 },
		 {  
		    "bureau_id":39,
		    "name":"Bureau of Emergency Management",
		    "personal_area":1190
		 },
		 {  
		    "bureau_id":22,
		    "name":"Bureau of Environmental Services",
		    "personal_area":1010
		 },
		 {  
		    "bureau_id":42,
		    "name":"Bureau of Fire and Police Disability and Retirement Fund",
		    "personal_area":1230
		 },
		 {  
		    "bureau_id":43,
		    "name":"Bureau of Human Resources",
		    "personal_area":1240
		 },
		 {  
		    "bureau_id":45,
		    "name":"Bureau of Internal Business Services",
		    "personal_area":1260
		 },
		 {  
		    "bureau_id":36,
		    "name":"Bureau of Parks and Recreation",
		    "personal_area":1160
		 },
		 {  
		    "bureau_id":34,
		    "name":"Bureau of Planning and Sustainability",
		    "personal_area":1140
		 },
		 {  
		    "bureau_id":46,
		    "name":"Bureau of Revenue & Financial Services",
		    "personal_area":1275
		 },
		 {  
		    "bureau_id":44,
		    "name":"Bureau of Technology Services",
		    "personal_area":1250
		 },
		 {  
		    "bureau_id":49,
		    "name":"City Budget Office",
		    "personal_area":1320
		 },
		 {  
		    "bureau_id":31,
		    "name":"Office of City Auditor Mary Hull Caballero",
		    "personal_area":1100
		 },
		 {  
		    "bureau_id":26,
		    "name":"Office of Commissioner Amanda Fritz",
		    "personal_area":1050
		 },
		 {  
		    "bureau_id":29,
		    "name":"Office of Commissioner Chloe Eudaly",
		    "personal_area":1080
		 },
		 {  
		    "bureau_id":28,
		    "name":"Office of Commissioner Dan Saltzman",
		    "personal_area":1070
		 },
		 {  
		    "bureau_id":27,
		    "name":"Office of Commissioner Nick Fish",
		    "personal_area":1060
		 },
		 {  
		    "bureau_id":37,
		    "name":"Office of Community & Civic Life",
		    "personal_area":1170
		 },
		 {  
		    "bureau_id":48,
		    "name":"Office of Equity and Human Rights",
		    "personal_area":1310
		 },
		 {  
		    "bureau_id":24,
		    "name":"Office of Government Relations",
		    "personal_area":1030
		 },
		 {  
		    "bureau_id":47,
		    "name":"Office of Management and Finance",
		    "personal_area":1290
		 },
		 {  
		    "bureau_id":38,
		    "name":"Office of Mayor Ted Wheeler",
		    "personal_area":1180
		 },
		 {  
		    "bureau_id":30,
		    "name":"Office of the City Attorney",
		    "personal_area":1090
		 },
		 {  
		    "bureau_id":35,
		    "name":"Police Bureau",
		    "personal_area":1150
		 },
		 {  
		    "bureau_id":32,
		    "name":"Portland Bureau of Transportation",
		    "personal_area":1120
		 },
		 {  
		    "bureau_id":23,
		    "name":"Portland Fire & Rescue",
		    "personal_area":1020
		 },
		 {  
		    "bureau_id":25,
		    "name":"Portland Housing Bureau",
		    "personal_area":1040
		 },
		 {  
		    "bureau_id":33,
		    "name":"Portland Water Bureau",
		    "personal_area":1130
		 }
		]
	};

	let vm = $.views.viewModels.Root.map(data);
	$.templates('#root-tmpl').link('#content', vm, domains);
});
<!doctype html>
<html>
	<head>
		<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
		<script src="https://www.jsviews.com/download/jsviews.min.js"></script>
		<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
		<link href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css" rel="stylesheet">
		<script src="https://www.jsviews.com/download/sample-tag-controls/jsviews-jqueryui-widgets.min.js"></script>

		<script id="root-tmpl" type="text/x-jsrender">
			<ul>
				{^{for bureaus() tmpl="#bureau-tmpl"}}
				{{/for}}
			</ul>
		</script>

		<script id="bureau-tmpl" type="text/x-jsrender">
			<li>
				{^{>name()}} ({^{>personal_area()}}) {^{>bureau_id()}}


				{^{selectmenu bureau_id() name="bureau_id" class="bureau-select"}}
					{^{for ~bureau}}
						<option data-link="value{:bureau_id}">{^{>name}} ({^{>personal_area}})</option>
					{{/for}}
				{{/selectmenu}}
			</li>
		</script>
	</head>
	<body>
		<div id="content"></div>	
	</body>
</html>


Solution

  • There are a number of issues with your current version. One is that you are cloning data items from domains.bureau[n] to data.bureaus[n]. If you choose a different drop-down item, then you would need the selected item to somehow get cloned to the data.bureau. It looks like you would also easily get some data integrity issues in a real situation where values could change (i.e. with editing, server data, etc.)

    Anyway, here is a suggested alternative design. data.bureaus is renamed to data.selected, and has only the selected_id which is a lookup to the corresponding bureau_id.

    The domains and current data.selected are all mapped to ViewModel instances. Note the line:

    getSelectedBureau.depends = "selected_id";  
    

    which ensures that when selected_id changes, the following also updates:

    {^{>selectedBureau()^name()}} ({^{>selectedBureau()^personal_area()}}) ...
    

    $(function() {
    
    $.views.viewModels({
    Root: {
    	getters: [
    		{getter: "selected", type: "SelectedBureau"},
    		{getter: "bureaus", type: "Bureau"}
    	]
    },
    SelectedBureau: {
    	getters: ["selected_id"],
    	extend: {
    		selectedBureau: getSelectedBureau,
    		bureaus: function() {
    			return vm.bureaus();
    		}
    	}
    },
    Bureau: {
    	id: "bureau_id",
    	getters: ["bureau_id","name","personal_area"]
    }
    });
    
    function getSelectedBureau() { // lookup to find the corresonding bureau VM
    let bureaus = vm.bureaus(),
    	l = bureaus.length;
    while (l--) {
    	if (this.selected_id() === "" + bureaus[l].bureau_id()) {
    		return bureaus[l];
    	}
    }
    }
    
    getSelectedBureau.depends = "selected_id"; // So selectedBureau() updates when selected_id changes
    
    let data = {
    selected: [
    	{
    		"selected_id":"40"
    	},
    	{
    		"selected_id":"30"
    	}
    ],
    "bureaus": [
    	{
    		"bureau_id":41,
    		"name":"Bureau of Development Services",
    		"personal_area":1210
    	},
    	{
    		"bureau_id":40,
    		"name":"Bureau of Emergency Communications",
    		"personal_area":1200
    	},
    	{
    		"bureau_id":49,
    		"name":"City Budget Office",
    		"personal_area":1320
    	},
    	{
    		"bureau_id":31,
    		"name":"Office of City Auditor Mary Hull Caballero",
    		"personal_area":1100
    	},
    	{
    		"bureau_id":30,
    		"name":"Office of the City Attorney",
    		"personal_area":1090
    	}
    ]
    };
    
    let vm = $.views.viewModels.Root.map(data);
    
    $.templates('#root-tmpl').link('#content', vm);
    
    });
    <!doctype html>
    <html>
    <head>
    <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
    <script src="https://www.jsviews.com/download/jsviews.min.js"></script>
    <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
    <link href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css" rel="stylesheet">
    <script src="https://www.jsviews.com/download/sample-tag-controls/jsviews-jqueryui-widgets.min.js"></script>
    
    <script id="root-tmpl" type="text/x-jsrender">
    <ul>
    {^{for selected() tmpl="#bureau-tmpl"/}}
    </ul>
    </script>
    
    <script id="bureau-tmpl" type="text/x-jsrender">
    <li>
    {^{>selectedBureau()^name()}} ({^{>selectedBureau()^personal_area()}}) {^{>selected_id()}}
    
    {^{selectmenu selected_id() name="bureau_id" class="bureau-select"}}
    	{^{for bureaus()}}
    		<option value="{{:bureau_id()}}">{{>name()}} ({{>personal_area()}})</option>
    	{{/for}}
    {{/selectmenu}}
    </li>
    </script>
    </head>
    <body>
    <div id="content"></div>	
    </body>
    </html>